228 lines
11 KiB
HTML
228 lines
11 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}Census 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 "Census Report" %}</h1>
|
|
<p class="text-sm text-gray-500 mt-1">{% trans "OPD, ER & Inpatient visit counts from HIS data" %}</p>
|
|
</div>
|
|
<a href="{% url 'dashboard:census_report_export' %}?hospital={{ selected_hospital }}&year={{ selected_year }}"
|
|
class="inline-flex items-center gap-2 px-4 py-2 bg-emerald-600 text-white text-sm font-medium rounded-lg hover:bg-emerald-700 transition">
|
|
<i data-lucide="download" class="w-4 h-4"></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="database" class="w-8 h-8 text-gray-400"></i>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-gray-700 mb-2">{% trans "No Visit Data Available" %}</h3>
|
|
<p class="text-gray-500">{% trans "There are no HIS patient visit records in the database for the selected hospital and year." %}</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 focus:ring-2 focus:ring-blue-500 focus:border-blue-500" 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-32">
|
|
<label class="block text-xs font-semibold text-gray-600 mb-1">{% trans "Year" %}</label>
|
|
<select name="year" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
{% for y in available_years %}
|
|
<option value="{{ y }}" {% if selected_year == y %}selected{% endif %}>{{ y }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</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="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
|
<i data-lucide="stethoscope" class="w-5 h-5 text-blue-600"></i>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "OPD Total" %}</p>
|
|
<p class="text-2xl font-bold text-gray-800">{{ year_totals.OPD_fmt }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-red-100 rounded-lg flex items-center justify-center">
|
|
<i data-lucide="siren" class="w-5 h-5 text-red-600"></i>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "ER Total" %}</p>
|
|
<p class="text-2xl font-bold text-gray-800">{{ year_totals.ER_fmt }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
|
<i data-lucide="bed-double" class="w-5 h-5 text-purple-600"></i>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500 font-medium">{% trans "Inpatient Total" %}</p>
|
|
<p class="text-2xl font-bold text-gray-800">{{ year_totals.IP_fmt }}</p>
|
|
</div>
|
|
</div>
|
|
</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 "Year-over-Year Comparison" %}</h3>
|
|
<div id="yoy-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 Trend" %} ({{ selected_year }})</h3>
|
|
<div id="monthly-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 "OPD Quarterly" %}</h3>
|
|
<div id="opd-quarterly-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 "ER Quarterly" %}</h3>
|
|
<div id="er-quarterly-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 "Inpatient Quarterly" %}</h3>
|
|
<div id="ip-quarterly-chart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{% for yt in yearly_tables %}
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6">
|
|
<div class="p-5 border-b border-gray-200">
|
|
<h3 class="text-lg font-semibold text-gray-800">
|
|
{% trans "Year" %} {{ yt.year }} - {% trans "Quarterly Breakdown" %}
|
|
{% if yt.is_selected %}<span class="ml-2 text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full">{% trans "Selected" %}</span>{% endif %}
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="bg-[#2F75B5] text-white">
|
|
<th class="px-4 py-3 text-left font-semibold">{% trans "Area" %}</th>
|
|
<th class="px-4 py-3 text-center font-semibold">Q1</th>
|
|
<th class="px-4 py-3 text-center font-semibold">Q2</th>
|
|
<th class="px-4 py-3 text-center font-semibold">Q3</th>
|
|
<th class="px-4 py-3 text-center font-semibold">Q4</th>
|
|
<th class="px-4 py-3 text-center font-semibold bg-emerald-600">{% trans "Total" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in yt.rows %}
|
|
<tr class="border-b border-gray-100 {% cycle '' 'bg-gray-50' %}">
|
|
<td class="px-4 py-3 font-medium text-gray-700">{{ row.area }}</td>
|
|
<td class="px-4 py-3 text-center text-gray-600">{{ row.q1_fmt }}</td>
|
|
<td class="px-4 py-3 text-center text-gray-600">{{ row.q2_fmt }}</td>
|
|
<td class="px-4 py-3 text-center text-gray-600">{{ row.q3_fmt }}</td>
|
|
<td class="px-4 py-3 text-center text-gray-600">{{ row.q4_fmt }}</td>
|
|
<td class="px-4 py-3 text-center font-semibold bg-emerald-50 text-emerald-700">{{ row.total_fmt }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
{% if not no_data and chart_data_json %}
|
|
<script>
|
|
(function() {
|
|
var chartData = JSON.parse('{{ chart_data_json|escapejs }}');
|
|
var yearsForComparison = {{ years_for_comparison|safe }};
|
|
|
|
var yoyAllSeries = [];
|
|
['OPD', 'ER', 'IP'].forEach(function(area) {
|
|
yearsForComparison.forEach(function(year) {
|
|
yoyAllSeries.push({
|
|
name: area + ' ' + year,
|
|
data: chartData.yoy[area][year] || [0, 0, 0, 0],
|
|
});
|
|
});
|
|
});
|
|
|
|
new ApexCharts(document.getElementById('yoy-chart'), {
|
|
chart: { type: 'bar', height: 300, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 4, columnWidth: '60%' } },
|
|
dataLabels: { enabled: false },
|
|
series: yoyAllSeries,
|
|
xaxis: { categories: chartData.quarters },
|
|
yaxis: { labels: { formatter: function(v) { return v.toLocaleString(); } } },
|
|
tooltip: { y: { formatter: function(v) { return v.toLocaleString() + ' visits'; } } },
|
|
legend: { position: 'bottom', fontSize: '11px' },
|
|
colors: ['#3B82F6', '#93C5FD', '#1E3A5F', '#EF4444', '#FCA5A5', '#7F1D1D', '#8B5CF6', '#C4B5FD', '#4C1D95'],
|
|
}).render();
|
|
|
|
new ApexCharts(document.getElementById('monthly-chart'), {
|
|
chart: { type: 'bar', height: 300, toolbar: { show: true } },
|
|
plotOptions: { bar: { borderRadius: 4 } },
|
|
dataLabels: { enabled: false },
|
|
series: [
|
|
{ name: 'OPD', data: chartData.monthly.OPD, color: '#3B82F6' },
|
|
{ name: 'ER', data: chartData.monthly.ER, color: '#EF4444' },
|
|
{ name: 'IP', data: chartData.monthly.IP, color: '#8B5CF6' },
|
|
],
|
|
xaxis: { categories: chartData.months },
|
|
yaxis: { labels: { formatter: function(v) { return v.toLocaleString(); } } },
|
|
tooltip: { y: { formatter: function(v) { return v.toLocaleString() + ' visits'; } } },
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
|
|
var areaChartIds = { OPD: 'opd-quarterly-chart', ER: 'er-quarterly-chart', IP: 'ip-quarterly-chart' };
|
|
var yearColors = ['#94A3B8', '#60A5FA', '#2F75B5'];
|
|
|
|
['OPD', 'ER', 'IP'].forEach(function(area) {
|
|
var series = [];
|
|
yearsForComparison.forEach(function(year, yi) {
|
|
series.push({
|
|
name: String(year),
|
|
data: chartData.yoy[area][year] || [0, 0, 0, 0],
|
|
color: yearColors[yi] || '#60A5FA',
|
|
});
|
|
});
|
|
new ApexCharts(document.getElementById(areaChartIds[area]), {
|
|
chart: { type: 'bar', height: 250, toolbar: { show: false } },
|
|
plotOptions: { bar: { borderRadius: 4 } },
|
|
dataLabels: { enabled: false },
|
|
series: series,
|
|
xaxis: { categories: chartData.quarters },
|
|
yaxis: { labels: { formatter: function(v) { return v.toLocaleString(); } } },
|
|
tooltip: { y: { formatter: function(v) { return v.toLocaleString() + ' visits'; } } },
|
|
legend: { position: 'bottom' },
|
|
}).render();
|
|
});
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|