HH/templates/analytics/kpi_report_pdf.html
2026-02-25 04:47:05 +03:00

915 lines
36 KiB
HTML

{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ report.indicator_title }} - {% trans "KPI Report" %}</title>
<!-- Tailwind CSS (for PDF preview only) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<!-- ApexCharts -->
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<!-- html2pdf.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
navy: '#005696',
blue: '#007bbd',
light: '#eef6fb',
slate: '#64748b',
}
}
}
}
</script>
<style>
/* Base styles that work without Tailwind */
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 0;
background: white;
color: #334155;
line-height: 1.5;
}
/* Utility classes that match Tailwind */
.bg-white { background-color: white; }
.bg-navy { background-color: #005696; }
.bg-blue { background-color: #007bbd; }
.bg-light { background-color: #eef6fb; }
.text-white { color: white; }
.text-navy { color: #005696; }
.text-slate { color: #64748b; }
.text-slate-800 { color: #1e293b; }
.font-bold { font-weight: 700; }
.font-semibold { font-weight: 600; }
.text-2xl { font-size: 1.5rem; }
.text-3xl { font-size: 1.875rem; }
.text-sm { font-size: 0.875rem; }
.text-xs { font-size: 0.75rem; }
.text-xl { font-size: 1.25rem; }
.text-lg { font-size: 1.125rem; }
.font-black { font-weight: 900; }
.rounded-lg { border-radius: 0.5rem; }
.rounded-xl { border-radius: 0.75rem; }
.rounded-2xl { border-radius: 1rem; }
.rounded-full { border-radius: 9999px; }
.border { border: 1px solid #e2e8f0; }
.shadow-sm { box-shadow: 0 1px 2px rgba(0,0,0,0.05); }
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1); }
.p-2 { padding: 0.5rem; }
.p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; }
.p-5 { padding: 1.25rem; }
.p-6 { padding: 1.5rem; }
.p-8 { padding: 2rem; }
.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.px-5 { padding-left: 1.25rem; padding-right: 1.25rem; }
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
.py-1_5 { padding-top: 0.375rem; padding-bottom: 0.375rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-2_5 { padding-top: 0.625rem; padding-bottom: 0.625rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
.m-0 { margin: 0; }
.mb-1 { margin-bottom: 0.25rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 0.75rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.mb-8 { margin-bottom: 2rem; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 0.75rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mt-12 { margin-top: 3rem; }
.mr-1 { margin-right: 0.25rem; }
.mr-2 { margin-right: 0.5rem; }
.ml-1 { margin-left: 0.25rem; }
.ml-2 { margin-left: 0.5rem; }
.mx-auto { margin-left: auto; margin-right: auto; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }
.gap-6 { gap: 1.5rem; }
.flex { display: flex; }
.inline-flex { display: inline-flex; }
.inline { display: inline; }
.inline-block { display: inline-block; }
.block { display: block; }
.hidden { display: none; }
.grid { display: grid; }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid-cols-5 { grid-template-columns: repeat(5, 1fr); }
.w-full { width: 100%; }
.w-3 { width: 0.75rem; }
.w-4 { width: 1rem; }
.w-5 { width: 1.25rem; }
.w-8 { width: 2rem; }
.h-3 { height: 0.75rem; }
.h-4 { height: 1rem; }
.h-5 { height: 1.25rem; }
.h-8 { height: 2rem; }
.h-10 { height: 2.5rem; }
.min-h-220 { min-height: 220px; }
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.items-start { align-items: flex-start; }
.items-center { align-items: center; }
.flex-wrap { flex-wrap: wrap; }
.flex-1 { flex: 1; }
.flex-col { flex-direction: column; }
.relative { position: relative; }
.absolute { position: absolute; }
.fixed { position: fixed; }
.top-4 { top: 1rem; }
.right-4 { right: 1rem; }
.z-50 { z-index: 50; }
.uppercase { text-transform: uppercase; }
.italic { font-style: italic; }
.whitespace-pre-line { white-space: pre-line; }
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.border-collapse { border-collapse: collapse; }
.border-b { border-bottom: 1px solid #e2e8f0; }
.border-b-2 { border-bottom: 2px solid; }
.border-t { border-top: 1px solid #e2e8f0; }
.border-t-2 { border-top: 2px solid; }
.border-l { border-left: 1px solid #e2e8f0; }
.border-slate-200 { border-color: #e2e8f0; }
.border-slate-300 { border-color: #cbd5e1; }
.border-slate-400 { border-color: #94a3b8; }
.border-navy { border-color: #005696; }
.border-green-200 { border-color: #bbf7d0; }
.border-red-200 { border-color: #fecaca; }
.border-blue-200 { border-color: #bfdbfe; }
.border-yellow-200 { border-color: #fde68a; }
.bg-green-50 { background-color: #f0fdf4; }
.bg-green-100 { background-color: #dcfce7; }
.bg-green-200 { background-color: #bbf7d0; }
.bg-green-600 { background-color: #16a34a; }
.bg-green-700 { background-color: #15803d; }
.bg-green-800 { background-color: #166534; }
.bg-red-50 { background-color: #fef2f2; }
.bg-red-100 { background-color: #fee2e2; }
.bg-red-200 { background-color: #fecaca; }
.bg-red-500 { background-color: #ef4444; }
.bg-red-600 { background-color: #dc2626; }
.bg-yellow-50 { background-color: #fefce8; }
.bg-yellow-100 { background-color: #fef9c3; }
.bg-yellow-200 { background-color: #fde68a; }
.bg-yellow-600 { background-color: #ca8a04; }
.bg-yellow-800 { background-color: #854d0e; }
.bg-blue-50 { background-color: #eff6ff; }
.bg-blue-100 { background-color: #dbeafe; }
.bg-blue-200 { background-color: #bfdbfe; }
.bg-slate-50 { background-color: #f8fafc; }
.bg-slate-100 { background-color: #f1f5f9; }
.bg-slate-200 { background-color: #e2e8f0; }
.bg-slate-300 { background-color: #cbd5e1; }
.bg-slate-400 { background-color: #94a3b8; }
.bg-slate-600 { background-color: #475569; }
.text-green-600 { color: #16a34a; }
.text-green-700 { color: #15803d; }
.text-green-800 { color: #166534; }
.text-red-500 { color: #ef4444; }
.text-red-600 { color: #dc2626; }
.text-red-800 { color: #991b1b; }
.text-yellow-600 { color: #ca8a04; }
.text-yellow-800 { color: #854d0e; }
.text-blue { color: #007bbd; }
.text-blue-600 { color: #2563eb; }
.text-blue-700 { color: #1d4ed8; }
.text-blue-800 { color: #1e40af; }
.hover\:opacity-90:hover { opacity: 0.9; }
.hover\:bg-blue:hover { background-color: #007bbd; }
.hover\:bg-slate-600:hover { background-color: #475569; }
.hover\:bg-slate-50:hover { background-color: #f8fafc; }
.hover\:-translate-y-1:hover { transform: translateY(-0.25rem); }
.hover\:shadow-lg:hover { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1); }
.hover\:underline:hover { text-decoration: underline; }
.transition { transition: all 0.2s; }
.transition-all { transition: all 0.2s; }
.transition-colors { transition: color 0.2s, background-color 0.2s, border-color 0.2s; }
.cursor-pointer { cursor: pointer; }
.cursor-not-allowed { cursor: not-allowed; }
.overflow-hidden { overflow: hidden; }
.overflow-x-auto { overflow-x: auto; }
.overflow-visible { overflow: visible; }
.list-disc { list-style-type: disc; }
.list-inside { list-style-position: inside; }
.space-y-1 > * + * { margin-top: 0.25rem; }
.space-y-2 > * + * { margin-top: 0.5rem; }
.space-y-4 > * + * { margin-top: 1rem; }
.pt-1 { padding-top: 0.25rem; }
.pt-3 { padding-top: 0.75rem; }
.pt-4 { padding-top: 1rem; }
.pb-1 { padding-bottom: 0.25rem; }
.pb-2 { padding-bottom: 0.5rem; }
.pb-4 { padding-bottom: 1rem; }
.pb-8 { padding-bottom: 2rem; }
.pl-4 { padding-left: 1rem; }
.pr-4 { padding-right: 1rem; }
.tracking-wider { letter-spacing: 0.05em; }
.tracking-tight { letter-spacing: -0.025em; }
.leading-tight { line-height: 1.25; }
/* Animation */
.animate-spin { animation: spin 1s linear infinite; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/* Print styles */
@media print {
.no-print { display: none !important; }
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
.excel-border { border: 1px solid #94a3b8; }
.page-break { page-break-after: always; }
.no-break { page-break-inside: avoid; }
/* Chart container styles */
#trendChart, #sourceChart {
max-width: 100% !important;
overflow: visible !important;
position: relative;
}
/* Ensure charts fit within PDF */
.apexcharts-canvas {
max-width: 100% !important;
}
/* Static chart images for PDF */
.chart-static-image {
max-width: 100% !important;
height: auto !important;
display: block;
background: white;
}
/* Ensure proper sizing for PDF capture */
.apexcharts-svg {
max-width: 100% !important;
}
#trendChart, #sourceChart {
min-height: 220px;
background: white;
}
/* Prevent horizontal overflow */
#report-content {
overflow-x: hidden !important;
}
/* Ensure proper sizing for PDF capture */
.apexcharts-svg {
max-width: 100% !important;
}
</style>
</head>
<body class="bg-white text-slate-800">
<!-- PDF Controls -->
<div class="no-print fixed top-4 right-4 z-50 flex gap-2">
<button onclick="generatePDF()" class="bg-navy text-white px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-blue transition-colors shadow-lg">
<i data-lucide="download" class="w-4 h-4"></i>
{% trans "Download PDF" %}
</button>
<button onclick="window.print()" class="bg-slate text-white px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-slate-600 transition-colors shadow-lg">
<i data-lucide="printer" class="w-4 h-4"></i>
{% trans "Print" %}
</button>
<a href="{% url 'analytics:kpi_report_detail' report.id %}" class="bg-white text-navy border border-navy px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-light transition-colors shadow-lg">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back" %}
</a>
</div>
<!-- Report Container -->
<div id="report-content" class="w-full mx-auto p-8">
<!-- Header with Logo -->
<div class="flex justify-between items-start mb-8 pb-4 border-b-2 border-navy no-break">
<div>
<h1 class="text-3xl font-bold text-navy">PX360</h1>
<p class="text-slate">{% trans "Patient Experience Management System" %}</p>
</div>
<div class="text-right">
<div class="px-3 py-1 bg-navy text-white text-sm font-bold rounded inline-block mb-1">
{{ report.kpi_id }}
</div>
<p class="text-sm text-slate">{{ report.report_period_display }}</p>
</div>
</div>
<!-- Report Title -->
<div class="mb-6 no-break">
<h2 class="text-2xl font-bold text-navy">{{ report.indicator_title }}</h2>
<p class="text-slate">
<i data-lucide="building-2" class="w-4 h-4 inline mr-1"></i>
{{ report.hospital.name }}
</p>
</div>
<!-- Main Data Table -->
<div class="mb-8 no-break">
<table class="w-full border-collapse excel-border">
<!-- Header -->
<thead>
<tr class="bg-navy text-white">
<th class="p-3 text-left text-sm font-semibold border excel-border">{% trans "KPI ID" %}</th>
<th class="p-3 text-left text-sm font-semibold border excel-border w-56">{% trans "Indicator Title" %}</th>
<th class="p-3 text-left text-sm font-semibold border excel-border">{% trans "Measure" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Jan" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Feb" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Mar" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Apr" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "May" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Jun" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Jul" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Aug" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Sep" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Oct" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Nov" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border w-10">{% trans "Dec" %}</th>
<th class="p-3 text-center text-sm font-semibold border excel-border bg-blue">{% trans "TOTAL" %}</th>
</tr>
</thead>
<tbody>
<!-- Numerator Row -->
<tr>
<td rowspan="3" class="p-3 border excel-border text-center text-blue font-bold align-middle">
{{ report.kpi_id }}
</td>
<td rowspan="3" class="p-3 border excel-border text-left font-bold text-navy align-middle text-sm">
{{ report.indicator_title }}
</td>
<td class="p-3 border excel-border text-left bg-slate-50 text-sm">
{{ report.numerator_label }}
</td>
{% for m in monthly_data %}
<td class="p-3 border excel-border text-center text-sm">{% if m %}{{ m.numerator }}{% else %}-{% endif %}</td>
{% endfor %}
<td class="p-3 border excel-border text-center font-bold bg-slate-100">{{ report.total_numerator }}</td>
</tr>
<!-- Denominator Row -->
<tr>
<td class="p-3 border excel-border text-left bg-slate-50 text-sm">
{{ report.denominator_label }}
</td>
{% for m in monthly_data %}
<td class="p-3 border excel-border text-center text-sm">{% if m %}{{ m.denominator }}{% else %}-{% endif %}</td>
{% endfor %}
<td class="p-3 border excel-border text-center font-bold bg-slate-100">{{ report.total_denominator }}</td>
</tr>
<!-- Result Row -->
<tr class="font-bold">
<td class="p-3 border excel-border text-left text-navy bg-slate-100">
{% trans "Result (%)" %}
</td>
{% for m in monthly_data %}
<td class="p-3 border excel-border text-center text-sm
{% if m and m.is_below_target %}text-red-600 bg-red-50{% elif m %}text-green-600{% endif %}">
{% if m %}{{ m.percentage }}%{% else %}-{% endif %}
</td>
{% endfor %}
<td class="p-3 border excel-border text-center font-bold bg-navy text-white">
{{ report.overall_result }}%
</td>
</tr>
</tbody>
</table>
</div>
<!-- Metadata Grid -->
<div class="grid grid-cols-5 gap-2 text-xs bg-light p-3 rounded-lg border excel-border mb-8 no-break">
<div><span class="font-bold text-navy">{% trans "Category:" %}</span> {{ report.category }}</div>
<div><span class="font-bold text-navy">{% trans "Type:" %}</span> {{ report.kpi_type }}</div>
<div>
<span class="font-bold text-navy">{% trans "Risk:" %}</span>
<span class="px-1.5 py-0.5 rounded text-xs
{% if report.risk_level == 'High' %}bg-red-200 text-red-800
{% elif report.risk_level == 'Medium' %}bg-yellow-200 text-yellow-800
{% else %}bg-green-200 text-green-800{% endif %}">
{{ report.risk_level }}
</span>
</div>
<div><span class="font-bold text-navy">{% trans "Data coll.:" %}</span> {{ report.data_collection_method }}</div>
<div><span class="font-bold text-navy">{% trans "Method:" %}</span> {{ report.data_collection_frequency }}</div>
<div><span class="font-bold text-navy">{% trans "Dimension:" %}</span> {{ report.dimension }}</div>
<div><span class="font-bold text-navy">{% trans "Gather freq.:" %}</span> {{ report.data_collection_frequency }}</div>
<div><span class="font-bold text-navy">{% trans "Reporting:" %}</span> {{ report.reporting_frequency }}</div>
<div><span class="font-bold text-navy">{% trans "Collector:" %}</span> {{ report.collector_name|default:"-" }}</div>
<div><span class="font-bold text-navy">{% trans "Analyzer:" %}</span> {{ report.analyzer_name|default:"-" }}</div>
</div>
<!-- Charts -->
<div class="mb-8">
<!-- Trend Chart -->
<div class="border excel-border p-4 rounded-lg bg-slate-50/50 mb-6">
<p class="text-xs font-bold text-navy uppercase mb-4 flex items-center gap-2">
<i data-lucide="trending-up" class="w-3 h-3"></i>
{% trans "Monthly Performance Trend (%)" %}
<span class="text-slate font-normal">[{% trans "Target:" %} {{ report.target_percentage }}%]</span>
</p>
<div id="trendChart" style="height: 220px; width: 100%;"></div>
</div>
<!-- Source Chart -->
<div class="border excel-border p-4 rounded-lg bg-slate-50/50" style="max-width: 400px; margin: 0 auto;">
<p class="text-xs font-bold text-navy uppercase mb-4 flex items-center gap-2 text-center">
<i data-lucide="pie-chart" class="w-3 h-3"></i>
{% trans "Complaints by Source" %}
</p>
<div id="sourceChart" style="height: 220px; width: 100%;"></div>
</div>
</div>
<!-- Department Breakdown -->
<div class="mb-8 no-break">
<h3 class="text-xs font-bold text-navy uppercase mb-4 flex items-center gap-2">
<i data-lucide="building" class="w-3 h-3"></i>
{% trans "Department Breakdown" %}
</h3>
<div class="grid grid-cols-2 border-t border-l excel-border">
{% for dept in department_breakdowns %}
<div class="p-4 border-r border-b excel-border
{% if dept.department_category == 'support' %}bg-slate-50{% endif %}">
<h4 class="text-white text-xs font-bold px-2 py-1 mb-2 inline-block
{% if dept.department_category == 'medical' %}bg-navy
{% elif dept.department_category == 'nursing' %}bg-slate
{% elif dept.department_category == 'admin' %}bg-blue
{% else %}bg-slate-400{% endif %}">
{{ dept.get_department_category_display }}
</h4>
<div class="text-xs text-slate space-y-1">
<p><span class="font-semibold">{% trans "Complaints:" %}</span> {{ dept.complaint_count }}</p>
<p><span class="font-semibold">{% trans "Resolved:" %}</span> {{ dept.resolved_count }}</p>
{% if dept.avg_resolution_days %}
<p><span class="font-semibold">{% trans "Avg Resolution:" %}</span> {{ dept.avg_resolution_days }} {% trans "days" %}</p>
{% endif %}
{% if dept.top_areas %}
<p class="mt-2 whitespace-pre-line italic">{{ dept.top_areas }}</p>
{% else %}
<p class="italic text-slate-400">{% trans "No complaints received for these departments." %}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
<!-- AI Analysis Section -->
{% if report.ai_analysis %}
<div class="page-break"></div>
<div class="mt-8 no-break">
<h3 class="text-lg font-bold text-navy mb-4 pb-2 border-b-2 border-navy">
<i data-lucide="brain" class="w-5 h-5 inline mr-2"></i>
{% trans "AI-Generated Analysis" %}
</h3>
{% with analysis=report.ai_analysis %}
<!-- Executive Summary -->
{% if analysis.executive_summary %}
<div class="mb-4 p-3 bg-blue-50 border border-blue-200 rounded">
<h4 class="text-sm font-bold text-blue mb-1">{% trans "Executive Summary" %}</h4>
<p class="text-sm text-navy">{{ analysis.executive_summary }}</p>
</div>
{% endif %}
<!-- Performance Analysis -->
{% if analysis.performance_analysis %}
<div class="mb-4">
<h4 class="text-sm font-bold text-navy mb-1">{% trans "Performance Analysis" %}</h4>
<p class="text-sm text-slate">{{ analysis.performance_analysis }}</p>
</div>
{% endif %}
<!-- Comparison to Target (for specific report types) -->
{% if analysis.comparison_to_target %}
<div class="mb-4">
<h4 class="text-sm font-bold text-navy mb-1">{% trans "Comparison to Target" %}</h4>
<p class="text-sm text-slate">{{ analysis.comparison_to_target }}</p>
</div>
{% endif %}
<!-- Key Findings -->
{% if analysis.key_findings %}
<div class="mb-4">
<h4 class="text-sm font-bold text-navy mb-1">{% trans "Key Findings" %}</h4>
<ul class="list-disc list-inside text-sm text-slate">
{% for finding in analysis.key_findings %}
<li>{{ finding }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<!-- Reasons for Delays -->
{% if analysis.reasons_for_delays %}
<div class="mb-4">
<h4 class="text-sm font-bold text-navy mb-1">{% trans "Reasons for Delays" %}</h4>
<ul class="list-disc list-inside text-sm text-slate">
{% for reason in analysis.reasons_for_delays %}
<li>{{ reason }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<!-- Delay Reasons (alternative format) -->
{% if analysis.delay_reasons %}
<div class="mb-4">
<h4 class="text-sm font-bold text-navy mb-1">{% trans "Reasons for Delays" %}</h4>
<ul class="list-disc list-inside text-sm text-slate">
{% for reason in analysis.delay_reasons %}
<li>{{ reason }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<!-- Resolution Time Analysis -->
{% if analysis.resolution_time_analysis %}
<div class="mb-4">
<h4 class="text-sm font-bold text-navy mb-1">{% trans "Resolution Time Breakdown" %}</h4>
<div class="grid grid-cols-4 gap-2 text-center text-sm">
{% if analysis.resolution_time_analysis.within_24h %}
<div class="p-2 bg-green-50 border border-green-200 rounded">
<p class="text-xs text-slate">{% trans "Within 24h" %}</p>
<p class="font-bold text-green-600">{{ analysis.resolution_time_analysis.within_24h.count }}</p>
<p class="text-xs text-green-600">{{ analysis.resolution_time_analysis.within_24h.percentage }}</p>
</div>
{% endif %}
{% if analysis.resolution_time_analysis.within_48h %}
<div class="p-2 bg-blue-50 border border-blue-200 rounded">
<p class="text-xs text-slate">{% trans "Within 48h" %}</p>
<p class="font-bold text-blue-600">{{ analysis.resolution_time_analysis.within_48h.count }}</p>
<p class="text-xs text-blue-600">{{ analysis.resolution_time_analysis.within_48h.percentage }}</p>
</div>
{% endif %}
{% if analysis.resolution_time_analysis.within_72h %}
<div class="p-2 bg-yellow-50 border border-yellow-200 rounded">
<p class="text-xs text-slate">{% trans "Within 72h" %}</p>
<p class="font-bold text-yellow-600">{{ analysis.resolution_time_analysis.within_72h.count }}</p>
<p class="text-xs text-yellow-600">{{ analysis.resolution_time_analysis.within_72h.percentage }}</p>
</div>
{% endif %}
{% if analysis.resolution_time_analysis.over_72h %}
<div class="p-2 bg-red-50 border border-red-200 rounded">
<p class="text-xs text-slate">{% trans "Over 72h" %}</p>
<p class="font-bold text-red-600">{{ analysis.resolution_time_analysis.over_72h.count }}</p>
<p class="text-xs text-red-600">{{ analysis.resolution_time_analysis.over_72h.percentage }}</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Recommendations -->
{% if analysis.recommendations %}
<div class="mb-4 p-3 bg-green-50 border border-green-200 rounded">
<h4 class="text-sm font-bold text-green-800 mb-1">{% trans "Recommendations" %}</h4>
<ul class="list-disc list-inside text-sm text-green-700">
{% for rec in analysis.recommendations %}
<li>{{ rec }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endwith %}
</div>
{% endif %}
<!-- Review and Approval Section -->
<div class="page-break"></div>
<div class="mt-12 pt-8 border-t-2 border-slate-300">
<h3 class="text-lg font-bold text-navy mb-6">{% trans "Review and Approval" %}</h3>
<div class="grid grid-cols-3 gap-6">
<!-- Reviewed By -->
<div class="border-t border-slate-400 pt-4">
<p class="text-sm font-bold text-navy mb-8">{% trans "Reviewed By:" %}</p>
<div class="mt-12">
<p class="text-sm text-slate border-b border-slate-300 pb-1 mb-2">&nbsp;</p>
<p class="text-xs text-slate">{% trans "Name & Signature" %}</p>
<p class="text-xs text-slate mt-2">{% trans "Date:" %} _______________</p>
</div>
</div>
<!-- Reviewed and Approved By 1 -->
<div class="border-t border-slate-400 pt-4">
<p class="text-sm font-bold text-navy mb-8">{% trans "Reviewed and Approved By:" %}</p>
<div class="mt-12">
<p class="text-sm text-slate border-b border-slate-300 pb-1 mb-2">&nbsp;</p>
<p class="text-xs text-slate">{% trans "Name & Signature" %}</p>
<p class="text-xs text-slate mt-2">{% trans "Date:" %} _______________</p>
</div>
</div>
<!-- Reviewed and Approved By 2 -->
<div class="border-t border-slate-400 pt-4">
<p class="text-sm font-bold text-navy mb-8">{% trans "Reviewed and Approved By:" %}</p>
<div class="mt-12">
<p class="text-sm text-slate border-b border-slate-300 pb-1 mb-2">&nbsp;</p>
<p class="text-xs text-slate">{% trans "Name & Signature" %}</p>
<p class="text-xs text-slate mt-2">{% trans "Date:" %} _______________</p>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="text-xs text-slate text-center border-t pt-4 mt-8">
<p>
{% trans "Generated by PX360" %} | {{ report.report_period_display }}
{% if report.generated_at %}| {{ report.generated_at|date:"Y-m-d H:i" }}{% endif %}
</p>
<p class="mt-1">© {% now "Y" %} Al Hammadi Hospitals. {% trans "All rights reserved." %}</p>
</div>
</div>
<script>
// Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
// Parse chart data safely
let trendData, sourceData;
try {
trendData = {{ trend_chart_data_json|safe }};
sourceData = {{ source_chart_data_json|safe }};
} catch (e) {
console.error('Error parsing chart data:', e);
trendData = { data: [0,0,0,0,0,0,0,0,0,0,0,0], labels: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], target: 95 };
sourceData = { data: [100], labels: ['No Data'] };
}
// Trend Chart
const trendChartEl = document.getElementById("trendChart");
if (trendChartEl) {
const trendOptions = {
series: [{
name: '{% trans "Performance %" %}',
data: trendData.data
}],
chart: {
type: 'line',
height: 220,
width: '100%',
toolbar: { show: false },
animations: { enabled: false }
},
stroke: {
curve: 'smooth',
width: 3
},
colors: ['#005696'],
xaxis: {
categories: trendData.labels,
labels: { style: { fontSize: '10px' } }
},
yaxis: {
max: 100,
labels: { formatter: function(val) { return val + '%'; }, style: { fontSize: '10px' } }
},
grid: {
borderColor: '#e2e8f0',
strokeDashArray: 4
},
annotations: trendData.target ? {
yaxis: [{
y: trendData.target,
borderColor: '#ef4444',
strokeDashArray: 4,
label: {
text: '{% trans "Target" %}: ' + trendData.target + '%',
style: { color: '#ef4444', background: '#fef2f2', fontSize: '10px' }
}
}]
} : {},
tooltip: {
y: { formatter: function(val) { return val + '%'; } }
}
};
try {
const trendChart = new ApexCharts(trendChartEl, trendOptions);
trendChart.render();
} catch (e) {
console.error('Error rendering trend chart:', e);
}
}
// Source Chart
const sourceChartEl = document.getElementById("sourceChart");
if (sourceChartEl) {
const sourceOptions = {
series: sourceData.data,
labels: sourceData.labels,
chart: {
type: 'donut',
height: 220,
width: '100%',
animations: { enabled: false }
},
colors: ['#005696', '#007bbd', '#64748b', '#94a3b8', '#cbd5e1'],
legend: {
position: 'bottom',
fontSize: '10px'
},
tooltip: {
y: { formatter: function(val) { return val + '%'; } }
}
};
try {
const sourceChart = new ApexCharts(sourceChartEl, sourceOptions);
sourceChart.render();
} catch (e) {
console.error('Error rendering source chart:', e);
}
}
// PDF Generation
async function generatePDF() {
const element = document.getElementById('report-content');
const btn = document.querySelector('button[onclick="generatePDF()"]');
const originalText = btn.innerHTML;
// Show loading
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> {% trans "Generating..." %}';
btn.disabled = true;
lucide.createIcons();
try {
// Wait for charts to fully render
await new Promise(resolve => setTimeout(resolve, 1000));
// Convert charts to images before PDF generation
await convertChartsToImages();
const opt = {
margin: [10, 10, 10, 10],
filename: '{{ report.kpi_id }}_{{ report.year }}_{{ report.month }}_{{ report.hospital.name|slugify }}.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: {
scale: 1.5,
useCORS: false, // Disable CORS to avoid tainted canvas issues
logging: false,
allowTaint: false, // Don't allow tainted canvas
letterRendering: true,
foreignObjectRendering: false, // Disable foreignObject rendering which causes issues
onclone: function(clonedDoc) {
// Hide any remaining SVGs in the clone, only show our static images
clonedDoc.querySelectorAll('.apexcharts-svg, .apexcharts-canvas').forEach(el => {
el.style.display = 'none';
});
}
},
jsPDF: {
unit: 'mm',
format: 'a4',
orientation: 'landscape'
}
};
await html2pdf().set(opt).from(element).save();
} catch (error) {
console.error('PDF generation error:', error);
alert('{% trans "Error generating PDF. Please try again." %}');
} finally {
// Restore charts after PDF generation (success or failure)
restoreChartsFromImages();
btn.innerHTML = originalText;
btn.disabled = false;
lucide.createIcons();
}
}
// Convert ApexCharts to static images for PDF capture
async function convertChartsToImages() {
const chartContainers = ['trendChart', 'sourceChart'];
for (const containerId of chartContainers) {
const container = document.getElementById(containerId);
if (!container) continue;
try {
// Find the SVG element (ApexCharts renders SVG)
const svg = container.querySelector('.apexcharts-svg') || container.querySelector('svg');
if (svg) {
// Clone the SVG to modify it for export
const svgClone = svg.cloneNode(true);
// Get dimensions
const rect = svg.getBoundingClientRect();
const width = rect.width || 600;
const height = rect.height || 220;
// Ensure SVG has proper attributes for standalone rendering
svgClone.setAttribute('width', width);
svgClone.setAttribute('height', height);
svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgClone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
// Inline any computed styles by copying them
const computedStyles = window.getComputedStyle(svg);
svgClone.style.fontFamily = computedStyles.fontFamily;
// Convert SVG to XML string
const svgData = new XMLSerializer().serializeToString(svgClone);
// Create SVG data URL directly (no canvas needed!)
// Use TextEncoder for proper UTF-8 handling
const utf8Bytes = new TextEncoder().encode(svgData);
const binaryString = Array.from(utf8Bytes).map(b => String.fromCharCode(b)).join('');
const svgBase64 = btoa(binaryString);
const dataUrl = 'data:image/svg+xml;base64,' + svgBase64;
// Create image element
const imgElement = document.createElement('img');
imgElement.src = dataUrl;
imgElement.style.width = width + 'px';
imgElement.style.height = height + 'px';
imgElement.style.maxWidth = '100%';
imgElement.className = 'chart-static-image';
imgElement.dataset.originalContainer = containerId;
// Wait for image to load
await new Promise((resolve) => {
imgElement.onload = resolve;
imgElement.onerror = resolve; // Continue on error
});
// Hide original chart and show image
svg.style.visibility = 'hidden';
container.appendChild(imgElement);
}
} catch (error) {
console.error(`Error converting ${containerId} to image:`, error);
// Continue without converting this chart
}
}
}
// Restore charts from images back to interactive charts
function restoreChartsFromImages() {
// Remove static images
document.querySelectorAll('.chart-static-image').forEach(img => {
img.remove();
});
// Show original SVGs
document.querySelectorAll('#trendChart svg, #sourceChart svg').forEach(svg => {
svg.style.visibility = '';
});
// Reset container positions
document.querySelectorAll('#trendChart, #sourceChart').forEach(container => {
container.style.position = '';
});
}
</script>
</body>
</html>