Files
TimeTracker/app/templates/tasks/view.html
T
Dries Peeters 0c316ac5e1 feat: Implement bulk operations and status management improvements
Major improvements:
- Add bulk operations functionality across clients, projects, and tasks
- Implement deletion and status management enhancements
- Add project code field with database migration (022)
- Improve inactive status handling for projects

Backend changes:
- Update project model with new code field and status logic
- Enhance routes for clients, projects, and tasks with bulk actions
- Add migration for project_code field (022_add_project_code_field.py)

Frontend updates:
- Refactor bulk actions widget component
- Update clients list and detail views with bulk operations
- Enhance project list, view, and kanban templates
- Improve task list, edit, view, and kanban displays
- Update base template with UI improvements
- Refine saved filters and time entry templates lists

Testing:
- Add test_project_inactive_status.py for status handling
- Update test_tasks_templates.py with new functionality

Documentation:
- Add BULK_OPERATIONS_IMPROVEMENTS.md
- Add DELETION_AND_STATUS_IMPROVEMENTS.md
- Add docs/QUICK_WINS_IMPLEMENTATION.md
- Update ALL_BUGFIXES_SUMMARY.md and IMPLEMENTATION_COMPLETE.md
2025-10-23 12:41:22 +02:00

168 lines
9.7 KiB
HTML

{% extends "base.html" %}
{% from "components/ui.html" import confirm_dialog %}
{% block content %}
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
<div>
<h1 class="text-2xl font-bold">{{ task.name }}</h1>
<p class="text-text-muted-light dark:text-text-muted-dark">Task details and history.</p>
</div>
{% if current_user.is_admin or task.created_by == current_user.id %}
<div class="flex gap-2">
<a href="{{ url_for('tasks.edit_task', task_id=task.id) }}" class="bg-primary text-white px-4 py-2 rounded-lg mt-4 md:mt-0">{{ _('Edit Task') }}</a>
{% if task.status in ['todo','review'] %}
<form method="POST" action="{{ url_for('tasks.update_task_status', task_id=task.id) }}" onsubmit="event.preventDefault(); window.showConfirm('{{ _('Start task and mark as In Progress?') }}', { title: '{{ _('Change Task Status') }}', confirmText: '{{ _('Start') }}' }).then(ok=>{ if(ok){ this.submit(); } });" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="status" value="in_progress">
<button type="submit" class="px-4 py-2 rounded-lg bg-emerald-600 text-white mt-4 md:mt-0">{{ _('Start') }}</button>
</form>
{% elif task.status == 'in_progress' %}
<form method="POST" action="{{ url_for('tasks.update_task_status', task_id=task.id) }}" onsubmit="event.preventDefault(); window.showConfirm('{{ _('Mark task as To Do?') }}', { title: '{{ _('Change Task Status') }}', confirmText: '{{ _('Change') }}' }).then(ok=>{ if(ok){ this.submit(); } });" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="status" value="todo">
<button type="submit" class="px-4 py-2 rounded-lg bg-amber-500 text-white mt-4 md:mt-0">{{ _('Pause') }}</button>
</form>
<form method="POST" action="{{ url_for('tasks.update_task_status', task_id=task.id) }}" onsubmit="event.preventDefault(); window.showConfirm('{{ _('Mark task as Done?') }}', { title: '{{ _('Complete Task') }}', confirmText: '{{ _('Complete') }}' }).then(ok=>{ if(ok){ this.submit(); } });" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="status" value="done">
<button type="submit" class="px-4 py-2 rounded-lg bg-sky-600 text-white mt-4 md:mt-0">{{ _('Complete') }}</button>
</form>
{% elif task.status == 'done' %}
<form method="POST" action="{{ url_for('tasks.update_task_status', task_id=task.id) }}" onsubmit="event.preventDefault(); window.showConfirm('{{ _('Reopen task to Review?') }}', { title: '{{ _('Reopen Task') }}', confirmText: '{{ _('Reopen') }}' }).then(ok=>{ if(ok){ this.submit(); } });" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="status" value="review">
<button type="submit" class="px-4 py-2 rounded-lg bg-gray-600 text-white mt-4 md:mt-0">{{ _('Reopen') }}</button>
</form>
{% endif %}
<button type="button" class="bg-red-600 text-white px-4 py-2 rounded-lg mt-4 md:mt-0"
onclick="document.getElementById('confirmDeleteTask-{{ task.id }}').classList.remove('hidden')">
{{ _('Delete Task') }}
</button>
<form id="confirmDeleteTask-{{ task.id }}-form" method="POST" action="{{ url_for('tasks.delete_task', task_id=task.id) }}" class="hidden">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
</form>
</div>
{% endif %}
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Left Column: Task Details -->
<div class="lg:col-span-2 space-y-6">
{% if task.description %}
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow">
<h2 class="text-lg font-semibold mb-4">Description</h2>
<div class="prose prose-sm dark:prose-invert max-w-none">{{ task.description | markdown | safe }}</div>
</div>
{% endif %}
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow">
<h2 class="text-lg font-semibold mb-4">Time Entries</h2>
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead class="border-b border-border-light dark:border-border-dark">
<tr>
<th class="p-4">Date</th>
<th class="p-4">Duration</th>
<th class="p-4">User</th>
<th class="p-4">Notes</th>
<th class="p-4">Actions</th>
</tr>
</thead>
<tbody>
{% for entry in time_entries %}
<tr class="border-b border-border-light dark:border-border-dark">
<td class="p-4">{{ entry.start_time.strftime('%Y-%m-%d') }}</td>
<td class="p-4">{{ entry.duration_formatted }}</td>
<td class="p-4">{{ entry.user.display_name }}</td>
<td class="p-4">{% if entry.notes %}<span title="{{ entry.notes }}">{{ entry.notes[:40] }}{% if entry.notes|length > 40 %}...{% endif %}</span>{% else %}-{% endif %}</td>
<td class="p-4">
<div class="flex gap-2">
<a href="{{ url_for('timer.edit_timer', timer_id=entry.id) }}" class="text-primary hover:text-primary-dark" title="{{ _('Edit entry') }}">
<i class="fas fa-edit"></i>
</a>
{% if current_user.is_admin or entry.user_id == current_user.id %}
<form id="confirmDeleteEntry-{{ entry.id }}-form" method="POST" action="{{ url_for('timer.delete_timer', timer_id=entry.id) }}" class="hidden">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
</form>
<button type="button" class="text-red-600 hover:text-red-800" title="{{ _('Delete entry') }}" onclick="document.getElementById('confirmDeleteEntry-{{ entry.id }}').classList.remove('hidden')">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="p-4 text-center text-text-muted-light dark:text-text-muted-dark">No time has been logged for this task.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Right Column: Metadata -->
<div class="lg:col-span-1 space-y-6">
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow">
<h2 class="text-lg font-semibold mb-4">Details</h2>
<div class="space-y-4">
<div>
<h3 class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">Status</h3>
<p>{{ task.status_display }}</p>
</div>
<div>
<h3 class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">Priority</h3>
<p>{{ task.priority_display }}</p>
</div>
<div>
<h3 class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">Project</h3>
<p class="flex items-center gap-2">
<span>{{ task.project.name }}</span>
{% if task.project.code_display %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-semibold tracking-wide uppercase bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-gray-200">{{ task.project.code_display }}</span>
{% endif %}
</p>
</div>
{% if task.assigned_user %}
<div>
<h3 class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">Assigned To</h3>
<p>{{ task.assigned_user.display_name }}</p>
</div>
{% endif %}
{% if task.due_date %}
<div>
<h3 class="text-sm font-medium text-text-muted-light dark:text-text-muted-dark">Due Date</h3>
<p class="{{ 'text-red-500' if task.is_overdue else '' }}">{{ task.due_date.strftime('%Y-%m-%d') }}</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% if current_user.is_admin or task.created_by == current_user.id %}
{{ confirm_dialog(
'confirmDeleteTask-' ~ task.id,
'Delete Task',
'Are you sure you want to delete this task? This action cannot be undone.',
'Delete',
'Cancel',
'danger'
) }}
{% endif %}
<!-- Delete Entry Confirmation Dialogs -->
{% for entry in time_entries %}
{% if current_user.is_admin or entry.user_id == current_user.id %}
{{ confirm_dialog(
'confirmDeleteEntry-' ~ entry.id,
'Delete Time Entry',
'Are you sure you want to delete this time entry? This action cannot be undone.',
'Delete',
'Cancel',
'danger'
) }}
{% endif %}
{% endfor %}
{% endblock %}