diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index b622d85..2aa9d24 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html
index fedc41e..d5191d4 100644
--- a/core/templates/core/payroll_dashboard.html
+++ b/core/templates/core/payroll_dashboard.html
@@ -65,7 +65,7 @@
-
+
+
+
+
@@ -463,6 +475,7 @@ document.addEventListener('DOMContentLoaded', function() {
const totals = {{ chart_totals_json|safe }};
const projectData = {{ project_chart_json|safe }};
const allOtData = {{ overtime_data_json|default:"[]"|safe }};
+ const otHistoryTotals = {{ ot_history_json|default:"[]"|safe }};
// Overtime Modal Logic
const otModalEl = document.getElementById('priceOTModal');
@@ -612,6 +625,31 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
});
+
+ // Chart 3: Overtime History (New)
+ new Chart(document.getElementById('otHistoryChart'), {
+ type: 'bar',
+ data: {
+ labels: labels,
+ datasets: [{
+ label: 'Overtime Paid (R)',
+ data: otHistoryTotals,
+ backgroundColor: '#f59e0b',
+ borderRadius: 4
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: { legend: { display: false } },
+ scales: {
+ y: {
+ beginAtZero: true,
+ ticks: { callback: v => 'R ' + v.toLocaleString() }
+ }
+ }
+ }
+ });
});
diff --git a/core/templates/core/work_log_list.html b/core/templates/core/work_log_list.html
index 78cb1d9..a53b1a8 100644
--- a/core/templates/core/work_log_list.html
+++ b/core/templates/core/work_log_list.html
@@ -3,6 +3,10 @@
{% block title %}Work Log History | LabourFlow{% endblock %}
+{% block head %}
+
+{% endblock %}
+
{% block content %}
@@ -343,9 +362,51 @@
.cal-day--selected { background-color: rgba(13, 110, 253, 0.08) !important; box-shadow: inset 0 0 0 2px var(--bs-primary); }
-{% if view_mode == 'calendar' %}
-{% endif %}
{% endblock %}
\ No newline at end of file
diff --git a/core/views.py b/core/views.py
index cb7ca5d..91158f9 100644
--- a/core/views.py
+++ b/core/views.py
@@ -359,6 +359,21 @@ def work_log_list(request):
total_amount = 0
combined_records = []
+ # Prepare Chart Data (Overtime) - Admin only
+ ot_chart_labels = []
+ ot_chart_data = []
+
+ if user_is_admin:
+ from django.db.models.functions import TruncMonth
+ ot_stats = adjustments.filter(type='OVERTIME') \
+ .annotate(month=TruncMonth('date')) \
+ .values('month') \
+ .annotate(total=Sum('amount')) \
+ .order_by('month')
+
+ ot_chart_labels = [s['month'].strftime('%b %Y') for s in ot_stats]
+ ot_chart_data = [float(s['total']) for s in ot_stats]
+
# Process Logs
for log in logs:
record = {
@@ -431,6 +446,8 @@ def work_log_list(request):
'selected_payment_status': payment_status,
'target_worker': target_worker,
'view_mode': view_mode,
+ 'ot_chart_labels': json.dumps(ot_chart_labels),
+ 'ot_chart_data': json.dumps(ot_chart_data),
}
if view_mode == 'calendar':
@@ -777,6 +794,8 @@ def payroll_dashboard(request):
all_project_names = list(Project.objects.values_list('name', flat=True).order_by('name'))
project_monthly = {name: [] for name in all_project_names}
+ ot_history_totals = [] # Overtime history
+
for year, month in chart_months:
chart_labels.append(f"{calendar.month_abbr[month]} {year}")
@@ -789,6 +808,14 @@ def payroll_dashboard(request):
date__gte=month_start, date__lte=month_end
).aggregate(total=Sum('amount'))['total'] or 0
chart_totals.append(float(month_paid))
+
+ # Overtime paid this month
+ ot_month_total = PayrollAdjustment.objects.filter(
+ type='OVERTIME',
+ date__gte=month_start,
+ date__lte=month_end
+ ).aggregate(total=Sum('amount'))['total'] or 0
+ ot_history_totals.append(float(ot_month_total))
# Per-project labour cost this month (from work logs × day rates)
month_logs = WorkLog.objects.filter(
@@ -826,6 +853,7 @@ def payroll_dashboard(request):
'chart_totals_json': json.dumps(chart_totals),
'project_chart_json': json.dumps(project_chart_data),
'overtime_data_json': json.dumps(all_ot_data),
+ 'ot_history_json': json.dumps(ot_history_totals),
}
return render(request, 'core/payroll_dashboard.html', context)