HH/templates/dashboard/admin_evaluation.html
2026-02-25 04:47:05 +03:00

774 lines
37 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{% trans "Admin Evaluation" %} - PX360{% endblock %}
{% block page_title %}{% trans "Admin Evaluation Dashboard" %}{% endblock %}
{% block content %}
<div class="space-y-6">
<!-- Page Header -->
<header class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-navy flex items-center gap-3">
<i data-lucide="shield-check" class="w-7 h-7"></i>
{% trans "Admin Evaluation Dashboard" %}
</h1>
<p class="text-sm text-slate mt-1">{% trans "Staff performance analysis for complaints and inquiries" %}</p>
</div>
<div class="text-right">
<p class="text-xs text-slate uppercase tracking-wider">{% trans "Last Updated" %}</p>
<p class="text-sm font-bold text-navy">{% now "j M Y, H:i" %}</p>
</div>
</header>
<!-- Filters -->
<div class="card">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Date Range -->
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Date Range" %}</label>
<select class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition" id="dateRange">
<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>
</select>
</div>
<!-- Hospital Filter (PX Admins only) -->
{% if hospitals.exists %}
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Hospital" %}</label>
<select class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition" id="hospitalFilter">
<option value="">{% trans "All Hospitals" %}</option>
{% for hospital in hospitals %}
<option value="{{ hospital.id }}" {% if selected_hospital_id == hospital.id|stringformat:"s" %}selected{% endif %}>
{{ hospital.name }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
<!-- Department Filter -->
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Department" %}</label>
<select class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition disabled:bg-gray-50 disabled:cursor-not-allowed" id="departmentFilter" {% if not selected_hospital_id and not request.user.hospital %}disabled{% endif %}>
<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>
<!-- Staff Multi-Select -->
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Compare Staff" %}</label>
<select class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition h-[42px]" id="staffFilter" multiple>
{% 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>
<button id="applyStaffFilter" class="mt-2 w-full px-4 py-2 bg-light0 text-white rounded-xl font-semibold hover:bg-navy transition">
{% trans "Apply Staff Filter" %}
</button>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-wrap gap-3">
<a href="{% url 'dashboard:department_benchmarks' %}?date_range={{ date_range }}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-navy text-navy rounded-lg font-semibold hover:bg-light transition">
<i data-lucide="bar-chart-2" class="w-4 h-4"></i>
{% trans "Department Benchmarks" %}
</a>
<button onclick="exportReport('csv')" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-green-500 text-green-500 rounded-lg font-semibold hover:bg-green-50 transition">
<i data-lucide="download" class="w-4 h-4"></i>
{% trans "Export CSV" %}
</button>
<button onclick="exportReport('json')" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-blue-500 text-blue-500 rounded-lg font-semibold hover:bg-blue-50 transition">
<i data-lucide="file-code" class="w-4 h-4"></i>
{% trans "Export JSON" %}
</button>
</div>
<!-- Summary Cards -->
<div id="summaryCards" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{% if performance_data.staff_metrics %}
<div class="card stat-card">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-bold text-slate uppercase tracking-wider mb-1">{% trans "Total Staff" %}</p>
<p class="text-3xl font-bold text-navy">{{ performance_data.staff_metrics|length }}</p>
<div class="flex items-center gap-1.5 mt-2">
<i data-lucide="building" class="w-4 h-4 text-blue"></i>
<span class="text-sm text-slate">{% trans "Active Staff" %}</span>
</div>
</div>
<div class="p-3 bg-blue-50 rounded-xl">
<i data-lucide="users" class="w-6 h-6 text-blue"></i>
</div>
</div>
</div>
<div class="card stat-card">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-bold text-slate uppercase tracking-wider mb-1">{% trans "Total Complaints" %}</p>
<p class="text-3xl font-bold text-navy" id="totalComplaints">0</p>
<div class="flex items-center gap-1.5 mt-2">
<i data-lucide="trending-up" class="w-4 h-4 text-red-500"></i>
<span class="text-xs text-slate">{% trans "Requires Attention" %}</span>
</div>
</div>
<div class="p-3 bg-red-50 rounded-xl">
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-500"></i>
</div>
</div>
</div>
<div class="card stat-card">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-bold text-slate uppercase tracking-wider mb-1">{% trans "Total Inquiries" %}</p>
<p class="text-3xl font-bold text-navy" id="totalInquiries">0</p>
<div class="flex items-center gap-1.5 mt-2">
<i data-lucide="message-circle" class="w-4 h-4 text-blue"></i>
<span class="text-xs text-slate">{% trans "Open Requests" %}</span>
</div>
</div>
<div class="p-3 bg-blue-50 rounded-xl">
<i data-lucide="message-circle" class="w-6 h-6 text-blue"></i>
</div>
</div>
</div>
<div class="card stat-card">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-bold text-slate uppercase tracking-wider mb-1">{% trans "Resolution Rate" %}</p>
<p class="text-3xl font-bold text-navy" id="resolutionRate">0%</p>
<div class="flex items-center gap-1.5 mt-2">
<i data-lucide="trending-up" class="w-4 h-4 text-green-500"></i>
<span class="text-sm font-bold text-green-500">{% trans "Performance" %}</span>
</div>
</div>
<div class="p-3 bg-green-50 rounded-xl">
<i data-lucide="check-circle" class="w-6 h-6 text-green-500"></i>
</div>
</div>
</div>
{% else %}
<div class="col-span-full card">
<div class="flex flex-col items-center justify-center py-8 text-center">
<div class="bg-blue-50 w-16 h-16 rounded-full flex items-center justify-center mb-4">
<i data-lucide="info" class="w-8 h-8 text-blue"></i>
</div>
<p class="text-sm text-slate">{% trans "No staff members with assigned complaints or inquiries found in the selected time period." %}</p>
</div>
</div>
{% endif %}
</div>
<!-- Tabs -->
{% if performance_data.staff_metrics %}
<div class="card">
<div class="border-b border-slate-100">
<nav class="flex gap-1" id="evaluationTabs">
<button class="px-6 py-3 rounded-lg font-semibold bg-navy text-white" data-tab="complaints" id="complaints-tab">
{% trans "Complaints" %}
</button>
<button class="px-6 py-3 rounded-lg font-semibold text-slate hover:text-navy hover:bg-light transition" data-tab="inquiries" id="inquiries-tab">
{% trans "Inquiries" %}
</button>
</nav>
</div>
<!-- Complaints Tab Content -->
<div id="complaints-content" class="tab-content">
<!-- Charts Row 1 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="pie-chart" class="w-4 h-4"></i>
{% trans "Complaint Source Breakdown" %}
</h3>
</div>
<div class="h-[320px]">
<canvas id="complaintSourceChart"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="bar-chart-2" class="w-4 h-4"></i>
{% trans "Complaint Status Distribution" %}
</h3>
</div>
<div class="h-[320px]">
<canvas id="complaintStatusChart"></canvas>
</div>
</div>
</div>
<!-- Charts Row 2 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="clock" class="w-4 h-4"></i>
{% trans "Complaint Activation Time" %}
</h3>
<p class="text-sm text-slate">{% trans "Time from creation to assignment" %}</p>
</div>
<div class="h-[320px]">
<canvas id="complaintActivationChart"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="gauge" class="w-4 h-4"></i>
{% trans "Complaint Response Time" %}
</h3>
<p class="text-sm text-slate">{% trans "Time to first response/update" %}</p>
</div>
<div class="h-[320px]">
<canvas id="complaintResponseChart"></canvas>
</div>
</div>
</div>
<!-- Staff Comparison Table -->
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="users" class="w-4 h-4"></i>
{% trans "Staff Complaint Performance" %}
</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-light">
<tr>
<th class="px-6 py-4 text-left text-xs font-bold text-slate uppercase tracking-wider">{% trans "Staff Name" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-slate uppercase tracking-wider">{% trans "Hospital" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-slate uppercase tracking-wider">{% trans "Department" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Total" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Internal" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "External" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Open" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Resolved" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Activation ≤2h" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Response ≤24h" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for staff in performance_data.staff_metrics %}
<tr class="hover:bg-light transition">
<td class="px-6 py-4">
<a href="{% url 'dashboard:staff_performance_detail' staff.id %}?date_range={{ date_range }}" class="font-bold text-navy hover:text-blue transition group">
{{ staff.name }}
</a>
</td>
<td class="px-6 py-4 text-sm text-slate">{{ staff.hospital|default:"-" }}</td>
<td class="px-6 py-4 text-sm text-slate">{{ staff.department|default:"-" }}</td>
<td class="px-6 py-4 text-center font-bold text-navy">{{ staff.complaints.total }}</td>
<td class="px-6 py-4 text-center">{{ staff.complaints.internal }}</td>
<td class="px-6 py-4 text-center">{{ staff.complaints.external }}</td>
<td class="px-6 py-4 text-center">
<span class="px-3 py-1 rounded-full text-xs font-bold bg-yellow-100 text-yellow-600">{{ staff.complaints.status.open }}</span>
</td>
<td class="px-6 py-4 text-center">
<span class="px-3 py-1 rounded-full text-xs font-bold bg-green-100 text-green-600">{{ staff.complaints.status.resolved }}</span>
</td>
<td class="px-6 py-4 text-center">{{ staff.complaints.activation_time.within_2h }}</td>
<td class="px-6 py-4 text-center">{{ staff.complaints.response_time.within_24h }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Inquiries Tab Content -->
<div id="inquiries-content" class="tab-content hidden">
<!-- Charts Row -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="bar-chart-2" class="w-4 h-4"></i>
{% trans "Inquiry Status Distribution" %}
</h3>
</div>
<div class="h-[320px]">
<canvas id="inquiryStatusChart"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="gauge" class="w-4 h-4"></i>
{% trans "Inquiry Response Time" %}
</h3>
<p class="text-sm text-slate">{% trans "Time to first response/update" %}</p>
</div>
<div class="h-[320px]">
<canvas id="inquiryResponseChart"></canvas>
</div>
</div>
</div>
<!-- Staff Comparison Table -->
<div class="card">
<div class="card-header">
<h3 class="card-title flex items-center gap-2">
<i data-lucide="users" class="w-4 h-4"></i>
{% trans "Staff Inquiry Performance" %}
</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-light">
<tr>
<th class="px-6 py-4 text-left text-xs font-bold text-slate uppercase tracking-wider">{% trans "Staff Name" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-slate uppercase tracking-wider">{% trans "Hospital" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-slate uppercase tracking-wider">{% trans "Department" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Total" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Open" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Resolved" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Response ≤24h" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Response ≤48h" %}</th>
<th class="px-6 py-4 text-center text-xs font-bold text-slate uppercase tracking-wider">{% trans "Response ≤72h" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for staff in performance_data.staff_metrics %}
<tr class="hover:bg-light transition">
<td class="px-6 py-4 font-bold text-navy">{{ staff.name }}</td>
<td class="px-6 py-4 text-sm text-slate">{{ staff.hospital|default:"-" }}</td>
<td class="px-6 py-4 text-sm text-slate">{{ staff.department|default:"-" }}</td>
<td class="px-6 py-4 text-center font-bold text-navy">{{ staff.inquiries.total }}</td>
<td class="px-6 py-4 text-center">
<span class="px-3 py-1 rounded-full text-xs font-bold bg-yellow-100 text-yellow-600">{{ staff.inquiries.status.open }}</span>
</td>
<td class="px-6 py-4 text-center">
<span class="px-3 py-1 rounded-full text-xs font-bold bg-green-100 text-green-600">{{ staff.inquiries.status.resolved }}</span>
</td>
<td class="px-6 py-4 text-center">{{ staff.inquiries.response_time.within_24h }}</td>
<td class="px-6 py-4 text-center">{{ staff.inquiries.response_time.within_48h }}</td>
<td class="px-6 py-4 text-center">{{ staff.inquiries.response_time.within_72h }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{{ performance_data|json_script:"performanceData" }}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
// Tab switching
const tabs = document.querySelectorAll('#evaluationTabs button[data-tab]');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
const tabName = this.dataset.tab;
// Update tab buttons
tabs.forEach(t => {
t.classList.remove('bg-light0', 'text-white');
t.classList.add('text-gray-500', 'hover:bg-gray-100');
});
this.classList.remove('text-gray-500', 'hover:bg-gray-100');
this.classList.add('bg-light0', 'text-white');
// Show/hide content
contents.forEach(content => {
if (content.id === `${tabName}-content`) {
content.classList.remove('hidden');
} else {
content.classList.add('hidden');
}
});
});
});
// Helper function to get safe numeric value
function getSafeNumber(obj, path, defaultValue = 0) {
if (!obj) return defaultValue;
const parts = path.split('.');
let value = obj;
for (const part of parts) {
if (value === null || value === undefined) return defaultValue;
value = value[part];
}
if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
return defaultValue;
}
return value;
}
// Calculate and display summary card statistics
const performanceDataEl = document.getElementById('performanceData');
if (performanceDataEl) {
const performanceData = JSON.parse(performanceDataEl.textContent);
const staffMetrics = performanceData.staff_metrics || [];
let totalComplaints = 0;
let resolvedComplaints = 0;
let totalInquiries = 0;
staffMetrics.forEach(staff => {
const complaints = staff.complaints || {};
const inquiries = staff.inquiries || {};
totalComplaints += getSafeNumber(complaints, 'total', 0);
resolvedComplaints += getSafeNumber(complaints.status, 'resolved', 0);
totalInquiries += getSafeNumber(inquiries, 'total', 0);
});
const resolutionRate = totalComplaints > 0 ? Math.round((resolvedComplaints / totalComplaints) * 100) : 0;
const totalComplaintsEl = document.getElementById('totalComplaints');
const totalInquiriesEl = document.getElementById('totalInquiries');
const resolutionRateEl = document.getElementById('resolutionRate');
if (totalComplaintsEl) totalComplaintsEl.textContent = totalComplaints;
if (totalInquiriesEl) totalInquiriesEl.textContent = totalInquiries;
if (resolutionRateEl) resolutionRateEl.textContent = resolutionRate + '%';
}
// Filter change handlers
const dateRange = document.getElementById('dateRange');
const hospitalFilter = document.getElementById('hospitalFilter');
const departmentFilter = document.getElementById('departmentFilter');
const staffFilter = document.getElementById('staffFilter');
function applyFilters() {
const params = new URLSearchParams();
params.set('date_range', dateRange.value);
if (hospitalFilter && hospitalFilter.value) {
params.set('hospital_id', hospitalFilter.value);
}
if (departmentFilter && departmentFilter.value) {
params.set('department_id', departmentFilter.value);
}
if (staffFilter) {
const selectedStaff = Array.from(staffFilter.selectedOptions).map(opt => opt.value);
selectedStaff.forEach(id => params.append('staff_ids', id));
}
window.location.href = '?' + params.toString();
}
if (dateRange) {
dateRange.addEventListener('change', applyFilters);
}
if (hospitalFilter) {
hospitalFilter.addEventListener('change', applyFilters);
}
if (departmentFilter) {
departmentFilter.addEventListener('change', applyFilters);
}
const applyStaffBtn = document.getElementById('applyStaffFilter');
if (applyStaffBtn) {
applyStaffBtn.addEventListener('click', applyFilters);
}
// Initialize Charts with Chart.js
const charts = {};
// Helper function to create or update chart
function createOrUpdateChart(canvasId, config) {
const canvas = document.getElementById(canvasId);
if (!canvas) {
console.warn('Canvas element not found:', canvasId);
return;
}
// Check if chart already exists
if (charts[canvasId]) {
charts[canvasId].destroy();
delete charts[canvasId];
}
try {
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, config);
charts[canvasId] = chart;
console.log('Chart created successfully:', canvasId);
} catch (error) {
console.error('Error creating chart:', canvasId, error);
}
}
if (performanceDataEl) {
const performanceData = JSON.parse(performanceDataEl.textContent);
const staffMetrics = performanceData.staff_metrics || [];
// Aggregate complaint data for charts
let internalTotal = 0, externalTotal = 0;
let statusOpen = 0, statusInProgress = 0, statusResolved = 0, statusClosed = 0;
let activationWithin2h = 0, activationMoreThan2h = 0;
let responseWithin24h = 0, responseWithin48h = 0, responseWithin72h = 0, responseMoreThan72h = 0;
staffMetrics.forEach(staff => {
const c = staff.complaints || {};
internalTotal += getSafeNumber(c, 'internal', 0);
externalTotal += getSafeNumber(c, 'external', 0);
statusOpen += getSafeNumber(c.status, 'open', 0);
statusInProgress += getSafeNumber(c.status, 'in_progress', 0);
statusResolved += getSafeNumber(c.status, 'resolved', 0);
statusClosed += getSafeNumber(c.status, 'closed', 0);
activationWithin2h += getSafeNumber(c.activation_time, 'within_2h', 0);
activationMoreThan2h += getSafeNumber(c.activation_time, 'more_than_2h', 0);
responseWithin24h += getSafeNumber(c.response_time, 'within_24h', 0);
responseWithin48h += getSafeNumber(c.response_time, 'within_48h', 0);
responseWithin72h += getSafeNumber(c.response_time, 'within_72h', 0);
responseMoreThan72h += getSafeNumber(c.response_time, 'more_than_72h', 0);
});
// Complaint Source Chart (Pie)
if (internalTotal > 0 || externalTotal > 0) {
createOrUpdateChart('complaintSourceChart', {
type: 'pie',
data: {
labels: ['{% trans "Internal" %}', '{% trans "External" %}'],
datasets: [{
data: [internalTotal, externalTotal],
backgroundColor: ['#6366f1', '#f59e0b']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
// Complaint Status Chart (Bar)
if (statusOpen + statusInProgress + statusResolved + statusClosed > 0) {
createOrUpdateChart('complaintStatusChart', {
type: 'bar',
data: {
labels: ['{% trans "Open" %}', '{% trans "In Progress" %}', '{% trans "Resolved" %}', '{% trans "Closed" %}'],
datasets: [{
data: [statusOpen, statusInProgress, statusResolved, statusClosed],
backgroundColor: ['#f59e0b', '#6366f1', '#10b981', '#6b7280']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Complaint Activation Chart (Bar)
if (activationWithin2h + activationMoreThan2h > 0) {
createOrUpdateChart('complaintActivationChart', {
type: 'bar',
data: {
labels: ['≤ 2 hours', '> 2 hours'],
datasets: [{
data: [activationWithin2h, activationMoreThan2h],
backgroundColor: ['#10b981', '#ef4444']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Complaint Response Chart (Bar)
if (responseWithin24h + responseWithin48h + responseWithin72h + responseMoreThan72h > 0) {
createOrUpdateChart('complaintResponseChart', {
type: 'bar',
data: {
labels: ['≤ 24h', '24-48h', '48-72h', '> 72h'],
datasets: [{
data: [responseWithin24h, responseWithin48h, responseWithin72h, responseMoreThan72h],
backgroundColor: ['#10b981', '#6366f1', '#f59e0b', '#ef4444']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Aggregate inquiry data
let inquiryStatusOpen = 0, inquiryStatusInProgress = 0, inquiryStatusResolved = 0, inquiryStatusClosed = 0;
let inquiryResponseWithin24h = 0, inquiryResponseWithin48h = 0, inquiryResponseWithin72h = 0, inquiryResponseMoreThan72h = 0;
staffMetrics.forEach(staff => {
const i = staff.inquiries || {};
inquiryStatusOpen += getSafeNumber(i.status, 'open', 0);
inquiryStatusInProgress += getSafeNumber(i.status, 'in_progress', 0);
inquiryStatusResolved += getSafeNumber(i.status, 'resolved', 0);
inquiryStatusClosed += getSafeNumber(i.status, 'closed', 0);
inquiryResponseWithin24h += getSafeNumber(i.response_time, 'within_24h', 0);
inquiryResponseWithin48h += getSafeNumber(i.response_time, 'within_48h', 0);
inquiryResponseWithin72h += getSafeNumber(i.response_time, 'within_72h', 0);
inquiryResponseMoreThan72h += getSafeNumber(i.response_time, 'more_than_72h', 0);
});
// Function to render inquiry charts
function renderInquiryCharts() {
console.log('Rendering inquiry charts...');
// Inquiry Status Chart (Bar)
if (inquiryStatusOpen + inquiryStatusInProgress + inquiryStatusResolved + inquiryStatusClosed > 0) {
createOrUpdateChart('inquiryStatusChart', {
type: 'bar',
data: {
labels: ['{% trans "Open" %}', '{% trans "In Progress" %}', '{% trans "Resolved" %}', '{% trans "Closed" %}'],
datasets: [{
data: [inquiryStatusOpen, inquiryStatusInProgress, inquiryStatusResolved, inquiryStatusClosed],
backgroundColor: ['#f59e0b', '#6366f1', '#10b981', '#6b7280']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Inquiry Response Chart (Bar)
if (inquiryResponseWithin24h + inquiryResponseWithin48h + inquiryResponseWithin72h + inquiryResponseMoreThan72h > 0) {
createOrUpdateChart('inquiryResponseChart', {
type: 'bar',
data: {
labels: ['≤ 24h', '24-48h', '48-72h', '> 72h'],
datasets: [{
data: [inquiryResponseWithin24h, inquiryResponseWithin48h, inquiryResponseWithin72h, inquiryResponseMoreThan72h],
backgroundColor: ['#10b981', '#6366f1', '#f59e0b', '#ef4444']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}
// Render inquiry charts when tab is clicked
const inquiryTab = document.getElementById('inquiries-tab');
if (inquiryTab) {
inquiryTab.addEventListener('click', function () {
setTimeout(renderInquiryCharts, 100);
});
}
}
// Export function
window.exportReport = function(format) {
const performanceDataEl = document.getElementById('performanceData');
if (!performanceDataEl) return;
const performanceData = JSON.parse(performanceDataEl.textContent);
const staffIds = performanceData.staff_metrics.map(s => s.id);
fetch('{% url "dashboard:export_staff_performance" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
},
body: JSON.stringify({
staff_ids: staffIds,
date_range: dateRange.value,
format: format
})
})
.then(response => {
if (format === 'json') {
return response.json();
} else {
return response.blob();
}
})
.then(data => {
if (format === 'json') {
console.log('Export data:', data);
alert('Export ready! Check console for data.');
} else {
// Download file
const url = window.URL.createObjectURL(data);
const a = document.createElement('a');
a.href = url;
a.download = `staff_performance_${new Date().toISOString().slice(0,10)}.${format}`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
})
.catch(error => {
console.error('Export error:', error);
alert('Export failed. Please try again.');
});
};
});
</script>
{% endblock %}