mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-06 20:40:38 -05:00
20b7401891
Major Features: - Invoice Expenses: Allow linking billable expenses to invoices with automatic total calculations - Add expenses to invoices via "Generate from Time/Costs" workflow - Display expenses in invoice view, edit forms, and PDF exports - Track expense states (approved, invoiced, reimbursed) with automatic unlinking on invoice deletion - Update PDF generator and CSV exports to include expense line items - Enhanced PDF Invoice Editor: Complete redesign using Konva.js for visual drag-and-drop layout design - Add 40+ draggable elements (company info, invoice data, shapes, text, advanced elements) - Implement comprehensive properties panel for precise element customization (position, fonts, colors, opacity) - Add canvas toolbar with alignment tools, zoom controls, and layer management - Support keyboard shortcuts (copy/paste, duplicate, arrow key positioning) - Save designs as JSON for editing and generate clean HTML/CSS for rendering - Add real-time preview with live data - Uploads Persistence: Implement Docker volume persistence for user-uploaded files - Add app_uploads volume to all Docker Compose configurations - Ensure company logos and avatars persist across container rebuilds and restarts - Create migration script for existing installations - Update directory structure with proper permissions (755 for dirs, 644 for files) Database & Backend: - Add invoice_pdf_design_json column to settings table via Alembic migration - Extend Invoice model with expenses relationship - Update admin routes for PDF layout designer endpoints - Enhance invoice routes to handle expense linking/unlinking Frontend & UI: - Redesign PDF layout editor template with Konva.js canvas (2484 lines, major overhaul) - Update invoice edit/view templates to display and manage expenses - Add expense sections to invoice forms with unlink functionality - Enhance UI components with keyboard shortcuts support - Update multiple templates for consistency and accessibility Testing & Documentation: - Add comprehensive test suites for invoice expenses, PDF layouts, and uploads persistence - Create detailed documentation for all new features (5 new docs) - Include migration guides and troubleshooting sections Infrastructure: - Update docker-compose files (main, example, remote, remote-dev, local-test) with uploads volume - Configure pytest for new test modules - Add template filters for currency formatting and expense display This update significantly enhances TimeTracker's invoice management capabilities, improves the PDF customization experience, and ensures uploaded files persist reliably across deployments.
263 lines
11 KiB
HTML
263 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ _('Overdue Tasks') }} - Time Tracker{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mt-4">
|
|
{% from "_components.html" import page_header %}
|
|
{% set actions %}
|
|
<a href="{{ url_for('tasks.list_tasks') }}" class="btn btn-outline-light btn-sm">
|
|
<i class="fas fa-arrow-left"></i> {{ _('Back to Tasks') }}
|
|
</a>
|
|
{% endset %}
|
|
{{ page_header('fas fa-exclamation-triangle', _('Overdue Tasks'), _('Requires immediate attention') ~ ' • ' ~ (tasks|length) ~ ' ' ~ _('items'), actions) }}
|
|
|
|
<!-- Overdue Summary -->
|
|
<div class="alert alert-warning" role="alert">
|
|
<h5 class="alert-heading">
|
|
<i class="fas fa-exclamation-triangle"></i> {{ _('Overdue Tasks Detected') }}
|
|
</h5>
|
|
<p class="mb-0">
|
|
{{ _('There are') }} <strong>{{ tasks|length }}</strong> {{ _('overdue tasks that require immediate attention.') }}
|
|
{{ _('Please review and update these tasks to prevent further delays.') }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Overdue Tasks List -->
|
|
{% if tasks %}
|
|
<div class="row">
|
|
{% for task in tasks %}
|
|
<div class="col-md-6 col-lg-4 mb-3">
|
|
<div class="card h-100 task-card {{ task.priority_class }} border-danger">
|
|
<div class="card-header d-flex justify-content-between align-items-center bg-danger text-white">
|
|
<span class="badge bg-danger">
|
|
<i class="fas fa-exclamation-triangle"></i> {{ _('Overdue') }}
|
|
</span>
|
|
<span class="badge priority-badge priority-{{ task.priority }}">
|
|
{{ task.priority_display }}
|
|
</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<a href="{{ url_for('tasks.view_task', task_id=task.id) }}" class="text-decoration-none">
|
|
{{ task.name }}
|
|
</a>
|
|
</h5>
|
|
{% if task.description %}
|
|
<p class="card-text text-muted">{{ task.description[:100] }}{% if task.description|length > 100 %}...{% endif %}</p>
|
|
{% endif %}
|
|
|
|
<div class="task-meta">
|
|
<small class="text-muted">
|
|
<i class="fas fa-project-diagram"></i> {{ task.project.name }}
|
|
</small>
|
|
<br><small class="text-muted">
|
|
<i class="fas fa-user"></i>
|
|
{% if task.assigned_user %}
|
|
{{ task.assigned_user.display_name }}
|
|
{% else %}
|
|
{{ _('Unassigned') }}
|
|
{% endif %}
|
|
</small>
|
|
<br><small class="text-danger fw-bold">
|
|
<i class="fas fa-calendar"></i> {{ _('Due:') }} {{ task.due_date.strftime('%Y-%m-%d') }}
|
|
<i class="fas fa-exclamation-triangle text-warning"></i>
|
|
</small>
|
|
{% if task.estimated_hours %}
|
|
<br><small class="text-muted">
|
|
<i class="fas fa-clock"></i> {{ _('Est:') }} {{ task.estimated_hours }}h
|
|
</small>
|
|
{% endif %}
|
|
{% if task.total_hours > 0 %}
|
|
<br><small class="text-muted">
|
|
<i class="fas fa-stopwatch"></i> {{ _('Actual:') }} {{ task.total_hours }}h
|
|
</small>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if task.estimated_hours and task.total_hours > 0 %}
|
|
<div class="progress mt-2" style="height: 8px;">
|
|
<div class="progress-bar" role="progressbar"
|
|
style="width: {{ task.progress_percentage }}%"
|
|
aria-valuenow="{{ task.progress_percentage }}"
|
|
aria-valuemin="0" aria-valuemax="100">
|
|
</div>
|
|
</div>
|
|
<small class="text-muted">{{ task.progress_percentage }}% complete</small>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="btn-group btn-group-sm w-100" role="group">
|
|
<a href="{{ url_for('tasks.view_task', task_id=task.id) }}" class="btn btn-outline-primary">
|
|
<i class="fas fa-eye"></i> {{ _('View') }}
|
|
</a>
|
|
{% if current_user.is_admin or task.created_by == current_user.id %}
|
|
<a href="{{ url_for('tasks.edit_task', task_id=task.id) }}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-edit"></i> {{ _('Edit') }}
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('timer.start_timer_for_project', project_id=task.project.id, task_id=task.id) }}"
|
|
class="btn btn-outline-success">
|
|
<i class="fas fa-play"></i> {{ _('Timer') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Bulk Actions -->
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="fas fa-tools"></i> Bulk Actions</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Update Due Dates</h6>
|
|
<p class="text-muted small">Extend due dates for multiple tasks at once</p>
|
|
<button type="button" class="btn btn-outline-warning" onclick="extendDueDates()">
|
|
<i class="fas fa-calendar-plus"></i> Extend Due Dates
|
|
</button>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Priority Management</h6>
|
|
<p class="text-muted small">Adjust priorities for overdue tasks</p>
|
|
<button type="button" class="btn btn-outline-info" onclick="adjustPriorities()">
|
|
<i class="fas fa-arrow-up"></i> Adjust Priorities
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-check-circle fa-3x text-success mb-3"></i>
|
|
<h3 class="text-success">{{ _('No Overdue Tasks!') }}</h3>
|
|
<p class="text-muted">{{ _('Great job! All tasks are currently on schedule.') }}</p>
|
|
<a href="{{ url_for('tasks.list_tasks') }}" class="btn btn-primary">
|
|
<i class="fas fa-list"></i> {{ _('View All Tasks') }}
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script type="application/json" id="i18n-json-tasks-overdue">
|
|
{
|
|
"enter_new_due_date": {{ _('Enter new due date (YYYY-MM-DD):')|tojson }},
|
|
"confirm_extend_prefix": {{ _('Are you sure you want to extend the due date to')|tojson }},
|
|
"confirm_extend_suffix": {{ _('for all overdue tasks?')|tojson }},
|
|
"feature_coming": {{ _('Bulk due date update feature coming soon!')|tojson }},
|
|
"enter_new_priority": {{ _('Enter new priority (low/medium/high/urgent):')|tojson }},
|
|
"confirm_set_priority_prefix": {{ _('Are you sure you want to set priority to')|tojson }},
|
|
"confirm_set_priority_suffix": {{ _('for all overdue tasks?')|tojson }},
|
|
"feature_priority_coming": {{ _('Bulk priority update feature coming soon!')|tojson }},
|
|
"invalid_priority": {{ _('Invalid priority. Please use: low, medium, high, or urgent')|tojson }}
|
|
}
|
|
</script>
|
|
<script>
|
|
var i18n_overdue = (function(){
|
|
try { var el = document.getElementById('i18n-json-tasks-overdue'); return el ? JSON.parse(el.textContent) : {}; }
|
|
catch(e) { return {}; }
|
|
})();
|
|
|
|
async function extendDueDates() {
|
|
const newDate = prompt(i18n_overdue.enter_new_due_date || 'Enter new due date (YYYY-MM-DD):', new Date().toISOString().split('T')[0]);
|
|
if (newDate) {
|
|
var p = i18n_overdue.confirm_extend_prefix || 'Are you sure you want to extend the due date to';
|
|
var s = i18n_overdue.confirm_extend_suffix || 'for all overdue tasks?';
|
|
const confirmed = await showConfirm(
|
|
p + ' ' + newDate + ' ' + s,
|
|
{
|
|
title: 'Extend Due Dates',
|
|
confirmText: 'Extend',
|
|
cancelText: 'Cancel',
|
|
variant: 'primary'
|
|
}
|
|
);
|
|
if (confirmed) {
|
|
// This would need to be implemented with a bulk update endpoint
|
|
alert(i18n_overdue.feature_coming || 'Bulk due date update feature coming soon!');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function adjustPriorities() {
|
|
const newPriority = prompt(i18n_overdue.enter_new_priority || 'Enter new priority (low/medium/high/urgent):', 'high');
|
|
if (newPriority && ['low', 'medium', 'high', 'urgent'].includes(newPriority.toLowerCase())) {
|
|
var p2 = i18n_overdue.confirm_set_priority_prefix || 'Are you sure you want to set priority to';
|
|
var s2 = i18n_overdue.confirm_set_priority_suffix || 'for all overdue tasks?';
|
|
const confirmed = await showConfirm(
|
|
p2 + ' ' + newPriority + ' ' + s2,
|
|
{
|
|
title: 'Adjust Priorities',
|
|
confirmText: 'Update',
|
|
cancelText: 'Cancel',
|
|
variant: 'primary'
|
|
}
|
|
);
|
|
if (confirmed) {
|
|
// This would need to be implemented with a bulk update endpoint
|
|
alert(i18n_overdue.feature_priority_coming || 'Bulk priority update feature coming soon!');
|
|
}
|
|
} else if (newPriority) {
|
|
alert(i18n_overdue.invalid_priority || 'Invalid priority. Please use: low, medium, high, or urgent');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.task-card {
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.task-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.priority-badge {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.priority-low {
|
|
background-color: #28a745 !important;
|
|
}
|
|
|
|
.priority-medium {
|
|
background-color: #ffc107 !important;
|
|
color: #212529 !important;
|
|
}
|
|
|
|
.priority-high {
|
|
background-color: #fd7e14 !important;
|
|
}
|
|
|
|
.priority-urgent {
|
|
background-color: #dc3545 !important;
|
|
}
|
|
|
|
.task-meta small {
|
|
display: block;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.progress {
|
|
background-color: #e9ecef;
|
|
}
|
|
|
|
.progress-bar {
|
|
background-color: #007bff;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
flex: 1;
|
|
}
|
|
|
|
.border-danger {
|
|
border-width: 2px !important;
|
|
}
|
|
</style>
|
|
{% endblock %}
|