HH/templates/organizations/department_detail.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

1378 lines
81 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}
{% load survey_filters %}
{% block title %}{{ department.get_localized_name }} - {% trans "Department" %} - PX360{% endblock %}
{% block extra_css %}
<style>
.page-header-gradient {
background: linear-gradient(135deg, #005696 0%, #0069a8 50%, #007bbd 100%);
color: white; padding: 1.5rem 2rem; border-radius: 1rem; margin-bottom: 1.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 86, 150, 0.2);
}
.stat-card {
background: white; border-radius: 1rem; border: 2px solid #e2e8f0; padding: 1.25rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.05); transition: all 0.3s ease;
}
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 8px 15px rgba(0,0,0,0.1); border-color: #005696; }
.tab-btn { padding: 0.75rem 1.5rem; font-size: 0.875rem; font-weight: 600; border-bottom: 3px solid transparent; transition: all 0.2s; }
.tab-btn.active { color: #005696; border-bottom-color: #005696; }
.tab-btn:not(.active) { color: #94a3b8; }
.tab-btn:not(.active):hover { color: #005696; background: #f8fafc; }
.table-row-hover { transition: all 0.2s ease; border-left: 3px solid transparent; }
.table-row-hover:hover { background: #f8fafc; border-left-color: #007bbd; }
</style>
{% endblock %}
{% block content %}
<div class="p-6">
<!-- Breadcrumb -->
<div class="flex items-center gap-2 text-sm text-slate-500 mb-4">
<a href="{% url 'organizations:department_list' %}" class="hover:text-navy transition">{% trans "Departments" %}</a>
<i data-lucide="chevron-right" class="w-4 h-4"></i>
<span class="text-navy font-semibold">{{ department.get_localized_name }}</span>
</div>
<!-- Header -->
<div class="page-header-gradient">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div class="flex items-center gap-3 mb-2">
<div class="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
<i data-lucide="building-2" class="w-6 h-6"></i>
</div>
<div>
<h1 class="text-2xl font-bold">{{ department.get_localized_name }}</h1>
{% if department.name_ar %}
<p class="text-white/70 text-sm">{{ department.name_ar }}</p>
{% endif %}
</div>
</div>
<div class="flex flex-wrap items-center gap-4 text-sm text-white/80 mt-3">
<span class="flex items-center gap-1"><i data-lucide="hash" class="w-4 h-4"></i> {{ department.code }}</span>
{% if department.category %}
<span class="flex items-center gap-1 px-2 py-0.5 bg-white/20 rounded-full text-xs font-bold uppercase">
{% if department.category == 'nursing' %}<i data-lucide="heart-pulse" class="w-3 h-3"></i>
{% elif department.category == 'support_services' %}<i data-lucide="wrench" class="w-3 h-3"></i>
{% elif department.category == 'medical' %}<i data-lucide="stethoscope" class="w-3 h-3"></i>
{% elif department.category == 'non_medical' %}<i data-lucide="briefcase" class="w-3 h-3"></i>
{% endif %}
{{ department.get_category_display }}
</span>
{% endif %}
<span class="flex items-center gap-1"><i data-lucide="hospital" class="w-4 h-4"></i> {{ department.hospital.name }}</span>
{% if department.location %}
<span class="flex items-center gap-1"><i data-lucide="map-pin" class="w-4 h-4"></i> {{ department.location }}</span>
{% endif %}
{% if department.phone %}
<span class="flex items-center gap-1"><i data-lucide="phone" class="w-4 h-4"></i> {{ department.phone }}</span>
{% endif %}
{% if department.email %}
<span class="flex items-center gap-1"><i data-lucide="mail" class="w-4 h-4"></i> {{ department.email }}</span>
{% endif %}
{% if department.parent %}
<span class="flex items-center gap-1"><i data-lucide="git-branch" class="w-4 h-4"></i> {% trans "Sub of" %} {{ department.parent.get_localized_name }}</span>
{% endif %}
</div>
</div>
<div class="flex items-center gap-3">
<div class="text-right">
<p class="text-white/60 text-xs uppercase tracking-wide">{% trans "Manager" %}</p>
{% if department.manager %}
<p class="font-bold">{{ department.manager.get_full_name }}</p>
{% else %}
<p class="text-white/50 italic">{% trans "Not assigned" %}</p>
{% endif %}
</div>
{% if department.manager %}
<div class="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
<i data-lucide="user" class="w-6 h-6"></i>
</div>
{% elif can_edit %}
<button onclick="document.getElementById('managerModal').classList.remove('hidden')"
class="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center hover:bg-white/30 transition" title="{% trans 'Set Manager' %}">
<i data-lucide="user-plus" class="w-6 h-6"></i>
</button>
{% endif %}
{% if can_edit %}
<button onclick="document.getElementById('managerModal').classList.remove('hidden')"
class="text-white/60 hover:text-white text-xs underline transition">
{% if department.manager %}{% trans "Change" %}{% else %}{% trans "Set" %}{% endif %}
</button>
{% endif %}
</div>
<div class="flex items-center gap-3">
<div class="text-right">
<p class="text-white/60 text-xs uppercase tracking-wide">{% trans "Champion" %}</p>
{% if department.respondent %}
<p class="font-bold">{{ department.respondent.get_full_name }}</p>
{% else %}
<p class="text-white/50 italic">{% trans "Not set" %}</p>
{% endif %}
</div>
{% if department.respondent %}
<div class="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
<i data-lucide="headphones" class="w-6 h-6"></i>
</div>
{% elif can_assign %}
<button onclick="document.getElementById('respondentModal').classList.remove('hidden')"
class="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center hover:bg-white/30 transition" title="{% trans 'Set Champion' %}">
<i data-lucide="user-plus" class="w-6 h-6"></i>
</button>
{% endif %}
{% if can_assign %}
<button onclick="document.getElementById('respondentModal').classList.remove('hidden')"
class="text-white/60 hover:text-white text-xs underline transition">
{% if department.respondent %}{% trans "Change" %}{% else %}{% trans "Set" %}{% endif %}
</button>
{% endif %}
</div>
{% if can_edit %}
<a href="{% url 'organizations:department_update' department.pk %}"
class="flex items-center gap-2 px-4 py-2 bg-white/20 hover:bg-white/30 rounded-xl text-sm font-semibold transition whitespace-nowrap">
<i data-lucide="pencil" class="w-4 h-4"></i> {% trans "Edit Department" %}
</a>
{% endif %}
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="stat-card">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-purple-50 rounded-lg flex items-center justify-center">
<i data-lucide="users" class="w-5 h-5 text-purple-600"></i>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold">{% trans "Staff" %}</p>
<p class="text-xl font-bold text-navy">{{ stats.staff_count }}</p>
</div>
</div>
</div>
<div class="stat-card">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-red-50 rounded-lg flex items-center justify-center">
<i data-lucide="file-text" class="w-5 h-5 text-red-600"></i>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold">{% trans "Open Complaints" %}</p>
<p class="text-xl font-bold text-red-600">{{ stats.open_complaints }}</p>
<p class="text-[10px] text-slate-400">{{ stats.total_complaints }} {% trans "total" %}</p>
</div>
</div>
</div>
<div class="stat-card">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-amber-50 rounded-lg flex items-center justify-center">
<i data-lucide="help-circle" class="w-5 h-5 text-amber-600"></i>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold">{% trans "Pending Inquiries" %}</p>
<p class="text-xl font-bold text-amber-600">{{ stats.pending_inquiries }}</p>
<p class="text-[10px] text-slate-400">{{ stats.total_inquiries }} {% trans "total" %}</p>
</div>
</div>
</div>
<div class="stat-card">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-blue-50 rounded-lg flex items-center justify-center">
<i data-lucide="eye" class="w-5 h-5 text-blue"></i>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold">{% trans "Open Observations" %}</p>
<p class="text-xl font-bold text-blue">{{ stats.open_observations }}</p>
<p class="text-[10px] text-slate-400">{{ stats.total_observations }} {% trans "total" %}</p>
</div>
</div>
</div>
</div>
{% if pending_actions %}
<div class="bg-white rounded-xl shadow-sm border-2 border-red-200 p-5 mb-6">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 bg-red-50 rounded-lg flex items-center justify-center">
<i data-lucide="alert-circle" class="w-5 h-5 text-red-600"></i>
</div>
<div>
<h3 class="text-sm font-bold text-navy">{% trans "Pending Actions" %}</h3>
<p class="text-xs text-slate-400">{{ pending_actions_count }} {% trans "items awaiting response" %}</p>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-slate-50">
<tr>
<th class="text-left px-4 py-2 text-xs font-bold text-slate-500 uppercase">{% trans "Type" %}</th>
<th class="text-left px-4 py-2 text-xs font-bold text-slate-500 uppercase">{% trans "Reference" %}</th>
<th class="text-left px-4 py-2 text-xs font-bold text-slate-500 uppercase">{% trans "Subject" %}</th>
<th class="text-center px-4 py-2 text-xs font-bold text-slate-500 uppercase">{% trans "SLA Deadline" %}</th>
<th class="text-center px-4 py-2 text-xs font-bold text-slate-500 uppercase">{% trans "Action" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for action in pending_actions %}
<tr class="hover:bg-slate-50 transition">
<td class="px-4 py-2">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-bold bg-{{ action.badge_color }}-100 text-{{ action.badge_color }}-700">
{{ action.type_label }}
</span>
</td>
<td class="px-4 py-2 font-mono text-xs">{{ action.reference }}</td>
<td class="px-4 py-2 text-slate-600">{{ action.subject|truncatechars:50 }}</td>
<td class="px-4 py-2 text-center">
{% if action.sla_due_at %}
{% if action.is_overdue %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-bold bg-red-100 text-red-700">
<i data-lucide="alert-triangle" class="w-3 h-3"></i>
{{ action.sla_due_at|date:"M d, H:i" }}
</span>
{% else %}
<span class="text-xs text-slate-500">{{ action.sla_due_at|date:"M d, H:i" }}</span>
{% endif %}
{% else %}
<span class="text-xs text-slate-300">-</span>
{% endif %}
</td>
<td class="px-4 py-2 text-center">
{% if action.type == 'complaint_department_response' %}
<button type="button"
onclick="openResponseModal('{{ action.item_id }}', '{{ action.reference }}', '{{ action.subject|escapejs }}', '{{ action.complaint_id }}')"
class="inline-flex items-center gap-1 px-3 py-1 bg-navy text-white rounded-lg text-xs font-bold hover:bg-blue transition">
<i data-lucide="message-square" class="w-3 h-3"></i>
{% trans "Respond" %}
</button>
{% else %}
<a href="{{ action.url }}"
class="inline-flex items-center gap-1 px-3 py-1 bg-navy text-white rounded-lg text-xs font-bold hover:bg-blue transition">
<i data-lucide="external-link" class="w-3 h-3"></i>
{% trans "Respond" %}
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Department Head Info -->
{% if staff_head %}
<div class="bg-gradient-to-r from-navy/5 to-blue/5 border border-navy/10 rounded-2xl p-5 mb-6 flex items-center gap-4">
<div class="w-14 h-14 bg-navy rounded-full flex items-center justify-center text-white font-bold text-lg">
{{ staff_head.first_name|truncatechars:1 }}{{ staff_head.last_name|truncatechars:1 }}
</div>
<div>
<div class="flex items-center gap-2">
<span class="font-bold text-navy">{{ staff_head.get_localized_name }}</span>
<span class="px-2 py-0.5 bg-navy text-white text-[10px] font-bold rounded-full uppercase">{% trans "Department Head" %}</span>
</div>
<p class="text-sm text-slate">{{ staff_head.get_localized_job_title }} &middot; {{ staff_head.employee_id }}</p>
</div>
</div>
{% endif %}
<!-- Tabs -->
<div class="bg-white rounded-t-2xl border border-slate-200 border-b-0">
<div class="flex border-b border-slate-200 px-4">
<button class="tab-btn {% if active_tab == 'analytics' %}active{% endif %}" onclick="switchDeptTab('analytics', this)">
<i data-lucide="bar-chart-3" class="w-4 h-4 inline mr-1"></i> {% trans "Dashboard" %}
</button>
{% if not user.is_department_respondent or user.is_px_admin or user.is_hospital_admin or user.is_department_manager %}
<button class="tab-btn {% if active_tab == 'staff' %}active{% endif %}" onclick="switchDeptTab('staff', this)">
<i data-lucide="users" class="w-4 h-4 inline mr-1"></i> {% trans "Staff" %} ({{ stats.staff_count }})
</button>
{% endif %}
<button class="tab-btn {% if active_tab == 'complaints' %}active{% endif %}" onclick="switchDeptTab('complaints', this)">
<i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i> {% trans "Complaints" %} ({{ stats.total_complaints }})
</button>
<button class="tab-btn {% if active_tab == 'inquiries' %}active{% endif %}" onclick="switchDeptTab('inquiries', this)">
<i data-lucide="help-circle" class="w-4 h-4 inline mr-1"></i> {% trans "Inquiries" %} ({{ stats.total_inquiries }})
</button>
<button class="tab-btn {% if active_tab == 'observations' %}active{% endif %}" onclick="switchDeptTab('observations', this)">
<i data-lucide="eye" class="w-4 h-4 inline mr-1"></i> {% trans "Observations" %} ({{ stats.total_observations }})
</button>
<button class="tab-btn {% if active_tab == 'standards' %}active{% endif %}" onclick="switchDeptTab('standards', this)">
<i data-lucide="clipboard-list" class="w-4 h-4 inline mr-1"></i> {% trans "Standards" %} ({{ standards_data|length }})
</button>
</div>
</div>
<!-- Search & Filter Bar -->
<form method="get" id="filterForm" class="bg-white border-x border-slate-200 px-4 py-3">
<input type="hidden" name="tab" id="filterTabInput" value="{{ active_tab }}">
<div class="flex flex-wrap items-center gap-3">
<div class="relative flex-1 min-w-[200px]">
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
<input type="text" name="search" value="{{ search_query|default:'' }}" placeholder="{% trans 'Search...' %}"
class="pl-9 pr-4 py-2 w-full border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
</div>
<div id="filter-complaints" class="hidden">
<select name="complaint_status" onchange="document.getElementById('filterForm').submit()"
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
<option value="">{% trans "All Complaints" %}</option>
<option value="open" {% if complaint_status_filter == 'open' %}selected{% endif %}>{% trans "Open" %}</option>
<option value="in_progress" {% if complaint_status_filter == 'in_progress' %}selected{% endif %}>{% trans "In Progress" %}</option>
<option value="resolved" {% if complaint_status_filter == 'resolved' %}selected{% endif %}>{% trans "Resolved" %}</option>
<option value="closed" {% if complaint_status_filter == 'closed' %}selected{% endif %}>{% trans "Closed" %}</option>
</select>
</div>
<div id="filter-inquiries" class="hidden">
<select name="inquiry_status" onchange="document.getElementById('filterForm').submit()"
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
<option value="">{% trans "All Inquiries" %}</option>
<option value="open" {% if inquiry_status_filter == 'open' %}selected{% endif %}>{% trans "Open" %}</option>
<option value="in_progress" {% if inquiry_status_filter == 'in_progress' %}selected{% endif %}>{% trans "In Progress" %}</option>
<option value="resolved" {% if inquiry_status_filter == 'resolved' %}selected{% endif %}>{% trans "Resolved" %}</option>
<option value="closed" {% if inquiry_status_filter == 'closed' %}selected{% endif %}>{% trans "Closed" %}</option>
</select>
</div>
<div id="filter-observations" class="hidden">
<select name="observation_status" onchange="document.getElementById('filterForm').submit()"
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
<option value="">{% trans "All Observations" %}</option>
<option value="new" {% if observation_status_filter == 'new' %}selected{% endif %}>{% trans "New" %}</option>
<option value="triaged" {% if observation_status_filter == 'triaged' %}selected{% endif %}>{% trans "Triaged" %}</option>
<option value="in_progress" {% if observation_status_filter == 'in_progress' %}selected{% endif %}>{% trans "In Progress" %}</option>
<option value="resolved" {% if observation_status_filter == 'resolved' %}selected{% endif %}>{% trans "Resolved" %}</option>
<option value="closed" {% if observation_status_filter == 'closed' %}selected{% endif %}>{% trans "Closed" %}</option>
</select>
</div>
{% if search_query or complaint_status_filter or inquiry_status_filter or observation_status_filter %}
<a href="?tab={{ active_tab }}" class="text-xs text-slate-400 hover:text-red-500 transition font-semibold">
<i data-lucide="x" class="w-3 h-3 inline"></i> {% trans "Clear" %}
</a>
{% endif %}
</div>
</form>
<!-- Tab Panels -->
<div class="bg-white rounded-b-2xl border border-slate-200 border-t-0 p-6">
{% if not user.is_department_respondent or user.is_px_admin or user.is_hospital_admin or user.is_department_manager %}
<!-- Staff Tab -->
<div id="panel-staff" class="tab-panel {% if active_tab != 'staff' %}hidden{% endif %}">
<table class="w-full">
<thead class="bg-slate-50 border-b">
<tr>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Name" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Job Title" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Employee ID" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{{ department.get_category_display }}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "User Account" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Complaints" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for s in staff_list %}
<tr class="table-row-hover">
<td class="px-4 py-3">
<div class="flex items-center gap-3">
<div class="w-9 h-9 bg-navy/10 rounded-full flex items-center justify-center text-navy font-bold text-xs">
{{ s.first_name|truncatechars:1 }}{{ s.last_name|truncatechars:1 }}
</div>
<div>
<span class="font-semibold text-navy text-sm">{{ s.get_localized_name }}</span>
{% if s.is_head %}
<span class="ml-1 px-1.5 py-0.5 bg-navy text-white text-[9px] font-bold rounded-full">{% trans "HEAD" %}</span>
{% endif %}
</div>
</div>
</td>
<td class="px-4 py-3 text-sm text-slate-700">{{ s.get_localized_job_title }}</td>
<td class="px-4 py-3 text-sm font-mono text-slate">{{ s.employee_id }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 bg-slate-100 text-slate-700 rounded text-xs font-semibold">{{ s.get_department_type_display }}</span>
</td>
<td class="px-4 py-3 text-sm text-slate">
{% if s.user %}
{% for g in s.user.groups.all %}
<span class="inline-block px-1.5 py-0.5 bg-blue-50 text-blue rounded text-[10px] font-bold mr-1">{{ g.name }}</span>
{% endfor %}
{% else %}
<span class="text-slate-400 text-xs">{% trans "No account" %}</span>
{% endif %}
</td>
<td class="px-4 py-3">
{% with count=staff_complaint_counts|get_item:s.pk %}
{% if count %}
<span class="inline-flex items-center justify-center w-7 h-7 rounded-full {% if count > 5 %}bg-red-50 text-red-600{% elif count > 2 %}bg-amber-50 text-amber-700{% else %}bg-slate-100 text-slate-600{% endif %} text-xs font-bold">{{ count }}</span>
{% else %}
<span class="text-slate-400 text-xs">0</span>
{% endif %}
{% endwith %}
</td>
</tr>
{% empty %}
<tr><td colspan="6" class="px-4 py-8 text-center text-slate">{% trans "No staff members in this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Complaints Tab -->
<div id="panel-complaints" class="tab-panel {% if active_tab != 'complaints' %}hidden{% endif %}">
<table class="w-full">
<thead class="bg-slate-50 border-b">
<tr>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Reference" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Classification" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Staff" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Patient" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Assigned To" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Status" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Created" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for c in complaints %}
<tr class="table-row-hover cursor-pointer" onclick="window.location='{% url 'complaints:complaint_detail' pk=c.pk %}'">
<td class="px-4 py-3 text-sm font-mono text-navy">{{ c.reference_number }}</td>
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs">
<div class="flex flex-wrap gap-1">
{% if c.domain %}<span class="px-1.5 py-0.5 bg-indigo-50 text-indigo-700 rounded text-[10px] font-semibold">{{ c.domain.get_localized_name }}</span>{% endif %}
{% if c.category %}<span class="px-1.5 py-0.5 bg-purple-50 text-purple-700 rounded text-[10px] font-semibold">{{ c.category.get_localized_name }}</span>{% endif %}
{% if c.subcategory_obj %}<span class="px-1.5 py-0.5 bg-blue-50 text-blue-700 rounded text-[10px] font-semibold">{{ c.subcategory_obj.get_localized_name }}</span>{% endif %}
{% if c.classification_obj %}<span class="px-1.5 py-0.5 bg-slate-100 text-slate-700 rounded text-[10px] font-semibold">{{ c.classification_obj.get_localized_name }}</span>{% endif %}
{% if not c.domain and not c.category and not c.subcategory_obj and not c.classification_obj %}<span class="text-slate-400">-</span>{% endif %}
</div>
</td>
<td class="px-4 py-3 text-sm text-slate max-w-xs">
{% if c.involved_staff.all %}
{% for inv in c.involved_staff.all %}
<div class="text-xs">{{ inv.staff.get_localized_name }}</div>
{% endfor %}
{% elif c.staff %}
<div class="text-xs">{{ c.staff.get_localized_name }}</div>
{% else %}
<span class="text-slate-400">-</span>
{% endif %}
</td>
<td class="px-4 py-3 text-sm text-slate">{{ c.patient.mrn|default:"-" }}</td>
<td class="px-4 py-3 text-sm text-slate">{{ c.assigned_to.get_full_name|default:"-" }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
{% if c.status == 'open' %}bg-blue-50 text-blue-700
{% elif c.status == 'in_progress' %}bg-amber-50 text-amber-700
{% elif c.status == 'resolved' %}bg-green-50 text-green-700
{% elif c.status == 'closed' %}bg-slate-100 text-slate-600
{% else %}bg-slate-50 text-slate-500{% endif %}">
{{ c.get_status_display }}
</span>
</td>
<td class="px-4 py-3 text-xs text-slate">{{ c.created_at|date:"Y-m-d" }}</td>
</tr>
{% empty %}
<tr><td colspan="7" class="px-4 py-8 text-center text-slate">{% trans "No complaints for this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Inquiries Tab (visible to everyone including respondents) -->
<div id="panel-inquiries" class="tab-panel {% if active_tab != 'inquiries' %}hidden{% endif %}">
{% if user.is_department_respondent and not user.is_px_admin and not user.is_hospital_admin and not user.is_department_manager %}
<div class="space-y-4">
{% for i in inquiries %}
<div class="border border-slate-200 rounded-xl p-5">
<p class="text-slate-800 text-sm whitespace-pre-wrap">{{ i.message }}</p>
{% if i.status in 'open,in_progress' %}
<div class="mt-4 pt-3 border-t border-slate-100">
<a href="{% url 'inquiries:inquiry_department_response' pk=i.pk %}" class="inline-flex items-center gap-2 px-4 py-2 bg-navy text-white rounded-lg text-sm font-semibold hover:bg-blue transition">
<i data-lucide="message-square" class="w-4 h-4"></i> {% trans "Respond" %}
</a>
</div>
{% endif %}
</div>
{% empty %}
<div class="text-center py-12 text-slate">
<i data-lucide="check-circle" class="w-12 h-12 mx-auto mb-3 text-green-300"></i>
<p class="font-semibold">{% trans "No pending inquiries" %}</p>
</div>
{% endfor %}
</div>
{% else %}
<table class="w-full">
<thead class="bg-slate-50 border-b">
<tr>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Reference" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Subject" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Contact" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Assigned To" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Status" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for i in inquiries %}
<tr class="table-row-hover">
<td class="px-4 py-3 text-sm font-mono text-navy">{{ i.reference_number }}</td>
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs truncate">{{ i.subject }}</td>
<td class="px-4 py-3 text-sm text-slate">{{ i.contact_name|default:"-" }}</td>
<td class="px-4 py-3 text-sm text-slate">{{ i.assigned_to.get_full_name|default:"-" }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
{% if i.status == 'open' %}bg-blue-50 text-blue-700
{% elif i.status == 'in_progress' %}bg-amber-50 text-amber-700
{% elif i.status == 'resolved' %}bg-green-50 text-green-700
{% else %}bg-slate-50 text-slate-500{% endif %}">
{{ i.get_status_display }}
</span>
</td>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
<a href="{% url 'inquiries:inquiry_detail' pk=i.pk %}" class="text-xs text-navy font-semibold hover:underline">
{% trans "View" %}
</a>
{% if can_respond and i.status in 'open,in_progress' %}
<a href="{% url 'inquiries:inquiry_department_response' pk=i.pk %}" class="text-xs text-green-600 font-semibold hover:underline">
{% trans "Respond" %}
</a>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="6" class="px-4 py-8 text-center text-slate">{% trans "No inquiries for this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<!-- Observations Tab -->
<div id="panel-observations" class="tab-panel {% if active_tab != 'observations' %}hidden{% endif %}">
<table class="w-full">
<thead class="bg-slate-50 border-b">
<tr>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Tracking Code" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Title" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Severity" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Assigned To" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Status" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Created" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for o in observations %}
<tr class="table-row-hover cursor-pointer" onclick="window.location='{% url 'observations:observation_detail' pk=o.pk %}'">
<td class="px-4 py-3 text-sm font-mono text-navy">{{ o.tracking_code }}</td>
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs truncate">{{ o.title|default:"-" }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
{% if o.severity == 'critical' %}bg-gray-800 text-white
{% elif o.severity == 'high' %}bg-red-50 text-red-700
{% elif o.severity == 'medium' %}bg-amber-50 text-amber-700
{% else %}bg-green-50 text-green-700{% endif %}">
{{ o.get_severity_display }}
</span>
</td>
<td class="px-4 py-3 text-sm text-slate">{{ o.assigned_to.get_full_name|default:"-" }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase bg-slate-50 text-slate-600">
{{ o.get_status_display }}
</span>
</td>
<td class="px-4 py-3 text-xs text-slate">{{ o.created_at|date:"Y-m-d" }}</td>
</tr>
{% empty %}
<tr><td colspan="6" class="px-4 py-8 text-center text-slate">{% trans "No observations for this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Analytics Tab -->
<div id="panel-analytics" class="tab-panel {% if active_tab != 'analytics' %}hidden{% endif %}">
<div id="analyticsLoading" class="flex items-center justify-center py-20">
<div class="flex flex-col items-center gap-3">
<div class="w-8 h-8 border-3 border-navy border-t-transparent rounded-full animate-spin"></div>
<p class="text-sm text-slate-400">{% trans "Loading analytics..." %}</p>
</div>
</div>
<div id="analyticsContent" class="hidden">
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 p-4 border-b border-slate-100">
<div class="bg-gradient-to-br from-indigo-50 to-white rounded-xl p-4 border border-indigo-100">
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Avg Physician Rating" %}</p>
<p id="kpi-avg-rating" class="text-2xl font-bold text-indigo-600">-</p>
<p class="text-[10px] text-slate-400 mt-1">{% trans "out of 5.0" %}</p>
</div>
<div class="bg-gradient-to-br from-green-50 to-white rounded-xl p-4 border border-green-100">
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Resolution Rate" %}</p>
<p id="kpi-resolution-rate" class="text-2xl font-bold text-green-600">-</p>
<p id="kpi-total-complaints" class="text-[10px] text-slate-400 mt-1"></p>
</div>
<div class="bg-gradient-to-br from-amber-50 to-white rounded-xl p-4 border border-amber-100">
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Open Actions" %}</p>
<p id="kpi-open-actions" class="text-2xl font-bold text-amber-600">-</p>
<p class="text-[10px] text-slate-400 mt-1">{% trans "PX actions pending" %}</p>
</div>
<div class="bg-gradient-to-br from-purple-50 to-white rounded-xl p-4 border border-purple-100">
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Satisfaction Rate" %}</p>
<p id="kpi-satisfaction-rate" class="text-2xl font-bold text-purple-600">-</p>
<p class="text-[10px] text-slate-400 mt-1">{% trans "resolution satisfaction" %}</p>
</div>
<div class="bg-gradient-to-br from-orange-50 to-white rounded-xl p-4 border border-orange-100">
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Reopened" %}</p>
<p id="kpi-reopened" class="text-2xl font-bold text-orange-600">-</p>
<p class="text-[10px] text-slate-400 mt-1">{% trans "reopened complaints" %}</p>
</div>
<div class="bg-gradient-to-br from-sky-50 to-white rounded-xl p-4 border border-sky-100">
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Reassigned" %}</p>
<p id="kpi-reassigned" class="text-2xl font-bold text-sky-600">-</p>
<p class="text-[10px] text-slate-400 mt-1">{% trans "reassigned complaints" %}</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 p-4">
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Complaint Trend" %}</h4>
<div id="chartComplaintTrend" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Physician Rating Trend" %}</h4>
<div id="chartPhysicianRating" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Complaint Status" %}</h4>
<div id="chartComplaintStatus" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Actions Overview" %}</h4>
<div id="chartActionsOverview" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Complaint Severity" %}</h4>
<div id="chartSeverity" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Satisfaction Distribution" %}</h4>
<div id="chartSatisfaction" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Top Physicians" %}</h4>
<div id="chartTopPhysicians" style="min-height: 280px;"></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4">
<h4 class="text-sm font-bold text-navy mb-3">{% trans "Top Categories" %}</h4>
<div id="chartTopCategories" style="min-height: 280px;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Standards Tab -->
<div id="panel-standards" class="tab-panel {% if active_tab != 'standards' %}hidden{% endif %}">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-slate-50 border-b">
<tr>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Code" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Title" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Activity Type" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Status" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Evidence" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for item in standards_data %}
<tr class="table-row-hover {% if not item.standard.is_assessable or item.standard.is_heading %}bg-slate-50{% endif %}" data-standard-id="{{ item.standard.id }}">
<td class="px-4 py-3 whitespace-nowrap">
<span class="inline-flex items-center px-3 py-1 rounded-lg text-xs font-semibold {% if not item.standard.is_assessable or item.standard.is_heading %}bg-slate-100 text-slate-500{% else %}bg-navy/10 text-navy{% endif %}">
{{ item.standard.code }}
</span>
</td>
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs truncate {% if not item.standard.is_assessable or item.standard.is_heading %}italic{% endif %}">
<a href="{% url 'standards:standard_detail' pk=item.standard.id %}" class="hover:text-navy transition">
{{ item.standard.title }}
</a>
</td>
<td class="px-4 py-3 whitespace-nowrap">
{% if item.standard.activity_type %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-[10px] font-medium bg-purple-100 text-purple-700">
{{ item.standard.activity_type.name }}
</span>
{% else %}
<span class="text-xs text-slate-400">-</span>
{% endif %}
</td>
<td class="px-4 py-3 whitespace-nowrap">
{% if not item.standard.is_assessable or item.standard.is_heading %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-[10px] font-medium bg-slate-100 text-slate-500">
{% trans "Informational" %}
</span>
{% else %}
<select class="compliance-status-select text-xs font-medium rounded-lg px-2 py-1 border border-slate-200 focus:ring-2 focus:ring-navy/20 outline-none cursor-pointer"
data-department-id="{{ department.pk }}"
data-standard-id="{{ item.standard.id }}"
onchange="updateComplianceStatus(this)">
<option value="not_assessed" {% if not item.compliance or item.compliance.status == 'not_assessed' %}selected{% endif %}>
{% trans "Not Assessed" %}
</option>
<option value="met" {% if item.compliance and item.compliance.status == 'met' %}selected{% endif %}>
{% trans "Met" %}
</option>
<option value="partially_met" {% if item.compliance and item.compliance.status == 'partially_met' %}selected{% endif %}>
{% trans "Partially Met" %}
</option>
<option value="not_met" {% if item.compliance and item.compliance.status == 'not_met' %}selected{% endif %}>
{% trans "Not Met" %}
</option>
</select>
<span class="compliance-status-indicator ml-1 hidden">
<i data-lucide="check-circle" class="w-3 h-3 text-green-500 inline"></i>
</span>
{% endif %}
</td>
<td class="px-4 py-3 whitespace-nowrap">
{% if not item.standard.is_assessable or item.standard.is_heading %}
<span class="text-xs text-slate-400">-</span>
{% else %}
<div class="flex items-center gap-2">
<a href="{% url 'standards:department_standards' pk=department.pk %}"
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 hover:bg-blue-200 transition"
id="evidence-count-{{ item.standard.id }}">
<i data-lucide="file-text" class="w-3 h-3 mr-1"></i>
{% if item.compliance %}{{ item.attachment_count }}{% else %}0{% endif %}
</a>
<button onclick="openEvidenceModal('{{ item.standard.id }}', '{{ department.pk }}', '{{ item.standard.code|escapejs }}')"
class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-slate-100 text-slate-500 hover:bg-navy hover:text-white transition"
title="{% trans 'Upload evidence' %}">
<i data-lucide="upload" class="w-3.5 h-3.5"></i>
</button>
</div>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="5" class="px-4 py-8 text-center text-slate">
<i data-lucide="inbox" class="w-8 h-8 mx-auto mb-2 text-slate-300"></i>
{% trans "No standards for this department" %}
</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Set Manager Modal -->
<div id="managerModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-2xl p-6 w-full max-w-lg mx-4 shadow-2xl">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
<i data-lucide="user-cog" class="w-5 h-5 text-indigo-600"></i>
</div>
<h3 class="text-xl font-bold text-navy">{% trans "Set Department Manager" %}</h3>
</div>
<p class="text-slate mb-4 text-sm">{% trans "Select a staff member to be the manager of this department." %}</p>
<form method="post" action="{% url 'organizations:set_department_manager' pk=department.pk %}">
{% csrf_token %}
<div class="mb-4">
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Manager" %} <span class="text-red-500">*</span></label>
<select name="manager_id" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none" required>
<option value="">{% trans "Select Staff Member" %}</option>
{% for m in managers %}
<option value="{{ m.id }}" {% if department.manager and department.manager.id == m.user_id %}selected{% endif %}>
{{ m.get_full_name }}{% if m.job_title %} ({{ m.get_localized_job_title }}){% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="flex gap-3">
<button type="button" onclick="document.getElementById('managerModal').classList.add('hidden')" class="flex-1 px-4 py-2 border border-slate-200 text-slate rounded-xl font-semibold hover:bg-slate-50 transition">
{% trans "Cancel" %}
</button>
<button type="submit" class="flex-1 px-4 py-2 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2">
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Save" %}
</button>
</div>
</form>
</div>
</div>
<!-- Set Champion Modal -->
<div id="respondentModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-2xl p-6 w-full max-w-lg mx-4 shadow-2xl">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<i data-lucide="headphones" class="w-5 h-5 text-blue"></i>
</div>
<h3 class="text-xl font-bold text-navy">{% trans "Set Department Champion" %}</h3>
</div>
<p class="text-slate mb-4 text-sm">{% trans "Select a staff member to be the default champion for inquiries in this department." %}</p>
<form method="post" action="{% url 'organizations:set_department_respondent' pk=department.pk %}">
{% csrf_token %}
<div class="mb-4">
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Champion" %} <span class="text-red-500">*</span></label>
<select name="respondent_id" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none" required>
<option value="">{% trans "Select Staff Member" %}</option>
{% for s in assignable_staff %}
<option value="{{ s.id }}" {% if department.respondent and department.respondent.id == s.id %}selected{% endif %}>
{{ s.get_full_name }} ({{ s.get_localized_job_title }})
</option>
{% endfor %}
</select>
</div>
<div class="flex gap-3">
<button type="button" onclick="document.getElementById('respondentModal').classList.add('hidden')" class="flex-1 px-4 py-2 border border-slate-200 text-slate rounded-xl font-semibold hover:bg-slate-50 transition">
{% trans "Cancel" %}
</button>
<button type="submit" class="flex-1 px-4 py-2 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2">
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Save" %}
</button>
</div>
</form>
</div>
</div>
<!-- Evidence Upload Modal -->
<div id="evidenceUploadModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-2xl p-6 w-full max-w-lg mx-4 shadow-2xl">
<div class="flex justify-between items-center mb-4">
<div>
<h3 class="text-xl font-bold text-navy">{% trans "Upload Evidence" %}</h3>
<p class="text-sm text-slate-500 mt-1" id="evidenceModalStandard">{% trans "Standard: " %}<span class="font-medium"></span></p>
</div>
<button onclick="closeEvidenceModal()" class="text-slate-400 hover:text-slate-600 transition">
<i data-lucide="x" class="w-6 h-6"></i>
</button>
</div>
<form id="evidenceUploadForm" class="space-y-4">
<input type="hidden" id="evidenceStandardId" name="standard_id">
<input type="hidden" id="evidenceDepartmentId" name="department_id">
<div>
<label class="block text-sm font-semibold text-slate mb-2">{% trans "File" %}</label>
<input type="file" id="evidenceFile" name="file"
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.zip"
class="block w-full text-sm text-slate border border-slate-200 rounded-lg cursor-pointer bg-white focus:outline-none focus:ring-2 focus:ring-navy focus:border-navy p-2">
<p class="mt-1 text-xs text-slate-400">{% trans "Accepted: PDF, DOC, XLS, images, ZIP (max 50MB)" %}</p>
</div>
<div>
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Description" %}</label>
<textarea id="evidenceDescription" name="description" rows="3"
class="w-full px-3 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-navy focus:border-transparent transition"
placeholder="{% trans 'Add a description for this evidence...' %}"></textarea>
</div>
<div id="evidenceUploadError" class="hidden text-sm text-red-600 bg-red-50 border border-red-200 p-3 rounded-lg"></div>
<div class="flex gap-3 pt-2">
<button type="button" onclick="submitEvidenceUpload()"
class="flex-1 px-4 py-2.5 bg-green-500 text-white font-medium rounded-xl hover:bg-green-600 transition flex items-center justify-center gap-2">
<i data-lucide="upload" class="w-4 h-4"></i>
{% trans "Upload" %}
</button>
<button type="button" onclick="closeEvidenceModal()"
class="flex-1 px-4 py-2.5 bg-slate-100 text-slate-700 font-medium rounded-xl hover:bg-slate-200 transition">
{% trans "Cancel" %}
</button>
</div>
</form>
</div>
</div>
<!-- Complaint Department Response Modal -->
<div id="responseModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-lg mx-4 overflow-hidden">
<div class="bg-gradient-to-r from-navy to-blue p-4 flex justify-between items-center">
<h3 class="text-white font-bold text-lg flex items-center gap-2">
<i data-lucide="message-square" class="w-5 h-5"></i>
{% trans "Submit Department Response" %}
</h3>
<button onclick="closeResponseModal()" class="text-white/80 hover:text-white transition">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="p-6 space-y-4">
<div class="bg-slate-50 rounded-xl p-4 border border-slate-200">
<p class="text-xs text-slate-500 uppercase font-bold mb-1">{% trans "Reference" %}</p>
<p id="responseModalRef" class="font-mono text-sm font-bold text-navy"></p>
<p class="text-xs text-slate-500 uppercase font-bold mb-1 mt-3">{% trans "Subject" %}</p>
<p id="responseModalSubject" class="text-sm text-slate-700"></p>
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 mb-2">
{% trans "Your Response" %} <span class="text-red-500">*</span>
</label>
<textarea id="responseNotes" rows="5"
class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl text-slate-700 focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 resize-none"
placeholder="{% trans 'Enter your department\'s response to this complaint...' %}"></textarea>
</div>
<div id="responseError" class="hidden text-sm text-red-600 bg-red-50 border border-red-200 p-3 rounded-lg"></div>
<div id="responseSuccess" class="hidden text-sm text-green-600 bg-green-50 border border-green-200 p-3 rounded-lg"></div>
<div class="flex gap-3 pt-2">
<button type="button" id="responseSubmitBtn" onclick="submitDepartmentResponse()"
class="flex-1 px-4 py-2.5 bg-navy text-white font-medium rounded-xl hover:bg-blue transition flex items-center justify-center gap-2">
<i data-lucide="send" class="w-4 h-4"></i>
{% trans "Submit Response" %}
</button>
<button type="button" onclick="closeResponseModal()"
class="flex-1 px-4 py-2.5 bg-slate-100 text-slate-700 font-medium rounded-xl hover:bg-slate-200 transition">
{% trans "Cancel" %}
</button>
</div>
</div>
</div>
</div>
<script>
const tabFilters = {
staff: [],
complaints: ['filter-complaints'],
inquiries: ['filter-inquiries'],
observations: ['filter-observations'],
standards: [],
analytics: [],
};
let analyticsLoaded = false;
const deptAnalyticsCharts = {};
function switchDeptTab(tab, btn) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.add('hidden'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById('panel-' + tab).classList.remove('hidden');
if (btn) btn.classList.add('active');
document.getElementById('filterTabInput').value = tab;
Object.values(tabFilters).flat().forEach(id => { const el = document.getElementById(id); if (el) el.classList.add('hidden'); });
(tabFilters[tab] || []).forEach(id => { const el = document.getElementById(id); if (el) el.classList.remove('hidden'); });
document.getElementById('filterForm').style.display = (tab === 'analytics') ? 'none' : '';
const url = new URL(window.location);
url.searchParams.set('tab', tab);
history.replaceState(null, '', url);
if (tab === 'analytics' && !analyticsLoaded) {
loadDepartmentAnalytics();
}
}
function updateComplianceStatus(selectEl) {
const departmentId = selectEl.dataset.departmentId;
const standardId = selectEl.dataset.standardId;
const status = selectEl.value;
const indicator = selectEl.nextElementSibling;
selectEl.disabled = true;
indicator.classList.remove('hidden');
fetch('/standards/api/compliance/update/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken') || document.querySelector('[name=csrfmiddlewaretoken]')?.value || '',
},
body: JSON.stringify({
department_id: departmentId,
standard_id: standardId,
status: status,
}),
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update select styling based on status
selectEl.classList.remove('bg-green-50', 'text-green-700', 'border-green-300',
'bg-yellow-50', 'text-yellow-700', 'border-yellow-300',
'bg-red-50', 'text-red-700', 'border-red-300',
'bg-slate-50', 'text-slate-600', 'border-slate-200');
if (status === 'met') {
selectEl.classList.add('bg-green-50', 'text-green-700', 'border-green-300');
} else if (status === 'partially_met') {
selectEl.classList.add('bg-yellow-50', 'text-yellow-700', 'border-yellow-300');
} else if (status === 'not_met') {
selectEl.classList.add('bg-red-50', 'text-red-700', 'border-red-300');
} else {
selectEl.classList.add('bg-slate-50', 'text-slate-600', 'border-slate-200');
}
} else {
alert('Error: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error updating compliance:', error);
alert('Failed to update compliance status.');
})
.finally(() => {
selectEl.disabled = false;
setTimeout(() => indicator.classList.add('hidden'), 1500);
});
}
// Evidence upload modal functions
let currentEvidenceStandardId = null;
function openEvidenceModal(standardId, departmentId, standardCode) {
currentEvidenceStandardId = standardId;
document.getElementById('evidenceStandardId').value = standardId;
document.getElementById('evidenceDepartmentId').value = departmentId;
document.getElementById('evidenceModalStandard').querySelector('span').textContent = standardCode;
document.getElementById('evidenceUploadError').classList.add('hidden');
document.getElementById('evidenceUploadForm').reset();
document.getElementById('evidenceUploadModal').classList.remove('hidden');
}
function closeEvidenceModal() {
document.getElementById('evidenceUploadModal').classList.add('hidden');
currentEvidenceStandardId = null;
}
function submitEvidenceUpload() {
const fileInput = document.getElementById('evidenceFile');
const descriptionInput = document.getElementById('evidenceDescription');
const standardId = document.getElementById('evidenceStandardId').value;
const departmentId = document.getElementById('evidenceDepartmentId').value;
const errorDiv = document.getElementById('evidenceUploadError');
if (!fileInput.files || fileInput.files.length === 0) {
errorDiv.textContent = '{% trans "Please select a file" %}';
errorDiv.classList.remove('hidden');
return;
}
const file = fileInput.files[0];
const maxSize = 50 * 1024 * 1024; // 50MB
if (file.size > maxSize) {
errorDiv.textContent = '{% trans "File size must be less than 50MB" %}';
errorDiv.classList.remove('hidden');
return;
}
errorDiv.classList.add('hidden');
const formData = new FormData();
formData.append('standard_id', standardId);
formData.append('department_id', departmentId);
formData.append('file', file);
formData.append('description', descriptionInput.value);
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
if (response.success) {
// Update evidence count badge
const countBadge = document.getElementById('evidence-count-' + standardId);
if (countBadge) {
countBadge.innerHTML = '<i data-lucide="file-text" class="w-3 h-3 mr-1"></i>' + response.attachment_count;
}
closeEvidenceModal();
// Re-init lucide icons for the updated badge
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
} else {
errorDiv.textContent = response.error || '{% trans "Upload failed" %}';
errorDiv.classList.remove('hidden');
}
} else {
errorDiv.textContent = '{% trans "Upload failed" %}';
errorDiv.classList.remove('hidden');
}
});
xhr.addEventListener('error', function() {
errorDiv.textContent = '{% trans "Upload failed" %}';
errorDiv.classList.remove('hidden');
});
xhr.open('POST', '{% url "standards:attachment_upload_ajax" %}', true);
xhr.setRequestHeader('X-CSRFToken', getCookie('csrftoken') || document.querySelector('[name=csrfmiddlewaretoken]')?.value || '');
xhr.send(formData);
}
// Close modal on outside click
document.addEventListener('click', function(e) {
const modal = document.getElementById('evidenceUploadModal');
if (e.target === modal) {
closeEvidenceModal();
}
});
// Apply initial styling to compliance selects
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.compliance-status-select').forEach(function(select) {
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
});
});
(function() {
const activeTab = '{{ active_tab }}';
(tabFilters[activeTab] || []).forEach(id => { const el = document.getElementById(id); if (el) el.classList.remove('hidden'); });
if (activeTab === 'analytics') {
document.getElementById('filterForm').style.display = 'none';
loadDepartmentAnalytics();
}
})();
function loadDepartmentAnalytics() {
fetch('{% url "organizations:department_analytics_api" pk=department.pk %}')
.then(r => r.json())
.then(data => {
if (data.error) {
document.getElementById('analyticsLoading').innerHTML =
'<p class="text-red-500 text-sm">' + data.error + '</p>';
return;
}
document.getElementById('analyticsLoading').classList.add('hidden');
document.getElementById('analyticsContent').classList.remove('hidden');
renderDeptKPIs(data.kpi || {});
renderDeptCharts(data);
analyticsLoaded = true;
})
.catch(err => {
document.getElementById('analyticsLoading').innerHTML =
'<p class="text-red-500 text-sm">Failed to load analytics</p>';
});
}
function renderDeptKPIs(kpi) {
document.getElementById('kpi-avg-rating').textContent = kpi.avg_physician_rating || '0.0';
document.getElementById('kpi-resolution-rate').textContent = (kpi.resolution_rate || 0) + '%';
document.getElementById('kpi-total-complaints').textContent = (kpi.total_complaints || 0) + ' {% trans "total" %}';
document.getElementById('kpi-open-actions').textContent = kpi.open_actions || 0;
document.getElementById('kpi-satisfaction-rate').textContent = (kpi.satisfaction_rate || 0) + '%';
document.getElementById('kpi-reopened').textContent = kpi.reopened || 0;
document.getElementById('kpi-reassigned').textContent = kpi.reassigned || 0;
}
function renderDeptCharts(data) {
const common = {
fontFamily: 'Inter, sans-serif',
toolbar: { show: false },
animations: { enabled: true },
};
// Complaint Trend (area)
if (data.complaint_trend && data.complaint_trend.length > 0) {
deptAnalyticsCharts.complaintTrend = new ApexCharts(document.querySelector("#chartComplaintTrend"), {
series: [{ name: '{% trans "Complaints" %}', data: data.complaint_trend.map(i => i.count) }],
chart: { type: 'area', height: 280, ...common },
colors: ['#005696'],
stroke: { curve: 'smooth', width: 3 },
fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.4, opacityTo: 0.05, stops: [0, 100] } },
dataLabels: { enabled: true, style: { fontSize: '11px' } },
xaxis: { categories: data.complaint_trend.map(i => i.period), labels: { style: { fontSize: '11px' } } },
yaxis: { labels: { style: { fontSize: '11px' } } },
grid: { borderColor: '#e2e8f0', strokeDashArray: 4 },
});
deptAnalyticsCharts.complaintTrend.render();
} else {
document.querySelector("#chartComplaintTrend").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No trend data available" %}</p>';
}
// Physician Rating Trend (area)
if (data.physician_rating_trend && data.physician_rating_trend.some(i => i.average_rating > 0)) {
deptAnalyticsCharts.physicianRating = new ApexCharts(document.querySelector("#chartPhysicianRating"), {
series: [{ name: '{% trans "Avg Rating" %}', data: data.physician_rating_trend.map(i => i.average_rating) }],
chart: { type: 'area', height: 280, ...common },
colors: ['#7c3aed'],
stroke: { curve: 'smooth', width: 3 },
fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.4, opacityTo: 0.05, stops: [0, 100] } },
dataLabels: { enabled: true, style: { fontSize: '11px' } },
xaxis: { categories: data.physician_rating_trend.map(i => i.period), labels: { style: { fontSize: '11px' } } },
yaxis: { min: 0, max: 5, labels: { style: { fontSize: '11px' }, formatter: v => Math.round(v * 10) / 10 } },
grid: { borderColor: '#e2e8f0', strokeDashArray: 4 },
tooltip: { y: { formatter: v => Math.round(v * 10) / 10 } },
});
deptAnalyticsCharts.physicianRating.render();
} else {
document.querySelector("#chartPhysicianRating").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No physician rating data" %}</p>';
}
// Complaint Status (donut)
if (data.complaint_status && data.complaint_status.length > 0) {
const statusColors = { 'Open': '#3b82f6', 'In Progress': '#f59e0b', 'Resolved': '#10b981', 'Closed': '#64748b', 'Partially Resolved': '#06b6d4', 'Cancelled': '#94a3b8', 'Contacted': '#8b5cf6', 'No Response': '#a1a1aa' };
deptAnalyticsCharts.complaintStatus = new ApexCharts(document.querySelector("#chartComplaintStatus"), {
series: data.complaint_status.map(i => i.count),
chart: { type: 'donut', height: 280, ...common },
labels: data.complaint_status.map(i => i.status),
colors: data.complaint_status.map(i => statusColors[i.status] || '#94a3b8'),
legend: { position: 'bottom', fontSize: '12px' },
dataLabels: { enabled: true, style: { fontSize: '11px' } },
});
deptAnalyticsCharts.complaintStatus.render();
} else {
document.querySelector("#chartComplaintStatus").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No status data" %}</p>';
}
// Actions Overview (donut)
if (data.actions_overview && data.actions_overview.length > 0) {
const actionColors = { 'Open': '#3b82f6', 'In Progress': '#f59e0b', 'Pending Approval': '#8b5cf6', 'Approved': '#06b6d4', 'Closed': '#10b981', 'Cancelled': '#94a3b8' };
deptAnalyticsCharts.actionsOverview = new ApexCharts(document.querySelector("#chartActionsOverview"), {
series: data.actions_overview.map(i => i.count),
chart: { type: 'donut', height: 280, ...common },
labels: data.actions_overview.map(i => i.status),
colors: data.actions_overview.map(i => actionColors[i.status] || '#94a3b8'),
legend: { position: 'bottom', fontSize: '12px' },
dataLabels: { enabled: true, style: { fontSize: '11px' } },
});
deptAnalyticsCharts.actionsOverview.render();
} else {
document.querySelector("#chartActionsOverview").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No actions data" %}</p>';
}
// Complaint Severity (bar)
if (data.complaint_severity && data.complaint_severity.length > 0) {
const sevColors = { 'Low': '#10b981', 'Medium': '#f59e0b', 'High': '#ef4444', 'Critical': '#7c2d12' };
deptAnalyticsCharts.severity = new ApexCharts(document.querySelector("#chartSeverity"), {
series: [{ name: '{% trans "Count" %}', data: data.complaint_severity.map(i => i.count) }],
chart: { type: 'bar', height: 280, ...common },
colors: data.complaint_severity.map(i => sevColors[i.severity] || '#64748b'),
plotOptions: { bar: { borderRadius: 6, distributed: true, columnWidth: '50%' } },
dataLabels: { enabled: true, style: { fontSize: '12px' } },
xaxis: { categories: data.complaint_severity.map(i => i.severity), labels: { style: { fontSize: '12px' } } },
yaxis: { labels: { style: { fontSize: '11px' } } },
grid: { borderColor: '#e2e8f0', strokeDashArray: 4 },
legend: { show: false },
});
deptAnalyticsCharts.severity.render();
} else {
document.querySelector("#chartSeverity").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No severity data" %}</p>';
}
// Satisfaction Distribution (donut)
if (data.satisfaction && data.satisfaction.length > 0) {
const satColors = { 'Satisfied': '#10b981', 'Neutral': '#f59e0b', 'Dissatisfied': '#ef4444', 'No Response': '#94a3b8', 'Escalated': '#7c3aed' };
deptAnalyticsCharts.satisfaction = new ApexCharts(document.querySelector("#chartSatisfaction"), {
series: data.satisfaction.map(i => i.count),
chart: { type: 'donut', height: 280, ...common },
labels: data.satisfaction.map(i => i.label),
colors: data.satisfaction.map(i => satColors[i.label] || '#94a3b8'),
legend: { position: 'bottom', fontSize: '12px' },
dataLabels: { enabled: true, style: { fontSize: '11px' } },
});
deptAnalyticsCharts.satisfaction.render();
} else {
document.querySelector("#chartSatisfaction").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No satisfaction data" %}</p>';
}
// Top Physicians (horizontal bar)
if (data.top_physicians && data.top_physicians.length > 0) {
deptAnalyticsCharts.topPhysicians = new ApexCharts(document.querySelector("#chartTopPhysicians"), {
series: [{ name: '{% trans "Rating" %}', data: data.top_physicians.map(i => i.rating) }],
chart: { type: 'bar', height: 280, ...common },
colors: ['#7c3aed'],
plotOptions: { bar: { borderRadius: 6, horizontal: true, barHeight: '60%' } },
dataLabels: { enabled: true, formatter: v => Math.round(v * 10) / 10, style: { fontSize: '11px' } },
xaxis: { categories: data.top_physicians.map(i => i.name), max: 5, labels: { style: { fontSize: '11px' } } },
grid: { borderColor: '#e2e8f0', strokeDashArray: 4 },
});
deptAnalyticsCharts.topPhysicians.render();
} else {
document.querySelector("#chartTopPhysicians").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No physician data" %}</p>';
}
// Top Categories (horizontal bar)
if (data.top_categories && data.top_categories.length > 0) {
deptAnalyticsCharts.topCategories = new ApexCharts(document.querySelector("#chartTopCategories"), {
series: [{ name: '{% trans "Count" %}', data: data.top_categories.map(i => i.count) }],
chart: { type: 'bar', height: 280, ...common },
colors: ['#005696'],
plotOptions: { bar: { borderRadius: 6, horizontal: true, barHeight: '60%' } },
dataLabels: { enabled: true, style: { fontSize: '11px' } },
xaxis: { categories: data.top_categories.map(i => i.category), labels: { style: { fontSize: '11px' } } },
grid: { borderColor: '#e2e8f0', strokeDashArray: 4 },
});
deptAnalyticsCharts.topCategories.render();
} else {
document.querySelector("#chartTopCategories").innerHTML = '<p class="text-slate-400 text-sm text-center py-10">{% trans "No category data" %}</p>';
}
}
// Complaint Department Response Modal
let currentResponseItemId = null;
let currentResponseComplaintId = null;
function openResponseModal(itemId, reference, subject, complaintId) {
currentResponseItemId = itemId;
currentResponseComplaintId = complaintId;
document.getElementById('responseModalRef').textContent = reference;
document.getElementById('responseModalSubject').textContent = subject;
document.getElementById('responseNotes').value = '';
document.getElementById('responseError').classList.add('hidden');
document.getElementById('responseSuccess').classList.add('hidden');
document.getElementById('responseModal').classList.remove('hidden');
}
function closeResponseModal() {
document.getElementById('responseModal').classList.add('hidden');
currentResponseItemId = null;
currentResponseComplaintId = null;
}
function submitDepartmentResponse() {
const notes = document.getElementById('responseNotes').value.trim();
const errorDiv = document.getElementById('responseError');
const successDiv = document.getElementById('responseSuccess');
const submitBtn = document.getElementById('responseSubmitBtn');
if (!notes) {
errorDiv.textContent = '{% trans "Please enter your response" %}';
errorDiv.classList.remove('hidden');
return;
}
if (!currentResponseItemId) {
errorDiv.textContent = '{% trans "Invalid request" %}';
errorDiv.classList.remove('hidden');
return;
}
errorDiv.classList.add('hidden');
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner"></span> {% trans "Submitting..." %}';
const formData = new FormData();
formData.append('response_notes', notes);
formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]')?.value || getCookie('csrftoken') || '');
fetch('/complaints/departments/' + currentResponseItemId + '/response/', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
})
.then(data => {
if (data.success) {
successDiv.textContent = '{% trans "Response submitted successfully!" %}';
successDiv.classList.remove('hidden');
setTimeout(() => {
closeResponseModal();
window.location.reload();
}, 1500);
} else {
throw new Error(data.error || '{% trans "Failed to submit response" %}');
}
})
.catch(error => {
errorDiv.textContent = error.message;
errorDiv.classList.remove('hidden');
submitBtn.disabled = false;
submitBtn.innerHTML = '{% trans "Submit Response" %}';
});
}
</script>
{% endblock %}