907 lines
57 KiB
HTML
907 lines
57 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
{% load static %}
|
|
|
|
{% block title %}{{ observation.tracking_code }} - {% trans "Observation Detail" %} - PX360{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.tab-active {
|
|
border-bottom: 3px solid #005696;
|
|
color: #005696;
|
|
font-weight: 700;
|
|
}
|
|
.tab-inactive {
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
}
|
|
.tab-inactive:hover {
|
|
color: #005696;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
|
|
|
<header class="mb-6">
|
|
<div class="flex items-center gap-2 text-sm text-slate mb-2">
|
|
<a href="{% url 'observations:observation_list' %}" class="hover:text-navy">{% trans "Observations" %}</a>
|
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
|
<span class="font-bold text-navy">{{ observation.tracking_code }}</span>
|
|
<span class="px-2.5 py-1 rounded-full text-[10px] font-bold uppercase
|
|
{% if observation.status == 'open' %}bg-amber-100 text-amber-700
|
|
{% elif observation.status == 'in_progress' %}bg-blue-100 text-blue-700
|
|
{% elif observation.status == 'resolved' %}bg-green-100 text-green-700
|
|
{% elif observation.status == 'closed' %}bg-slate-100 text-slate-600
|
|
{% else %}bg-slate-100 text-slate-600{% endif %}">
|
|
{{ observation.get_status_display }}
|
|
</span>
|
|
<span class="px-2.5 py-1 rounded-full text-[10px] font-bold uppercase
|
|
{% if observation.severity == 'low' %}bg-green-100 text-green-700
|
|
{% elif observation.severity == 'medium' %}bg-yellow-100 text-yellow-700
|
|
{% elif observation.severity == 'high' %}bg-orange-100 text-orange-700
|
|
{% elif observation.severity == 'critical' %}bg-red-500 text-white
|
|
{% else %}bg-slate-100 text-slate-600{% endif %}">
|
|
{{ observation.get_severity_display }}
|
|
</span>
|
|
{% if observation.is_anonymous %}
|
|
<span class="px-2.5 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-500 italic">
|
|
<i data-lucide="eye-off" class="w-3 h-3 inline me-1"></i>{% trans "Anonymous" %}
|
|
</span>
|
|
{% endif %}
|
|
{% if observation.is_overdue %}
|
|
<span class="px-2.5 py-1 rounded-full text-[10px] font-bold uppercase bg-red-500 text-white">
|
|
<i data-lucide="clock" class="w-3 h-3 inline me-1"></i>{% trans "Overdue" %}
|
|
</span>
|
|
{% endif %}
|
|
{% if not observation.sent_to_department %}
|
|
<span class="px-2.5 py-1 rounded-full text-[10px] font-bold uppercase bg-amber-100 text-amber-700">
|
|
<i data-lucide="alert-circle" class="w-3 h-3 inline me-1"></i>{% trans "Not Sent to Dept" %}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<h1 class="text-2xl font-bold text-navy">
|
|
{% if observation.title %}{{ observation.title }}{% else %}{{ observation.description|truncatewords:10 }}{% endif %}
|
|
</h1>
|
|
{% comment %} <a href="{% url 'observations:observation_pdf' pk=observation.pk %}" target="_blank"
|
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-slate-200 rounded-lg text-xs font-semibold text-slate hover:border-navy hover:text-navy transition">
|
|
<i data-lucide="file-text" class="w-3.5 h-3.5"></i> {% trans "PDF" %}
|
|
</a> {% endcomment %}
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Workflow Stepper -->
|
|
{% include "core/workflow_stepper.html" %}
|
|
|
|
<nav class="bg-white px-6 flex gap-6 border-b shadow-sm mb-6 rounded-t-2xl">
|
|
<button class="py-4 text-sm tab-active" onclick="switchTab('details')" id="tab-details">{% trans "Details" %}</button>
|
|
<button class="py-4 text-sm tab-inactive" onclick="switchTab('timeline')" id="tab-timeline">{% trans "Timeline" %}</button>
|
|
{% comment %} <button class="py-4 text-sm tab-inactive" onclick="switchTab('attachments')" id="tab-attachments">{% trans "Attachments" %}</button> {% endcomment %}
|
|
{% if can_convert %}
|
|
<button class="py-4 text-sm tab-inactive" onclick="switchTab('rca')" id="tab-rca">{% trans "RCA" %}</button>
|
|
{% endif %}
|
|
<button class="py-4 text-sm tab-inactive" onclick="switchTab('notes')" id="tab-notes">
|
|
{% trans "Notes" %}
|
|
{% if generic_notes_count %}<span class="ml-1 px-1.5 py-0.5 bg-slate-100 text-slate-600 text-xs rounded-full">{{ generic_notes_count }}</span>{% endif %}
|
|
</button>
|
|
</nav>
|
|
|
|
<main class="grid grid-cols-12 gap-6">
|
|
<div class="col-span-8 space-y-6">
|
|
|
|
<div id="panel-details" class="tab-panel space-y-6">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<div class="bg-slate-50 p-4 rounded-xl border-l-4 border-blue">
|
|
<p class="text-sm leading-relaxed text-slate" style="white-space: pre-wrap;">{{ observation.description }}</p>
|
|
</div>
|
|
{% if observation.description_en %}
|
|
<div class="bg-slate-50 p-3 rounded-xl border-l-4 border-green-500 mt-3">
|
|
<p class="text-sm leading-relaxed text-slate" style="white-space: pre-wrap;">{{ observation.description_en }}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if observation.emotion or observation.short_description_en or observation.suggested_actions or observation.suggested_action_en %}
|
|
<div class="mt-4 pt-4 border-t border-slate-100">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<i data-lucide="sparkles" class="w-4 h-4 text-blue-600"></i>
|
|
<span class="text-[10px] font-bold text-slate uppercase">{% trans "AI Analysis" %}</span>
|
|
</div>
|
|
{% if observation.emotion and observation.emotion != 'neutral' %}
|
|
<div class="flex items-center gap-3 mb-2">
|
|
<span class="px-2.5 py-1 bg-navy/10 rounded-lg text-xs font-bold text-navy flex items-center gap-1.5">
|
|
<i data-lucide="frown" class="w-3 h-3"></i> {{ observation.emotion|title }}
|
|
</span>
|
|
<span class="text-xs text-slate">{% trans "Confidence" %}: {{ observation.emotion_confidence_percent|floatformat:0 }}%</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if observation.short_description_en %}
|
|
<p class="text-sm text-slate-700 mb-1">{{ observation.short_description_en }}</p>
|
|
{% if observation.short_description_ar %}
|
|
<p class="text-sm text-slate-700" dir="rtl">{{ observation.short_description_ar }}</p>
|
|
{% endif %}
|
|
{% elif observation.suggested_action_en %}
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
|
<p class="text-sm text-slate-700">{{ observation.suggested_action_en }}</p>
|
|
{% if observation.suggested_action_ar %}
|
|
<p class="text-sm text-slate-700 mt-1" dir="rtl">{{ observation.suggested_action_ar }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% elif observation.suggested_actions %}
|
|
<div class="space-y-2">
|
|
{% for action in observation.suggested_actions %}
|
|
<div class="flex items-center gap-2 p-2 bg-slate-50 rounded-lg border border-slate-200">
|
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase
|
|
{% if action.priority == 'high' %}bg-orange-100 text-orange-700
|
|
{% elif action.priority == 'medium' %}bg-yellow-100 text-yellow-700
|
|
{% else %}bg-green-100 text-green-700{% endif %}">
|
|
{{ action.priority }}
|
|
</span>
|
|
<p class="text-sm text-slate-700 truncate flex-1">{{ action.action_en }}</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mt-4 pt-4 border-t border-slate-100 grid grid-cols-4 gap-4">
|
|
<div>
|
|
<p class="text-[10px] font-bold text-slate uppercase">{% trans "Category" %}</p>
|
|
<p class="text-sm font-bold text-navy">{{ observation.category.get_localized_name|default:"-" }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-[10px] font-bold text-slate uppercase">{% trans "Location" %}</p>
|
|
<p class="text-sm font-bold text-navy">
|
|
{% if observation.legacy_location %}{{ observation.legacy_location.name_en }}{% elif observation.location_text %}{{ observation.location_text }}{% else %}-{% endif %}
|
|
</p>
|
|
{% if observation.legacy_main_section %}
|
|
<p class="text-xs text-slate">{{ observation.legacy_main_section.name_en }}{% if observation.legacy_subsection %} > {{ observation.legacy_subsection.name_en }}{% endif %}</p>
|
|
{% endif %}
|
|
</div>
|
|
<div>
|
|
<p class="text-[10px] font-bold text-slate uppercase">{% trans "Incident Date" %}</p>
|
|
<p class="text-sm font-bold text-navy">{{ observation.incident_datetime|date:"d M Y" }}</p>
|
|
</div>
|
|
<div>
|
|
{% comment %} <p class="text-[10px] font-bold text-slate uppercase">{% trans "Deadline" %}</p>
|
|
<p class="text-sm font-bold {% if observation.is_overdue %}text-red-600{% else %}text-navy{% endif %}">
|
|
{% if observation.due_at %}{{ observation.due_at|date:"d M Y" }}{% else %}-{% endif %}
|
|
{% if observation.is_overdue %}<i data-lucide="alert-triangle" class="w-3 h-3 inline ms-1"></i>{% endif %}
|
|
</p> {% endcomment %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if observation.taxonomy_domain or observation.taxonomy_category or observation.taxonomy_subcategory or observation.taxonomy_classification %}
|
|
<div class="mt-3 pt-3 border-t border-slate-100 flex items-center gap-2 flex-wrap">
|
|
<span class="text-[10px] font-bold text-slate uppercase">{% trans "Taxonomy" %}:</span>
|
|
{% if observation.taxonomy_domain %}<span class="text-sm text-navy font-medium">{{ observation.taxonomy_domain.name_en }}</span>{% endif %}
|
|
{% if observation.taxonomy_category %}<i data-lucide="chevron-right" class="w-3 h-3 text-slate-300"></i><span class="text-sm text-navy font-medium">{{ observation.taxonomy_category.name_en }}</span>{% endif %}
|
|
{% if observation.taxonomy_subcategory %}<i data-lucide="chevron-right" class="w-3 h-3 text-slate-300"></i><span class="text-sm text-navy font-medium">{{ observation.taxonomy_subcategory.name_en }}</span>{% endif %}
|
|
{% if observation.taxonomy_classification %}<i data-lucide="chevron-right" class="w-3 h-3 text-slate-300"></i><span class="text-sm text-navy font-medium">{{ observation.taxonomy_classification.name_en }}</span>{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mt-3 pt-3 border-t border-slate-100">
|
|
{% if observation.is_anonymous %}
|
|
<div class="flex items-center gap-2 text-sm text-slate">
|
|
<i data-lucide="eye-off" class="w-4 h-4"></i>
|
|
<span>{% trans "Anonymous submission" %}</span>
|
|
</div>
|
|
{% else %}
|
|
<div class="flex items-center gap-4 text-sm">
|
|
{% comment %} <i data-lucide="user" class="w-4 h-4 text-slate flex-shrink-0"></i>
|
|
{% if observation.reporter_name %}
|
|
<span class="font-bold text-navy">{{ observation.reporter_name }}</span>
|
|
{% endif %}
|
|
{% if observation.reporter_phone %}
|
|
<span class="text-slate">|</span>
|
|
<span class="text-slate">{{ observation.reporter_phone }}</span>
|
|
{% endif %}
|
|
{% if observation.reporter_email %}
|
|
<span class="text-slate">|</span>
|
|
<span class="text-slate">{{ observation.reporter_email }}</span>
|
|
{% endif %}
|
|
{% if not observation.reporter_name and not observation.reporter_phone and not observation.reporter_email %}
|
|
<span class="text-slate">{% trans "Reporter info not available" %}</span>
|
|
{% endif %} {% endcomment %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
{% if observation.sent_to_department and observation.assigned_department %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<div class="flex items-center gap-2 mb-4 pb-4 border-b
|
|
{% if observation.department_responded_at %}border-blue-200{% elif observation.dept_response_is_overdue %}border-red-200{% else %}border-amber-200{% endif %}">
|
|
<div class="w-10 h-10 rounded-xl flex items-center justify-center
|
|
{% if observation.department_responded_at %}bg-blue-100{% elif observation.dept_response_is_overdue %}bg-red-100{% else %}bg-amber-100{% endif %}">
|
|
<i data-lucide="building-2" class="w-5 h-5
|
|
{% if observation.department_responded_at %}text-blue-600{% elif observation.dept_response_is_overdue %}text-red-600{% else %}text-amber-600{% endif %}"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-navy">
|
|
{% blocktrans with dept=observation.assigned_department.get_localized_name|default:observation.assigned_department.name %}Response from {{ dept }}{% endblocktrans %}
|
|
</h3>
|
|
{% if observation.department_responded_at %}
|
|
<span class="text-xs text-navy"><i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i>{% trans "Received" %}</span>
|
|
{% elif observation.dept_response_is_overdue %}
|
|
<span class="text-xs text-red-600"><i data-lucide="alert-triangle" class="w-3 h-3 inline mr-1"></i>{% trans "OVERDUE" %}</span>
|
|
{% else %}
|
|
<span class="text-xs text-amber-600"><i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>{% trans "Awaiting" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
{% if observation.forwarded_to_dept_at %}
|
|
<div class="flex items-center gap-3 mb-4 text-sm text-slate-600">
|
|
<span class="flex items-center gap-1">
|
|
<i data-lucide="send" class="w-4 h-4"></i>
|
|
{% trans "Sent:" %} {{ observation.forwarded_to_dept_at|date:"Y-m-d H:i" }}
|
|
</span>
|
|
{% if observation.dept_response_sla_due_at and not observation.department_responded_at %}
|
|
<span>•</span>
|
|
<span class="flex items-center gap-1 {% if observation.dept_response_is_overdue %}text-red-600 font-semibold{% endif %}">
|
|
<i data-lucide="timer" class="w-4 h-4"></i>
|
|
{% trans "Deadline:" %} {{ observation.dept_response_sla_due_at|date:"Y-m-d H:i" }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if observation.department_responded_at %}
|
|
<div class="flex items-center gap-3 mb-4 text-sm text-slate-600">
|
|
<span class="flex items-center gap-1">
|
|
<i data-lucide="user" class="w-4 h-4"></i>
|
|
{{ observation.department_responded_by.get_full_name|default:"-" }}
|
|
</span>
|
|
<span>•</span>
|
|
<span class="flex items-center gap-1">
|
|
<i data-lucide="calendar" class="w-4 h-4"></i>
|
|
{{ observation.department_responded_at|date:"Y-m-d H:i" }}
|
|
</span>
|
|
</div>
|
|
{% if observation.department_response_en %}
|
|
<div class="mb-4">
|
|
<p class="text-[10px] font-bold text-slate uppercase mb-1">English</p>
|
|
<div class="prose prose-sm max-w-none bg-slate-50 rounded-xl p-4">
|
|
<p class="text-sm text-slate-700 leading-relaxed">{{ observation.department_response_en|linebreaks }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if observation.department_response_ar %}
|
|
<div class="mb-4">
|
|
<p class="text-[10px] font-bold text-slate uppercase mb-1">العربية</p>
|
|
<div class="prose prose-sm max-w-none bg-slate-50 rounded-xl p-4">
|
|
<p class="text-sm text-slate-700 leading-relaxed" dir="rtl">{{ observation.department_response_ar|linebreaks }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if observation.department_response_summary_en %}
|
|
<div class="mt-4 bg-light border border-blue-200 rounded-xl p-4">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<i data-lucide="sparkles" class="w-4 h-4 text-navy"></i>
|
|
<span class="text-sm font-bold text-navy">{% trans "AI Summary" %}</span>
|
|
</div>
|
|
<p class="text-sm text-slate-700 leading-relaxed">{{ observation.department_response_summary_en }}</p>
|
|
{% if observation.department_response_summary_ar %}
|
|
<p class="text-sm text-slate-700 leading-relaxed mt-2" dir="rtl">{{ observation.department_response_summary_ar }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="text-center py-6">
|
|
<div class="inline-block w-8 h-8 border-4 border-amber-200 border-t-amber-500 rounded-full animate-spin mb-3"></div>
|
|
<p class="text-slate text-sm">{% trans "Waiting for department response..." %}</p>
|
|
{% if observation.dept_response_sla_due_at %}
|
|
<p class="text-xs text-slate-400 mt-1">{% trans "Deadline:" %} {{ observation.dept_response_sla_due_at|date:"Y-m-d H:i" }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% if can_send_reminder %}
|
|
<div class="mt-3 flex gap-2">
|
|
<form method="post" action="{% url 'observations:observation_send_dept_response_reminder' observation.pk %}" class="flex-1">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="reminder_type" value="first">
|
|
<button type="submit" class="w-full bg-amber-500 text-white px-4 py-2 rounded-xl font-semibold hover:bg-amber-600 transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="bell" class="w-4 h-4"></i> {% trans "Send Reminder" %}
|
|
</button>
|
|
</form>
|
|
{% if observation.dept_response_reminder_sent_at %}
|
|
<form method="post" action="{% url 'observations:observation_send_dept_response_reminder' observation.pk %}" class="flex-1">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="reminder_type" value="second">
|
|
<button type="submit" class="w-full bg-red-500 text-white px-4 py-2 rounded-xl font-semibold hover:bg-red-600 transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4"></i> {% trans "Urgent Reminder" %}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% if can_respond_to_department %}
|
|
<div class="mt-3">
|
|
<button type="button"
|
|
onclick="openDeptResponseModal('observation', '{{ observation.pk }}', '{% url 'observations:observation_department_response' observation.pk %}', '{{ observation.tracking_code }}', '{% if observation.title %}{{ observation.title|escapejs }}{% else %}{{ observation.description|truncatewords:10|escapejs }}{% endif %}', '{{ observation.department_response_en|default:'' }}', '{{ observation.department_response_ar|default:'' }}')"
|
|
class="w-full bg-navy text-white px-4 py-2.5 rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="message-square" class="w-4 h-4"></i>
|
|
{% if observation.department_responded_at %}{% trans "Update Response" %}{% else %}{% trans "Submit Response" %}{% endif %}
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
{% else %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<div class="text-center py-12">
|
|
<i data-lucide="building-2" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
|
|
<p class="text-slate text-sm">{% trans "No department assigned to this observation" %}</p>
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if observation.responded_at and observation.response %}
|
|
<section class="bg-white rounded-2xl p-4 shadow-sm border border-slate-100">
|
|
<h3 class="text-lg font-bold text-navy mb-4">{% trans "Response to Reporter" %}</h3>
|
|
<div class="bg-emerald-50 border border-emerald-200 rounded-xl p-4">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<i data-lucide="check-circle" class="w-5 h-5 text-emerald-600"></i>
|
|
<span class="font-bold text-emerald-800">{% trans "Response Sent" %}</span>
|
|
{% if observation.responded_by %}
|
|
<span class="text-xs text-slate ml-2">— {{ observation.responded_by.get_full_name }} • {{ observation.responded_at|date:"M d, Y H:i" }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="bg-white/60 rounded-lg p-3">
|
|
<p class="text-sm text-slate-700 leading-relaxed">{{ observation.response|linebreaks }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div id="panel-timeline" class="tab-panel hidden">
|
|
{% include "observations/partials/observation_timeline_panel.html" %}
|
|
</div>
|
|
|
|
<div id="panel-attachments" class="tab-panel hidden">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 flex items-center gap-2">
|
|
<i data-lucide="paperclip" class="w-5 h-5 text-blue"></i>
|
|
{% trans "Attachments" %} {% if attachments %}({{ attachments.count }}){% endif %}
|
|
</h3>
|
|
{% if attachments %}
|
|
<div class="space-y-3">
|
|
{% for attachment in attachments %}
|
|
<div class="flex items-center justify-between p-4 bg-slate-50 border border-slate-100 rounded-xl">
|
|
<div class="flex items-center gap-3">
|
|
<i data-lucide="file" class="w-5 h-5 text-blue"></i>
|
|
<div>
|
|
<div class="font-semibold text-gray-800">{{ attachment.filename }}</div>
|
|
<div class="text-xs text-slate">{{ attachment.file_type }} - {{ attachment.file_size|filesizeformat }}</div>
|
|
</div>
|
|
</div>
|
|
<a href="{{ attachment.file.url }}" target="_blank" class="p-2 rounded-lg bg-navy text-white hover:bg-blue transition">
|
|
<i data-lucide="download" class="w-4 h-4"></i>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<i data-lucide="paperclip" class="w-12 h-12 mx-auto mb-3 text-gray-300"></i>
|
|
<p class="text-slate text-sm">{% trans "No attachments" %}</p>
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
|
|
|
|
<div id="panel-rca" class="tab-panel hidden">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 flex items-center gap-2">
|
|
<i data-lucide="search" class="w-5 h-5 text-purple-600"></i>
|
|
{% trans "Root Cause Analyses" %}
|
|
</h3>
|
|
<div class="space-y-3">
|
|
{% if linked_rcas %}
|
|
{% for rca in linked_rcas %}
|
|
<a href="{% url 'rca:rca_detail' pk=rca.pk %}" class="block p-3 rounded-lg border border-slate-200 hover:border-blue-300 transition group">
|
|
<p class="text-sm font-bold text-navy group-hover:text-blue truncate">{{ rca.title }}</p>
|
|
<div class="flex items-center gap-2 mt-1">
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase
|
|
{% if rca.status == 'draft' %}bg-slate-100 text-slate-600
|
|
{% elif rca.status == 'in_progress' %}bg-blue-100 text-blue-700
|
|
{% elif rca.status == 'review' %}bg-yellow-100 text-yellow-700
|
|
{% elif rca.status == 'approved' %}bg-green-100 text-green-700
|
|
{% elif rca.status == 'closed' %}bg-gray-100 text-gray-600{% endif %}">
|
|
{{ rca.get_status_display }}
|
|
</span>
|
|
<span class="text-[10px] text-slate">{{ rca.created_at|date:"M d, Y" }}</span>
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<i data-lucide="search" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
|
|
<p class="text-slate text-sm">{% trans "No Root Cause Analyses linked to this observation" %}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div id="panel-notes" class="tab-panel hidden">
|
|
{% include "partials/notes_panel.html" with notes=generic_notes notes_count=generic_notes_count %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-4 space-y-6">
|
|
{% if observation.status not in 'resolved,closed' %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm">{% trans "Quick Actions" %}</h3>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
{% if observation.status == 'open' and not observation.activated_at %}
|
|
{% if can_convert %}
|
|
<form method="post" action="{% url 'observations:observation_activate' observation.id %}" class="contents">
|
|
{% csrf_token %}
|
|
<button type="submit"
|
|
class="col-span-2 p-3 border-blue-200 bg-blue-50 rounded-xl hover:bg-blue-100 flex items-center justify-center gap-2 group transition">
|
|
<i data-lucide="play-circle" class="w-5 h-5 text-blue"></i>
|
|
<span class="text-[10px] font-bold text-blue uppercase">{% trans "Activate" %}</span>
|
|
</button>
|
|
</form>
|
|
<form method="post" action="{% url 'observations:observation_change_status' observation.id %}" class="col-span-2 contents" onsubmit="return confirm('{% trans "Are you sure you want to cancel this observation?" %}')">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="status" value="closed">
|
|
<button type="submit" class="col-span-2 p-3 border-red-200 bg-red-50 rounded-xl hover:bg-red-100 flex items-center justify-center gap-2 group transition">
|
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500"></i>
|
|
<span class="text-[10px] font-bold text-red-600 uppercase">{% trans "Cancel Observation" %}</span>
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="col-span-2 bg-yellow-50 border border-yellow-200 rounded-xl p-3 text-center">
|
|
<i data-lucide="lock" class="w-4 h-4 text-yellow-600 mx-auto mb-1"></i>
|
|
<p class="text-xs text-yellow-700">{% trans "Activate this observation to perform actions" %}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
{% if can_convert and observation.status not in 'closed,cancelled' %}
|
|
<button onclick="showRespondModal()" class="col-span-2 p-3 border-navy bg-navy text-white rounded-xl hover:bg-blue transition flex items-center justify-center gap-2 group">
|
|
<i data-lucide="message-square" class="w-5 h-5"></i>
|
|
<span class="text-[10px] font-bold uppercase">{% trans "Respond to Patient" %}</span>
|
|
</button>
|
|
{% endif %}
|
|
{% if can_convert %}
|
|
<button onclick="document.getElementById('obsAssignForm').classList.toggle('hidden')" class="p-3 border rounded-xl hover:bg-light flex flex-col items-center gap-2 group transition">
|
|
<i data-lucide="user-plus" class="w-5 h-5 text-slate group-hover:text-blue"></i>
|
|
<span class="text-[10px] font-bold uppercase">{% if observation.assigned_to %}{% trans "Reassign" %}{% else %}{% trans "Assign" %}{% endif %}</span>
|
|
</button>
|
|
{% endif %}
|
|
{% if can_admin and not observation.action_id %}
|
|
<a href="{% url 'observations:observation_convert_to_action' observation.id %}" class="p-3 border-green-200 bg-green-50 rounded-xl hover:bg-green-100 flex flex-col items-center gap-2 group transition">
|
|
<i data-lucide="arrow-right-circle" class="w-5 h-5 text-green-600"></i>
|
|
<span class="text-[10px] font-bold text-green-700 uppercase">{% trans "Convert To Action" %}</span>
|
|
</a>
|
|
{% endif %}
|
|
{% if can_send_to_department and not observation.department_responded_at %}
|
|
<button onclick="showSendModal('{{ observation.id }}', 'observation')" class="p-3 border-indigo-200 bg-indigo-50 rounded-xl hover:bg-indigo-100 flex items-center justify-center gap-2 group transition">
|
|
<i data-lucide="send" class="w-5 h-5 text-indigo-600"></i>
|
|
<span class="text-[10px] font-bold text-indigo-700 uppercase">{% trans "Send to Dept" %}</span>
|
|
</button>
|
|
{% endif %}
|
|
{% if can_convert and observation.status == 'in_progress' %}
|
|
<form method="post" action="{% url 'observations:observation_change_status' observation.id %}" class="contents" onsubmit="return confirm('{% trans "Mark this observation as resolved?" %}')">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="status" value="resolved">
|
|
<button type="submit" class="p-3 border-green-200 bg-green-50 rounded-xl hover:bg-green-100 flex items-center justify-center gap-2 group transition">
|
|
<i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i>
|
|
<span class="text-[10px] font-bold text-green-700 uppercase">{% trans "Resolve" %}</span>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% comment %} {% if can_triage and observation.status != 'closed' and observation.status != 'cancelled' and observation.status != 'rejected' %}
|
|
<button onclick="document.getElementById('escalateModal').style.display='flex'" class="p-3 border-red-200 bg-red-50 rounded-xl hover:bg-red-100 flex flex-col items-center gap-2 group transition">
|
|
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-500"></i>
|
|
<span class="text-[10px] font-bold text-red-600 uppercase">{% trans "Escalate" %}</span>
|
|
</button>
|
|
{% endif %} {% endcomment %}
|
|
{% if can_convert %}
|
|
<a href="{% url 'rca:rca_create' %}?related_model=observation&related_id={{ observation.pk }}" class="p-3 border rounded-xl hover:bg-light flex flex-col items-center gap-2 group transition">
|
|
<i data-lucide="search" class="w-5 h-5 text-slate group-hover:text-purple-600"></i>
|
|
<span class="text-[10px] font-bold uppercase">{% trans "RCA" %}</span>
|
|
</a>
|
|
{% endif %}
|
|
{% if can_admin %}
|
|
<a href="{% url 'projects:project_create' %}?related_model=observation&related_id={{ observation.pk }}" class="p-3 border rounded-xl hover:bg-light flex flex-col items-center gap-2 group transition">
|
|
<i data-lucide="folder-plus" class="w-5 h-5 text-slate group-hover:text-teal-600"></i>
|
|
<span class="text-[10px] font-bold uppercase">{% trans "QI Project" %}</span>
|
|
</a>
|
|
{% endif %}
|
|
{% if observation.status == 'resolved' or observation.status == 'closed' %}
|
|
{% if can_triage %}
|
|
<form method="post" action="{% url 'observations:observation_reopen' observation.id %}" class="col-span-2 contents">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="note" value="Observation reopened">
|
|
<button type="submit" class="col-span-2 p-3 border-amber-200 bg-amber-50 rounded-xl hover:bg-amber-100 flex items-center justify-center gap-2 group transition">
|
|
<i data-lucide="rotate-ccw" class="w-5 h-5 text-amber-600"></i>
|
|
<span class="text-[10px] font-bold text-amber-700 uppercase">{% trans "Reopen" %}</span>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if can_delete %}
|
|
<form method="post" action="{% url 'observations:observation_soft_delete' observation.id %}" id="obs-delete-form" class="col-span-2 contents">
|
|
{% csrf_token %}
|
|
<button type="button" onclick="if(confirm('{% trans "Are you sure you want to delete this observation?" %}'))document.getElementById('obs-delete-form').submit()"
|
|
class="col-span-2 p-3 border-red-200 bg-red-50 rounded-xl hover:bg-red-100 flex items-center justify-center gap-2 group transition">
|
|
<i data-lucide="trash-2" class="w-5 h-5 text-red-500"></i>
|
|
<span class="text-[10px] font-bold text-red-600 uppercase">{% trans "Delete" %}</span>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if can_convert %}
|
|
<section id="obsAssignForm" class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100 hidden">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="user-plus" class="w-4 h-4"></i>
|
|
{% if observation.assigned_to %}{% trans "Reassign" %}{% else %}{% trans "Assign" %}{% endif %}
|
|
</h3>
|
|
<form method="post" action="{% url 'observations:observation_assign' observation.id %}">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<select name="user_id" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none" required>
|
|
<option value="">{% trans "Select User" %}</option>
|
|
{% for u in assignable_users %}
|
|
<option value="{{ u.id }}" {% if observation.assigned_to and observation.assigned_to.id == u.id %}selected{% endif %}>{{ u.get_full_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="w-full px-4 py-2.5 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition text-sm inline-flex items-center justify-center gap-2">
|
|
<i data-lucide="user-plus" class="w-4 h-4"></i>
|
|
{% if observation.assigned_to %}{% trans "Reassign" %}{% else %}{% trans "Assign" %}{% endif %}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="users" class="w-4 h-4"></i> {% trans "Assignment" %}
|
|
</h3>
|
|
<ul class="space-y-3 text-[11px]">
|
|
<li class="flex justify-between border-b border-slate-100 pb-2">
|
|
<span class="text-slate font-semibold">{% trans "Department" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.assigned_department.get_localized_name|default:_("Not assigned") }}</span>
|
|
</li>
|
|
<li class="flex justify-between">
|
|
<span class="text-slate font-semibold">{% trans "Assigned To" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.assigned_to.get_full_name|default:_("Not assigned") }}</span>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
{% if observation.person_noted or observation.department_noted or observation.communication_method or observation.communication_datetime or observation.patient_file_number %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="phone-call" class="w-4 h-4"></i> {% trans "Communication" %}
|
|
</h3>
|
|
<ul class="space-y-3 text-[11px]">
|
|
{% if observation.patient_file_number %}
|
|
<li class="flex justify-between border-b border-slate-100 pb-2">
|
|
<span class="text-slate font-semibold">{% trans "File Number" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.patient_file_number }}</span>
|
|
</li>
|
|
{% endif %}
|
|
{% if observation.person_noted %}
|
|
<li class="flex justify-between border-b border-slate-100 pb-2">
|
|
<span class="text-slate font-semibold">{% trans "Person Noted" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.person_noted }}</span>
|
|
</li>
|
|
{% endif %}
|
|
{% if observation.department_noted %}
|
|
<li class="flex justify-between border-b border-slate-100 pb-2">
|
|
<span class="text-slate font-semibold">{% trans "Department Noted" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.department_noted.get_localized_name }}</span>
|
|
</li>
|
|
{% endif %}
|
|
{% if observation.communication_method %}
|
|
<li class="flex justify-between border-b border-slate-100 pb-2">
|
|
<span class="text-slate font-semibold">{% trans "Via" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.communication_method }}</span>
|
|
</li>
|
|
{% endif %}
|
|
{% if observation.communication_datetime %}
|
|
<li class="flex justify-between">
|
|
<span class="text-slate font-semibold">{% trans "Contacted At" %}</span>
|
|
<span class="text-navy font-bold">{{ observation.communication_datetime|date:"M d, Y H:i" }}</span>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if px_action %}
|
|
<section class="bg-navy rounded-2xl p-5 shadow-lg text-white">
|
|
<h3 class="font-bold flex items-center gap-2 mb-3">
|
|
<i data-lucide="link" class="w-4 h-4"></i> {% trans "Linked PX Action" %}
|
|
</h3>
|
|
<p class="text-sm opacity-90 mb-3">{{ px_action.title|truncatewords:10 }}</p>
|
|
<a href="{% url 'actions:action_detail' px_action.id %}" class="inline-flex items-center gap-2 px-4 py-2 bg-white text-navy rounded-xl font-semibold text-sm hover:bg-gray-100 transition">
|
|
<i data-lucide="arrow-right" class="w-4 h-4"></i> {% trans "View Action" %}
|
|
</a>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if can_triage %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="sliders-horizontal" class="w-4 h-4"></i> {% trans "Triage" %}
|
|
</h3>
|
|
<form method="post" action="{% url 'observations:observation_triage' observation.id %}">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label class="block text-[10px] font-bold text-slate uppercase tracking-wider mb-2">{% trans "Department" %}</label>
|
|
{{ triage_form.assigned_department }}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="block text-[10px] font-bold text-slate uppercase tracking-wider mb-2">{% trans "Assign To" %}</label>
|
|
{{ triage_form.assigned_to }}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="block text-[10px] font-bold text-slate uppercase tracking-wider mb-2">{% trans "Status" %}</label>
|
|
{{ triage_form.status }}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="block text-[10px] font-bold text-slate uppercase tracking-wider mb-2">{% trans "Note" %}</label>
|
|
{{ triage_form.note }}
|
|
</div>
|
|
<button type="submit" class="w-full px-4 py-2.5 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition text-sm inline-flex items-center justify-center gap-2">
|
|
<i data-lucide="check-circle" class="w-4 h-4"></i> {% trans "Update" %}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
{% endif %}
|
|
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
function switchTab(tabName) {
|
|
document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.add('hidden'));
|
|
document.getElementById('panel-' + tabName).classList.remove('hidden');
|
|
document.querySelectorAll('main nav button').forEach(tab => {
|
|
tab.classList.remove('tab-active');
|
|
tab.classList.add('tab-inactive');
|
|
});
|
|
document.getElementById('tab-' + tabName).classList.remove('tab-inactive');
|
|
document.getElementById('tab-' + tabName).classList.add('tab-active');
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
|
|
function switchObsRecipientType(type) {
|
|
const staffLabel = document.getElementById('obsRecipientLabelStaff');
|
|
const deptEmailLabel = document.getElementById('obsRecipientLabelDeptEmail');
|
|
const hint = document.getElementById('obsDeptEmailHint');
|
|
if (type === 'staff') {
|
|
staffLabel.classList.add('border-indigo-500', 'bg-indigo-50', 'text-indigo-700', 'font-semibold');
|
|
staffLabel.classList.remove('border-slate-200', 'text-slate-500');
|
|
deptEmailLabel.classList.remove('border-indigo-500', 'bg-indigo-50', 'text-indigo-700', 'font-semibold');
|
|
deptEmailLabel.classList.add('border-slate-200', 'text-slate-500');
|
|
if (hint) hint.style.display = 'none';
|
|
} else {
|
|
deptEmailLabel.classList.add('border-indigo-500', 'bg-indigo-50', 'text-indigo-700', 'font-semibold');
|
|
deptEmailLabel.classList.remove('border-slate-200', 'text-slate-500');
|
|
staffLabel.classList.remove('border-indigo-500', 'bg-indigo-50', 'text-indigo-700', 'font-semibold');
|
|
staffLabel.classList.add('border-slate-200', 'text-slate-500');
|
|
if (hint) hint.style.display = '';
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
});
|
|
|
|
function showRespondModal() {
|
|
document.getElementById('respondModal').style.display = 'flex';
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).style.display = 'none';
|
|
}
|
|
|
|
function generateObsAIResponse() {
|
|
const btn = document.getElementById('generateAiBtn');
|
|
const suggestionsDiv = document.getElementById('aiSuggestions');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="inline-block w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"></span> {% trans "Generating..." %}';
|
|
suggestionsDiv.classList.add('hidden');
|
|
fetch('{% url "observations:observation_generate_ai_response" observation.pk %}', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCSRFToken() },
|
|
credentials: 'same-origin'
|
|
})
|
|
.then(response => { if (!response.ok) return response.json().then(data => { throw new Error(data.error || 'Request failed'); }); return response.json(); })
|
|
.then(data => {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i data-lucide="sparkles" class="w-4 h-4"></i> {% trans "Generate AI Response" %}';
|
|
lucide.createIcons();
|
|
if (data.success) {
|
|
document.getElementById('aiSuggestionEnText').textContent = data.response_en;
|
|
document.getElementById('aiSuggestionArText').textContent = data.response_ar;
|
|
suggestionsDiv.classList.remove('hidden');
|
|
} else { alert(data.error || '{% trans "Failed to generate response" %}'); }
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i data-lucide="sparkles" class="w-4 h-4"></i> {% trans "Generate AI Response" %}';
|
|
lucide.createIcons();
|
|
alert(error.message || '{% trans "An error occurred while generating response" %}');
|
|
});
|
|
}
|
|
|
|
function useObsAISuggestion(lang) {
|
|
var text = '';
|
|
var card = null;
|
|
if (lang === 'en') {
|
|
text = document.getElementById('aiSuggestionEnText').textContent;
|
|
card = document.getElementById('aiSuggestionEn');
|
|
} else {
|
|
text = document.getElementById('aiSuggestionArText').textContent;
|
|
card = document.getElementById('aiSuggestionAr');
|
|
}
|
|
document.getElementById('responseText').value = text;
|
|
card.classList.add('border-navy', 'bg-navy/5');
|
|
setTimeout(() => card.classList.remove('border-navy', 'bg-navy/5'), 1500);
|
|
}
|
|
</script>
|
|
|
|
<!-- Respond to Reporter Modal -->
|
|
<div id="respondModal" style="display:none" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto">
|
|
<div class="p-6 border-b border-slate-200">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-xl font-bold text-navy flex items-center gap-2">
|
|
<i data-lucide="message-square" class="w-5 h-5"></i>
|
|
{% trans "Send Response to Reporter" %}
|
|
</h3>
|
|
<button type="button" onclick="closeModal('respondModal')" class="text-slate-400 hover:text-slate-600 transition">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<form method="post" action="{% url 'observations:observation_respond' observation.pk %}" id="respondForm">
|
|
{% csrf_token %}
|
|
<div class="p-6">
|
|
{% if observation.short_description_en or observation.short_description_ar %}
|
|
<div class="bg-light/50 border border-slate-200 rounded-2xl p-4 mb-5">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<i data-lucide="sparkles" class="w-4 h-4 text-navy"></i>
|
|
<span class="text-sm font-bold text-navy">{% trans "AI Summary" %}</span>
|
|
</div>
|
|
{% if observation.short_description_en %}
|
|
<p class="text-sm text-slate-700 leading-relaxed">{{ observation.short_description_en }}</p>
|
|
{% endif %}
|
|
{% if observation.short_description_ar %}
|
|
<p class="text-sm text-slate-700 leading-relaxed mt-2" dir="rtl">{{ observation.short_description_ar }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mb-5">
|
|
<button type="button" id="generateAiBtn" onclick="generateObsAIResponse()" class="w-full inline-flex items-center justify-center gap-2 px-4 py-3 bg-navy text-white rounded-xl font-bold hover:bg-blue transition text-sm shadow-lg">
|
|
<i data-lucide="sparkles" class="w-4 h-4"></i>
|
|
{% trans "Generate AI Response" %}
|
|
</button>
|
|
</div>
|
|
|
|
<div id="aiSuggestions" class="hidden mb-5 space-y-3">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<i data-lucide="sparkles" class="w-4 h-4 text-navy"></i>
|
|
<span class="text-sm font-semibold text-navy">{% trans "AI Generated Response (click to use)" %}</span>
|
|
</div>
|
|
<div id="aiSuggestionEn" onclick="useObsAISuggestion('en')" class="border-2 border-slate-200 rounded-xl p-3 cursor-pointer hover:border-navy transition">
|
|
<p class="text-[10px] font-bold text-slate uppercase mb-1">English</p>
|
|
<p class="text-sm text-slate-700" id="aiSuggestionEnText"></p>
|
|
</div>
|
|
<div id="aiSuggestionAr" onclick="useObsAISuggestion('ar')" class="border-2 border-slate-200 rounded-xl p-3 cursor-pointer hover:border-navy transition">
|
|
<p class="text-[10px] font-bold text-slate uppercase mb-1">العربية</p>
|
|
<p class="text-sm text-slate-700" dir="rtl" id="aiSuggestionArText"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-navy mb-2">{% trans "Your Response" %} <span class="text-red-500">*</span></label>
|
|
<textarea name="response" id="responseText" rows="8"
|
|
class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 resize-none text-sm"
|
|
placeholder="{% trans "Enter your response to the reporter..." %}" required>{{ observation.response|default:'' }}</textarea>
|
|
</div>
|
|
|
|
<p class="text-xs text-slate-400 mt-1">
|
|
<i data-lucide="info" class="w-3 h-3 inline mr-1"></i>
|
|
{% trans "At least one language is required. The response will be visible to the reporter on the tracking page." %}
|
|
</p>
|
|
</div>
|
|
<div class="p-6 border-t border-slate-200 flex gap-3">
|
|
<button type="submit" class="flex-1 px-4 py-2.5 bg-navy text-white rounded-xl font-semibold hover:bg-navy/90 transition text-sm inline-flex items-center justify-center gap-2">
|
|
<i data-lucide="send" class="w-4 h-4"></i>
|
|
{% if observation.responded_at %}{% trans "Update Response" %}{% else %}{% trans "Send Response" %}{% endif %}
|
|
</button>
|
|
<button type="button" onclick="closeModal('respondModal')" class="px-4 py-2.5 bg-white border-2 border-slate-200 rounded-xl font-semibold text-slate-600 hover:bg-slate-50 transition text-sm">
|
|
{% trans "Cancel" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Escalate Observation Modal -->
|
|
<div id="escalateModal" style="display:none" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
|
|
<div class="bg-white rounded-2xl p-6 w-full max-w-2xl mx-4 shadow-2xl max-h-[90vh] overflow-y-auto">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 bg-red-100 rounded-full flex items-center justify-center">
|
|
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-500"></i>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-navy">{% trans "Escalate Observation" %}</h3>
|
|
</div>
|
|
<form method="post" action="{% url 'observations:observation_escalate' observation.pk %}">
|
|
{% csrf_token %}
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Escalate To" %} <span class="text-red-500">*</span></label>
|
|
<select name="escalate_to" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none" required>
|
|
<option value="">{% trans "Select Person" %}</option>
|
|
{% regroup escalation_targets by group as target_groups %}
|
|
{% for group in target_groups %}
|
|
{% if group.grouper == 'department_roles' %}
|
|
<optgroup label="{% trans 'Department Roles' %}">
|
|
{% else %}
|
|
<optgroup label="{% trans 'Managers & Admins' %}">
|
|
{% endif %}
|
|
{% for target in group.list %}
|
|
<option value="{{ target.staff.id }}">
|
|
{{ target.staff.get_full_name }}{% if target.role_label %} ({{ target.role_label }}){% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</optgroup>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Reason" %}</label>
|
|
<textarea name="reason" rows="3" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none" placeholder="{% trans 'Reason for escalation...' %}"></textarea>
|
|
</div>
|
|
<div class="border-t border-slate-100 pt-4 mb-4">
|
|
<p class="text-xs font-bold text-slate uppercase mb-3">{% trans "Email Preview" %} <span class="text-slate-400 font-normal normal-case">({% trans "editable" %})</span></p>
|
|
<div class="mb-3">
|
|
<label class="block text-xs font-semibold text-slate mb-1">{% trans "Subject" %}</label>
|
|
<input type="text" name="email_subject" value="{{ escalation_email_subject }}"
|
|
class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate mb-1">{% trans "Body" %}</label>
|
|
<textarea name="email_body" rows="8"
|
|
class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none font-mono whitespace-pre">{{ escalation_email_body }}</textarea>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-3">
|
|
<button type="button" onclick="document.getElementById('escalateModal').style.display='none'" 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-red-500 text-white rounded-xl font-semibold hover:bg-red-600 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="send" class="w-4 h-4"></i> {% trans "Send Escalation Email" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% include "components/send_to_modal.html" with users=assignable_users departments=departments %}
|
|
{% include "components/department_response_modal.html" %}
|
|
|
|
{% endblock %}
|