Autosave: 20260213-093348

This commit is contained in:
Flatlogic Bot 2026-02-13 09:33:50 +00:00
parent f3ada22ab3
commit 23f7726fb9
4 changed files with 131 additions and 4 deletions

View File

@ -65,7 +65,7 @@
</div>
<!-- Charts Section -->
<div class="row mb-5">
<div class="row mb-3">
<div class="col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white fw-bold small text-uppercase text-muted">Monthly Payroll Totals</div>
@ -83,6 +83,18 @@
</div>
</div>
</div>
<!-- Overtime Chart Row -->
<div class="row mb-5">
<div class="col-12">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white fw-bold small text-uppercase text-muted">Overtime History</div>
<div class="card-body">
<canvas id="otHistoryChart" height="150"></canvas>
</div>
</div>
</div>
</div>
<!-- Filter Tabs -->
<ul class="nav nav-pills mb-4">
@ -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() }
}
}
}
});
});
</script>

View File

@ -3,6 +3,10 @@
{% block title %}Work Log History | LabourFlow{% endblock %}
{% block head %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
{% endblock %}
{% block content %}
<div class="dashboard-header pb-5">
<div class="container">
@ -32,7 +36,7 @@
</div>
<!-- Filter Card -->
<div class="card border-0 shadow-sm">
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<form method="get" class="row g-3">
<input type="hidden" name="view" value="{{ view_mode }}">
@ -104,6 +108,21 @@
</div>
</div>
</div>
<!-- Overtime Chart (Admin Only) -->
{% if is_admin_user %}
<div class="card border-0 shadow-sm mb-4" id="otChartContainer" style="display: none;">
<div class="card-header bg-white fw-bold small text-uppercase text-muted">Overtime History (Monthly)</div>
<div class="card-body">
<div style="height: 200px;">
<canvas id="otHistoryChart"></canvas>
</div>
<p class="text-muted small mt-2 mb-0 fst-italic text-center">
Showing overtime costs for the current filters.
</p>
</div>
</div>
{% endif %}
</div>
</div>
@ -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); }
</style>
{% if view_mode == 'calendar' %}
<script>
document.addEventListener('DOMContentLoaded', function() {
{% if is_admin_user %}
const otLabels = {{ ot_chart_labels|safe|default:"[]" }};
const otData = {{ ot_chart_data|safe|default:"[]" }};
const chartContainer = document.getElementById('otChartContainer');
// Only show chart if we have data and more than 0 entries
if (otData.length > 0 && otData.some(val => val > 0)) {
chartContainer.style.display = 'block';
const ctx = document.getElementById('otHistoryChart');
if (ctx) {
new Chart(ctx, {
type: 'bar',
data: {
labels: otLabels,
datasets: [{
label: 'Overtime Paid (R)',
data: otData,
backgroundColor: '#f59e0b',
borderRadius: 4,
barThickness: 40
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: v => 'R ' + v.toLocaleString() }
},
x: {
grid: { display: false }
}
}
}
});
}
}
{% endif %}
{% if view_mode == 'calendar' %}
const detailData = {{ calendar_detail_json|safe }};
const panel = document.getElementById('dayDetailPanel');
const panelTitle = document.getElementById('dayDetailTitle');
@ -458,7 +519,7 @@ document.addEventListener('DOMContentLoaded', function() {
el.classList.remove('cal-day--selected');
});
});
{% endif %}
});
</script>
{% endif %}
{% endblock %}

View File

@ -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)