HH/templates/complaints/analytics.html
2026-02-22 08:35:53 +03:00

316 lines
15 KiB
HTML

{% extends 'layouts/base.html' %}
{% load i18n %}
{% block title %}{% trans "Complaints Analytics" %} - PX360{% endblock %}
{% block content %}
<div class="mb-8">
<div class="flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-gray-800 mb-2">{% trans "Complaints Analytics" %}</h1>
<p class="text-gray-400">{% trans "Comprehensive complaints metrics and insights" %}</p>
</div>
<div>
<form method="get" class="inline-flex">
<select name="date_range" class="px-4 py-3 border border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition" onchange="this.form.submit()">
<option value="7" {% if date_range == 7 %}selected{% endif %}>{% trans "Last 7 Days" %}</option>
<option value="30" {% if date_range == 30 %}selected{% endif %}>{% trans "Last 30 Days" %}</option>
<option value="90" {% if date_range == 90 %}selected{% endif %}>{% trans "Last 90 Days" %}</option>
</select>
</form>
</div>
</div>
</div>
<!-- Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-white p-6 rounded-2xl shadow-sm border-l-4 border-blue-500 border border-gray-50 hover:shadow-md transition">
<div class="flex justify-between items-start">
<div>
<div class="text-xs font-bold text-blue-600 uppercase mb-1">{% trans "Total Complaints" %}</div>
<div class="text-3xl font-bold text-gray-800 mb-2">{{ dashboard_summary.status_counts.total }}</div>
<small class="text-gray-500 flex items-center gap-1">
{% if dashboard_summary.trend.percentage_change > 0 %}
<i data-lucide="trending-up" class="w-4 h-4 text-red-500"></i> +{{ dashboard_summary.trend.percentage_change }}%
{% elif dashboard_summary.trend.percentage_change < 0 %}
<i data-lucide="trending-down" class="w-4 h-4 text-green-500"></i> {{ dashboard_summary.trend.percentage_change }}%
{% else %}
<i data-lucide="minus" class="w-4 h-4 text-gray-400"></i> 0%
{% endif %}
{% trans "vs last period" %}
</small>
</div>
<div class="bg-blue-100 p-3 rounded-xl">
<i data-lucide="activity" class="text-blue-500 w-6 h-6"></i>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border-l-4 border-orange-500 border border-gray-50 hover:shadow-md transition">
<div class="flex justify-between items-start">
<div>
<div class="text-xs font-bold text-orange-600 uppercase mb-1">{% trans "Open" %}</div>
<div class="text-3xl font-bold text-gray-800">{{ dashboard_summary.status_counts.open }}</div>
</div>
<div class="bg-orange-100 p-3 rounded-xl">
<i data-lucide="folder-open" class="text-orange-500 w-6 h-6"></i>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border-l-4 border-red-500 border border-gray-50 hover:shadow-md transition">
<div class="flex justify-between items-start">
<div>
<div class="text-xs font-bold text-red-600 uppercase mb-1">{% trans "Overdue" %}</div>
<div class="text-3xl font-bold text-red-500">{{ dashboard_summary.status_counts.overdue }}</div>
</div>
<div class="bg-red-100 p-3 rounded-xl">
<i data-lucide="alert-triangle" class="text-red-500 w-6 h-6"></i>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border-l-4 border-green-500 border border-gray-50 hover:shadow-md transition">
<div class="flex justify-between items-start">
<div>
<div class="text-xs font-bold text-green-600 uppercase mb-1">{% trans "Resolved" %}</div>
<div class="text-3xl font-bold text-gray-800">{{ dashboard_summary.status_counts.resolved }}</div>
</div>
<div class="bg-green-100 p-3 rounded-xl">
<i data-lucide="check-circle" class="text-green-500 w-6 h-6"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Complaints Trend -->
<div class="lg:col-span-2 bg-white rounded-2xl shadow-sm border border-gray-50 p-6">
<h3 class="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="trending-up" class="w-5 h-5"></i> {% trans "Complaints Trend" %}
</h3>
<div id="trendChart"></div>
</div>
<!-- Top Categories -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50 p-6">
<h3 class="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="pie-chart" class="w-5 h-5"></i> {% trans "Top Categories" %}
</h3>
<div id="categoryChart"></div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- SLA Compliance -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50 p-6">
<h3 class="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="clock" class="w-5 h-5"></i> {% trans "SLA Compliance" %}
</h3>
<div class="text-center mb-6">
<h2 class="{% if sla_compliance.overall_compliance_rate >= 80 %}text-green-500{% elif sla_compliance.overall_compliance_rate >= 60 %}text-orange-500{% else %}text-red-500{% endif %} text-4xl font-bold mb-2">
{{ sla_compliance.overall_compliance_rate }}%
</h2>
<p class="text-gray-400">{% trans "Overall Compliance Rate" %}</p>
</div>
<div class="grid grid-cols-2 gap-4 text-center">
<div class="bg-green-50 rounded-xl p-4">
<h4 class="text-2xl font-bold text-green-600">{{ sla_compliance.on_time }}</h4>
<small class="text-gray-500">{% trans "On Time" %}</small>
</div>
<div class="bg-red-50 rounded-xl p-4">
<h4 class="text-2xl font-bold text-red-500">{{ sla_compliance.overdue }}</h4>
<small class="text-gray-500">{% trans "Overdue" %}</small>
</div>
</div>
</div>
<!-- Resolution Rate -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50 p-6">
<h3 class="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="check-square" class="w-5 h-5"></i> {% trans "Resolution Metrics" %}
</h3>
<div class="mb-6">
<div class="flex justify-between mb-2">
<span class="text-gray-600">{% trans "Resolution Rate" %}</span>
<strong class="text-gray-800">{{ resolution_rate.resolution_rate }}%</strong>
</div>
<div class="h-3 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-green-500 rounded-full" style="width: {{ resolution_rate.resolution_rate }}%"></div>
</div>
</div>
<div class="grid grid-cols-2 gap-4 text-center">
<div>
<h4 class="text-2xl font-bold text-gray-800">{{ resolution_rate.resolved }}</h4>
<small class="text-gray-500">{% trans "Resolved" %}</small>
</div>
<div>
<h4 class="text-2xl font-bold text-gray-800">{{ resolution_rate.pending }}</h4>
<small class="text-gray-500">{% trans "Pending" %}</small>
</div>
</div>
{% if resolution_rate.avg_resolution_time_hours %}
<div class="mt-6 text-center bg-gray-50 rounded-xl p-4">
<p class="text-gray-600 mb-1">{% trans "Avg Resolution Time" %}</p>
<h5 class="text-xl font-bold text-gray-800">{{ resolution_rate.avg_resolution_time_hours }} {% trans "hours" %}</h5>
</div>
{% endif %}
</div>
</div>
<!-- Overdue Complaints -->
{% if overdue_complaints %}
<div class="bg-white rounded-2xl shadow-sm border border-gray-50">
<div class="p-6 border-b border-gray-100">
<h3 class="text-lg font-bold text-red-500 flex items-center gap-2">
<i data-lucide="alert-triangle" class="w-5 h-5"></i> {% trans "Overdue Complaints" %}
</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "ID" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Source" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Title" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Patient" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Severity" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Due Date" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Assigned To" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
{% for complaint in overdue_complaints %}
<tr class="hover:bg-gray-50 transition">
<td class="px-6 py-4">
<a href="{% url 'complaints:complaint_detail' complaint.id %}" class="text-navy hover:underline">
<code class="bg-gray-100 px-2 py-1 rounded text-sm font-semibold text-gray-700">{{ complaint.id|slice:8 }}</code>
</a>
</td>
<td class="px-6 py-4">
{% if complaint.source_name %}
<span class="px-2.5 py-1 rounded-lg text-xs font-bold bg-blue-100 text-blue-600" title="{% trans 'PX Source' %}: {{ complaint.source_name }}">
<i data-lucide="cloud-arrow-down" class="w-3 h-3 inline mr-1"></i> {{ complaint.source_name|truncatechars:12 }}
</span>
{% elif complaint.complaint_source_type == 'internal' %}
<span class="px-2.5 py-1 rounded-lg text-xs font-bold bg-indigo-100 text-indigo-600" title="{% trans 'Internal' %}">
<i data-lucide="building" class="w-3 h-3 inline mr-1"></i> {% trans "Internal" %}
</span>
{% else %}
<span class="px-2.5 py-1 rounded-lg text-xs font-bold bg-gray-100 text-gray-600" title="{% trans 'External' %}">
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% trans "Patient" %}
</span>
{% endif %}
</td>
<td class="px-6 py-4">
<span class="text-gray-700">{{ complaint.title|truncatechars:50 }}</span>
</td>
<td class="px-6 py-4">
<span class="text-gray-700">{{ complaint.patient_full_name }}</span>
</td>
<td class="px-6 py-4">
<span class="px-2.5 py-1 rounded-lg text-xs font-bold {% if complaint.severity == 'critical' %}bg-red-500 text-white{% elif complaint.severity == 'high' %}bg-orange-100 text-orange-600{% else %}bg-gray-100 text-gray-600{% endif %}">
{{ complaint.severity }}
</span>
</td>
<td class="px-6 py-4">
<span class="text-red-500 font-semibold">{{ complaint.due_at|date:"Y-m-d H:i" }}</span>
</td>
<td class="px-6 py-4">
<span class="text-gray-700">{{ complaint.assigned_to_full_name|default:"Unassigned" }}</span>
</td>
<td class="px-6 py-4">
<a href="{% url 'complaints:complaint_detail' complaint.id %}" class="p-2 bg-light text-navy rounded-lg hover:bg-blue-100 transition">
<i data-lucide="eye" class="w-4 h-4"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<script>
// Trend Chart - ApexCharts
var trendOptions = {
series: [{
name: '{% trans "Complaints" %}',
data: {{ trends.data|safe }}
}],
chart: {
type: 'line',
height: 320,
toolbar: {
show: false
}
},
stroke: {
curve: 'smooth',
width: 3
},
colors: ['#4bc0c0'],
xaxis: {
categories: {{ trends.labels|safe }},
labels: {
style: {
fontSize: '12px'
}
}
},
yaxis: {
min: 0,
forceNiceScale: true,
labels: {
style: {
fontSize: '12px'
}
}
},
grid: {
borderColor: '#e7e7e7',
strokeDashArray: 5
},
tooltip: {
theme: 'light'
}
};
var trendChart = new ApexCharts(document.querySelector("#trendChart"), trendOptions);
trendChart.render();
// Category Chart - ApexCharts
var categoryOptions = {
series: [{% for cat in top_categories.categories %}{{ cat.count }}{% if not forloop.last %},{% endif %}{% endfor %}],
chart: {
type: 'donut',
height: 360
},
labels: [{% for cat in top_categories.categories %}'{{ cat.category }}'{% if not forloop.last %},{% endif %}{% endfor %}],
colors: ['#ff6384', '#36a2eb', '#ffce56', '#4bc0c0', '#9966ff', '#ff9f40'],
legend: {
position: 'bottom',
fontSize: '12px'
},
dataLabels: {
enabled: true,
formatter: function (val) {
return val.toFixed(1) + "%"
}
},
plotOptions: {
pie: {
donut: {
size: '65%'
}
}
},
tooltip: {
theme: 'light'
}
};
var categoryChart = new ApexCharts(document.querySelector("#categoryChart"), categoryOptions);
categoryChart.render();
</script>
{% endblock %}