892 lines
50 KiB
HTML
892 lines
50 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}Complaints Yearly Report - PX360{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-[1400px] mx-auto">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">{% trans "Complaints Yearly Report Card" %}</h1>
|
|
<p class="text-sm text-gray-500 mt-1">{% trans "Step 2/Yearly — KPI tables, source breakdowns, escalated analysis, response rates" %}</p>
|
|
</div>
|
|
<a href="{% url 'dashboard:complaint_quarterly_export' %}?hospital={{ selected_hospital }}&start_date={{ start_date }}&end_date={{ end_date }}"
|
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition"
|
|
style="background-color: #059669; color: white;">
|
|
<i data-lucide="download" class="w-4 h-4" style="color: white;"></i>
|
|
{% trans "Export Excel" %}
|
|
</a>
|
|
</div>
|
|
|
|
{% if no_data %}
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
|
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<i data-lucide="bar-chart-3" class="w-8 h-8 text-gray-400"></i>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-gray-700 mb-2">{% trans "No Data" %}</h3>
|
|
<p class="text-gray-500">{% trans "No complaint records for the selected hospital." %}</p>
|
|
</div>
|
|
{% else %}
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
|
|
<form method="get" class="flex flex-wrap gap-4 items-end">
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">{% trans "Hospital" %}</label>
|
|
<select name="hospital" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm" data-tomselect>
|
|
{% for h in hospitals %}
|
|
<option value="{{ h.id }}" {% if selected_hospital == h.id|stringformat:'s' %}selected{% endif %}>{{ h.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="w-40">
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">{% trans "Start Date" %}</label>
|
|
<input type="date" name="start_date" value="{{ start_date }}" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm">
|
|
</div>
|
|
<div class="w-40">
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">{% trans "End Date" %}</label>
|
|
<input type="date" name="end_date" value="{{ end_date }}" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm">
|
|
</div>
|
|
<button type="submit" class="px-4 py-2 bg-navy text-white text-sm font-medium rounded-lg hover:bg-navy/90 transition">
|
|
{% trans "Apply" %}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="mb-6 border-b border-gray-200">
|
|
<nav class="flex gap-1 -mb-px" id="report-tabs">
|
|
<button onclick="switchTab('dashboard')" data-tab="dashboard"
|
|
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-blue-600 text-blue-600">
|
|
{% trans "Dashboard" %}
|
|
</button>
|
|
<button onclick="switchTab('kpi')" data-tab="kpi"
|
|
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
1. KPI
|
|
</button>
|
|
<button onclick="switchTab('first-table')" data-tab="first-table"
|
|
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
2. 1st Table
|
|
</button>
|
|
<button onclick="switchTab('escalated')" data-tab="escalated"
|
|
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
3. Escalated
|
|
</button>
|
|
<button onclick="switchTab('each-dep')" data-tab="each-dep"
|
|
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
4. Each Dep.
|
|
</button>
|
|
<button onclick="switchTab('response-rates')" data-tab="response-rates"
|
|
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
5-7. Response Rates
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<div id="tab-dashboard">
|
|
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "Total Complaints" %}</p>
|
|
<p class="text-2xl font-bold text-gray-800">{{ kpi_data.totals.total }}</p>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "Resolution Rate" %}</p>
|
|
<p class="text-2xl font-bold {% if kpi_data.totals.resolution_rate >= 0.95 %}text-emerald-600{% elif kpi_data.totals.resolution_rate >= 0.9 %}text-amber-600{% else %}text-red-600{% endif %}">
|
|
{{ kpi_data.totals.resolution_rate|floatformat:1 }}%
|
|
</p>
|
|
<p class="text-[10px] text-gray-400">{% trans "Target: 95%" %}</p>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "72h Resolution Rate" %}</p>
|
|
<p class="text-2xl font-bold {% if kpi_data.totals.resolved_72h_rate >= 0.95 %}text-emerald-600{% elif kpi_data.totals.resolved_72h_rate >= 0.9 %}text-amber-600{% else %}text-red-600{% endif %}">
|
|
{{ kpi_data.totals.resolved_72h_rate|floatformat:1 }}%
|
|
</p>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "Satisfaction Rate" %}</p>
|
|
<p class="text-2xl font-bold {% if satisfaction_data.totals.rate >= 0.95 %}text-emerald-600{% elif satisfaction_data.totals.rate >= 0.7 %}text-amber-600{% else %}text-red-600{% endif %}">
|
|
{{ satisfaction_data.totals.rate|floatformat:1 }}%
|
|
</p>
|
|
<p class="text-[10px] text-gray-400">{{ satisfaction_data.totals.surveyed }} {% trans "surveyed" %}</p>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "Escalated" %}</p>
|
|
<p class="text-2xl font-bold text-gray-800">{{ escalated.total_escalated }}</p>
|
|
<p class="text-[10px] text-gray-400">{{ escalated.escalation_rate|floatformat:1 }}% {% trans "rate" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Resolution Rate Trend" %}</h3>
|
|
<div id="resolution-trend-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Monthly Complaints" %}</h3>
|
|
<div id="monthly-totals-chart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "External vs Internal" %}</h3>
|
|
<div id="source-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "By Location" %}</h3>
|
|
<div id="location-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "By Department Type" %}</h3>
|
|
<div id="dept-type-chart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if response_rates.internal.total > 0 %}
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Internal Response Rate" %}</h3>
|
|
<p class="text-xs text-gray-400 mb-2">{% trans "Total:" %} {{ response_rates.internal.total }}</p>
|
|
<div id="rr-internal-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "MOH Response Rate" %}</h3>
|
|
<p class="text-xs text-gray-400 mb-2">{% trans "Total:" %} {{ response_rates.moh.total }}</p>
|
|
<div id="rr-moh-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "CHI Response Rate" %}</h3>
|
|
<p class="text-xs text-gray-400 mb-2">{% trans "Total:" %} {{ response_rates.chi.total }}</p>
|
|
<div id="rr-chi-chart"></div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div id="tab-kpi" class="hidden">
|
|
{% for block in kpi_blocks %}
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ block.label }}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left min-w-[280px]">{{ block.numerator_label }}</th>
|
|
{% for m in month_labels %}<th class="border border-gray-300 px-2 py-1.5 text-center min-w-[50px]">{{ m }}</th>{% endfor %}
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center min-w-[50px] font-bold">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1">{{ block.numerator_label }}</td>
|
|
{% for m in block.numerator_values %}<td class="border border-gray-300 px-2 py-1 text-center">{{ m }}</td>{% endfor %}
|
|
<td class="border border-gray-300 px-2 py-1 text-center font-semibold">{{ block.total_num }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1">{{ block.denominator_label }}</td>
|
|
{% for m in block.denominator_values %}<td class="border border-gray-300 px-2 py-1 text-center">{{ m }}</td>{% endfor %}
|
|
<td class="border border-gray-300 px-2 py-1 text-center font-semibold">{{ block.total_den }}</td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="border border-gray-300 px-2 py-1 font-semibold">{{ block.result_label }}</td>
|
|
{% for m in block.result_values %}<td class="border border-gray-300 px-2 py-1 text-center font-semibold">{{ m|floatformat:1 }}%</td>{% endfor %}
|
|
<td class="border border-gray-300 px-2 py-1 text-center font-bold">{{ block.total_res|floatformat:1 }}%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div id="tab-first-table" class="hidden">
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Source Breakdown" %}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">%</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Number</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Type</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Total</th>
|
|
{% for m in month_labels %}<th class="border border-gray-300 px-2 py-1.5 text-center">{{ m }}</th>{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="bg-blue-50">
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.external.pct|floatformat:1 }}%</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.external.count }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 font-semibold">External</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.external.count }}</td>
|
|
{% for m in month_labels %}<td class="border border-gray-300 px-2 py-1 text-center">{{ source_breakdown.forloop.counter0|default:'' }}</td>{% endfor %}
|
|
</tr>
|
|
{% for sub in source_sub_external %}
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1"></td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ sub.total }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 pl-6">{{ sub.label }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ sub.total }}</td>
|
|
{% for v in sub.monthly %}<td class="border border-gray-300 px-2 py-1 text-center">{{ v.count }}</td>{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
<tr class="bg-green-50">
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.internal.pct|floatformat:1 }}%</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.internal.count }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 font-semibold">Internal</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.internal.count }}</td>
|
|
{% for v in source_internal_monthly %}<td class="border border-gray-300 px-2 py-1 text-center">{{ v }}</td>{% endfor %}
|
|
</tr>
|
|
{% for sub in source_sub_internal %}
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1"></td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ sub.total }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 pl-6">{{ sub.label }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ sub.total }}</td>
|
|
{% for v in sub.monthly %}<td class="border border-gray-300 px-2 py-1 text-center">{{ v.count }}</td>{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
<tr class="bg-gray-100 font-semibold">
|
|
<td class="border border-gray-300 px-2 py-1"></td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.total }}</td>
|
|
<td class="border border-gray-300 px-2 py-1">Total</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ source_totals.total }}</td>
|
|
{% for v in source_total_monthly %}<td class="border border-gray-300 px-2 py-1 text-center">{{ v }}</td>{% endfor %}
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Location Breakdown" %}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">%</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Number</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Location</th>
|
|
{% for m in month_labels %}<th class="border border-gray-300 px-2 py-1.5 text-center">{{ m }}</th>{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in location_rows %}
|
|
<tr{% if row.is_total %} class="bg-gray-100 font-semibold"{% endif %}>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.pct|floatformat:1 }}%</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.total }}</td>
|
|
<td class="border border-gray-300 px-2 py-1">{{ row.label }}</td>
|
|
{% for v in row.monthly %}<td class="border border-gray-300 px-2 py-1 text-center">{{ v }}</td>{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Department Type Breakdown" %}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">%</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Number</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Department</th>
|
|
{% for m in month_labels %}<th class="border border-gray-300 px-2 py-1.5 text-center">{{ m }}</th>{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in dept_type_rows %}
|
|
<tr{% if row.is_total %} class="bg-gray-100 font-semibold"{% endif %}>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.pct|floatformat:1 }}%</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.total }}</td>
|
|
<td class="border border-gray-300 px-2 py-1">{{ row.label }}</td>
|
|
{% for v in row.monthly %}<td class="border border-gray-300 px-2 py-1 text-center">{{ v }}</td>{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Monthly Summary" %}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Month</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">MOH</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">CHI</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Insurance</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Internal</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Total</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">MOH %</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">CHI %</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for ms in monthly_summary %}
|
|
<tr{% if ms.month == 'TOTAL' %} class="bg-gray-100 font-semibold"{% endif %}>
|
|
<td class="border border-gray-300 px-2 py-1">{{ ms.month }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ ms.moh }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ ms.chi }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ ms.insurance }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ ms.internal }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center font-semibold">{{ ms.total }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ ms.moh_pct|floatformat:1 }}%</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ ms.chi_pct|floatformat:1 }}%</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5 mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Total Complaints - External & Internal" %}</h3>
|
|
<div id="source-bar-chart"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Source Totals" %}</h3>
|
|
<table class="w-full text-xs border-collapse max-w-lg">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Source</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Count</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left"></th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Percentage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in source_total_rows %}
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1">{{ row.label }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.count }}</td>
|
|
<td class="border border-gray-300 px-2 py-1">{{ row.pct_label }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.pct|floatformat:1 }}%</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% for lr in location_ratio_tables %}
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ lr.title }}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Month</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">{{ lr.complaint_col }}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">{{ lr.patient_col }}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Percentage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in lr.rows %}
|
|
<tr{% if row.is_total %} class="bg-gray-100 font-semibold"{% endif %}>
|
|
<td class="border border-gray-300 px-2 py-1">{{ row.month }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.complaints }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.patients }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.ratio|floatformat:4 }}%</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Department Type Monthly" %}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">Month</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Medical</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Admin</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Nursing</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Support Services</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in dept_monthly_rows %}
|
|
<tr{% if row.is_total %} class="bg-gray-100 font-semibold"{% elif row.is_pct %} class="bg-gray-50"{% endif %}>
|
|
<td class="border border-gray-300 px-2 py-1{% if row.is_total or row.is_pct %} font-semibold{% endif %}">{{ row.month }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.medical }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.admin }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.nursing }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ row.support }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center font-semibold">{{ row.total }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5 mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Total Complaints - Main Department" %}</h3>
|
|
<div id="dept-pie-chart"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="tab-escalated" class="hidden">
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Escalated Complaints by Category" %}</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
|
{% for cat in escalated_table %}
|
|
<div>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">{{ cat.label }}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center w-16">Escalated</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for sub in cat.sub_depts %}
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1">{{ sub.name }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ sub.count }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% if cat.sub_depts %}
|
|
<tr class="bg-gray-100 font-semibold">
|
|
<td class="border border-gray-300 px-2 py-1">Total</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ cat.total_escalated }}</td>
|
|
</tr>
|
|
{% endif %}
|
|
<tr class="bg-gray-50">
|
|
<td class="border border-gray-300 px-2 py-1">Total Complaints</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ cat.total_complaints }}</td>
|
|
</tr>
|
|
<tr class="bg-amber-50 font-semibold">
|
|
<td class="border border-gray-300 px-2 py-1">% Escalated</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ cat.escalation_rate|floatformat:1 }}%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Escalated Complaints Summary" %}</h3>
|
|
<table class="text-xs border-collapse">
|
|
<tbody>
|
|
<tr class="bg-gray-100">
|
|
<td class="border border-gray-300 px-3 py-1.5 font-semibold">{% trans "Total Number of Escalated Complaints" %}</td>
|
|
<td class="border border-gray-300 px-3 py-1.5 text-center font-bold">{{ escalated.total_escalated }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-3 py-1.5 pl-6">{% trans "Internal Complaints" %}</td>
|
|
<td class="border border-gray-300 px-3 py-1.5 text-center">{{ escalated.by_source.internal|default:0 }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-3 py-1.5 pl-6">{% trans "MOH" %}</td>
|
|
<td class="border border-gray-300 px-3 py-1.5 text-center">{{ escalated.by_source.moh|default:0 }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-3 py-1.5 pl-6">{% trans "CHI" %}</td>
|
|
<td class="border border-gray-300 px-3 py-1.5 text-center">{{ escalated.by_source.chi|default:0 }}</td>
|
|
</tr>
|
|
<tr class="bg-gray-100 font-semibold">
|
|
<td class="border border-gray-300 px-3 py-1.5">{% trans "Total" %}</td>
|
|
<td class="border border-gray-300 px-3 py-1.5 text-center">{{ escalated.total_escalated }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div class="mt-3 text-xs text-gray-500">
|
|
{% trans "Total Escalated Comp." %}: {{ escalated.total_escalated }} |
|
|
{% trans "Total Number of Comp." %}: {{ escalated.total_complaints }} |
|
|
{% trans "%" %}: {{ escalated.escalation_rate|floatformat:1 }}%
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Escalated by Source (External & Internal)" %}</h3>
|
|
<div id="escalated-source-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Escalated Rate by Department" %}</h3>
|
|
<div id="escalated-rate-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Escalated Count by Department" %}</h3>
|
|
<div id="escalated-count-chart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="tab-each-dep" class="hidden">
|
|
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Complaints by Sub-Department" %}</h3>
|
|
<div class="flex border-b border-gray-200 mb-4">
|
|
{% for cat in dept_category_tables %}
|
|
<button onclick="switchDeptTab({{ forloop.counter0 }})"
|
|
class="dept-tab-btn px-4 py-2 text-xs font-semibold transition-colors whitespace-nowrap
|
|
{% if forloop.first %}text-navy border-b-2 border-navy{% else %}text-gray-500 hover:text-gray-700{% endif %}"
|
|
data-idx="{{ forloop.counter0 }}">
|
|
{{ cat.label }}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
{% for cat in dept_category_tables %}
|
|
<div class="dept-tab-panel {% if not forloop.first %}hidden{% endif %}" data-idx="{{ forloop.counter0 }}">
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1 text-left">{{ cat.label }}</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">MOH</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">CHI</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">Ins.</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">Int.</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">Total</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">Esc.</th>
|
|
<th class="border border-gray-300 px-1 py-1 text-center w-10">Days</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for d in cat.depts %}
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-0.5">{{ d.name }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ d.moh }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ d.chi }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ d.insurance }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ d.internal }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center font-semibold">{{ d.total }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ d.escalated }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ d.avg_response_days|floatformat:1 }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% if cat.depts %}
|
|
<tr class="bg-gray-100 font-semibold">
|
|
<td class="border border-gray-300 px-2 py-0.5">Total</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ cat.totals.moh }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ cat.totals.chi }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ cat.totals.insurance }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ cat.totals.internal }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ cat.totals.total }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center">{{ cat.totals.escalated }}</td>
|
|
<td class="border border-gray-300 px-1 py-0.5 text-center"></td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<script>
|
|
function switchDeptTab(idx) {
|
|
document.querySelectorAll('.dept-tab-panel').forEach(function(p) { p.classList.add('hidden'); });
|
|
document.querySelectorAll('.dept-tab-btn').forEach(function(b) {
|
|
b.classList.remove('text-navy', 'border-b-2', 'border-navy');
|
|
b.classList.add('text-gray-500');
|
|
});
|
|
var panel = document.querySelector('.dept-tab-panel[data-idx="' + idx + '"]');
|
|
if (panel) panel.classList.remove('hidden');
|
|
var btn = document.querySelector('.dept-tab-btn[data-idx="' + idx + '"]');
|
|
if (btn) { btn.classList.remove('text-gray-500'); btn.classList.add('text-navy', 'border-b-2', 'border-navy'); }
|
|
}
|
|
</script>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Source Totals" %}</h3>
|
|
<table class="text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-3 py-1.5 text-left">{% trans "Source" %}</th>
|
|
<th class="border border-gray-300 px-3 py-1.5 text-center">{% trans "Count" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in per_dept_source_rows %}
|
|
<tr{% if row.is_total %} class="bg-gray-100 font-semibold"{% endif %}>
|
|
<td class="border border-gray-300 px-3 py-1.5">{{ row.label }}</td>
|
|
<td class="border border-gray-300 px-3 py-1.5 text-center">{{ row.count }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Average Response Rate in Days" %}</h3>
|
|
<div id="avg-response-chart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="tab-response-rates" class="hidden">
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-2">{% trans "Internal Complaints Response Rate" %}</h3>
|
|
<p class="text-xs text-gray-400 mb-3">{% trans "Total:" %} {{ response_rates.internal.total }}</p>
|
|
<div id="rr-internal-detail-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-2">{% trans "MOH Complaints Response Rate" %}</h3>
|
|
<p class="text-xs text-gray-400 mb-3">{% trans "Total:" %} {{ response_rates.moh.total }}</p>
|
|
<div id="rr-moh-detail-chart"></div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-2">{% trans "CHI Complaints Response Rate" %}</h3>
|
|
<p class="text-xs text-gray-400 mb-3">{% trans "Total:" %} {{ response_rates.chi.total }}</p>
|
|
<div id="rr-chi-detail-chart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{% for src in response_rate_source_list %}
|
|
{% if src.total > 0 %}
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6 overflow-x-auto">
|
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ src.label }} — {% trans "Individual Complaints" %}</h3>
|
|
<table class="w-full text-xs border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-800 text-white">
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">{% trans "Reference" %}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-left">{% trans "Department" %}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">{% trans "Hours" %}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">{% trans "Bucket" %}</th>
|
|
<th class="border border-gray-300 px-2 py-1.5 text-center">{% trans "Status" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in src.individual %}
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1">{{ item.ref }}</td>
|
|
<td class="border border-gray-300 px-2 py-1">{{ item.dept }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ item.hours|floatformat:1 }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ item.bucket }}</td>
|
|
<td class="border border-gray-300 px-2 py-1 text-center">{{ item.status }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
</div>
|
|
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
{% if not no_data and chart_data_json %}
|
|
<script>
|
|
function switchTab(tabId) {
|
|
document.querySelectorAll('[id^="tab-"]').forEach(function(el) { el.classList.add('hidden'); });
|
|
document.getElementById('tab-' + tabId).classList.remove('hidden');
|
|
document.querySelectorAll('.tab-btn').forEach(function(btn) {
|
|
btn.classList.remove('border-blue-600', 'text-blue-600');
|
|
btn.classList.add('border-transparent', 'text-gray-500');
|
|
});
|
|
var activeBtn = document.querySelector('[data-tab="' + tabId + '"]');
|
|
activeBtn.classList.add('border-blue-600', 'text-blue-600');
|
|
activeBtn.classList.remove('border-transparent', 'text-gray-500');
|
|
}
|
|
|
|
(function() {
|
|
var cd = JSON.parse('{{ chart_data_json|escapejs }}');
|
|
|
|
new ApexCharts(document.getElementById('resolution-trend-chart'), {
|
|
chart: { type: 'line', height: 280, toolbar: { show: true } },
|
|
series: [
|
|
{ name: 'Resolution Rate', data: cd.kpi_resolution.values },
|
|
{ name: '72h Rate', data: cd.kpi_72h.values },
|
|
],
|
|
xaxis: { categories: cd.kpi_resolution.months },
|
|
yaxis: { min: 0, max: 1, labels: { formatter: function(v) { return (v * 100).toFixed(0) + '%'; } } },
|
|
stroke: { curve: 'smooth', width: 3 },
|
|
colors: ['#3B82F6', '#10B981'],
|
|
annotations: {
|
|
yaxis: [
|
|
{ y: cd.kpi_resolution.target, borderColor: '#10B981', label: { text: 'Target 95%' } },
|
|
{ y: cd.kpi_resolution.threshold, borderColor: '#F59E0B', label: { text: 'Threshold 90%' } },
|
|
]
|
|
},
|
|
tooltip: { y: { formatter: function(v) { return (v * 100).toFixed(1) + '%'; } } },
|
|
}).render();
|
|
|
|
new ApexCharts(document.getElementById('monthly-totals-chart'), {
|
|
chart: { type: 'bar', height: 280, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 3 } },
|
|
dataLabels: { enabled: false },
|
|
series: [{ name: 'Complaints', data: cd.monthly_totals.values, color: '#6366F1' }],
|
|
xaxis: { categories: cd.monthly_totals.months },
|
|
}).render();
|
|
|
|
new ApexCharts(document.getElementById('source-chart'), {
|
|
chart: { type: 'pie', height: 250 },
|
|
series: [cd.source_distribution.external, cd.source_distribution.internal],
|
|
labels: ['External', 'Internal'],
|
|
colors: ['#3B82F6', '#10B981'],
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
|
|
new ApexCharts(document.getElementById('location-chart'), {
|
|
chart: { type: 'pie', height: 250 },
|
|
series: [cd.location_distribution.IP, cd.location_distribution.OP, cd.location_distribution.ER],
|
|
labels: ['Inpatient', 'Outpatient', 'Emergency'],
|
|
colors: ['#8B5CF6', '#3B82F6', '#EF4444'],
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
|
|
new ApexCharts(document.getElementById('dept-type-chart'), {
|
|
chart: { type: 'pie', height: 250 },
|
|
series: [cd.dept_type_distribution.Medical, cd.dept_type_distribution.Admin, cd.dept_type_distribution.Nursing, cd.dept_type_distribution.Support],
|
|
labels: ['Medical', 'Admin', 'Nursing', 'Support'],
|
|
colors: ['#3B82F6', '#F59E0B', '#8B5CF6', '#6B7280'],
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
|
|
function makeRRChart(elId, data) {
|
|
if (!data || data.total === 0) return;
|
|
var buckets = ['24h', '48h', '72h', '72h+'];
|
|
var counts = buckets.map(function(b) { return data.counts[b] || 0; });
|
|
new ApexCharts(document.getElementById(elId), {
|
|
chart: { type: 'donut', height: 200 },
|
|
series: counts,
|
|
labels: buckets,
|
|
colors: ['#10B981', '#3B82F6', '#F59E0B', '#EF4444'],
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
}
|
|
|
|
{% if response_rates.internal.total > 0 %}
|
|
var rr = JSON.parse('{{ response_rates|escapejs }}');
|
|
makeRRChart('rr-internal-chart', rr.internal);
|
|
if (rr.moh && rr.moh.total > 0) makeRRChart('rr-moh-chart', rr.moh);
|
|
if (rr.chi && rr.chi.total > 0) makeRRChart('rr-chi-chart', rr.chi);
|
|
{% endif %}
|
|
|
|
var msData = JSON.parse('{{ monthly_summary_json|escapejs }}');
|
|
var msMonths = msData.filter(function(r) { return r.month !== 'TOTAL'; }).map(function(r) { return r.month; });
|
|
var msMoh = msData.filter(function(r) { return r.month !== 'TOTAL'; }).map(function(r) { return r.moh; });
|
|
var msChi = msData.filter(function(r) { return r.month !== 'TOTAL'; }).map(function(r) { return r.chi; });
|
|
var msIns = msData.filter(function(r) { return r.month !== 'TOTAL'; }).map(function(r) { return r.insurance; });
|
|
var msInt = msData.filter(function(r) { return r.month !== 'TOTAL'; }).map(function(r) { return r.internal; });
|
|
|
|
var srcSeries = [];
|
|
var srcColors = [];
|
|
if (msMoh.some(function(v) { return v > 0; })) { srcSeries.push({ name: 'MOH', data: msMoh }); srcColors.push('#3B82F6'); }
|
|
if (msChi.some(function(v) { return v > 0; })) { srcSeries.push({ name: 'CHI', data: msChi }); srcColors.push('#F59E0B'); }
|
|
if (msIns.some(function(v) { return v > 0; })) { srcSeries.push({ name: 'Insurance', data: msIns }); srcColors.push('#EF4444'); }
|
|
srcSeries.push({ name: 'Internal', data: msInt }); srcColors.push('#10B981');
|
|
|
|
new ApexCharts(document.getElementById('source-bar-chart'), {
|
|
chart: { type: 'bar', height: 350, stacked: true, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 2 } },
|
|
dataLabels: { enabled: false },
|
|
series: srcSeries,
|
|
xaxis: { categories: msMonths },
|
|
colors: srcColors,
|
|
legend: { position: 'top' },
|
|
tooltip: { shared: true, intersect: false },
|
|
}).render();
|
|
|
|
var dtRows = JSON.parse('{{ dept_type_rows_json|escapejs }}');
|
|
var dtNonTotal = dtRows.filter(function(r) { return !r.is_total; });
|
|
var dtLabels = dtNonTotal.map(function(r) { return r.label; });
|
|
var dtValues = dtNonTotal.map(function(r) { return r.total; });
|
|
|
|
new ApexCharts(document.getElementById('dept-pie-chart'), {
|
|
chart: { type: 'pie', height: 350 },
|
|
series: dtValues,
|
|
labels: dtLabels,
|
|
colors: ['#3B82F6', '#F59E0B', '#8B5CF6', '#6B7280'],
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
|
|
var escData = JSON.parse('{{ escalated_json|escapejs }}');
|
|
var escSource = escData.by_source;
|
|
var escSourceLabels = ['Internal Complaints'];
|
|
var escSourceValues = [escSource.internal || 0];
|
|
var escSourceColors = ['#3B82F6'];
|
|
if ((escSource.moh || 0) > 0) {
|
|
escSourceLabels.push('MOH');
|
|
escSourceValues.push(escSource.moh || 0);
|
|
escSourceColors.push('#10B981');
|
|
}
|
|
if ((escSource.chi || 0) > 0) {
|
|
escSourceLabels.push('CHI');
|
|
escSourceValues.push(escSource.chi || 0);
|
|
escSourceColors.push('#F59E0B');
|
|
}
|
|
|
|
new ApexCharts(document.getElementById('escalated-source-chart'), {
|
|
chart: { type: 'pie', height: 300 },
|
|
series: escSourceValues,
|
|
labels: escSourceLabels,
|
|
colors: escSourceColors,
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
|
|
var escCatLabels = Object.keys(escData.by_category_rates);
|
|
var escRateValues = escCatLabels.map(function(k) { return parseFloat((escData.by_category_rates[k] * 100).toFixed(1)); });
|
|
|
|
new ApexCharts(document.getElementById('escalated-rate-chart'), {
|
|
chart: { type: 'bar', height: 300, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 3, horizontal: true } },
|
|
dataLabels: { enabled: true, formatter: function(v) { return v + '%'; } },
|
|
series: [{ name: 'Escalated Rate', data: escRateValues }],
|
|
xaxis: { categories: escCatLabels, labels: { formatter: function(v) { return v + '%'; } } },
|
|
colors: ['#EF4444'],
|
|
}).render();
|
|
|
|
var escCountValues = escCatLabels.map(function(k) { return escData.by_category_counts[k]; });
|
|
|
|
new ApexCharts(document.getElementById('escalated-count-chart'), {
|
|
chart: { type: 'bar', height: 300, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 3, horizontal: true } },
|
|
dataLabels: { enabled: true },
|
|
series: [{ name: 'Escalated Count', data: escCountValues }],
|
|
xaxis: { categories: escCatLabels },
|
|
colors: ['#8B5CF6'],
|
|
}).render();
|
|
|
|
var pdData = JSON.parse('{{ per_dept_json|escapejs }}');
|
|
var avgResp = pdData.avg_response_days;
|
|
var avgLabels = Object.keys(avgResp).map(function(k) {
|
|
var map = {medical: 'Medical', non_medical: 'Non-Medical', nursing: 'Nursing', support: 'Support Services'};
|
|
return map[k] || k;
|
|
});
|
|
var avgValues = Object.keys(avgResp).map(function(k) { return parseFloat((avgResp[k]).toFixed(1)); });
|
|
|
|
new ApexCharts(document.getElementById('avg-response-chart'), {
|
|
chart: { type: 'bar', height: 280, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 3, horizontal: true } },
|
|
dataLabels: { enabled: true, formatter: function(v) { return v + ' days'; } },
|
|
series: [{ name: 'Avg Response (Days)', data: avgValues }],
|
|
xaxis: { categories: avgLabels },
|
|
colors: ['#3B82F6'],
|
|
}).render();
|
|
|
|
var rrDetail = JSON.parse('{{ response_rates_json|escapejs }}');
|
|
var rrBucketColors = ['#10B981', '#3B82F6', '#F59E0B', '#EF4444'];
|
|
var rrBuckets = ['24h', '48h', '72h', '72h+'];
|
|
var rrChartConfigs = [
|
|
{key: 'internal', elId: 'rr-internal-detail-chart'},
|
|
{key: 'moh', elId: 'rr-moh-detail-chart'},
|
|
{key: 'chi', elId: 'rr-chi-detail-chart'},
|
|
];
|
|
rrChartConfigs.forEach(function(cfg) {
|
|
var data = rrDetail[cfg.key];
|
|
if (!data || data.total === 0) return;
|
|
var el = document.getElementById(cfg.elId);
|
|
if (!el) return;
|
|
var counts = rrBuckets.map(function(b) { return data.counts[b] || 0; });
|
|
new ApexCharts(el, {
|
|
chart: { type: 'donut', height: 280 },
|
|
series: counts,
|
|
labels: rrBuckets,
|
|
colors: rrBucketColors,
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
});
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|