mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-06 20:40:38 -05:00
0c316ac5e1
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
168 lines
9.7 KiB
HTML
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 %}
|