HH/templates/organizations/department_detail.html
ismail badb6a9ebf
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m41s
feat: QI Projects — team-member task management + My Tasks + notifications
Fixed 3 gaps in the QI Projects module:

Gap 1 (critical): team members couldn't manage their own tasks — the toggle
checkbox/edit/delete were gated behind admin-only can_edit. Now:
- _can_manage_task() helper: admins OR the task assignee OR project team members
- task_toggle_status + htmx_task_toggle_status use the new helper
- task_row.html shows the toggle for assignees (task.assigned_to.user_id check)

Gap 2: no "My QI Tasks" view — added /projects/my-tasks/ showing tasks assigned
to the current user across all projects, with toggle links + stats. Sidebar link.

Gap 3: no notification on task assignment — added apps/projects/signals.py
(post_save on QIProjectTask → create_in_app_notification). apps.py ready() wired.

Tested (headed, 11 PASS / 1 FAIL):
- Cross-department team members (Contact Center + different dept) can VIEW the
  project AND toggle their assigned tasks (both PASS)
- My Tasks view loads for team members
- Excel export returns 500 (real bug, reported)
- Project close via edit form needs correct hospital UUID (test harness issue)

Also bundles accumulated in-progress work across complaints, observations,
organizations, templates, and other modules.

Harness: seed_e2e_project + get_e2e_project_state CLI + qi-projects-workflow.spec.ts
2026-06-16 23:55:58 +03:00

1929 lines
119 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;
text-decoration: none; color: inherit;
}
.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>
{% 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>
<a href="{% url 'organizations:department_complaints_list' department.pk %}?status=open" class="stat-card block">
<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>
</a>
<a href="{% url 'organizations:department_inquiries_list' department.pk %}?status=open" class="stat-card block">
<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>
</a>
<a href="{% url 'organizations:department_observations_list' department.pk %}?status=open" class="stat-card block">
<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>
</a>
</div>
{% if pending_actions and can_respond %}
<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="openDeptResponseModal('complaint', '{{ action.item_id }}', '/complaints/departments/{{ action.item_id }}/response/', '{{ action.reference }}', '{{ action.subject|escapejs }}')"
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>
{% elif action.type == 'observation_response' %}
<button type="button"
onclick="openDeptResponseModal('observation', '{{ action.item_id }}', '{{ action.url }}', '{{ action.reference }}', '{{ action.subject|escapejs }}', '{{ action.existing_en|escapejs }}', '{{ action.existing_ar|escapejs }}')"
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>
{% elif action.type == 'inquiry_response' %}
<button type="button"
onclick="openDeptResponseModal('inquiry', '{{ action.item_id }}', '{{ action.url }}', '{{ action.reference }}', '{{ action.subject|escapejs }}', '{{ action.existing_en|escapejs }}', '{{ action.existing_ar|escapejs }}')"
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>
{% if pending_actions_has_more %}
<div class="mt-3 pt-3 border-t border-slate-100 text-center">
<span class="text-xs text-slate-400">{% trans "Showing 10 of" %} {{ pending_actions_count }} &mdash; {% trans "respond to items to see more" %}</span>
</div>
{% endif %}
</div>
{% endif %}
{% if active_investigations and can_respond %}
<div class="bg-white rounded-xl shadow-sm border border-blue-200 p-5 mb-6">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 bg-blue-50 rounded-lg flex items-center justify-center">
<i data-lucide="search" class="w-5 h-5 text-blue-600"></i>
</div>
<div>
<h3 class="text-sm font-bold text-navy">{% trans "Active Investigations" %}</h3>
<p class="text-xs text-slate-400">{{ active_investigations|length }} {% trans "investigation(s) in progress" %}</p>
</div>
</div>
<div class="space-y-3">
{% for inv in active_investigations %}
<div class="border border-slate-200 rounded-xl p-4 hover:border-blue-300 transition">
<div class="flex items-start justify-between gap-3 mb-2">
<div class="flex-1 min-w-0">
<a href="{% url 'complaints:complaint_detail' pk=inv.complaint.pk %}" class="font-mono text-xs font-bold text-navy hover:underline">
{{ inv.complaint.reference_number }}
</a>
<p class="text-sm text-slate mt-0.5">{{ inv.complaint.title|truncatechars:60 }}</p>
</div>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase whitespace-nowrap
{% if inv.status == 'questions_sent' %}bg-amber-100 text-amber-700
{% elif inv.status == 'answers_received' %}bg-blue-100 text-blue-700{% endif %}">
{{ inv.get_status_display }}
</span>
</div>
<div class="flex items-center gap-3 text-xs text-slate mb-3">
<span class="inline-flex items-center gap-1">
<i data-lucide="help-circle" class="w-3.5 h-3.5"></i>
{{ inv.questions.count }} {% trans "questions" %}
</span>
<span class="inline-flex items-center gap-1">
{% with answered=inv.responses.all|length %}
<i data-lucide="users" class="w-3.5 h-3.5"></i>
{{ answered }} {% trans "staff" %}
{% endwith %}
</span>
</div>
<div class="space-y-1 mb-3">
{% for resp in inv.responses.all %}
<div class="flex items-center gap-2 text-xs">
{% if resp.is_completed %}
<i data-lucide="check-circle" class="w-3.5 h-3.5 text-green-500 shrink-0"></i>
<span class="text-slate">{{ resp.staff.get_full_name }}</span>
<span class="text-slate ml-auto">{{ resp.completed_at|date:"M d, H:i" }}</span>
{% else %}
<i data-lucide="clock" class="w-3.5 h-3.5 text-amber-500 shrink-0"></i>
<span class="text-slate">{{ resp.staff.get_full_name }}</span>
<span class="text-amber-600 font-semibold ml-auto">{% trans "Pending" %}</span>
{% endif %}
</div>
{% endfor %}
</div>
{% if inv.status == 'answers_received' and inv.explanation.token %}
<a href="/complaints/{{ inv.complaint.id }}/investigate/review/{{ inv.explanation.token }}/"
class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-navy text-white rounded-lg text-xs font-semibold hover:bg-blue transition">
<i data-lucide="eye" class="w-3.5 h-3.5"></i> {% trans "Review & Submit Reply" %}
</a>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if my_pending_responses or my_assigned_complaints %}
<div class="bg-white rounded-xl shadow-sm border-2 border-amber-200 p-5 mb-6">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 bg-amber-50 rounded-lg flex items-center justify-center">
<i data-lucide="bell-ring" class="w-5 h-5 text-amber-600"></i>
</div>
<div>
<h3 class="text-sm font-bold text-navy">{% trans "Action Required From You" %}</h3>
<p class="text-xs text-slate-400">{% trans "Items assigned to you that need your response" %}</p>
</div>
</div>
<div class="space-y-3">
{% for resp in my_pending_responses %}
<div class="border border-amber-200 rounded-xl p-4 bg-amber-50/30">
<div class="flex items-start justify-between gap-3 mb-2">
<div class="flex-1 min-w-0">
<a href="{% url 'complaints:complaint_detail' pk=resp.investigation.complaint.pk %}" class="font-mono text-xs font-bold text-navy hover:underline">
{{ resp.investigation.complaint.reference_number }}
</a>
<p class="text-sm text-slate mt-0.5">{{ resp.investigation.complaint.title|truncatechars:60 }}</p>
</div>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase whitespace-nowrap bg-amber-100 text-amber-700">
{% trans "Investigation" %}
</span>
</div>
<div class="flex items-center gap-3 text-xs text-slate mb-3">
<span class="inline-flex items-center gap-1">
<i data-lucide="help-circle" class="w-3.5 h-3.5"></i>
{{ resp.investigation.questions.count }} {% trans "questions to answer" %}
</span>
{% if resp.investigation.champion %}
<span class="inline-flex items-center gap-1">
<i data-lucide="user" class="w-3.5 h-3.5"></i>
{{ resp.investigation.champion.get_full_name }}
</span>
{% endif %}
</div>
<a href="/complaints/{{ resp.investigation.complaint.id }}/investigate/respond/{{ resp.token }}/"
class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-amber-500 text-white rounded-lg text-xs font-semibold hover:bg-amber-600 transition">
<i data-lucide="message-square" class="w-3.5 h-3.5"></i> {% trans "Answer Questions" %}
</a>
</div>
{% endfor %}
{% for c in my_assigned_complaints %}
<div class="border border-blue-200 rounded-xl p-4 bg-blue-50/30">
<div class="flex items-start justify-between gap-3 mb-2">
<div class="flex-1 min-w-0">
<a href="{% url 'complaints:complaint_detail' pk=c.pk %}" class="font-mono text-xs font-bold text-navy hover:underline">
{{ c.reference_number }}
</a>
<p class="text-sm text-slate mt-0.5">{{ c.title|truncatechars:60 }}</p>
</div>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase whitespace-nowrap bg-blue-100 text-blue-700">
{% trans "Assigned to You" %}
</span>
</div>
<div class="flex items-center gap-3 text-xs text-slate mb-3">
<span class="inline-flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3.5 h-3.5"></i>
{{ c.get_severity_display }}
</span>
<span class="inline-flex items-center gap-1">
<i data-lucide="clock" class="w-3.5 h-3.5"></i>
{% if c.is_overdue %}
<span class="text-red-600 font-semibold">{% trans "Overdue" %}</span>
{% else %}
{{ c.due_at|date:"M d, H:i" }}
{% endif %}
</span>
</div>
<a href="{% url 'complaints:complaint_detail' pk=c.pk %}"
class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-navy text-white rounded-lg text-xs font-semibold hover:bg-blue transition">
<i data-lucide="external-link" class="w-3.5 h-3.5"></i> {% trans "View Complaint" %}
</a>
</div>
{% endfor %}
</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>
<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>
{% if can_manage_roles %}
<button class="tab-btn {% if active_tab == 'roles' %}active{% endif %}" onclick="switchDeptTab('roles', this)">
<i data-lucide="user-cog" class="w-4 h-4 inline mr-1"></i> {% trans "Roles" %}
</button>
{% endif %}
<button class="tab-btn {% if active_tab == 'sections' %}active{% endif %}" onclick="switchDeptTab('sections', this)">
<i data-lucide="layers" class="w-4 h-4 inline mr-1"></i> {% trans "Sections" %} ({{ org_sections|length }})
</button>
<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 == 'suggestions' %}active{% endif %}" onclick="switchDeptTab('suggestions', this)">
<i data-lucide="lightbulb" class="w-4 h-4 inline mr-1"></i> {% trans "Suggestions" %} ({{ stats.total_suggestions }})
</button>
<button class="tab-btn {% if active_tab == 'appreciations' %}active{% endif %}" onclick="switchDeptTab('appreciations', this)">
<i data-lucide="heart" class="w-4 h-4 inline mr-1"></i> {% trans "Appreciations" %} ({{ stats.total_appreciations }})
</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>
<div id="filter-suggestions" class="hidden">
<select name="suggestion_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 Suggestions" %}</option>
<option value="submitted" {% if suggestion_status_filter == 'submitted' %}selected{% endif %}>{% trans "Submitted" %}</option>
<option value="reviewed" {% if suggestion_status_filter == 'reviewed' %}selected{% endif %}>{% trans "Reviewed" %}</option>
<option value="acknowledged" {% if suggestion_status_filter == 'acknowledged' %}selected{% endif %}>{% trans "Acknowledged" %}</option>
<option value="closed" {% if suggestion_status_filter == 'closed' %}selected{% endif %}>{% trans "Closed" %}</option>
</select>
</div>
<div id="filter-appreciations" class="hidden">
<select name="appreciation_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 Appreciations" %}</option>
<option value="draft" {% if appreciation_status_filter == 'draft' %}selected{% endif %}>{% trans "Draft" %}</option>
<option value="activated" {% if appreciation_status_filter == 'activated' %}selected{% endif %}>{% trans "Activated" %}</option>
<option value="sent" {% if appreciation_status_filter == 'sent' %}selected{% endif %}>{% trans "Sent" %}</option>
<option value="acknowledged" {% if appreciation_status_filter == 'acknowledged' %}selected{% endif %}>{% trans "Acknowledged" %}</option>
</select>
</div>
{% if search_query or complaint_status_filter or inquiry_status_filter or observation_status_filter or suggestion_status_filter or appreciation_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">
<!-- 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 cursor-pointer"
onclick="window.location='{% url 'organizations:department_staff_detail' department.pk s.pk %}'">
<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>
<!-- Roles Tab -->
{% if can_manage_roles %}
<div id="panel-roles" class="tab-panel {% if active_tab != 'roles' %}hidden{% endif %}">
<div class="p-6">
<div class="flex items-center justify-between mb-6">
<div>
<h3 class="font-bold text-navy text-lg flex items-center gap-2">
<i data-lucide="user-cog" class="w-5 h-5"></i> {% trans "Department Roles" %}
</h3>
<p class="text-sm text-slate-500 mt-1">{% trans "Manage who receives complaints, inquiries, and observations for this department." %}</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for role in role_data %}
<div class="bg-white border-2 border-slate-200 rounded-2xl p-5 hover:border-navy/30 transition">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl flex items-center justify-center text-white text-sm font-bold
{% if role.field == 'champion' %}bg-amber-500{% elif 'manager' in role.field %}bg-navy{% elif 'supervisor' in role.field %}bg-emerald-600{% else %}bg-indigo-600{% endif %}">
<i data-lucide="{% if role.field == 'champion' %}crown{% elif 'manager' in role.field %}briefcase{% elif 'supervisor' in role.field %}clipboard-check{% else %}shield{% endif %}" class="w-5 h-5"></i>
</div>
<div>
<h4 class="font-bold text-navy text-sm">{{ role.label }}</h4>
{% if role.field == 'champion' %}
<span class="text-[10px] text-amber-600 font-semibold uppercase">{% trans "Primary Contact" %}</span>
{% endif %}
</div>
</div>
</div>
<div class="min-h-[48px]">
{% if role.staff %}
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-xs font-bold text-navy">
{{ role.staff.get_full_name|first|upper }}
</div>
<div>
<p class="text-sm font-semibold text-slate-800">{{ role.staff.get_full_name }}</p>
<p class="text-[11px] text-slate-400">{{ role.staff.email|default:"No email" }}</p>
</div>
</div>
{% else %}
<div class="flex items-center gap-2 px-3 py-2 bg-slate-50 rounded-lg">
<i data-lucide="user-x" class="w-4 h-4 text-slate-300"></i>
<span class="text-sm text-slate-400 italic">{% trans "Not assigned" %}</span>
</div>
{% endif %}
</div>
{% if can_manage_roles %}
<div class="mt-3 pt-3 border-t border-slate-100">
<button type="button"
onclick="openRoleModal('{{ role.field }}', '{{ role.label }}', '{% if role.staff %}{{ role.staff.id }}{% endif %}')"
class="text-xs font-semibold text-navy hover:text-blue transition flex items-center gap-1">
<i data-lucide="{% if role.staff %}pencil{% else %}plus{% endif %}" class="w-3.5 h-3.5"></i>
{% if role.staff %}{% trans "Change" %}{% else %}{% trans "Assign" %}{% endif %}
</button>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Sections Tab -->
<div id="panel-sections" class="tab-panel {% if active_tab != 'sections' %}hidden{% endif %}">
<div class="p-4">
<div class="flex justify-between items-center mb-4">
<h3 class="font-semibold text-slate-800">{% trans "Department Sections" %}</h3>
<a href="{% url 'organizations:orgsection_create' %}?department={{ department.pk }}" class="inline-flex items-center gap-2 px-4 py-2 bg-[#005696] text-white rounded-xl text-sm font-semibold hover:bg-[#004670] transition">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add Section" %}
</a>
</div>
<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 "Section Name" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Location" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Champion" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Supervisor" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Deputy" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Sub-Sections" %}</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 sec in org_sections %}
<tr class="table-row-hover">
<td class="px-4 py-3">
<a href="{% url 'organizations:orgsection_detail' sec.pk %}" class="font-semibold text-[#005696] hover:underline">{{ sec.name_en }}</a>
{% if sec.name_ar %}<br><span class="text-xs text-slate-500" dir="rtl">{{ sec.name_ar }}</span>{% endif %}
</td>
<td class="px-4 py-3 text-slate-700 text-sm">{{ sec.location_type|default:"-" }}{% if sec.sub_location %} / {{ sec.sub_location }}{% endif %}</td>
<td class="px-4 py-3 text-slate-700 text-sm">{{ sec.champion.get_full_name|default:"-" }}</td>
<td class="px-4 py-3 text-slate-700 text-sm">{{ sec.supervisor.get_full_name|default:"-" }}</td>
<td class="px-4 py-3 text-slate-700 text-sm">{{ sec.deputy_supervisor.get_full_name|default:"-" }}</td>
<td class="px-4 py-3 text-center">
<span class="px-2 py-0.5 bg-sky-100 text-sky-600 rounded-full text-xs font-bold">{{ sec.subsections.count }}</span>
</td>
<td class="px-4 py-3">
{% if sec.status == 'active' %}
<span class="px-2 py-0.5 rounded-full text-xs font-bold bg-green-100 text-green-600">{% trans "Active" %}</span>
{% else %}
<span class="px-2 py-0.5 rounded-full text-xs font-bold bg-slate-100 text-slate-600">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td class="px-4 py-3">
<div class="flex gap-1">
<a href="{% url 'organizations:orgsection_detail' sec.pk %}" class="p-1.5 text-[#005696] hover:bg-sky-50 rounded-lg transition" title="{% trans 'View' %}"><i data-lucide="eye" class="w-4 h-4"></i></a>
<a href="{% url 'organizations:orgsection_update' sec.pk %}" class="p-1.5 text-[#005696] hover:bg-sky-50 rounded-lg transition" title="{% trans 'Edit' %}"><i data-lucide="edit-2" class="w-4 h-4"></i></a>
<a href="{% url 'organizations:orgsection_delete' sec.pk %}" class="p-1.5 text-red-500 hover:bg-red-50 rounded-lg transition" title="{% trans 'Delete' %}"><i data-lucide="trash-2" class="w-4 h-4"></i></a>
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="8" class="px-4 py-8 text-center text-slate-500">{% trans "No sections in this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- 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 'organizations:department_complaint_detail' department.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>
{% if stats.total_complaints > 0 %}
<div class="border-t border-slate-100 px-4 py-3 bg-slate-50 rounded-b-xl">
<a href="{% url 'organizations:department_complaints_list' department.pk %}" class="text-sm text-navy font-semibold hover:underline flex items-center gap-1">
<i data-lucide="external-link" class="w-4 h-4"></i>
{% blocktrans count counter=stats.total_complaints %}View all {{ counter }} complaint{% plural %}View all {{ counter }} complaints{% endblocktrans %}
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</a>
</div>
{% endif %}
</div>
<!-- Inquiries Tab -->
<div id="panel-inquiries" class="tab-panel {% if active_tab != 'inquiries' %}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 "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 "Created" %}</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 text-xs text-slate">{{ i.created_at|date:"Y-m-d" }}</td>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
<a href="{% url 'organizations:department_inquiry_detail' department.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="#" onclick="return openDeptResponseModal('inquiry', '{{ i.pk }}', '{% url 'inquiries:inquiry_department_response' pk=i.pk %}', '{{ i.reference_number }}', '{{ i.subject|escapejs }}', '{{ i.department_response_en|default:'' }}', '{{ i.department_response_ar|default:'' }}')" class="text-xs text-green-600 font-semibold hover:underline">
{% trans "Respond" %}
</a>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="7" class="px-4 py-8 text-center text-slate">{% trans "No inquiries for this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
{% if stats.total_inquiries > 0 %}
<div class="border-t border-slate-100 px-4 py-3 bg-slate-50 rounded-b-xl">
<a href="{% url 'organizations:department_inquiries_list' department.pk %}" class="text-sm text-navy font-semibold hover:underline flex items-center gap-1">
<i data-lucide="external-link" class="w-4 h-4"></i>
{% blocktrans count counter=stats.total_inquiries %}View all {{ counter }} inquiry{% plural %}View all {{ counter }} inquiries{% endblocktrans %}
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</a>
</div>
{% 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>
<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 o in observations %}
<tr class="table-row-hover">
<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>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
<a href="{% url 'organizations:department_observation_detail' department.pk o.pk %}" class="text-xs text-navy font-semibold hover:underline">
{% trans "View" %}
</a>
{% if can_respond and not o.department_responded_at %}
<a href="#" onclick="return openDeptResponseModal('observation', '{{ o.pk }}', '{% url 'observations:observation_department_response' pk=o.pk %}', '{{ o.tracking_code }}', '{% if o.title %}{{ o.title|escapejs }}{% else %}{{ o.description|truncatewords:10|escapejs }}{% endif %}', '{{ o.department_response_en|default:'' }}', '{{ o.department_response_ar|default:'' }}')" class="text-xs text-green-600 font-semibold hover:underline">
{% trans "Respond" %}
</a>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="7" class="px-4 py-8 text-center text-slate">{% trans "No observations for this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
{% if stats.total_observations > 0 %}
<div class="border-t border-slate-100 px-4 py-3 bg-slate-50 rounded-b-xl">
<a href="{% url 'organizations:department_observations_list' department.pk %}" class="text-sm text-navy font-semibold hover:underline flex items-center gap-1">
<i data-lucide="external-link" class="w-4 h-4"></i>
{% blocktrans count counter=stats.total_observations %}View all {{ counter }} observation{% plural %}View all {{ counter }} observations{% endblocktrans %}
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</a>
</div>
{% endif %}
</div>
<!-- Suggestions Tab -->
<div id="panel-suggestions" class="tab-panel {% if active_tab != 'suggestions' %}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 "Title" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Priority" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Category" %}</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 "Created" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for s in suggestions %}
<tr class="table-row-hover cursor-pointer" onclick="openRecordModal('suggestion', '{{ s.pk }}')">
<td class="px-4 py-3 text-sm font-mono text-navy">{{ s.pk|truncatechars:8 }}</td>
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs truncate">{{ s.title|default:"-" }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
{% if s.priority == 'urgent' %}bg-red-100 text-red-700
{% elif s.priority == 'high' %}bg-orange-100 text-orange-700
{% elif s.priority == 'medium' %}bg-amber-100 text-amber-700
{% else %}bg-green-100 text-green-700{% endif %}">
{{ s.get_priority_display }}
</span>
</td>
<td class="px-4 py-3 text-sm text-slate">{{ s.get_category_display|default:"-" }}</td>
<td class="px-4 py-3 text-sm text-slate">
{% if s.is_anonymous %}
<span class="text-slate-400 italic">{% trans "Anonymous" %}</span>
{% else %}
{{ s.contact_name|default:"-" }}
{% endif %}
</td>
<td class="px-4 py-3 text-sm text-slate">{{ s.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 s.status == 'submitted' %}bg-blue-50 text-blue-700
{% elif s.status == 'reviewed' %}bg-amber-50 text-amber-700
{% elif s.status == 'acknowledged' %}bg-green-50 text-green-700
{% elif s.status == 'closed' %}bg-slate-100 text-slate-600
{% else %}bg-slate-50 text-slate-500{% endif %}">
{{ s.get_status_display }}
</span>
</td>
<td class="px-4 py-3 text-xs text-slate">{{ s.created_at|date:"Y-m-d" }}</td>
</tr>
{% empty %}
<tr><td colspan="8" class="px-4 py-8 text-center text-slate">{% trans "No suggestions for this department" %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Appreciations Tab -->
<div id="panel-appreciations" class="tab-panel {% if active_tab != 'appreciations' %}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 "Category" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Sender" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Recipient" %}</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 "Sent" %}</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 a in appreciations %}
<tr class="table-row-hover cursor-pointer" onclick="openRecordModal('appreciation', '{{ a.pk }}')">
<td class="px-4 py-3 text-sm font-mono text-navy">{{ a.pk|truncatechars:8 }}</td>
<td class="px-4 py-3">
{% if a.category %}
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase" style="background: {{ a.category.color }}15; color: {{ a.category.color }}">
{{ a.category.name_en }}
</span>
{% else %}
<span class="text-slate-400">-</span>
{% endif %}
</td>
<td class="px-4 py-3 text-sm text-slate">
{% if a.is_anonymous %}
<span class="text-slate-400 italic">{% trans "Anonymous" %}</span>
{% elif a.sender %}
{{ a.sender.get_full_name }}
{% else %}
-
{% endif %}
</td>
<td class="px-4 py-3 text-sm text-slate">{{ a.get_recipient_name|default:"-" }}</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
{% if a.status == 'draft' %}bg-slate-100 text-slate-600
{% elif a.status == 'activated' %}bg-blue-50 text-blue-700
{% elif a.status == 'sent' %}bg-green-50 text-green-700
{% elif a.status == 'acknowledged' %}bg-purple-50 text-purple-700
{% else %}bg-slate-50 text-slate-500{% endif %}">
{{ a.get_status_display }}
</span>
</td>
<td class="px-4 py-3 text-xs text-slate">{{ a.sent_at|date:"Y-m-d"|default:"-" }}</td>
<td class="px-4 py-3 text-xs text-slate">{{ a.created_at|date:"Y-m-d" }}</td>
</tr>
{% empty %}
<tr><td colspan="7" class="px-4 py-8 text-center text-slate">{% trans "No appreciations 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>
<!-- 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>
</div>
</div>
<!-- Set Department Role Modal -->
<div id="roleModal" 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-navy/10 rounded-full flex items-center justify-center">
<i data-lucide="user-cog" class="w-5 h-5 text-navy"></i>
</div>
<div>
<h3 class="text-xl font-bold text-navy" id="roleModalTitle">{% trans "Set Role" %}</h3>
<p class="text-sm text-slate-500" id="roleModalHint"></p>
</div>
</div>
<form method="post" action="{% url 'organizations:set_department_role' pk=department.pk %}">
{% csrf_token %}
<input type="hidden" name="role_field" id="roleModalField" value="">
<div class="mb-4">
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Select Staff Member" %}</label>
<select name="staff_id" id="roleModalSelect" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none">
<option value="">{% trans "-- Clear / Remove --" %}</option>
{% for s in managers %}
<option value="{{ s.id }}" id="roleOption_{{ s.id }}">
{{ s.get_full_name }}{% if s.job_title %} ({{ s.job_title }}){% endif %}{% if s.department %} — {{ s.department.get_localized_name }}{% endif %}
</option>
{% endfor %}
</select>
<p class="text-xs text-slate-400 mt-1">{% trans "Leave blank to remove the current assignee." %}</p>
</div>
<div class="flex gap-3">
<button type="button" onclick="document.getElementById('roleModal').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>
<!-- Department Response Modal -->
{% include "components/department_response_modal.html" %}
<!-- Record Detail Modal -->
<div id="recordDetailModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4" onclick="if(event.target===this)closeRecordModal()">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[85vh] flex flex-col overflow-hidden" onclick="event.stopPropagation()">
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-200 bg-slate-50">
<div class="flex items-center gap-3">
<div id="recordModalIcon" class="w-10 h-10 rounded-xl flex items-center justify-center"></div>
<div>
<div class="flex items-center gap-2">
<span id="recordModalRef" class="font-mono text-sm font-bold text-navy"></span>
<span id="recordModalStatusBadge" class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase"></span>
<span id="recordModalSeverityBadge" class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase"></span>
</div>
<p id="recordModalTypeLabel" class="text-xs text-slate-400 mt-0.5"></p>
</div>
</div>
<button onclick="closeRecordModal()" class="w-8 h-8 rounded-lg hover:bg-slate-200 flex items-center justify-center transition">
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
</button>
</div>
<div id="recordModalContent" class="flex-1 overflow-y-auto px-6 py-5 space-y-4">
<div id="recordModalLoading" class="flex items-center justify-center py-16">
<div class="w-8 h-8 border-3 border-navy border-t-transparent rounded-full animate-spin"></div>
</div>
<div id="recordModalBody" class="hidden space-y-4"></div>
</div>
</div>
</div>
<script>
{% trans "Complaint" as t_complaint %}
{% trans "Inquiry" as t_inquiry %}
{% trans "Observation" as t_observation %}
{% trans "Suggestion" as t_suggestion %}
{% trans "Appreciation" as t_appreciation %}
{% trans "Description" as t_description %}
{% trans "Patient" as t_patient %}
{% trans "MRN" as t_mrn %}
{% trans "Staff Involved" as t_staff_involved %}
{% trans "Department" as t_department %}
{% trans "Assigned To" as t_assigned_to %}
{% trans "Location" as t_location %}
{% trans "Classification" as t_classification %}
{% trans "Source" as t_source %}
{% trans "Created" as t_created %}
{% trans "SLA Deadline" as t_sla_deadline %}
{% trans "Overdue" as t_overdue %}
{% trans "Escalated" as t_escalated %}
{% trans "Expected Result" as t_expected_result %}
{% trans "AI Summary" as t_ai_summary %}
{% trans "Contact" as t_contact %}
{% trans "Category" as t_category %}
{% trans "Reporter" as t_reporter %}
{% trans "Subject" as t_subject %}
{% trans "No description" as t_no_desc %}
{% trans "Sender" as t_sender %}
{% trans "Recipient" as t_recipient %}
{% trans "Message" as t_message %}
{% trans "Priority" as t_priority %}
{% trans "Sent" as t_sent %}
{% trans "Visibility" as t_visibility %}
{% trans "Sentiment" as t_sentiment %}
{% trans "Acknowledged" as t_acknowledged %}
var recordModalLabels = {
complaint: '{{ t_complaint|escapejs }}',
inquiry: '{{ t_inquiry|escapejs }}',
observation: '{{ t_observation|escapejs }}',
suggestion: '{{ t_suggestion|escapejs }}',
appreciation: '{{ t_appreciation|escapejs }}',
description: '{{ t_description|escapejs }}',
patient: '{{ t_patient|escapejs }}',
mrn: '{{ t_mrn|escapejs }}',
staff_involved: '{{ t_staff_involved|escapejs }}',
department: '{{ t_department|escapejs }}',
assigned_to: '{{ t_assigned_to|escapejs }}',
location: '{{ t_location|escapejs }}',
classification: '{{ t_classification|escapejs }}',
source: '{{ t_source|escapejs }}',
created: '{{ t_created|escapejs }}',
sla_deadline: '{{ t_sla_deadline|escapejs }}',
overdue: '{{ t_overdue|escapejs }}',
escalated: '{{ t_escalated|escapejs }}',
expected_result: '{{ t_expected_result|escapejs }}',
ai_summary: '{{ t_ai_summary|escapejs }}',
contact: '{{ t_contact|escapejs }}',
category: '{{ t_category|escapejs }}',
reporter: '{{ t_reporter|escapejs }}',
subject: '{{ t_subject|escapejs }}',
no_desc: '{{ t_no_desc|escapejs }}',
sender: '{{ t_sender|escapejs }}',
recipient: '{{ t_recipient|escapejs }}',
message: '{{ t_message|escapejs }}',
priority: '{{ t_priority|escapejs }}',
sent: '{{ t_sent|escapejs }}',
visibility: '{{ t_visibility|escapejs }}',
sentiment: '{{ t_sentiment|escapejs }}',
acknowledged: '{{ t_acknowledged|escapejs }}',
};
var recordApiUrls = {
complaint: '/organizations/api/dept-complaint/',
inquiry: '/organizations/api/dept-inquiry/',
observation: '/organizations/api/dept-observation/',
suggestion: '/organizations/api/dept-suggestion/',
appreciation: '/organizations/api/dept-appreciation/',
};
var recordTypeConfig = {
complaint: { icon: 'alert-circle', color: 'red', bg: 'bg-red-50', text: 'text-red-600' },
inquiry: { icon: 'help-circle', color: 'amber', bg: 'bg-amber-50', text: 'text-amber-600' },
observation: { icon: 'eye', color: 'purple', bg: 'bg-purple-50', text: 'text-purple-600' },
suggestion: { icon: 'lightbulb', color: 'amber', bg: 'bg-amber-50', text: 'text-amber-600' },
appreciation: { icon: 'heart', color: 'pink', bg: 'bg-pink-50', text: 'text-pink-600' },
};
function openRecordModal(type, pk) {
var modal = document.getElementById('recordDetailModal');
var config = recordTypeConfig[type];
var iconEl = document.getElementById('recordModalIcon');
iconEl.className = 'w-10 h-10 rounded-xl flex items-center justify-center ' + config.bg + ' ' + config.text;
iconEl.innerHTML = '<i data-lucide="' + config.icon + '" class="w-5 h-5"></i>';
document.getElementById('recordModalTypeLabel').textContent = recordModalLabels[type];
document.getElementById('recordModalRef').textContent = '';
document.getElementById('recordModalStatusBadge').textContent = '';
document.getElementById('recordModalSeverityBadge').textContent = '';
document.getElementById('recordModalStatusBadge').className = 'px-2 py-0.5 rounded-full text-[10px] font-bold uppercase hidden';
document.getElementById('recordModalSeverityBadge').className = 'px-2 py-0.5 rounded-full text-[10px] font-bold uppercase hidden';
document.getElementById('recordModalLoading').classList.remove('hidden');
document.getElementById('recordModalBody').classList.add('hidden');
modal.classList.remove('hidden');
lucide.createIcons();
fetch(recordApiUrls[type] + pk + '/')
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.error) { closeRecordModal(); alert(d.error); return; }
renderRecordModal(type, d);
})
.catch(function() { closeRecordModal(); });
return false;
}
function renderRecordModal(type, d) {
document.getElementById('recordModalRef').textContent = d.reference;
var sb = document.getElementById('recordModalStatusBadge');
sb.textContent = d.status_display || d.status;
sb.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold uppercase ' + statusColorClass(d.status);
sb.classList.remove('hidden');
var svb = document.getElementById('recordModalSeverityBadge');
if (d.severity && d.severity_display) {
svb.textContent = d.severity_display;
svb.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold uppercase ' + severityColorClass(d.severity);
svb.classList.remove('hidden');
}
var html = '';
if (d.title) {
html += '<h3 class="text-lg font-bold text-navy">' + escHtml(d.title) + '</h3>';
}
html += '<div class="bg-slate-50 rounded-xl p-4">';
html += '<p class="text-xs font-semibold text-slate-400 uppercase mb-1">' + recordModalLabels.description + '</p>';
html += '<p class="text-sm text-slate-700 whitespace-pre-wrap">' + escHtml(d.description || recordModalLabels.no_desc) + '</p>';
html += '</div>';
html += '<div class="grid grid-cols-2 gap-3">';
if (type === 'complaint') {
if (d.patient && d.patient !== '-') html += infoCard(recordModalLabels.patient, d.patient);
if (d.patient_mrn && d.patient_mrn !== '-') html += infoCard(recordModalLabels.mrn, d.patient_mrn);
if (d.staff && d.staff.length > 0) html += infoCard(recordModalLabels.staff_involved, d.staff.join(', '));
}
if (type === 'inquiry') {
html += infoCard(recordModalLabels.contact, [d.contact_name, d.contact_phone, d.contact_email].filter(function(v) { return v && v !== '-'; }).join(' | '));
if (d.category && d.category !== '-') html += infoCard(recordModalLabels.category, d.category);
}
if (type === 'observation') {
html += infoCard(recordModalLabels.reporter, d.reporter || '-');
if (d.category && d.category !== '-') html += infoCard(recordModalLabels.category, d.category);
}
if (type === 'suggestion') {
html += infoCard(recordModalLabels.contact, d.contact_name || '-');
if (d.category && d.category !== '-') html += infoCard(recordModalLabels.category, d.category);
if (d.priority && d.priority_display) html += infoCard(recordModalLabels.priority, d.priority_display);
if (d.sentiment) html += infoCard(recordModalLabels.sentiment, d.sentiment);
}
if (type === 'appreciation') {
html += infoCard(recordModalLabels.sender, d.sender || '-');
html += infoCard(recordModalLabels.recipient, d.recipient || '-');
if (d.category && d.category !== '-') html += infoCard(recordModalLabels.category, d.category);
if (d.visibility) html += infoCard(recordModalLabels.visibility, d.visibility);
}
html += infoCard(recordModalLabels.department, d.department);
html += infoCard(recordModalLabels.assigned_to, d.assigned_to);
html += infoCard(recordModalLabels.location, d.location);
if (d.taxonomy && d.taxonomy.length > 0) html += infoCard(recordModalLabels.classification, d.taxonomy.join(' > '));
if (d.source) html += infoCard(recordModalLabels.source, d.source);
html += infoCard(recordModalLabels.created, d.created_at);
if (d.due_at) {
var deadlineText = d.due_at;
if (d.is_overdue) deadlineText += ' <span class="text-red-600 font-bold">(' + recordModalLabels.overdue + ')</span>';
html += infoCard(recordModalLabels.sla_deadline, deadlineText);
}
if (d.escalated) html += infoCard('', '<span class="text-red-600 font-bold">' + recordModalLabels.escalated + '</span>');
html += '</div>';
if (d.expected_result) {
html += '<div class="bg-blue-50 rounded-xl p-4">';
html += '<p class="text-xs font-semibold text-blue-600 uppercase mb-1">' + recordModalLabels.expected_result + '</p>';
html += '<p class="text-sm text-slate-700">' + escHtml(d.expected_result) + '</p>';
html += '</div>';
}
if (d.ai_brief || d.ai_summary) {
var aiText = d.ai_brief || d.ai_summary;
html += '<div class="bg-indigo-50 rounded-xl p-4">';
html += '<p class="text-xs font-semibold text-indigo-600 uppercase mb-1">' + recordModalLabels.ai_summary + '</p>';
html += '<p class="text-sm text-slate-700">' + escHtml(aiText) + '</p>';
html += '</div>';
}
if (type === 'appreciation') {
if (d.description_ar) {
html += '<div class="bg-slate-50 rounded-xl p-4">';
html += '<p class="text-xs font-semibold text-slate-400 uppercase mb-1">العربية</p>';
html += '<p class="text-sm text-slate-700 whitespace-pre-wrap" dir="rtl">' + escHtml(d.description_ar) + '</p>';
html += '</div>';
}
if (d.sent_at) html += '<div class="bg-green-50 rounded-xl p-3 text-center"><span class="text-xs text-green-700 font-semibold"><i data-lucide="send" class="w-3 h-3 inline mr-1"></i>' + recordModalLabels.sent + ': ' + escHtml(d.sent_at) + '</span></div>';
if (d.acknowledged_at) html += '<div class="bg-purple-50 rounded-xl p-3 text-center"><span class="text-xs text-purple-700 font-semibold"><i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i>' + recordModalLabels.acknowledged + ': ' + escHtml(d.acknowledged_at) + '</span></div>';
}
document.getElementById('recordModalBody').innerHTML = html;
document.getElementById('recordModalLoading').classList.add('hidden');
document.getElementById('recordModalBody').classList.remove('hidden');
lucide.createIcons();
}
function infoCard(label, value) {
return '<div class="bg-white border border-slate-100 rounded-lg p-3">'
+ (label ? '<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">' + label + '</p>' : '')
+ '<p class="text-sm text-navy font-medium">' + value + '</p>'
+ '</div>';
}
function statusColorClass(status) {
var map = {
open: 'bg-blue-50 text-blue-700', in_progress: 'bg-amber-50 text-amber-700',
resolved: 'bg-green-50 text-green-700', closed: 'bg-slate-100 text-slate-600',
cancelled: 'bg-slate-50 text-slate-500', contacted: 'bg-cyan-50 text-cyan-700',
contacted_no_response: 'bg-orange-50 text-orange-700', new: 'bg-sky-50 text-sky-700',
triaged: 'bg-teal-50 text-teal-700', assigned: 'bg-teal-50 text-teal-700',
pending_external: 'bg-violet-50 text-violet-700',
submitted: 'bg-blue-50 text-blue-700', reviewed: 'bg-amber-50 text-amber-700',
acknowledged: 'bg-green-50 text-green-700',
draft: 'bg-slate-100 text-slate-600', activated: 'bg-cyan-50 text-cyan-700',
sent: 'bg-green-50 text-green-700',
};
return map[status] || 'bg-slate-50 text-slate-500';
}
function severityColorClass(severity) {
var map = {
critical: 'bg-gray-800 text-white', high: 'bg-red-50 text-red-700',
medium: 'bg-amber-50 text-amber-700', low: 'bg-green-50 text-green-700',
};
return map[severity] || 'bg-slate-50 text-slate-500';
}
function escHtml(str) {
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function closeRecordModal() {
document.getElementById('recordDetailModal').classList.add('hidden');
}
const tabFilters = {
staff: [],
complaints: ['filter-complaints'],
inquiries: ['filter-inquiries'],
observations: ['filter-observations'],
suggestions: ['filter-suggestions'],
appreciations: ['filter-appreciations'],
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' || tab === 'roles') ? 'none' : '';
const url = new URL(window.location);
url.searchParams.set('tab', tab);
history.replaceState(null, '', url);
if (tab === 'analytics' && !analyticsLoaded) {
loadDepartmentAnalytics();
}
if (tab === 'roles' && window.lucide) lucide.createIcons();
}
function openRoleModal(roleField, roleLabel, currentStaffId) {
document.getElementById('roleModalField').value = roleField;
document.getElementById('roleModalTitle').textContent = roleLabel;
document.getElementById('roleModalHint').textContent = '';
var sel = document.getElementById('roleModalSelect');
sel.value = currentStaffId || '';
document.getElementById('roleModal').classList.remove('hidden');
}
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>';
}
}
// Department Response Modal is now handled by shared component
// openDeptResponseModal / closeDeptResponseModal / submitDeptResponse are in department_response_modal.html
</script>
{% endblock %}