HH/templates/organizations/department_staff_detail.html
ismail 7369d08012
All checks were successful
Build and Push Docker Image / build (push) Successful in 4m14s
feat: unified reference numbers + feedback modules QA audit
Reference numbers (unified scheme PREFIX-YYYYMM-HOSP-NNNN, e.g. CMP-202606-HHN-0001):
- new ReferenceSequence model + generate_reference() helper (apps/core)
- Complaint/Inquiry/Observation/Appreciation/Suggestion emit unified refs via save()
- prefix-based auto-routing in public track API (CMP/INQ/OBS trackable; APR/SGT internal-only)
- removed legacy CMP-/INQ- generators in ui_views, integrations, px_sources
- migrations: core.0003_referencesequence, appreciation.0006, feedback.0008, observations.0012
- unit tests (format, sanitization, monthly reset, 40-thread concurrency)

QA audit:
- isolated E2E hospital sandbox mirroring HH-N + 10 role users (create_e2e_isolated_env)
- feedback-modules-audit.spec.ts + audit helper (headed, run-to-completion)
- reports/feedback-modules-qa-report.md

Also bundles accumulated in-progress work across complaints, observations,
organizations, templates, and other modules.
2026-06-14 14:29:23 +03:00

699 lines
40 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{{ staff.get_localized_name }} - {{ department.get_localized_name }} - PX360{% endblock %}
{% block content %}
<div class="p-6 max-w-5xl mx-auto">
<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>
<a href="{% url 'organizations:department_detail' department.pk %}" class="hover:text-navy transition">{{ department.get_localized_name }}</a>
<i data-lucide="chevron-right" class="w-4 h-4"></i>
<a href="{% url 'organizations:department_detail' department.pk %}?tab=staff" class="hover:text-navy transition">{% trans "Staff" %}</a>
<i data-lucide="chevron-right" class="w-4 h-4"></i>
<span class="text-navy font-semibold">{{ staff.get_localized_name }}</span>
</div>
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-navy/10 rounded-xl flex items-center justify-center text-navy font-bold text-lg">
{{ staff.first_name|truncatechars:1 }}{{ staff.last_name|truncatechars:1 }}
</div>
<div>
<div class="flex items-center gap-2">
<h1 class="text-xl font-bold text-navy">{{ staff.get_localized_name }}</h1>
{% if staff.is_head %}
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-navy text-white uppercase">
<i data-lucide="crown" class="w-3 h-3 inline"></i> {% trans "Head" %}
</span>
{% endif %}
</div>
<p class="text-sm text-slate-500">
{{ staff.get_localized_job_title|default:"—" }}
{% if staff.specialization %} &middot; {{ staff.specialization }}{% endif %}
</p>
</div>
</div>
<div class="flex items-center gap-3">
{% if can_edit_staff %}
<button onclick="openEditStaffModal()"
class="px-3 py-2 bg-navy text-white text-sm font-semibold rounded-xl hover:bg-navy/90 transition flex items-center gap-1.5">
<i data-lucide="pencil" class="w-4 h-4"></i> {% trans "Edit" %}
</button>
{% endif %}
<a href="{% url 'organizations:department_detail' department.pk %}?tab=staff" class="text-sm text-slate hover:text-navy transition flex items-center gap-1">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to staff" %}
</a>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="space-y-6">
<div class="bg-white rounded-2xl border border-slate-200 p-6">
<h3 class="text-sm font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="user" class="w-4 h-4"></i> {% trans "Personal Info" %}
</h3>
<div class="space-y-3">
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Name" %}</p>
<p class="text-sm font-medium text-navy">{{ staff.first_name }} {{ staff.last_name }}</p>
</div>
{% if staff.first_name_ar or staff.last_name_ar %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Name (Arabic)" %}</p>
<p class="text-sm font-medium text-navy" dir="rtl">{{ staff.first_name_ar }} {{ staff.last_name_ar }}</p>
</div>
{% endif %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Employee ID" %}</p>
<p class="text-sm font-mono text-navy">{{ staff.employee_id }}</p>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Staff Type" %}</p>
<p class="text-sm text-navy">{{ staff.get_staff_type_display }}</p>
</div>
{% if staff.license_number %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "License No." %}</p>
<p class="text-sm text-navy">{{ staff.license_number }}</p>
</div>
{% endif %}
</div>
</div>
<div class="bg-white rounded-2xl border border-slate-200 p-6">
<h3 class="text-sm font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="building-2" class="w-4 h-4"></i> {% trans "Organization" %}
</h3>
<div class="space-y-3">
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Hospital" %}</p>
<p class="text-sm font-medium text-navy">{{ staff.hospital.name }}</p>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Department" %}</p>
<p class="text-sm font-medium text-navy">{{ department.get_localized_name }}</p>
</div>
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{{ department.get_category_display }}</p>
<p class="text-sm text-navy">{{ staff.get_department_type_display }}</p>
</div>
{% if staff.section_fk %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Section" %}</p>
<p class="text-sm text-navy">{{ staff.section_fk.name }}</p>
</div>
{% endif %}
{% if staff.subsection_fk %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Subsection" %}</p>
<p class="text-sm text-navy">{{ staff.subsection_fk.name }}</p>
</div>
{% endif %}
{% if staff.report_to %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Reports To" %}</p>
<a href="{% url 'organizations:department_staff_detail' department.pk staff.report_to.pk %}" class="text-sm text-navy font-semibold hover:underline">
{{ staff.report_to.get_localized_name }}
</a>
</div>
{% endif %}
</div>
</div>
{% if staff.email or staff.phone %}
<div class="bg-white rounded-2xl border border-slate-200 p-6">
<h3 class="text-sm font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="phone" class="w-4 h-4"></i> {% trans "Contact" %}
</h3>
<div class="space-y-3">
{% if staff.email %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Email" %}</p>
<p class="text-sm text-navy">{{ staff.email }}</p>
</div>
{% endif %}
{% if staff.phone %}
<div>
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Phone" %}</p>
<p class="text-sm text-navy">{{ staff.phone }}</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
{% if direct_reports %}
<div class="bg-white rounded-2xl border border-slate-200 p-6">
<h3 class="text-sm font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="users" class="w-4 h-4"></i> {% trans "Direct Reports" %} ({{ direct_reports|length }})
</h3>
<div class="space-y-2">
{% for dr in direct_reports %}
<a href="{% url 'organizations:department_staff_detail' department.pk dr.pk %}" class="flex items-center gap-2 px-3 py-2 bg-slate-50 rounded-xl hover:bg-slate-100 transition">
<div class="w-7 h-7 bg-navy/10 rounded-full flex items-center justify-center text-navy font-bold text-[10px]">
{{ dr.first_name|truncatechars:1 }}{{ dr.last_name|truncatechars:1 }}
</div>
<span class="text-sm font-medium text-navy">{{ dr.get_localized_name }}</span>
{% if dr.get_localized_job_title %}
<span class="text-xs text-slate-400">&middot; {{ dr.get_localized_job_title }}</span>
{% endif %}
</a>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<div class="lg:col-span-2">
{% if complaint_counts.total > 0 %}
<div class="flex flex-wrap gap-3 mb-4">
<div class="px-3 py-2 bg-slate-100 rounded-xl text-center min-w-[80px]">
<p class="text-lg font-bold text-navy">{{ complaint_counts.total }}</p>
<p class="text-[10px] text-slate uppercase font-bold">{% trans "Total" %}</p>
</div>
{% if complaint_counts.open %}
<div class="px-3 py-2 bg-blue-50 rounded-xl text-center min-w-[80px]">
<p class="text-lg font-bold text-blue-700">{{ complaint_counts.open }}</p>
<p class="text-[10px] text-blue-600 uppercase font-bold">{% trans "Open" %}</p>
</div>
{% endif %}
{% if complaint_counts.in_progress %}
<div class="px-3 py-2 bg-amber-50 rounded-xl text-center min-w-[80px]">
<p class="text-lg font-bold text-amber-700">{{ complaint_counts.in_progress }}</p>
<p class="text-[10px] text-amber-600 uppercase font-bold">{% trans "In Progress" %}</p>
</div>
{% endif %}
{% if complaint_counts.resolved %}
<div class="px-3 py-2 bg-green-50 rounded-xl text-center min-w-[80px]">
<p class="text-lg font-bold text-green-700">{{ complaint_counts.resolved }}</p>
<p class="text-[10px] text-green-600 uppercase font-bold">{% trans "Resolved" %}</p>
</div>
{% endif %}
{% if complaint_counts.closed %}
<div class="px-3 py-2 bg-slate-50 rounded-xl text-center min-w-[80px]">
<p class="text-lg font-bold text-slate-600">{{ complaint_counts.closed }}</p>
<p class="text-[10px] text-slate uppercase font-bold">{% trans "Closed" %}</p>
</div>
{% endif %}
</div>
{% endif %}
<div class="bg-white rounded-2xl border border-slate-200 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between">
<h3 class="text-sm font-bold text-navy flex items-center gap-2">
<i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Complaints" %} ({{ complaint_counts.total }})
</h3>
</div>
{% if complaint_rows %}
<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 "Patient" %}</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 "Severity" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Role" %}</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 row in complaint_rows %}
{% with c=row.complaint %}
<tr class="hover:bg-slate-50 transition cursor-pointer"
onclick="openRecordModal('complaint', '{{ 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 truncate">{{ c.title|default:"—" }}</td>
<td class="px-4 py-3 text-sm text-slate">{{ c.patient.mrn|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">
{% if c.severity %}
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
{% if c.severity == 'critical' %}bg-gray-800 text-white
{% elif c.severity == 'high' %}bg-red-50 text-red-700
{% elif c.severity == 'medium' %}bg-amber-50 text-amber-700
{% else %}bg-green-50 text-green-700{% endif %}">
{{ c.get_severity_display }}
</span>
{% else %}
<span class="text-slate-400 text-xs"></span>
{% endif %}
</td>
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-[10px] font-semibold
{% if row.role == 'Primary' %}bg-navy/10 text-navy
{% else %}bg-slate-100 text-slate-600{% endif %}">
{{ row.role }}
</span>
</td>
<td class="px-4 py-3 text-xs text-slate">{{ c.created_at|date:"Y-m-d" }}</td>
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
{% else %}
<div class="px-6 py-12 text-center">
<i data-lucide="inbox" class="w-10 h-10 mx-auto text-slate-300 mb-3"></i>
<p class="text-slate text-sm">{% trans "No complaints found for this staff member in this department." %}</p>
</div>
{% endif %}
</div>
{% if appreciations %}
<div class="bg-white rounded-2xl border border-slate-200 overflow-hidden mt-6">
<div class="px-6 py-4 border-b border-slate-100">
<h3 class="text-sm font-bold text-navy flex items-center gap-2">
<i data-lucide="heart" class="w-4 h-4 text-pink-500"></i> {% trans "Appreciations" %} ({{ appreciations|length }})
</h3>
</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 "Category" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Message" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "From" %}</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 "Date" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for a in appreciations %}
<tr class="hover:bg-slate-50 transition cursor-pointer"
onclick="openRecordModal('appreciation', '{{ a.pk }}')">
<td class="px-4 py-3">
{% if a.category %}
<span class="px-2 py-1 rounded-full text-[10px] font-bold" style="background: {{ a.category.color }}15; color: {{ a.category.color }}">
{{ a.category.name_en }}
</span>
{% else %}
<span class="text-slate-400 text-xs"></span>
{% endif %}
</td>
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs truncate">{{ a.message_en|default:"—" }}</td>
<td class="px-4 py-3 text-sm text-slate">
{% if a.is_anonymous %}
<span class="text-slate-400 italic">{% trans "Anonymous" %}</span>
{% else %}
{{ a.sender.get_full_name|default:"—" }}
{% endif %}
</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:a.created_at|date:"Y-m-d" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
{% if can_edit_staff %}
<!-- Edit Staff Modal -->
<div id="editStaffModal" 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)closeEditStaffModal()">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-lg 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 class="w-10 h-10 bg-navy/10 rounded-xl flex items-center justify-center">
<i data-lucide="user-pen" class="w-5 h-5 text-navy"></i>
</div>
<h3 class="text-base font-bold text-navy">{% trans "Edit Staff Info" %}</h3>
</div>
<button onclick="closeEditStaffModal()" 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>
<form method="post" action="{% url 'organizations:department_staff_update' department.pk staff.pk %}" id="editStaffForm">
{% csrf_token %}
<div class="flex-1 overflow-y-auto px-6 py-5 space-y-5">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "First Name" %} *</label>
<input type="text" name="first_name" value="{{ staff.first_name }}" required
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Last Name" %} *</label>
<input type="text" name="last_name" value="{{ staff.last_name }}" required
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "First Name (Arabic)" %}</label>
<input type="text" name="first_name_ar" value="{{ staff.first_name_ar }}" dir="rtl"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Last Name (Arabic)" %}</label>
<input type="text" name="last_name_ar" value="{{ staff.last_name_ar }}" dir="rtl"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Job Title" %}</label>
<input type="text" name="job_title" value="{{ staff.job_title }}"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Job Title (Arabic)" %}</label>
<input type="text" name="job_title_ar" value="{{ staff.job_title_ar }}" dir="rtl"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Email" %}</label>
<input type="email" name="email" value="{{ staff.email }}"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Phone" %}</label>
<input type="tel" name="phone" value="{{ staff.phone }}"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Specialization" %}</label>
<input type="text" name="specialization" value="{{ staff.specialization }}"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "License No." %}</label>
<input type="text" name="license_number" value="{{ staff.license_number|default:'' }}"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
</div>
</div>
<div>
<label class="block text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Gender" %}</label>
<select name="gender"
class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-navy focus:border-transparent">
<option value=""></option>
<option value="male" {% if staff.gender == 'male' %}selected{% endif %}>{% trans "Male" %}</option>
<option value="female" {% if staff.gender == 'female' %}selected{% endif %}>{% trans "Female" %}</option>
<option value="other" {% if staff.gender == 'other' %}selected{% endif %}>{% trans "Other" %}</option>
</select>
</div>
</div>
<div class="px-6 py-4 border-t border-slate-200 bg-slate-50 flex items-center justify-end gap-3">
<button type="button" onclick="closeEditStaffModal()"
class="px-4 py-2 text-sm text-slate-600 hover:text-slate-800 transition">{% trans "Cancel" %}</button>
<button type="submit"
class="px-5 py-2 bg-navy text-white text-sm font-semibold rounded-xl hover:bg-navy/90 transition flex items-center gap-1.5">
<i data-lucide="save" class="w-4 h-4"></i> {% trans "Save Changes" %}
</button>
</div>
</form>
</div>
</div>
{% endif %}
<!-- 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 "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 "Sent" as t_sent %}
{% trans "Visibility" as t_visibility %}
{% trans "Acknowledged" as t_acknowledged %}
var recordModalLabels = {
complaint: '{{ t_complaint|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 }}',
sent: '{{ t_sent|escapejs }}',
visibility: '{{ t_visibility|escapejs }}',
acknowledged: '{{ t_acknowledged|escapejs }}',
};
var recordApiUrls = {
complaint: '/organizations/api/dept-complaint/',
appreciation: '/organizations/api/dept-appreciation/',
};
var recordTypeConfig = {
complaint: { icon: 'alert-circle', color: 'red', bg: 'bg-red-50', text: 'text-red-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 === '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',
draft: 'bg-slate-100 text-slate-600', activated: 'bg-cyan-50 text-cyan-700',
sent: 'bg-green-50 text-green-700', acknowledged: 'bg-purple-50 text-purple-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');
}
function openEditStaffModal() {
document.getElementById('editStaffModal').classList.remove('hidden');
lucide.createIcons();
}
function closeEditStaffModal() {
document.getElementById('editStaffModal').classList.add('hidden');
}
</script>
{% endblock %}