707 lines
26 KiB
HTML
707 lines
26 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% trans "Analytics Dashboard" %} - PX360{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* PX360 App Theme Variables */
|
|
:root {
|
|
--hh-navy: #005696;
|
|
--hh-blue: #007bbd;
|
|
--hh-light: #eef6fb;
|
|
--hh-slate: #64748b;
|
|
--hh-success: #10b981;
|
|
--hh-warning: #f59e0b;
|
|
--hh-danger: #ef4444;
|
|
}
|
|
|
|
/* Page Header */
|
|
.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%);
|
|
}
|
|
|
|
/* Section Cards */
|
|
.section-card {
|
|
background: white;
|
|
border-radius: 1rem;
|
|
border: 2px solid #e2e8f0;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
overflow: hidden;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.section-card:hover {
|
|
border-color: #005696;
|
|
box-shadow: 0 10px 25px -5px rgba(0, 86, 150, 0.15);
|
|
}
|
|
|
|
.section-header {
|
|
padding: 1rem 1.5rem;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
background: linear-gradient(to right, #f8fafc, #f1f5f9);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.section-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 0.75rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* KPI Cards */
|
|
.kpi-card {
|
|
background: white;
|
|
border-radius: 1rem;
|
|
border: 2px solid #e2e8f0;
|
|
padding: 1.25rem;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
transition: all 0.3s ease;
|
|
height: 100%;
|
|
}
|
|
.kpi-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
border-color: #005696;
|
|
}
|
|
|
|
.kpi-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 0.75rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.kpi-value {
|
|
font-size: 1.75rem;
|
|
font-weight: 800;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.kpi-label {
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
color: #64748b;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.025em;
|
|
}
|
|
|
|
/* Chart Cards */
|
|
.chart-card {
|
|
background: white;
|
|
border-radius: 1rem;
|
|
border: 2px solid #e2e8f0;
|
|
overflow: hidden;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
transition: all 0.3s ease;
|
|
}
|
|
.chart-card:hover {
|
|
border-color: #005696;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.chart-header {
|
|
background: linear-gradient(135deg, #f8fafc, #eef6fb);
|
|
padding: 1rem 1.25rem;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.chart-header h3 {
|
|
color: #005696;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
}
|
|
|
|
/* Form Styling - PX360 Theme */
|
|
.form-select-px360 {
|
|
background: white;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
padding: 0.625rem 1rem;
|
|
color: #1e293b;
|
|
font-size: 0.875rem;
|
|
min-width: 200px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.form-select-px360:hover {
|
|
border-color: #005696;
|
|
}
|
|
.form-select-px360:focus {
|
|
outline: none;
|
|
border-color: #005696;
|
|
box-shadow: 0 0 0 3px rgba(0, 86, 150, 0.1);
|
|
}
|
|
|
|
/* Data Tables */
|
|
.data-table {
|
|
width: 100%;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
}
|
|
.data-table th {
|
|
background: #f8fafc;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
padding: 0.875rem 1rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
color: #64748b;
|
|
text-align: left;
|
|
}
|
|
.data-table td {
|
|
border-bottom: 1px solid #e2e8f0;
|
|
padding: 0.875rem 1rem;
|
|
color: #1e293b;
|
|
}
|
|
.data-table tr:hover td {
|
|
background: #f8fafc;
|
|
}
|
|
|
|
/* Status Badges */
|
|
.badge {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
.badge-blue { background: #dbeafe; color: #1d4ed8; }
|
|
.badge-green { background: #d1fae5; color: #047857; }
|
|
.badge-orange { background: #fef3c7; color: #b45309; }
|
|
.badge-red { background: #fee2e2; color: #b91c1c; }
|
|
.badge-purple { background: #f3e8ff; color: #7c3aed; }
|
|
|
|
/* Progress Bars */
|
|
.progress-bar {
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
background: #e2e8f0;
|
|
overflow: hidden;
|
|
}
|
|
.progress-fill {
|
|
height: 100%;
|
|
border-radius: 4px;
|
|
transition: width 0.5s ease;
|
|
}
|
|
|
|
/* Animations */
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
.animate-in {
|
|
animation: fadeIn 0.4s ease-out forwards;
|
|
}
|
|
.delay-100 { animation-delay: 100ms; }
|
|
.delay-200 { animation-delay: 200ms; }
|
|
.delay-300 { animation-delay: 300ms; }
|
|
.delay-400 { animation-delay: 400ms; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="px-4 py-6 max-w-[1600px] mx-auto">
|
|
|
|
<!-- Page Header -->
|
|
<div class="page-header-gradient animate-in">
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 relative z-10">
|
|
<div>
|
|
<h1 class="text-2xl font-bold flex items-center gap-3">
|
|
<i data-lucide="bar-chart-3" class="w-8 h-8"></i>
|
|
{% trans "Analytics Dashboard" %}
|
|
</h1>
|
|
<p class="mt-1 opacity-90">{% trans "Comprehensive overview of patient experience metrics" %}</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<select class="form-select-px360" onchange="window.location.href='?hospital='+this.value">
|
|
<option value="">{% trans "All Hospitals" %}</option>
|
|
{% for hospital in hospitals %}
|
|
<option value="{{ hospital.id }}" {% if selected_hospital and selected_hospital.id == hospital.id %}selected{% endif %}>
|
|
{{ hospital.name_en }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button onclick="refreshDashboard()" class="p-2.5 bg-white/20 hover:bg-white/30 rounded-xl transition" title="{% trans 'Refresh' %}">
|
|
<i data-lucide="refresh-cw" class="w-5 h-5 text-white"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- KPI Grid Row 1 -->
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
|
|
<!-- Total Complaints -->
|
|
<div class="kpi-card animate-in">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="kpi-icon" style="background: linear-gradient(135deg, #fee2e2, #fecaca);">
|
|
<i data-lucide="message-square-warning" class="w-5 h-5 text-red-600"></i>
|
|
</div>
|
|
<span class="badge badge-red">{{ kpis.open_complaints }} {% trans "open" %}</span>
|
|
</div>
|
|
<p class="kpi-value">{{ kpis.total_complaints }}</p>
|
|
<p class="kpi-label mt-1">{% trans "Total Complaints" %}</p>
|
|
</div>
|
|
|
|
<!-- Resolution Rate -->
|
|
<div class="kpi-card animate-in delay-100">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="kpi-icon" style="background: linear-gradient(135deg, #d1fae5, #a7f3d0);">
|
|
<i data-lucide="check-circle" class="w-5 h-5 text-emerald-600"></i>
|
|
</div>
|
|
</div>
|
|
<p class="kpi-value">{{ kpis.sla_compliance }}%</p>
|
|
<p class="kpi-label mt-1">{% trans "SLA Compliance" %}</p>
|
|
</div>
|
|
|
|
<!-- Avg Resolution -->
|
|
<div class="kpi-card animate-in delay-200">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="kpi-icon" style="background: linear-gradient(135deg, #f3e8ff, #e9d5ff);">
|
|
<i data-lucide="clock" class="w-5 h-5 text-purple-600"></i>
|
|
</div>
|
|
</div>
|
|
<p class="kpi-value">{{ kpis.avg_resolution_hours }}h</p>
|
|
<p class="kpi-label mt-1">{% trans "Avg Resolution" %}</p>
|
|
</div>
|
|
|
|
<!-- Total Actions -->
|
|
<div class="kpi-card animate-in delay-300">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="kpi-icon" style="background: linear-gradient(135deg, #fef3c7, #fde68a);">
|
|
<i data-lucide="zap" class="w-5 h-5 text-orange-600"></i>
|
|
</div>
|
|
<span class="badge badge-orange">{{ kpis.open_actions }} {% trans "open" %}</span>
|
|
</div>
|
|
<p class="kpi-value">{{ kpis.total_actions }}</p>
|
|
<p class="kpi-label mt-1">{% trans "Total Actions" %}</p>
|
|
</div>
|
|
|
|
<!-- Survey Score -->
|
|
<div class="kpi-card animate-in delay-400">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="kpi-icon" style="background: linear-gradient(135deg, #dbeafe, #bfdbfe);">
|
|
<i data-lucide="star" class="w-5 h-5 text-blue-600"></i>
|
|
</div>
|
|
</div>
|
|
<p class="kpi-value">{{ kpis.avg_survey_score }}</p>
|
|
<p class="kpi-label mt-1">{% trans "Avg Survey Score" %}</p>
|
|
</div>
|
|
|
|
<!-- NPS Score -->
|
|
<div class="kpi-card animate-in">
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="kpi-icon" style="background: linear-gradient(135deg, #005696, #007bbd);">
|
|
<i data-lucide="thumbs-up" class="w-5 h-5 text-white"></i>
|
|
</div>
|
|
</div>
|
|
<p class="kpi-value">{{ kpis.nps_score }}</p>
|
|
<p class="kpi-label mt-1">{% trans "NPS Score" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section: Complaints -->
|
|
<div class="section-card mb-6 animate-in delay-100">
|
|
<div class="section-header">
|
|
<div class="section-icon" style="background: linear-gradient(135deg, #fee2e2, #fecaca);">
|
|
<i data-lucide="message-square-warning" class="w-6 h-6 text-red-600"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-bold text-navy">{% trans "Complaints Analytics" %}</h2>
|
|
<p class="text-sm text-slate">{% trans "Status, sources, and severity breakdown" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Status Distribution -->
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<i data-lucide="pie-chart" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Status Distribution" %}</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="complaintStatusChart" class="h-64 w-full"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sources -->
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<i data-lucide="git-branch" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Complaint Sources" %}</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="complaintSourcesChart" class="h-64 w-full"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Severity -->
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<i data-lucide="alert-triangle" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Severity Levels" %}</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="severityChart" class="h-64 w-full"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section: Actions -->
|
|
<div class="section-card mb-6 animate-in delay-200">
|
|
<div class="section-header">
|
|
<div class="section-icon" style="background: linear-gradient(135deg, #fef3c7, #fde68a);">
|
|
<i data-lucide="zap" class="w-6 h-6 text-orange-600"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-bold text-navy">{% trans "PX Actions Analytics" %}</h2>
|
|
<p class="text-sm text-slate">{% trans "Action status and categories" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<!-- Action Status -->
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<i data-lucide="layers" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Action Status" %}</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="actionStatusChart" class="h-64 w-full"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Categories -->
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<i data-lucide="tag" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Action Categories" %}</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="actionCategoriesChart" class="h-64 w-full"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section: Surveys -->
|
|
<div class="section-card mb-6 animate-in delay-300">
|
|
<div class="section-header">
|
|
<div class="section-icon" style="background: linear-gradient(135deg, #f3e8ff, #e9d5ff);">
|
|
<i data-lucide="clipboard-check" class="w-6 h-6 text-purple-600"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-bold text-navy">{% trans "Survey Analytics" %}</h2>
|
|
<p class="text-sm text-slate">{% trans "Patient satisfaction and NPS trends" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- NPS Card -->
|
|
<div class="chart-card flex flex-col justify-center items-center">
|
|
<div class="chart-header w-full">
|
|
<i data-lucide="thumbs-up" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Net Promoter Score" %}</h3>
|
|
</div>
|
|
<div class="p-6 text-center">
|
|
<div class="text-6xl font-bold text-purple-600 mb-2">{{ kpis.nps_score }}</div>
|
|
<div class="text-sm text-slate mb-4">{% trans "Industry Avg: +32" %}</div>
|
|
<div class="w-full bg-slate-200 h-2 rounded-full overflow-hidden" style="width: 200px;">
|
|
{% widthratio kpis.nps_score|add:100 2 1 as nps_width %}
|
|
<div class="bg-purple-500 h-full" style="width: {{ nps_width }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Score Trend -->
|
|
<div class="chart-card lg:col-span-2">
|
|
<div class="chart-header">
|
|
<i data-lucide="trending-up" class="w-5 h-5 text-navy"></i>
|
|
<h3>{% trans "Survey Score Trend" %}</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="surveyTrendChart" class="h-64 w-full"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Department Performance Table -->
|
|
<div class="section-card animate-in delay-400">
|
|
<div class="section-header">
|
|
<div class="section-icon" style="background: linear-gradient(135deg, #dbeafe, #bfdbfe);">
|
|
<i data-lucide="building-2" class="w-6 h-6 text-blue-600"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-bold text-navy">{% trans "Department Performance" %}</h2>
|
|
<p class="text-sm text-slate">{% trans "Performance metrics by department" %}</p>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "Department" %}</th>
|
|
<th class="text-center">{% trans "Complaints" %}</th>
|
|
<th class="text-center">{% trans "Actions" %}</th>
|
|
<th class="text-center">{% trans "Survey Avg" %}</th>
|
|
<th class="text-center">{% trans "Resolution Rate" %}</th>
|
|
<th class="text-center">{% trans "Status" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for dept in department_stats %}
|
|
<tr>
|
|
<td class="font-medium text-navy">{{ dept.name }}</td>
|
|
<td class="text-center">{{ dept.complaints }}</td>
|
|
<td class="text-center">{{ dept.actions }}</td>
|
|
<td class="text-center">{{ dept.survey_avg }}</td>
|
|
<td class="text-center">
|
|
<div class="flex items-center justify-center gap-2">
|
|
<div class="progress-bar" style="width: 60px;">
|
|
<div class="progress-fill {% if dept.resolution_rate >= 80 %}bg-emerald-500{% elif dept.resolution_rate >= 60 %}bg-blue-500{% else %}bg-orange-500{% endif %}" style="width: {{ dept.resolution_rate }}%"></div>
|
|
</div>
|
|
<span class="text-sm">{{ dept.resolution_rate }}%</span>
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
{% if dept.resolution_rate >= 80 %}
|
|
<span class="badge badge-green">{% trans "Excellent" %}</span>
|
|
{% elif dept.resolution_rate >= 60 %}
|
|
<span class="badge badge-blue">{% trans "Good" %}</span>
|
|
{% else %}
|
|
<span class="badge badge-orange">{% trans "Needs Work" %}</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="6" class="text-center py-8 text-slate">
|
|
{% trans "No department data available" %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
function refreshDashboard() {
|
|
const icon = document.querySelector('[data-lucide="refresh-cw"]');
|
|
icon.classList.add('animate-spin');
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 500);
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
lucide.createIcons();
|
|
|
|
// Initialize charts
|
|
initCharts();
|
|
});
|
|
|
|
function initCharts() {
|
|
// Chart.js defaults
|
|
if (typeof Chart === 'undefined') return;
|
|
|
|
Chart.defaults.color = '#64748b';
|
|
Chart.defaults.borderColor = '#e2e8f0';
|
|
|
|
// 1. Complaint Status Chart
|
|
const statusCtx = document.getElementById('complaintStatusChart');
|
|
if (statusCtx) {
|
|
new Chart(statusCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Open', 'In Progress', 'Resolved', 'Closed'],
|
|
datasets: [{
|
|
data: [{{ kpis.open_complaints|default:0 }}, {{ kpis.in_progress_complaints|default:0 }}, {{ kpis.resolved_complaints|default:0 }}, {{ kpis.closed_complaints|default:0 }}],
|
|
backgroundColor: ['#f59e0b', '#3b82f6', '#10b981', '#64748b'],
|
|
borderColor: '#ffffff',
|
|
borderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { padding: 15, usePointStyle: true } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 2. Complaint Sources Chart
|
|
const sourcesCtx = document.getElementById('complaintSourcesChart');
|
|
if (sourcesCtx) {
|
|
new Chart(sourcesCtx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: ['Internal', 'External', 'Walk-in', 'Phone', 'Email', 'Web'],
|
|
datasets: [{
|
|
data: [{{ kpis.internal_complaints|default:0 }}, {{ kpis.external_complaints|default:0 }}, {{ kpis.walkin_complaints|default:0 }}, {{ kpis.phone_complaints|default:0 }}, {{ kpis.email_complaints|default:0 }}, {{ kpis.web_complaints|default:0 }}],
|
|
backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4'],
|
|
borderColor: '#ffffff',
|
|
borderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { padding: 10, usePointStyle: true, boxWidth: 10 } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 3. Severity Chart
|
|
const severityCtx = document.getElementById('severityChart');
|
|
if (severityCtx) {
|
|
new Chart(severityCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['Critical', 'High', 'Medium', 'Low'],
|
|
datasets: [{
|
|
label: 'Complaints',
|
|
data: [{{ kpis.critical_complaints|default:0 }}, {{ kpis.high_complaints|default:0 }}, {{ kpis.medium_complaints|default:0 }}, {{ kpis.low_complaints|default:0 }}],
|
|
backgroundColor: ['#ef4444', '#f97316', '#f59e0b', '#10b981'],
|
|
borderRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: { beginAtZero: true, grid: { color: '#e2e8f0' } },
|
|
x: { grid: { display: false } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 4. Action Status Chart
|
|
const actionStatusCtx = document.getElementById('actionStatusChart');
|
|
if (actionStatusCtx) {
|
|
new Chart(actionStatusCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Open', 'In Progress', 'Pending Approval', 'Closed'],
|
|
datasets: [{
|
|
data: [{{ kpis.open_actions|default:0 }}, {{ kpis.in_progress_actions|default:0 }}, {{ kpis.pending_actions|default:0 }}, {{ kpis.closed_actions|default:0 }}],
|
|
backgroundColor: ['#3b82f6', '#f59e0b', '#8b5cf6', '#10b981'],
|
|
borderColor: '#ffffff',
|
|
borderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { padding: 15, usePointStyle: true } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 5. Action Categories Chart
|
|
const actionCatCtx = document.getElementById('actionCategoriesChart');
|
|
if (actionCatCtx) {
|
|
new Chart(actionCatCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['Training', 'Process', 'Policy', 'Facility', 'Other'],
|
|
datasets: [{
|
|
label: 'Actions',
|
|
data: [{{ kpis.training_actions|default:0 }}, {{ kpis.process_actions|default:0 }}, {{ kpis.policy_actions|default:0 }}, {{ kpis.facility_actions|default:0 }}, {{ kpis.other_actions|default:0 }}],
|
|
backgroundColor: '#3b82f6',
|
|
borderRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: { beginAtZero: true, grid: { color: '#e2e8f0' } },
|
|
x: { grid: { display: false } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 6. Survey Trend Chart
|
|
const surveyCtx = document.getElementById('surveyTrendChart');
|
|
if (surveyCtx) {
|
|
new Chart(surveyCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
|
datasets: [{
|
|
label: 'Survey Score',
|
|
data: [{{ kpis.survey_trend_1|default:4.2 }}, {{ kpis.survey_trend_2|default:4.3 }}, {{ kpis.survey_trend_3|default:4.1 }}, {{ kpis.survey_trend_4|default:4.4 }}, {{ kpis.survey_trend_5|default:4.5 }}, {{ kpis.survey_trend_6|default:kpis.avg_survey_score }}],
|
|
borderColor: '#8b5cf6',
|
|
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
|
fill: true,
|
|
tension: 0.4,
|
|
pointRadius: 4,
|
|
pointBackgroundColor: '#8b5cf6'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: { beginAtZero: false, min: 1, max: 5, grid: { color: '#e2e8f0' } },
|
|
x: { grid: { display: false } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
{% endblock %}
|