mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-05 19:20:21 -06:00
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:
@@ -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')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user