mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-05 11:59:42 -05:00
c93a37f126
Implement comprehensive overtime tracking feature that allows users to set their standard working hours per day and automatically calculates overtime for hours worked beyond that threshold. Core Features: - Add standard_hours_per_day field to User model (default: 8.0 hours) - Create Alembic migration (031_add_standard_hours_per_day.py) - Implement overtime calculation utilities (app/utils/overtime.py) * calculate_daily_overtime: per-day overtime calculation * calculate_period_overtime: multi-day overtime aggregation * get_daily_breakdown: detailed day-by-day analysis * get_weekly_overtime_summary: weekly overtime statistics * get_overtime_statistics: comprehensive overtime metrics User Interface: - Add "Overtime Settings" section to user settings page - Display overtime data in user reports (regular vs overtime hours) - Show "Days with Overtime" badge in reports - Add overtime analytics API endpoint (/api/analytics/overtime) - Improve input field styling with cleaner appearance (no spinners) Reports Enhancement: - Standardize form input styling across all report pages - Replace inline Tailwind classes with consistent form-input class - Add FontAwesome icons to form labels for better UX - Improve button hover states and transitions Testing: - Add comprehensive unit tests (tests/test_overtime.py) - Add smoke tests for quick validation (tests/test_overtime_smoke.py) - Test coverage for models, utilities, and various overtime scenarios Documentation: - OVERTIME_FEATURE_DOCUMENTATION.md: complete feature guide - OVERTIME_IMPLEMENTATION_SUMMARY.md: technical implementation details - docs/features/OVERTIME_TRACKING.md: quick start guide This change enables organizations to track employee overtime accurately based on individual working hour configurations, providing better insights into work patterns and resource allocation.
79 lines
3.3 KiB
HTML
79 lines
3.3 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h1 class="text-2xl font-bold">Task Report</h1>
|
|
</div>
|
|
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow mb-6">
|
|
<form method="GET" class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<label for="project_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
<i class="fas fa-project-diagram mr-1"></i>Project
|
|
</label>
|
|
<select name="project_id" id="project_id" class="form-input">
|
|
<option value="">All Projects</option>
|
|
{% for project in projects %}
|
|
<option value="{{ project.id }}" {% if selected_project == project.id %}selected{% endif %}>{{ project.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="user_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
<i class="fas fa-user mr-1"></i>User
|
|
</label>
|
|
<select name="user_id" id="user_id" class="form-input">
|
|
<option value="">All Users</option>
|
|
{% for user in users %}
|
|
<option value="{{ user.id }}" {% if selected_user == user.id %}selected{% endif %}>{{ user.display_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
<i class="fas fa-calendar mr-1"></i>Start Date
|
|
</label>
|
|
<input type="date" name="start_date" id="start_date" value="{{ start_date }}" class="form-input">
|
|
</div>
|
|
<div>
|
|
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
<i class="fas fa-calendar mr-1"></i>End Date
|
|
</label>
|
|
<input type="date" name="end_date" id="end_date" value="{{ end_date }}" class="form-input">
|
|
</div>
|
|
<div class="self-end">
|
|
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition">
|
|
<i class="fas fa-filter mr-2"></i>Filter
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow">
|
|
<table class="w-full text-left">
|
|
<thead>
|
|
<tr>
|
|
<th class="p-2">Task</th>
|
|
<th class="p-2">Project</th>
|
|
<th class="p-2">Completed At</th>
|
|
<th class="p-2">Hours</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for task_row in tasks %}
|
|
<tr class="border-b border-border-light dark:border-border-dark">
|
|
<td class="p-2">{{ task_row.task.name }}</td>
|
|
<td class="p-2">{{ task_row.project.name }}</td>
|
|
<td class="p-2">{{ task_row.completed_at.strftime('%Y-%m-%d') }}</td>
|
|
<td class="p-2">{{ "%.2f"|format(task_row.hours) }}</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="4" class="p-4 text-center">No completed tasks for the selected period.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endblock %}
|