HH/templates/dashboard/standards_dashboard.html
ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

368 lines
19 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}Standards Compliance - 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 "Standards Compliance" %}</h1>
<p class="text-sm text-gray-500 mt-1">{% trans "CBAHI & MOH accreditation standards tracking" %}</p>
</div>
</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="shield-check" class="w-8 h-8 text-gray-400"></i>
</div>
<h3 class="text-lg font-semibold text-gray-700 mb-2">{% trans "No Standards Data" %}</h3>
<p class="text-gray-500">{% trans "No compliance records found. Import standards data using the management command." %}</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-48">
<label class="block text-xs font-semibold text-gray-600 mb-1">{% trans "Source" %}</label>
<select name="source" 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">
<option value="">{% trans "All Sources" %}</option>
{% for s in sources %}
<option value="{{ s.code }}" {% if selected_source == s.code %}selected{% endif %}>{{ s.name }}</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-4 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="list-checks" class="w-5 h-5 text-blue-600"></i>
</div>
<div>
<p class="text-xs text-gray-500">{% trans "Total Standards" %}</p>
<p class="text-xl font-bold text-gray-800">{{ summary.total }}</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-emerald-100 rounded-lg flex items-center justify-center">
<i data-lucide="check-circle-2" class="w-5 h-5 text-emerald-600"></i>
</div>
<div>
<p class="text-xs text-gray-500">{% trans "Met" %}</p>
<p class="text-xl font-bold text-emerald-600">{{ summary.met_pct }}%</p>
<p class="text-xs text-gray-400">{{ summary.met }} {{ "standard"|pluralize }}</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-amber-100 rounded-lg flex items-center justify-center">
<i data-lucide="alert-circle" class="w-5 h-5 text-amber-600"></i>
</div>
<div>
<p class="text-xs text-gray-500">{% trans "Partially Met" %}</p>
<p class="text-xl font-bold text-amber-600">{{ summary.partially_met_pct }}%</p>
<p class="text-xs text-gray-400">{{ summary.partially_met }}</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="x-circle" class="w-5 h-5 text-red-600"></i>
</div>
<div>
<p class="text-xs text-gray-500">{% trans "Not Met" %}</p>
<p class="text-xl font-bold text-red-600">{{ summary.not_met_pct }}%</p>
<p class="text-xs text-gray-400">{{ summary.not_met }}</p>
</div>
</div>
</div>
</div>
{% if score_gauge %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Overall Score" %} (MOH Framework)</h3>
<div id="score-gauge"></div>
<div class="text-center mt-2">
<span class="text-2xl font-bold text-gray-800">{{ score_gauge.score }}</span>
<span class="text-gray-500"> / {{ score_gauge.max_score }}</span>
<span class="text-sm ml-2 font-semibold {% if score_gauge.pct >= 80 %}text-emerald-600{% elif score_gauge.pct >= 60 %}text-amber-600{% else %}text-red-600{% endif %}">({{ score_gauge.pct }}%)</span>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Status Distribution" %}</h3>
<div id="status-donut"></div>
</div>
</div>
{% else %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Status Distribution" %}</h3>
<div id="status-donut"></div>
</div>
{% endif %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4">{% trans "Compliance by Category" %}</h3>
<div id="category-chart"></div>
</div>
{% if category_breakdown %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">{% trans "Category Breakdown" %}</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-600">{% trans "Category" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Total" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Met" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Partially Met" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Not Met" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Compliance %" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for cat in category_breakdown %}
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-800">{{ cat.name_ar|default:cat.name }}</td>
<td class="px-4 py-3 text-center">{{ cat.total }}</td>
<td class="px-4 py-3 text-center text-emerald-600 font-medium">{{ cat.met }}</td>
<td class="px-4 py-3 text-center text-amber-600 font-medium">{{ cat.partially_met }}</td>
<td class="px-4 py-3 text-center text-red-600 font-medium">{{ cat.not_met }}</td>
<td class="px-4 py-3 text-center">
<div class="flex items-center justify-center gap-2">
<div class="w-20 bg-gray-200 rounded-full h-2">
<div class="h-2 rounded-full {% if cat.met_pct >= 80 %}bg-emerald-500{% elif cat.met_pct >= 60 %}bg-amber-500{% else %}bg-red-500{% endif %}" style="width: {{ cat.met_pct }}%"></div>
</div>
<span class="text-xs font-medium">{{ cat.met_pct }}%</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% if corrective_actions %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 flex items-center gap-2">
<i data-lucide="wrench" class="w-4 h-4 text-amber-600"></i>
<h3 class="text-sm font-semibold text-gray-700">{% trans "Corrective Actions" %} ({{ corrective_actions|length }})</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-600">{% trans "Standard" %}</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-600">{% trans "Category" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Status" %}</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-600">{% trans "Corrective Action" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Priority" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Target Date" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for ca in corrective_actions %}
<tr class="hover:bg-gray-50">
<td class="px-4 py-3">
<div class="font-medium text-gray-800 text-xs">{{ ca.code }}</div>
<div class="text-xs text-gray-500 mt-0.5 max-w-xs truncate">{{ ca.title }}</div>
</td>
<td class="px-4 py-3 text-xs text-gray-600">{{ ca.category }}</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex px-2 py-0.5 text-xs font-medium rounded-full
{% if ca.status == 'Met' %}bg-emerald-100 text-emerald-700
{% elif ca.status == 'Partially Met' %}bg-amber-100 text-amber-700
{% elif ca.status == 'Not Met' %}bg-red-100 text-red-700
{% else %}bg-gray-100 text-gray-700{% endif %}">
{{ ca.status_ar|default:ca.status }}
</span>
</td>
<td class="px-4 py-3 text-xs text-gray-700 max-w-md">{{ ca.corrective_action }}</td>
<td class="px-4 py-3 text-center">
{% if ca.priority %}
<span class="inline-flex px-2 py-0.5 text-xs font-medium rounded-full
{% if ca.priority == 'High' %}bg-red-100 text-red-700
{% elif ca.priority == 'Medium' %}bg-amber-100 text-amber-700
{% else %}bg-blue-100 text-blue-700{% endif %}">
{{ ca.priority }}
</span>
{% endif %}
</td>
<td class="px-4 py-3 text-center text-xs text-gray-600">{{ ca.target_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% if standards_table %}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<div class="flex items-center gap-2">
<i data-lucide="shield-check" class="w-4 h-4 text-blue-600"></i>
<h3 class="text-sm font-semibold text-gray-700">{% trans "All Standards" %} ({{ standards_table|length }})</h3>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm" id="standards-table">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-600">{% trans "Code" %}</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-600">{% trans "Standard" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Category" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Status" %}</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-gray-600">{% trans "Assessment" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for s in standards_table %}
<tr class="hover:bg-gray-50 cursor-pointer" onclick="toggleDetail(this)">
<td class="px-4 py-3 text-xs font-mono text-gray-600">{{ s.code }}</td>
<td class="px-4 py-3 text-xs text-gray-800 max-w-sm">{{ s.title }}</td>
<td class="px-4 py-3 text-center text-xs text-gray-600">{{ s.category }}</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex px-2 py-0.5 text-xs font-medium rounded-full
{% if s.status_raw == 'met' %}bg-emerald-100 text-emerald-700
{% elif s.status_raw == 'partially_met' %}bg-amber-100 text-amber-700
{% elif s.status_raw == 'not_met' %}bg-red-100 text-red-700
{% elif s.status_raw == 'not_applicable' %}bg-gray-100 text-gray-500
{% else %}bg-gray-100 text-gray-700{% endif %}">
{{ s.status_ar|default:s.status }}
</span>
</td>
<td class="px-4 py-3 text-center text-xs text-gray-500">{{ s.assessment_method }}</td>
</tr>
{% if s.recommendations %}
<tr class="hidden detail-row">
<td colspan="5" class="px-6 py-3 bg-gray-50">
<div class="text-xs">
<span class="font-semibold text-gray-700">{% trans "Recommendations:" %}</span>
<span class="text-gray-600 ml-1">{{ s.recommendations }}</span>
</div>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function toggleDetail(row) {
const detail = row.nextElementSibling;
if (detail && detail.classList.contains('detail-row')) {
detail.classList.toggle('hidden');
}
}
(function() {
const chartData = JSON.parse('{{ chart_data_json|escapejs }}');
const statusChart = chartData.status_distribution;
const categoryChart = chartData.category_compliance;
const scoreGauge = chartData.score_gauge;
if (statusChart) {
const opts = {
chart: { type: 'donut', height: 280, fontFamily: 'inherit' },
labels: statusChart.labels_ar,
series: statusChart.series,
colors: statusChart.colors,
legend: { position: 'bottom', fontSize: '12px' },
dataLabels: { enabled: true, style: { fontSize: '12px' } },
plotOptions: {
pie: {
donut: {
size: '65%',
labels: {
show: true,
total: {
show: true,
label: '{% trans "Total" %}',
fontSize: '14px',
fontWeight: 600,
}
}
}
}
},
};
new ApexCharts(document.querySelector('#status-donut'), opts).render();
}
if (categoryChart && categoryChart.categories.length > 0) {
const opts = {
chart: { type: 'bar', height: 300, stacked: true, fontFamily: 'inherit', toolbar: { show: false } },
series: categoryChart.series,
xaxis: { categories: categoryChart.categories },
colors: ['#10b981', '#f59e0b', '#ef4444'],
legend: { position: 'top', fontSize: '12px' },
plotOptions: { bar: { horizontal: true, borderRadius: 4 } },
yaxis: { labels: { style: { fontSize: '11px' } } },
tooltip: { shared: true, intersect: false },
};
new ApexCharts(document.querySelector('#category-chart'), opts).render();
}
if (scoreGauge) {
const opts = {
chart: { type: 'radialBar', height: 220, fontFamily: 'inherit' },
series: [scoreGauge.pct],
colors: [scoreGauge.pct >= 80 ? '#10b981' : scoreGauge.pct >= 60 ? '#f59e0b' : '#ef4444'],
plotOptions: {
radialBar: {
startAngle: -135,
endAngle: 135,
hollow: { size: '60%' },
dataLabels: {
name: { show: false },
value: {
show: true,
fontSize: '28px',
fontWeight: 700,
formatter: function(val) { return val + '%'; },
}
}
}
},
};
new ApexCharts(document.querySelector('#score-gauge'), opts).render();
}
})();
</script>
{% endblock %}