HH/templates/dashboard/employee_evaluation_charts.html
ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

663 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{% trans "Evaluation Charts" %} - PX360{% endblock %}
{% block extra_css %}
<style>
:root {
--hh-navy: #005696;
--hh-blue: #007bbd;
--hh-light: #eef6fb;
--hh-slate: #64748b;
--hh-success: #10b981;
--hh-warning: #f59e0b;
--hh-danger: #ef4444;
}
.page-header-gradient {
background: linear-gradient(135deg, var(--hh-navy) 0%, #0069a8 50%, var(--hh-blue) 100%);
color: white;
padding: 2rem 2.5rem;
border-radius: 1rem;
margin-bottom: 2rem;
box-shadow: 0 10px 15px -3px rgba(0, 86, 150, 0.2);
position: relative;
overflow: hidden;
}
.page-header-gradient::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
}
.form-select-px360 {
width: 100%;
padding: 0.625rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
font-size: 0.875rem;
transition: all 0.2s ease;
background: white;
}
.form-select-px360:focus {
outline: none;
border-color: var(--hh-navy);
box-shadow: 0 0 0 3px rgba(0, 86, 150, 0.1);
}
.chart-card {
background: white;
border: 2px solid #e2e8f0;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.chart-card:hover {
border-color: var(--hh-navy);
box-shadow: 0 10px 25px -5px rgba(0, 86, 150, 0.15);
}
.chart-card-header {
padding: 1rem 1.5rem;
border-bottom: 2px solid #e2e8f0;
background: linear-gradient(to right, #f8fafc, #f1f5f9);
display: flex;
align-items: center;
gap: 0.75rem;
}
.chart-card-body {
padding: 1rem;
}
.comparison-data-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: 0.875rem;
}
.comparison-data-table th,
.comparison-data-table td {
padding: 0.75rem 1rem;
text-align: center;
border-bottom: 1px solid #e2e8f0;
white-space: nowrap;
}
.comparison-data-table th {
background: #f8fafc;
font-weight: 700;
color: #1e293b;
border-bottom: 2px solid #cbd5e1;
}
.comparison-data-table td.sticky-col {
position: sticky;
left: 0;
background: white;
font-weight: 600;
text-align: left;
z-index: 5;
border-right: 2px solid #e2e8f0;
min-width: 180px;
}
.comparison-data-table .category-row td {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
font-weight: 700;
color: #005696;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.5rem 1rem;
border-bottom: 2px solid #cbd5e1;
border-top: 1px solid #e2e8f0;
}
.comparison-data-table .category-row:first-child td {
border-top: none;
}
@media (max-width: 768px) {
.comparison-data-table td.sticky-col {
min-width: 140px;
}
.comparison-data-table th,
.comparison-data-table td {
padding: 0.5rem;
font-size: 0.75rem;
}
}
@media print {
.page-header-gradient { box-shadow: none; }
.chart-card { break-inside: avoid; border: 1px solid #ccc; box-shadow: none; }
.no-print { display: none !important; }
}
</style>
{% endblock %}
{% block content %}
<div class="px-6 py-4">
<!-- Page Header -->
<div class="page-header-gradient">
<div class="flex justify-between items-center">
<div>
<h1 class="text-2xl font-bold flex items-center gap-3">
<i data-lucide="bar-chart-2" class="w-8 h-8"></i>
{% trans "Employee Evaluation Charts & Comparison" %}
</h1>
<p class="mt-1 opacity-90">
{% trans "From:" %} {{ evaluation_data.start_date|date:"d M Y" }}
{% trans "To:" %} {{ evaluation_data.end_date|date:"d M Y" }}
</p>
</div>
<div class="text-right">
<p class="text-xs opacity-70 uppercase tracking-wider">{% trans "Last Updated" %}</p>
<p class="text-sm font-bold">{% now "j M Y, H:i" %}</p>
</div>
</div>
</div>
<!-- Filters -->
<div class="bg-white border-2 border-gray-200 rounded-xl mb-6 p-6 no-print">
<div class="flex items-center gap-2 mb-4">
<i data-lucide="filter" class="w-5 h-5 text-navy"></i>
<h3 class="font-bold text-navy">{% trans "Filters" %}</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
<div>
<label class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">{% trans "Date Range" %}</label>
<select class="form-select-px360" id="dateRange" onchange="toggleCustomDates()">
<option value="7d" {% if date_range == '7d' %}selected{% endif %}>{% trans "Last 7 Days" %}</option>
<option value="30d" {% if date_range == '30d' %}selected{% endif %}>{% trans "Last 30 Days" %}</option>
<option value="90d" {% if date_range == '90d' %}selected{% endif %}>{% trans "Last 90 Days" %}</option>
<option value="this_month" {% if date_range == 'this_month' %}selected{% endif %}>{% trans "This Month" %}</option>
<option value="last_month" {% if date_range == 'last_month' %}selected{% endif %}>{% trans "Last Month" %}</option>
<option value="quarter" {% if date_range == 'quarter' %}selected{% endif %}>{% trans "This Quarter" %}</option>
<option value="year" {% if date_range == 'year' %}selected{% endif %}>{% trans "This Year" %}</option>
<option value="custom" {% if date_range == 'custom' %}selected{% endif %}>{% trans "Custom Range" %}</option>
</select>
<div id="customDateFields" class="mt-2 space-y-2 {% if date_range != 'custom' %}hidden{% endif %}">
<div>
<label class="block text-xs font-semibold text-slate mb-1">{% trans "From" %}</label>
<input type="date" id="customStart" class="form-select-px360 w-full"
value="{% if custom_start %}{{ custom_start|date:'Y-m-d' }}{% endif %}">
</div>
<div>
<label class="block text-xs font-semibold text-slate mb-1">{% trans "To" %}</label>
<input type="date" id="customEnd" class="form-select-px360 w-full"
value="{% if custom_end %}{{ custom_end|date:'Y-m-d' }}{% endif %}">
</div>
<button onclick="applyCustomDates()" class="w-full px-4 py-2 text-white rounded-xl font-bold transition text-sm" style="background: linear-gradient(135deg, var(--hh-navy), var(--hh-blue));">
{% trans "Apply" %}
</button>
</div>
</div>
<!-- Status Filter -->
<div>
<label class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">{% trans "Status" %}</label>
<select class="form-select-px360" id="statusFilter" onchange="updateFilters()">
<option value="active" {% if status_filter == 'active' %}selected{% endif %}>{% trans "Active" %}</option>
<option value="inactive" {% if status_filter == 'inactive' %}selected{% endif %}>{% trans "Inactive" %}</option>
<option value="all" {% if status_filter == 'all' %}selected{% endif %}>{% trans "All" %}</option>
</select>
</div>
{% comment %} <div>
<label class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">{% trans "Department" %}</label>
<select class="form-select-px360" id="departmentFilter" onchange="updateFilters()">
<option value="">{% trans "All Departments" %}</option>
{% for department in departments %}
<option value="{{ department.id }}" {% if selected_department_id == department.id|stringformat:"s" %}selected{% endif %}>
{{ department.name }}
</option>
{% endfor %}
</select>
</div> {% endcomment %}
<div>
<label class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">{% trans "Select Staff" %}</label>
<select class="form-select-px360" id="staffFilter" data-tomselect multiple size="1" onchange="updateFilters()">
{% for staff in staff_list %}
<option value="{{ staff.id }}" {% if staff.id|stringformat:"s" in selected_staff_ids %}selected{% endif %}>
{{ staff.first_name }} {{ staff.last_name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-200 flex gap-2 no-print">
{% comment %} <button onclick="window.print()" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-200 rounded-xl text-sm font-semibold text-slate-600 hover:bg-gray-50 transition">
<i data-lucide="printer" class="w-4 h-4"></i> {% trans "Print" %}
</button> {% endcomment %}
<a href="{% url 'dashboard:employee_evaluation' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-200 rounded-xl text-sm font-semibold text-slate-600 hover:bg-gray-50 transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Evaluation" %}
</a>
</div>
</div>
{% if evaluation_data.staff_metrics %}
<!-- ============================================ -->
<!-- COMPARISON TABLE (same as employee_evaluation) -->
<!-- ============================================ -->
<div class="bg-white border-2 border-gray-200 rounded-xl mb-8 overflow-hidden">
<div class="p-4 border-b border-gray-200 flex items-center gap-2">
<i data-lucide="table" class="w-5 h-5 text-navy"></i>
<h3 class="font-bold text-navy">{% trans "Comparison Table" %}</h3>
</div>
<div class="overflow-x-auto">
<table class="comparison-data-table">
<thead>
<tr>
<th class="sticky-col">{% trans "Metric" %}</th>
{% for staff in evaluation_data.staff_metrics %}
<th>{{ staff.name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr class="category-row">
<td colspan="{{ evaluation_data.staff_metrics|length|add:1 }}">
<span class="font-bold text-navy">{% trans "RESPONSE TIME" %}</span>
</td>
</tr>
<tr data-metric="response_24h">
<td class="sticky-col">{% trans "24h Response Rate" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaints_response_time.percentages.24h }}%</td>
{% endfor %}
</tr>
<tr data-metric="response_48h">
<td class="sticky-col">{% trans "48h Response Rate" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaints_response_time.percentages.48h }}%</td>
{% endfor %}
</tr>
<tr data-metric="response_overdue">
<td class="sticky-col">{% trans ">72h Overdue Rate" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaints_response_time.percentages.more_than_72h }}%</td>
{% endfor %}
</tr>
<tr class="category-row">
<td colspan="{{ evaluation_data.staff_metrics|length|add:1 }}">
<span class="font-bold text-navy">{% trans "COMPLAINTS" %}</span>
</td>
</tr>
<tr data-metric="total_complaints">
<td class="sticky-col">{% trans "Total Complaints" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaints_response_time.total }}</td>
{% endfor %}
</tr>
<tr data-metric="moh_complaints">
<td class="sticky-col">{% trans "MOH Complaints" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaint_sources.counts.MOH }}</td>
{% endfor %}
</tr>
<tr data-metric="cchi_complaints">
<td class="sticky-col">{% trans "CCHI Complaints" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaint_sources.counts.CCHI }}</td>
{% endfor %}
</tr>
<tr data-metric="patient_complaints">
<td class="sticky-col">{% trans "Patient Complaints" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.complaint_sources.counts.Patients }}</td>
{% endfor %}
</tr>
<tr class="category-row">
<td colspan="{{ evaluation_data.staff_metrics|length|add:1 }}">
<span class="font-bold text-navy">{% trans "PERFORMANCE" %}</span>
</td>
</tr>
<tr data-metric="delay_rate">
<td class="sticky-col">{% trans "Delay Rate" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.delays_activation.percentages.delays }}%</td>
{% endfor %}
</tr>
<tr data-metric="activation_rate">
<td class="sticky-col">{% trans "Activation Rate" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.delays_activation.percentages.activated }}%</td>
{% endfor %}
</tr>
<tr class="category-row">
<td colspan="{{ evaluation_data.staff_metrics|length|add:1 }}">
<span class="font-bold text-navy">{% trans "OTHER" %}</span>
</td>
</tr>
<tr data-metric="escalations">
<td class="sticky-col">{% trans "Total Escalated" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.escalated_complaints.total_escalated }}</td>
{% endfor %}
</tr>
<tr data-metric="inquiries">
<td class="sticky-col">{% trans "Total Inquiries" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.inquiries.total }}</td>
{% endfor %}
</tr>
<tr data-metric="notes">
<td class="sticky-col">{% trans "Total Notes" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.notes.total }}</td>
{% endfor %}
</tr>
<tr data-metric="completion_rate">
<td class="sticky-col">{% trans "Report Completion" %}</td>
{% for staff in evaluation_data.staff_metrics %}
<td>{{ staff.report_completion.completion_percentage }}%</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
<!-- ============================================ -->
<!-- CHARTS SECTION -->
<!-- ============================================ -->
<div class="mb-6">
<div class="flex items-center gap-2 mb-4">
<i data-lucide="bar-chart-2" class="w-5 h-5 text-navy"></i>
<h3 class="text-xl font-bold text-navy">{% trans "Visual Comparison" %}</h3>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Chart 1: Response Time Comparison -->
<div class="chart-card">
<div class="chart-card-header">
<i data-lucide="clock" class="w-5 h-5 text-cyan-600"></i>
<h4 class="font-bold text-gray-800">{% trans "Response Time Comparison" %}</h4>
</div>
<div class="chart-card-body">
<div id="chartResponseTime" style="min-height: 320px;"></div>
</div>
</div>
<!-- Chart 2: Complaint Volume by Source -->
<div class="chart-card">
<div class="chart-card-header">
<i data-lucide="layers" class="w-5 h-5 text-indigo-600"></i>
<h4 class="font-bold text-gray-800">{% trans "Complaint Volume by Source" %}</h4>
</div>
<div class="chart-card-body">
<div id="chartComplaintSources" style="min-height: 320px;"></div>
</div>
</div>
<!-- Chart 3: Delay vs Activation Rate -->
<div class="chart-card">
<div class="chart-card-header">
<i data-lucide="activity" class="w-5 h-5 text-orange-600"></i>
<h4 class="font-bold text-gray-800">{% trans "Delay vs Activation Rate" %}</h4>
</div>
<div class="chart-card-body">
<div id="chartDelayActivation" style="min-height: 320px;"></div>
</div>
</div>
<!-- Chart 4: Performance Radar -->
<div class="chart-card">
<div class="chart-card-header">
<i data-lucide="radar" class="w-5 h-5 text-purple-600"></i>
<h4 class="font-bold text-gray-800">{% trans "Performance Radar" %}</h4>
</div>
<div class="chart-card-body">
<div id="chartPerformanceRadar" style="min-height: 320px;"></div>
</div>
</div>
<!-- Chart 5: Escalations & Inquiries -->
<div class="chart-card">
<div class="chart-card-header">
<i data-lucide="trending-up" class="w-5 h-5 text-red-600"></i>
<h4 class="font-bold text-gray-800">{% trans "Escalations & Inquiries" %}</h4>
</div>
<div class="chart-card-body">
<div id="chartEscalationsInquiries" style="min-height: 320px;"></div>
</div>
</div>
<!-- Chart 6: Report Completion -->
<div class="chart-card">
<div class="chart-card-header">
<i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i>
<h4 class="font-bold text-gray-800">{% trans "Report Completion" %}</h4>
</div>
<div class="chart-card-body">
<div id="chartReportCompletion" style="min-height: 320px;"></div>
</div>
</div>
</div>
{% else %}
<!-- No Data State -->
<div class="bg-white border-2 border-gray-200 rounded-xl p-12 text-center">
<div class="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center mx-auto mb-6">
<i data-lucide="bar-chart-2" class="w-10 h-10 text-blue-500"></i>
</div>
<h3 class="text-xl font-bold text-navy mb-2">{% trans "No Data Available" %}</h3>
<p class="text-slate max-w-md mx-auto">{% trans "Select staff members and a date range to generate comparison charts." %}</p>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
{{ evaluation_data|json_script:"evaluationData" }}
<script>
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
var dataEl = document.getElementById('evaluationData');
if (!dataEl) return;
var evaluationData = JSON.parse(dataEl.textContent);
if (!evaluationData || !evaluationData.staff_metrics || evaluationData.staff_metrics.length === 0) return;
var staff = evaluationData.staff_metrics;
var colors = ['#005696', '#007bbd', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6'];
var names = staff.map(function(s) { return s.name; });
// ---- Chart 1: Response Time Comparison (Grouped Bar) ----
new ApexCharts(document.getElementById('chartResponseTime'), {
chart: { type: 'bar', height: 320, toolbar: { show: true } },
series: [
{ name: '{% trans "24h" %}', data: staff.map(function(s) { return parseFloat(s.complaints_response_time.percentages['24h']) || 0; }) },
{ name: '{% trans "48h" %}', data: staff.map(function(s) { return parseFloat(s.complaints_response_time.percentages['48h']) || 0; }) },
{ name: '{% trans "72h" %}', data: staff.map(function(s) { return parseFloat(s.complaints_response_time.percentages['72h']) || 0; }) },
{ name: '{% trans ">72h" %}', data: staff.map(function(s) { return parseFloat(s.complaints_response_time.percentages.more_than_72h) || 0; }) }
],
colors: ['#10b981', '#007bbd', '#f59e0b', '#ef4444'],
xaxis: { categories: names },
yaxis: { labels: { formatter: function(v) { return v + '%'; } } },
plotOptions: { bar: { borderRadius: 4, columnWidth: '60%' } },
dataLabels: { enabled: false },
tooltip: { y: { formatter: function(v) { return v + '%'; } } },
legend: { position: 'top' }
}).render();
// ---- Chart 2: Complaint Volume by Source (Stacked Bar) ----
new ApexCharts(document.getElementById('chartComplaintSources'), {
chart: { type: 'bar', height: 320, toolbar: { show: true }, stacked: true },
series: [
{ name: 'MOH', data: staff.map(function(s) { return parseInt(s.complaint_sources.counts.MOH) || 0; }) },
{ name: 'CCHI', data: staff.map(function(s) { return parseInt(s.complaint_sources.counts.CCHI) || 0; }) },
{ name: '{% trans "Patients" %}', data: staff.map(function(s) { return parseInt(s.complaint_sources.counts.Patients) || 0; }) }
],
colors: ['#005696', '#007bbd', '#10b981'],
xaxis: { categories: names },
plotOptions: { bar: { borderRadius: 4, columnWidth: '60%' } },
dataLabels: { enabled: false },
legend: { position: 'top' }
}).render();
// ---- Chart 3: Delay vs Activation Rate (Grouped Bar) ----
new ApexCharts(document.getElementById('chartDelayActivation'), {
chart: { type: 'bar', height: 320, toolbar: { show: true } },
series: [
{ name: '{% trans "Delay Rate" %}', data: staff.map(function(s) { return parseFloat(s.delays_activation.percentages.delays) || 0; }) },
{ name: '{% trans "Activation Rate" %}', data: staff.map(function(s) { return parseFloat(s.delays_activation.percentages.activated) || 0; }) }
],
colors: ['#ef4444', '#10b981'],
xaxis: { categories: names },
yaxis: { labels: { formatter: function(v) { return v + '%'; } } },
plotOptions: { bar: { borderRadius: 4, columnWidth: '60%' } },
dataLabels: { enabled: false },
tooltip: { y: { formatter: function(v) { return v + '%'; } } },
legend: { position: 'top' }
}).render();
// ---- Chart 4: Performance Radar ----
var radarSeries = [];
for (var i = 0; i < staff.length; i++) {
var s = staff[i];
radarSeries.push({
name: s.name,
data: [
parseFloat(s.complaints_response_time.percentages['24h']) || 0,
parseFloat(s.complaints_response_time.percentages['48h']) || 0,
parseFloat(s.delays_activation.percentages.activated) || 0,
parseFloat(s.report_completion.completion_percentage) || 0,
Math.min(100, Math.max(0, 100 - (parseFloat(s.delays_activation.percentages.delays) || 0))),
Math.min(100, Math.max(0, 100 - (parseFloat(s.complaints_response_time.percentages.more_than_72h) || 0)))
]
});
}
new ApexCharts(document.getElementById('chartPerformanceRadar'), {
chart: { type: 'radar', height: 320, toolbar: { show: true } },
series: radarSeries,
colors: colors.slice(0, staff.length),
xaxis: {
categories: [
'{% trans "24h Response" %}',
'{% trans "48h Response" %}',
'{% trans "Activation" %}',
'{% trans "Completion" %}',
'{% trans "Low Delay" %}',
'{% trans "Low Overdue" %}'
]
},
yaxis: { max: 100, tickAmount: 5 },
stroke: { width: 2 },
fill: { opacity: 0.15 },
markers: { size: 3 },
legend: { position: 'top' }
}).render();
// ---- Chart 5: Escalations & Inquiries ----
new ApexCharts(document.getElementById('chartEscalationsInquiries'), {
chart: { type: 'bar', height: 320, toolbar: { show: true } },
series: [
{ name: '{% trans "Escalated" %}', data: staff.map(function(s) { return parseInt(s.escalated_complaints.total_escalated) || 0; }) },
{ name: '{% trans "Inquiries" %}', data: staff.map(function(s) { return parseInt(s.inquiries.total) || 0; }) },
{ name: '{% trans "Notes" %}', data: staff.map(function(s) { return parseInt(s.notes.total) || 0; }) }
],
colors: ['#ef4444', '#007bbd', '#8b5cf6'],
xaxis: { categories: names },
plotOptions: { bar: { borderRadius: 4, columnWidth: '60%' } },
dataLabels: { enabled: false },
legend: { position: 'top' }
}).render();
// ---- Chart 6: Report Completion (Radial Bar) ----
new ApexCharts(document.getElementById('chartReportCompletion'), {
chart: { type: 'radialBar', height: 320, toolbar: { show: true } },
series: staff.map(function(s) { return parseFloat(s.report_completion.completion_percentage) || 0; }),
labels: names,
colors: colors.slice(0, staff.length),
plotOptions: {
radialBar: {
hollow: { size: '35%' },
dataLabels: {
name: { fontSize: '12px' },
value: { fontSize: '14px', formatter: function(v) { return v + '%'; } },
total: {
show: true,
label: '{% trans "Average" %}',
formatter: function() {
var total = 0;
staff.forEach(function(s) { total += parseFloat(s.report_completion.completion_percentage) || 0; });
return (total / staff.length).toFixed(1) + '%';
}
}
}
}
},
legend: { position: 'bottom' }
}).render();
});
function toggleCustomDates() {
var dateRange = document.getElementById('dateRange').value;
var customFields = document.getElementById('customDateFields');
if (dateRange === 'custom') {
customFields.classList.remove('hidden');
} else {
customFields.classList.add('hidden');
updateFilters();
}
}
function applyCustomDates() {
var startDate = document.getElementById('customStart').value;
var endDate = document.getElementById('customEnd').value;
if (!startDate || !endDate) {
alert('{% trans "Please select both start and end dates." %}');
return;
}
if (startDate > endDate) {
alert('{% trans "Start date must be before end date." %}');
return;
}
updateFilters();
}
function updateFilters() {
var dateRange = document.getElementById('dateRange').value;
var department = document.getElementById('departmentFilter')?.value || '';
var status = document.getElementById('statusFilter')?.value || 'active';
var staffSelect = document.getElementById('staffFilter');
var selectedStaff = Array.from(staffSelect.selectedOptions).map(function(opt) { return opt.value; });
var params = new URLSearchParams();
params.append('date_range', dateRange);
if (dateRange === 'custom') {
var customStart = document.getElementById('customStart').value;
var customEnd = document.getElementById('customEnd').value;
if (customStart) params.append('custom_start', customStart);
if (customEnd) params.append('custom_end', customEnd);
}
if (department) params.append('department_id', department);
if (status && status !== 'all') params.append('status', status);
selectedStaff.forEach(function(id) { params.append('staff_ids', id); });
window.location.href = window.location.pathname + '?' + params.toString();
}
</script>
{% endblock %}