feat(tasks): add overdue filter; fix(ui): solid light-mode dropdown/navbar

- Backend:
  - Accept `overdue` query param in `tasks.list_tasks` and `tasks.my_tasks`
  - Filter tasks where `due_date < today` and status in ['todo','in_progress','review']
  - Use local date via `now_in_app_timezone().date()` to respect app timezone

- UI:
  - Add “Show overdue only” checkbox to `tasks/list.html` and `tasks/my_tasks.html`
  - Preserve checkbox state via template context

- Styles:
  - Enforce opaque white backgrounds for light-mode navbar and mobile navbar
  - Disable backdrop blur to prevent bleed-through
  - Normalize light-mode dropdown menu colors/borders for readability
  - Add light-theme fallback for `--bs-body-bg`
  - Tweak mobile navbar dropdown borders/shadows

Files:
- app/routes/tasks.py
- app/templates/tasks/list.html
- app/templates/tasks/my_tasks.html
- app/static/base.css
- app/static/mobile.css

Testing:
- Tasks and My Tasks: check “Show overdue only” → only overdue, non-completed/non-cancelled tasks appear
- Light mode: navbar and dropdowns show solid white backgrounds with clear borders/hover states
- Mobile: collapsed navbar menu is full-width, opaque, readable
This commit is contained in:
Dries Peeters
2025-09-05 10:27:24 +02:00
parent 34d4a38231
commit ae38d436d0
5 changed files with 92 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ from app.models import Task, Project, User, TimeEntry
from datetime import datetime, date
from decimal import Decimal
from app.utils.db import safe_commit
from app.utils.timezone import now_in_app_timezone
tasks_bp = Blueprint('tasks', __name__)
@@ -18,6 +19,8 @@ def list_tasks():
project_id = request.args.get('project_id', type=int)
assigned_to = request.args.get('assigned_to', type=int)
search = request.args.get('search', '').strip()
overdue_param = request.args.get('overdue', '').strip().lower()
overdue = overdue_param in ['1', 'true', 'on', 'yes']
query = Task.query
@@ -43,6 +46,14 @@ def list_tasks():
)
)
# Overdue filter (uses application's local date)
if overdue:
today_local = now_in_app_timezone().date()
query = query.filter(
Task.due_date < today_local,
Task.status.in_(['todo', 'in_progress', 'review'])
)
# Show user's tasks first, then others
if not current_user.is_admin:
query = query.filter(
@@ -72,7 +83,8 @@ def list_tasks():
priority=priority,
project_id=project_id,
assigned_to=assigned_to,
search=search
search=search,
overdue=overdue
)
@tasks_bp.route('/tasks/create', methods=['GET', 'POST'])
@@ -339,6 +351,8 @@ def my_tasks():
project_id = request.args.get('project_id', type=int)
search = request.args.get('search', '').strip()
task_type = request.args.get('task_type', '') # '', 'assigned', 'created'
overdue_param = request.args.get('overdue', '').strip().lower()
overdue = overdue_param in ['1', 'true', 'on', 'yes']
query = Task.query
@@ -374,6 +388,14 @@ def my_tasks():
)
)
# Overdue filter (uses application's local date)
if overdue:
today_local = now_in_app_timezone().date()
query = query.filter(
Task.due_date < today_local,
Task.status.in_(['todo', 'in_progress', 'review'])
)
tasks = query.order_by(
Task.priority.desc(),
Task.due_date.asc(),
@@ -392,7 +414,8 @@ def my_tasks():
priority=priority,
project_id=project_id,
search=search,
task_type=task_type
task_type=task_type,
overdue=overdue
)
@tasks_bp.route('/tasks/overdue')

View File

@@ -23,6 +23,8 @@
--card-spacing: 1.5rem;
--mobile-section-spacing: 1.5rem;
--mobile-card-spacing: 1rem;
/* Bootstrap interop (light theme fallback) */
--bs-body-bg: #ffffff;
}
/* Dark theme variables */
@@ -597,8 +599,10 @@ main {
/* Enhanced Navigation Layout */
.navbar {
background: white !important;
backdrop-filter: blur(10px);
background: #ffffff !important;
background-color: #ffffff !important; /* ensure solid fill */
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important; /* avoid translucency */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border-bottom: 1px solid var(--border-color);
padding: 1rem 0;
@@ -611,6 +615,13 @@ main {
background: #0b1220 !important;
border-bottom-color: var(--border-color);
}
[data-theme="light"] .navbar,
html[data-theme="light"] .navbar {
background: #ffffff !important;
background-color: #ffffff !important;
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
}
[data-theme="dark"] .navbar-collapse {
background: #0b1220 !important;
}
@@ -921,6 +932,36 @@ h6 { font-size: 1rem; }
[data-theme="dark"] .dropdown-item { color: var(--text-secondary) !important; }
[data-theme="dark"] .dropdown-item:hover { background-color: #111827 !important; color: var(--text-primary) !important; }
/* Light mode dropdown fixes (ensure solid background and proper contrast) */
[data-theme="light"] .dropdown-menu,
.navbar .dropdown-menu {
--bs-dropdown-bg: #ffffff;
--bs-dropdown-link-color: var(--text-primary);
--bs-dropdown-link-hover-bg: var(--light-color);
--bs-dropdown-link-hover-color: var(--text-primary);
--bs-dropdown-border-color: var(--border-color);
background-color: #ffffff !important;
border: 1px solid var(--border-color);
box-shadow: var(--card-shadow-hover);
}
[data-theme="light"] .dropdown-item,
.navbar .dropdown-item {
color: var(--text-primary) !important;
}
[data-theme="light"] .dropdown-item:hover,
.navbar .dropdown-item:hover {
background-color: var(--light-color) !important;
color: var(--text-primary) !important;
}
[data-theme="light"] .dropdown-divider,
.navbar .dropdown-divider {
background-color: var(--border-color) !important;
opacity: 1;
}
/* Ensure dropdowns inside cards stack above adjacent content */
.card .dropdown,
.mobile-card .dropdown {

View File

@@ -234,7 +234,10 @@
.navbar {
min-height: var(--mobile-nav-height);
padding: 0.75rem 0;
background: white !important;
background: #ffffff !important;
background-color: #ffffff !important;
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
@@ -257,7 +260,7 @@
}
.navbar-collapse {
background: var(--bs-body-bg);
background: var(--bs-body-bg, #ffffff);
border-top: 1px solid var(--border-color);
margin-top: 0.75rem;
padding: 1rem 0;
@@ -302,7 +305,7 @@
float: none;
width: 100%;
margin: 0;
border: 2px solid var(--primary-color) !important;
border: 1px solid var(--border-color) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.25) !important;
background: var(--bs-dropdown-bg, #ffffff) !important;
background-color: var(--bs-dropdown-bg, #ffffff) !important; /* ensure solid bg */
@@ -406,7 +409,7 @@
display: block !important;
transform: none !important;
background: var(--bs-dropdown-bg, #ffffff) !important;
border: 2px solid var(--primary-color) !important;
border: 1px solid var(--border-color) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.25) !important;
}
@@ -416,7 +419,7 @@
.navbar .dropdown-menu[data-bs-popper="static"] {
display: block !important;
background: var(--bs-dropdown-bg, #ffffff) !important;
border: 2px solid var(--primary-color) !important;
border: 1px solid var(--border-color) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.25) !important;
z-index: 9999 !important;
}

View File

@@ -150,6 +150,14 @@
{% endfor %}
</select>
</div>
<div class="col-md-2 col-sm-6 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="overdue" name="overdue" {% if overdue %}checked{% endif %}>
<label class="form-check-label" for="overdue">
Show overdue only
</label>
</div>
</div>
<div class="col-12">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">

View File

@@ -147,6 +147,14 @@
<option value="created" {% if task_type == 'created' %}selected{% endif %}>Created by Me</option>
</select>
</div>
<div class="col-md-2 col-sm-6 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="overdue" name="overdue" {% if overdue %}checked{% endif %}>
<label class="form-check-label" for="overdue">
Show overdue only
</label>
</div>
</div>
<div class="col-12">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">