Files
TimeTracker/app/templates/tasks/list.html
T
Dries Peeters f5c3c3f59f fix: resolve keyboard shortcut conflicts and notification errors
Fixed multiple issues with keyboard shortcuts and browser notifications:

Keyboard Shortcuts:
- Fixed Ctrl+/ not working to focus search input
- Resolved conflict between three event handlers (base.html, commands.js, keyboard-shortcuts-advanced.js)
- Changed inline handler from Ctrl+K to Ctrl+/ to avoid command palette conflict
- Updated search bar UI badge to display Ctrl+/ instead of Ctrl+K
- Removed conflicting ? key handler from commands.js (now uses Shift+? for shortcuts panel)
- Improved key detection to properly handle special characters like / and ?
- Added debug logging for troubleshooting keyboard events

Final keyboard mapping:
- Ctrl+K: Open Command Palette
- Ctrl+/: Focus Search Input
- Shift+?: Show All Keyboard Shortcuts
- Esc: Close Modals/Panels

Notification System:
- Fixed "right-hand side of 'in' should be an object" error in smart-notifications.js
- Changed notification permission request to follow browser security policies
- Permission now checked silently on load, only requested on user interaction
- Added "Enable Notifications" banner in notification center panel
- Fixed service worker sync check to properly verify registration object

Browser Compatibility:
- All fixes respect browser security policies for notification permissions
- Graceful degradation when service worker features unavailable
- Works correctly on Chrome, Firefox, Safari, and Edge

Files modified:
- app/static/enhanced-search.js
- app/static/keyboard-shortcuts-advanced.js
- app/static/smart-notifications.js
- app/templates/base.html
- app/static/commands.js

Closes issues with keyboard shortcuts not responding and browser console errors.
2025-10-20 13:00:39 +02:00

173 lines
10 KiB
HTML

{% extends "base.html" %}
{% from "components/ui.html" import page_header, stat_card, badge %}
{% block content %}
{% set breadcrumbs = [
{'text': 'Tasks'}
] %}
{{ page_header(
icon_class='fas fa-tasks',
title_text='Tasks',
subtitle_text='Manage your tasks here',
breadcrumbs=breadcrumbs,
actions_html='<a href="' + url_for("tasks.create_task") + '" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors"><i class="fas fa-plus mr-2"></i>Create Task</a>'
) }}
<!-- Task Summary Cards -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 stagger-animation">
{% set todo_count = tasks|selectattr('status', 'equalto', 'todo')|list|length %}
{% set in_progress_count = tasks|selectattr('status', 'equalto', 'in_progress')|list|length %}
{% set review_count = tasks|selectattr('status', 'equalto', 'review')|list|length %}
{% set done_count = tasks|selectattr('status', 'equalto', 'done')|list|length %}
{{ stat_card('To Do', todo_count, 'fas fa-list', 'slate-500') }}
{{ stat_card('In Progress', in_progress_count, 'fas fa-spinner', 'blue-500') }}
{{ stat_card('In Review', review_count, 'fas fa-eye', 'amber-500') }}
{{ stat_card('Completed', done_count, 'fas fa-check-circle', 'green-500') }}
</div>
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow mb-6">
<h2 class="text-lg font-semibold mb-4">Filter Tasks</h2>
<form method="GET" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" data-filter-form>
<div class="lg:col-span-1">
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Search</label>
<input type="text" name="search" id="search" value="{{ search or '' }}" class="form-input">
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Status</label>
<select name="status" id="status" class="form-input">
<option value="">All</option>
<option value="todo" {% if status == 'todo' %}selected{% endif %}>To Do</option>
<option value="in_progress" {% if status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="review" {% if status == 'review' %}selected{% endif %}>Review</option>
<option value="done" {% if status == 'done' %}selected{% endif %}>Done</option>
<option value="cancelled" {% if status == 'cancelled' %}selected{% endif %}>Cancelled</option>
</select>
</div>
<div>
<label for="priority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Priority</label>
<select name="priority" id="priority" class="form-input">
<option value="">All</option>
<option value="low" {% if priority == 'low' %}selected{% endif %}>Low</option>
<option value="medium" {% if priority == 'medium' %}selected{% endif %}>Medium</option>
<option value="high" {% if priority == 'high' %}selected{% endif %}>High</option>
<option value="urgent" {% if priority == 'urgent' %}selected{% endif %}>Urgent</option>
</select>
</div>
<div>
<label for="project_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Project</label>
<select name="project_id" id="project_id" class="form-input">
<option value="">All</option>
{% for project in projects %}
<option value="{{ project.id }}" {% if project_id == project.id %}selected{% endif %}>{{ project.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="assigned_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Assigned To</label>
<select name="assigned_to" id="assigned_to" class="form-input">
<option value="">All</option>
{% for user in users %}
<option value="{{ user.id }}" {% if assigned_to == user.id %}selected{% endif %}>{{ user.display_name }}</option>
{% endfor %}
</select>
</div>
<div class="flex items-center pt-5">
<input type="checkbox" name="overdue" id="overdue" value="1" {% if overdue %}checked{% endif %} class="h-4 w-4 rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<label for="overdue" class="ml-2 block text-sm text-gray-900 dark:text-gray-300">Overdue only</label>
</div>
<div class="col-span-full flex justify-end">
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg">Filter</button>
</div>
</form>
</div>
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow overflow-x-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">
{{ tasks|length }} task{{ 's' if tasks|length != 1 else '' }} found
</h3>
<div class="flex gap-2">
<button type="button" class="px-3 py-1.5 text-sm bg-background-light dark:bg-background-dark rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Export to CSV">
<i class="fas fa-download mr-1"></i> Export
</button>
<button type="button" class="px-3 py-1.5 text-sm bg-background-light dark:bg-background-dark rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Bulk actions">
<i class="fas fa-check-square mr-1"></i> Bulk
</button>
</div>
</div>
<table class="table table-zebra w-full text-left" data-enhanced>
<thead class="border-b border-border-light dark:border-border-dark">
<tr>
<th class="p-4" data-sortable>Name</th>
<th class="p-4" data-sortable>Project</th>
<th class="p-4" data-sortable>Priority</th>
<th class="p-4" data-sortable>Status</th>
<th class="p-4 table-number" data-sortable>Due</th>
<th class="p-4 table-number" data-sortable>Progress</th>
<th class="p-4">Actions</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr class="border-b border-border-light dark:border-border-dark">
<td class="p-4">{{ task.name }}</td>
<td class="p-4">{{ task.project.name }}</td>
<td class="p-4">
{% set p = task.priority %}
{% set pcls = {'low':'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300',
'medium':'bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-300',
'high':'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300',
'urgent':'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'}[p] if p in ['low','medium','high','urgent'] else 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300' %}
<span class="px-2 py-1 rounded-full text-xs font-medium {{ pcls }}">{{ task.priority_display }}</span>
</td>
<td class="p-4">
{% set s = task.status %}
{% set scls = {'todo':'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300',
'in_progress':'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300',
'review':'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300',
'done':'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300',
'cancelled':'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-200'}[s] if s in ['todo','in_progress','review','done','cancelled'] else 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300' %}
<span class="px-2 py-1 rounded-full text-xs font-medium {{ scls }}">{{ task.status_display }}</span>
</td>
<td class="p-4 table-number">
{% if task.due_date %}
{% set overdue = task.is_overdue %}
<span class="chip {{ 'chip-danger' if overdue else 'chip-neutral' }}">{{ task.due_date.strftime('%Y-%m-%d') }}</span>
{% else %}
<span class="text-text-muted-light dark:text-text-muted-dark"></span>
{% endif %}
</td>
<td class="p-4 table-number">
{% set pct = task.progress_percentage or 0 %}
<div class="w-28 h-2 bg-gray-200 dark:bg-gray-700 rounded">
<div class="h-2 rounded {{ 'bg-emerald-500' if pct>=100 else 'bg-primary' }}" style="width: {{ [pct,100]|min }}%"></div>
</div>
</td>
<td class="p-4">
<a href="{{ url_for('tasks.view_task', task_id=task.id) }}" class="text-primary hover:underline">View</a>
</td>
</tr>
{% else %}
{% endfor %}
</tbody>
</table>
{% if not tasks %}
{% from "components/ui.html" import empty_state %}
{% set actions %}
<a href="{{ url_for('tasks.create_task') }}" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors">
<i class="fas fa-plus mr-2"></i>Create Your First Task
</a>
<a href="{{ url_for('main.help') }}" class="bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-4 py-2 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">
<i class="fas fa-question-circle mr-2"></i>Learn More
</a>
{% endset %}
{{ empty_state('fas fa-tasks', 'No Tasks Found', 'Get started by creating your first task to organize your work and track progress.', actions) }}
{% endif %}
</tbody>
</table>
</div>
{% endblock %}
{% block content_after %}{% endblock %}