From 64543d155054200702304156ddcc1703eb93cc9b Mon Sep 17 00:00:00 2001 From: Dries Peeters Date: Wed, 3 Sep 2025 14:17:57 +0200 Subject: [PATCH 1/6] fix(tasks,ui): correct Task View dropdown behavior and unify filter UI - Ensure Task View dropdown shows correct options and preserves selected state - Normalize dropdown/filter markup across tasks, clients, projects, reports, timer - Update routes to pass/handle filter params and template context - Align styles for dropdown spacing/alignment; improve mobile behavior - Keep base layout consistent for shared components --- app/routes/tasks.py | 70 +++++- app/static/base.css | 112 +++++++++- app/templates/base.html | 2 + app/templates/tasks/list.html | 133 +++++------ app/templates/tasks/my_tasks.html | 129 ++++++----- templates/clients/list.html | 146 ++++++++----- templates/clients/view.html | 248 ++++++++++++++------- templates/projects/list.html | 303 +++++++++++++++++--------- templates/projects/view.html | 185 ++++++++-------- templates/reports/index.html | 120 ++++++---- templates/reports/project_report.html | 114 ++++++---- templates/reports/summary.html | 78 ++++--- templates/reports/user_report.html | 112 ++++++---- templates/timer/manual_entry.html | 206 +++++++++++------ 14 files changed, 1300 insertions(+), 658 deletions(-) diff --git a/app/routes/tasks.py b/app/routes/tasks.py index 1e6e7250..a7f27d55 100644 --- a/app/routes/tasks.py +++ b/app/routes/tasks.py @@ -332,22 +332,68 @@ def delete_task(task_id): @tasks_bp.route('/tasks/my-tasks') @login_required def my_tasks(): - """Show current user's tasks""" + """Show current user's tasks with filters and pagination""" + page = request.args.get('page', 1, type=int) status = request.args.get('status', '') - - query = Task.query.filter( - db.or_( - Task.assigned_to == current_user.id, - Task.created_by == current_user.id + priority = request.args.get('priority', '') + project_id = request.args.get('project_id', type=int) + search = request.args.get('search', '').strip() + task_type = request.args.get('task_type', '') # '', 'assigned', 'created' + + query = Task.query + + # Restrict to current user's tasks depending on task_type filter + if task_type == 'assigned': + query = query.filter(Task.assigned_to == current_user.id) + elif task_type == 'created': + query = query.filter(Task.created_by == current_user.id) + else: + query = query.filter( + db.or_( + Task.assigned_to == current_user.id, + Task.created_by == current_user.id + ) ) - ) - + + # Apply filters if status: query = query.filter_by(status=status) - - tasks = query.order_by(Task.priority.desc(), Task.due_date.asc(), Task.created_at.asc()).all() - - return render_template('tasks/my_tasks.html', tasks=tasks, status=status) + + if priority: + query = query.filter_by(priority=priority) + + if project_id: + query = query.filter_by(project_id=project_id) + + if search: + like = f"%{search}%" + query = query.filter( + db.or_( + Task.name.ilike(like), + Task.description.ilike(like) + ) + ) + + tasks = query.order_by( + Task.priority.desc(), + Task.due_date.asc(), + Task.created_at.asc() + ).paginate(page=page, per_page=20, error_out=False) + + # Provide projects for filter dropdown + projects = Project.query.filter_by(status='active').order_by(Project.name).all() + + return render_template( + 'tasks/my_tasks.html', + tasks=tasks.items, + pagination=tasks, + projects=projects, + status=status, + priority=priority, + project_id=project_id, + search=search, + task_type=task_type + ) @tasks_bp.route('/tasks/overdue') @login_required diff --git a/app/static/base.css b/app/static/base.css index 6c1cf473..c1d02c57 100644 --- a/app/static/base.css +++ b/app/static/base.css @@ -77,7 +77,8 @@ main { border-radius: var(--border-radius); transition: var(--transition); background: white; - overflow: hidden; + /* Allow dropdown menus within cards to overflow properly */ + overflow: visible; margin-bottom: var(--card-spacing); } @@ -85,7 +86,7 @@ main { margin-bottom: 0; } -.card:hover { +.card.hover-lift:hover { box-shadow: var(--card-shadow-hover); transform: translateY(-2px); } @@ -256,6 +257,16 @@ main { color: var(--text-primary); } +/* Keep outline secondary buttons light when opened/active */ +.btn-outline-secondary:focus, +.btn-outline-secondary:active, +.btn-outline-secondary.dropdown-toggle.show, +.show > .btn-outline-secondary.dropdown-toggle { + background: var(--light-color); + border-color: var(--text-secondary); + color: var(--text-primary); +} + /* Unify small/large sizes */ .btn-sm { padding: 0.4rem 0.65rem; @@ -705,11 +716,55 @@ h6 { font-size: 1rem; } background: #ffffff !important; -webkit-backdrop-filter: none !important; backdrop-filter: none !important; - z-index: 1055; /* above navbar (1030) */ + z-index: 1060; /* above navbar (1030) and our backdrop */ + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + box-shadow: var(--card-shadow-hover); + margin-top: 0.5rem; + overflow: visible; /* allow soft shadow rounding */ + position: absolute !important; /* ensure above backdrop and positioned by Bootstrap */ + pointer-events: auto; /* capture interactions */ + background-clip: padding-box; /* ensure solid fill to rounded corners */ +} + +/* Ensure dropdowns inside cards stack above adjacent content */ +.card .dropdown, +.mobile-card .dropdown { + position: relative; + z-index: 2000; } .dropdown-item { - background-color: transparent; + background-color: #ffffff !important; /* make items opaque */ } +.dropdown-menu::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #ffffff; /* solid background to avoid transparency */ + border-radius: inherit; + z-index: -1; /* sit behind menu content but within menu stacking */ +} + +/* Solid hover state for items to further avoid transparency feel */ +.dropdown-item:hover, .dropdown-item:focus { + background-color: var(--light-color) !important; +} + +/* Backdrop to block interactions behind open dropdowns */ +/* Removed custom dropdown backdrop; rely on Bootstrap defaults */ + +/* Increase dropdown item touch targets and spacing */ +.dropdown-item { + padding: 0.6rem 1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.dropdown-item i { width: 1rem; text-align: center; } /* Enhanced Mobile Components */ .mobile-stack { @@ -927,3 +982,52 @@ h6 { font-size: 1rem; } } +/* Shared summary cards used across pages (invoices, reports) */ +.summary-card { + transition: all 0.3s ease; + border-radius: 12px; +} + +.summary-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.summary-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; +} + +.summary-label { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 4px; +} + +.summary-value { + font-size: 20px; + font-weight: 700; + color: var(--text-primary); +} + +.empty-state { + padding: 2rem; +} + +.empty-state i { + opacity: 0.5; +} + +@media (max-width: 768px) { + .summary-card { margin-bottom: 1rem; } + .summary-value { font-size: 18px; } +} + diff --git a/app/templates/base.html b/app/templates/base.html index 09826e6b..6d6a03f3 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -235,6 +235,8 @@ nav.classList.remove('scrolled'); } }, { passive: true }); + + // Use Bootstrap's default dropdown behavior; no custom backdrop {% if current_user.is_authenticated %} diff --git a/app/templates/tasks/list.html b/app/templates/tasks/list.html index b4e4c074..449f143c 100644 --- a/app/templates/tasks/list.html +++ b/app/templates/tasks/list.html @@ -4,84 +4,93 @@ {% block content %}
- -
-
-
-
-
-
-
-
- -
-
-

Task Management

-

Organize and track your project tasks efficiently

-
-
-
-
-
- - New Task - - -
-
+ +
+
+
+
+
+

+ + Tasks +

+ {{ tasks|length }} total +
+
- +
-
-
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'todo')|list|length }}
- To Do +
+
+
+
+
+
+ +
+
+
+
To Do
+
{{ tasks|selectattr('status', 'equalto', 'todo')|list|length }}
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'in_progress')|list|length }}
- In Progress +
+
+
+
+
+
+
+
+ +
+
+
+
In Progress
+
{{ tasks|selectattr('status', 'equalto', 'in_progress')|list|length }}
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'review')|list|length }}
- Review +
+
+
+
+
+
+
+
+ +
+
+
+
Review
+
{{ tasks|selectattr('status', 'equalto', 'review')|list|length }}
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'done')|list|length }}
- Completed +
+
+
+
+
+
+
+
+ +
+
+
+
Completed
+
{{ tasks|selectattr('status', 'equalto', 'done')|list|length }}
diff --git a/app/templates/tasks/my_tasks.html b/app/templates/tasks/my_tasks.html index 26a8abc0..17071d14 100644 --- a/app/templates/tasks/my_tasks.html +++ b/app/templates/tasks/my_tasks.html @@ -4,72 +4,103 @@ {% block content %}
- -
-
-
-
-
-
-
-
- -
-
-

My Tasks

-

Tasks assigned to you and tasks you've created

-
-
-
-
- + +
+
+
+
+
+

+ + My Tasks +

+ {{ tasks|length }} total +
+
- +
-
-
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'todo')|list|length }}
- To Do +
+
+
+
+
+
+ +
+
+
+
To Do
+
{{ tasks|selectattr('status', 'equalto', 'todo')|list|length }}
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'in_progress')|list|length }}
- In Progress +
+
+
+
+
+
+
+
+ +
+
+
+
In Progress
+
{{ tasks|selectattr('status', 'equalto', 'in_progress')|list|length }}
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'review')|list|length }}
- Review +
+
+
+
+
+
+
+
+ +
+
+
+
Review
+
{{ tasks|selectattr('status', 'equalto', 'review')|list|length }}
-
-
-
-
{{ tasks|selectattr('status', 'equalto', 'done')|list|length }}
- Completed +
+
+
+
+
+
+
+
+ +
+
+
+
Completed
+
{{ tasks|selectattr('status', 'equalto', 'done')|list|length }}
diff --git a/templates/clients/list.html b/templates/clients/list.html index baa6fc1b..4b2aa6ca 100644 --- a/templates/clients/list.html +++ b/templates/clients/list.html @@ -7,50 +7,19 @@
-

- Clients -

- {% if current_user.is_admin %} - - New Client - - {% endif %} -
-
-
- - -
-
-
-
-
- Filters -
+
+

+ + Clients +

+ {{ clients|length }} total
-
-
-
- - -
-
- - -
-
- - -
-
+
+ {% if current_user.is_admin %} + + New Client + + {% endif %}
@@ -59,17 +28,28 @@
-
-
-
- Client List ({{ clients|length }}) -
+
+
+
+
+ All Clients +
+
+
+ + + + +
+
+
-
+
{% if clients %}
- - +
+ @@ -83,7 +63,7 @@
{% for client in clients %} - +
Name Contact Person
{{ client.name }} @@ -109,17 +89,18 @@
{% endif %}
- {{ client.total_projects }} + {{ client.total_projects }} {% if client.active_projects > 0 %}
{{ client.active_projects }} active {% endif %}
- {% if client.status == 'active' %} - Active - {% else %} - Inactive - {% endif %} + {% set status_map = { + 'active': {'bg': 'bg-success', 'label': 'Active'}, + 'inactive': {'bg': 'bg-secondary', 'label': 'Inactive'} + } %} + {% set sc = status_map.get(client.status, status_map['inactive']) %} + {{ sc.label }}
@@ -180,4 +161,53 @@

No Clients Found

+ + +{% block extra_js %} + +{% endblock %} {% endblock %} diff --git a/templates/clients/view.html b/templates/clients/view.html index ad5cba5f..28dfe1b3 100644 --- a/templates/clients/view.html +++ b/templates/clients/view.html @@ -21,80 +21,157 @@

+
+
+

+ + {{ client.name }} +

+ {% set status_map = { + 'active': {'bg': 'bg-success', 'label': 'Active'}, + 'inactive': {'bg': 'bg-secondary', 'label': 'Inactive'} + } %} + {% set sc = status_map.get(client.status, status_map['inactive']) %} + {{ sc.label }} +
+
+ {% if current_user.is_admin %} + + Edit + + {% endif %} + + Back + +
+
+ + + +
+
+
+
+
+ +
+
+
Total Projects
+
{{ client.total_projects }}
+
+
+
+
+
+
+
+
+ +
+
+
Active Projects
+
{{ client.active_projects }}
+
+
+
+
+
+
+
+
+ +
+
+
Total Hours
+
{{ "%.1f"|format(client.total_hours) }}
+
+
+
+
+
+
+
+
+ +
+
+
Est. Total Cost
+
{{ "%.2f"|format(client.estimated_total_cost) }} {{ currency }}
+
+
+
-
-
-
- Client Information -
+
+
+
+ Client Information +
- Status: - {% if client.status == 'active' %} - Active - {% else %} - Inactive - {% endif %} +
+ Status + {{ sc.label }} +
- {% if client.description %}
- Description: -

{{ client.description }}

+
Description
+
{{ client.description }}
{% endif %} - {% if client.contact_person %}
- Contact Person: -

{{ client.contact_person }}

+
+ Contact Person + {{ client.contact_person }} +
{% endif %} - {% if client.email %}
- Email: -

- {{ client.email }} -

+
+ Email + {{ client.email }} +
{% endif %} - {% if client.phone %}
- Phone: -

{{ client.phone }}

+
+ Phone + {{ client.phone }} +
{% endif %} - {% if client.address %}
- Address: -

{{ client.address }}

+
Address
+
{{ client.address }}
{% endif %} - {% if client.default_hourly_rate %}
- Default Hourly Rate: -

{{ "%.2f"|format(client.default_hourly_rate) }} {{ currency }}

+
+ Default Hourly Rate + {{ "%.2f"|format(client.default_hourly_rate) }} {{ currency }} +
{% endif %}
-
-
-
- Statistics -
+
+
+
+ Statistics +
@@ -123,33 +200,31 @@

{{ "%.2f"|format(client.estimated_total_cost) }}

{% if current_user.is_admin %} -
-
-
- Actions -
+
+
+
+ Status & Actions +
{% if client.status == 'active' %} -
-
{% else %}
-
{% endif %} {% if client.total_projects == 0 %} -
+
{% endif %} @@ -160,22 +235,23 @@
-
-
-
- Projects ({{ projects|length }}) -
+
+
+
+ Projects + {{ projects|length }} total +
{% if current_user.is_admin %} - - New Project + + New Project {% endif %}
-
+
{% if projects %}
- - +
+ @@ -194,21 +270,21 @@
{{ project.name }} {% if project.description %} -
{{ project.description[:50] }}{% if project.description|length > 50 %}...{% endif %} +
{{ project.description[:50] }}{% if project.description|length > 50 %}...{% endif %} {% endif %}
Project Name Status {% if project.status == 'active' %} - Active + Active {% else %} - Archived + Archived {% endif %} {% if project.billable %} - Yes + Yes {% else %} - No + No {% endif %} @@ -229,12 +305,12 @@
{% else %}
- -

No Projects Found

-

This client doesn't have any projects yet.

- {% if current_user.is_admin %} - - Create First Project - - {% endif %} +
+ +
No projects found
+

This client doesn't have any projects yet.

+ {% if current_user.is_admin %} + + Create First Project + + {% endif %} +
{% endif %} @@ -262,4 +340,24 @@

No Projects Found

+ {% endblock %} diff --git a/templates/projects/list.html b/templates/projects/list.html index d3c1fa18..34cf068d 100644 --- a/templates/projects/list.html +++ b/templates/projects/list.html @@ -6,17 +6,77 @@
-
-

- Projects -

- {% if current_user.is_admin %} -
+
+
+

+ + Projects +

+ {{ projects|length }} total +
+
+ {% if current_user.is_admin %} - New Project + New Project + {% endif %} +
+
+
+
+ + {# Summary cards similar to invoices #} + {% set _active = 0 %} + {% set _archived = 0 %} + {% set _total_hours = 0 %} + {% for p in projects %} + {% if p.status == 'active' %}{% set _active = _active + 1 %}{% endif %} + {% if p.status == 'archived' %}{% set _archived = _archived + 1 %}{% endif %} + {% set _total_hours = _total_hours + (p.total_hours or 0) %} + {% endfor %} +
+
+
+
+
+
+
Total Projects
+
{{ projects|length }}
+
+
+
+
+
+
+
+
+
+
Active
+
{{ _active }}
+
+
+
+
+
+
+
+
+
+
Archived
+
{{ _archived }}
+
+
+
+
+
+
+
+
+
+
Total Hours
+
{{ '%.1f'|format(_total_hours) }}h
+
- {% endif %}
@@ -71,24 +131,35 @@
-
-
-
- Projects ({{ projects|length }}) -
+
+
+
+
+ All Projects +
+
+
+ + + + +
+
+
-
+
{% if projects %}
- - +
+ - - - - - - + + + + + + @@ -105,13 +176,13 @@
ProjectClientStatusHoursRateActionsProjectClientStatusHoursRateActions
- {{ project.client }} + {{ project.client }} {% if project.status == 'active' %} - Active + Active {% else %} - Archived + Archived {% endif %} @@ -128,7 +199,7 @@
{% if project.hourly_rate %} - ${{ "%.2f"|format(project.hourly_rate) }}/h + {{ currency }}{{ "%.2f"|format(project.hourly_rate) }}/h {% else %} - {% endif %} @@ -158,16 +229,16 @@
{% else %}
-
- +
+ +
No projects found
+

Create your first project to get started.

+ {% if current_user.is_admin %} + + Create Project + + {% endif %}
-
No projects found
-

Create your first project to get started

- {% if current_user.is_admin %} - - Create Project - - {% endif %}
{% endif %}
@@ -208,74 +279,112 @@