All checks were successful
Build and Push Docker Image / build (push) Successful in 4m14s
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.
185 lines
9.8 KiB
HTML
185 lines
9.8 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Manager Review" %} - {{ complaint.reference_number }} - PX360{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="p-6 max-w-4xl 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>
|
|
<span class="text-navy font-semibold">{% trans "Manager Review" %}</span>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3 mb-6">
|
|
<div class="w-10 h-10 bg-amber-50 rounded-xl flex items-center justify-center">
|
|
<i data-lucide="shield-check" class="w-5 h-5 text-amber-600"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-xl font-bold text-navy">{% trans "Review Champion Response" %}</h1>
|
|
<p class="text-sm text-slate-500">{{ complaint.reference_number }} · {{ department.get_localized_name }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-6">
|
|
{% if complaint.title %}
|
|
<div class="bg-white rounded-2xl border border-slate-200 p-6">
|
|
<h3 class="text-sm font-bold text-navy mb-2 flex items-center gap-2">
|
|
<i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Complaint" %}
|
|
</h3>
|
|
<p class="text-sm text-slate-700">{{ complaint.title }}</p>
|
|
{% if complaint.description %}
|
|
<p class="text-sm text-slate-500 mt-2 whitespace-pre-wrap">{{ complaint.description }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="bg-white rounded-2xl border border-slate-200 p-6">
|
|
<h3 class="text-sm font-bold text-navy mb-3 flex items-center gap-2">
|
|
<i data-lucide="message-square" class="w-4 h-4"></i> {% trans "Champion Response" %}
|
|
</h3>
|
|
{% if involved_dept.response_notes_en %}
|
|
<div class="bg-slate-50 rounded-xl p-4 border border-slate-200 mb-3">
|
|
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "English" %}</p>
|
|
<p class="text-sm text-slate-700 whitespace-pre-wrap">{{ involved_dept.response_notes_en }}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% if involved_dept.response_notes_ar %}
|
|
<div class="bg-slate-50 rounded-xl p-4 border border-slate-200">
|
|
<p class="text-[10px] text-slate-400 uppercase font-bold mb-1">{% trans "Arabic" %}</p>
|
|
<p class="text-sm text-slate-700 whitespace-pre-wrap" dir="rtl">{{ involved_dept.response_notes_ar }}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% if involved_dept.response_submitted_at %}
|
|
<p class="text-xs text-slate-400 mt-2">
|
|
{% trans "Submitted" %}: {{ involved_dept.response_submitted_at|date:"Y-m-d H:i" }}
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<form method="post" id="managerReviewForm">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="review_action" id="reviewAction" value="">
|
|
|
|
{% if questions %}
|
|
<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="clipboard-list" class="w-4 h-4"></i> {% trans "Review Questions" %}
|
|
</h3>
|
|
<div class="space-y-5">
|
|
{% for q in questions %}
|
|
<div class="border-b border-slate-100 pb-4 last:border-0 last:pb-0">
|
|
<label class="block text-sm font-semibold text-slate-700 mb-2">
|
|
{{ q.get_localized_text }}
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
|
|
{% if q.question_type == 'text' %}
|
|
<input type="text" name="question_{{ q.pk }}" required
|
|
class="w-full px-4 py-2.5 border-2 border-slate-200 rounded-xl text-sm focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20">
|
|
|
|
{% elif q.question_type == 'textarea' %}
|
|
<textarea name="question_{{ q.pk }}" rows="3" required
|
|
class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl text-sm focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 resize-none"></textarea>
|
|
|
|
{% elif q.question_type == 'yes_no' %}
|
|
<div class="flex gap-4 mt-1">
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="radio" name="question_{{ q.pk }}" value="yes" required
|
|
class="w-4 h-4 text-navy border-slate-300 focus:ring-navy">
|
|
<span class="text-sm text-slate-700">{% trans "Yes" %}</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="radio" name="question_{{ q.pk }}" value="no"
|
|
class="w-4 h-4 text-navy border-slate-300 focus:ring-navy">
|
|
<span class="text-sm text-slate-700">{% trans "No" %}</span>
|
|
</label>
|
|
</div>
|
|
|
|
{% elif q.question_type == 'rating' %}
|
|
<div class="flex gap-2 mt-1">
|
|
{% for i in "12345" %}
|
|
<label class="cursor-pointer">
|
|
<input type="radio" name="question_{{ q.pk }}" value="{{ i }}" required
|
|
class="hidden peer">
|
|
<div class="w-10 h-10 rounded-lg border-2 border-slate-200 flex items-center justify-center text-sm font-bold text-slate-400 peer-checked:bg-navy peer-checked:text-white peer-checked:border-navy transition hover:border-navy">
|
|
{{ i }}
|
|
</div>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% elif q.question_type == 'multiple_choice' %}
|
|
<div class="space-y-2 mt-1">
|
|
{% for choice in q.choices_json %}
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="radio" name="question_{{ q.pk }}" value="{{ choice }}" required
|
|
class="w-4 h-4 text-navy border-slate-300 focus:ring-navy">
|
|
<span class="text-sm text-slate-700">{{ choice }}</span>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div id="rejectionReasonBox" class="hidden bg-white rounded-2xl border border-red-200 p-6">
|
|
<h3 class="text-sm font-bold text-red-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="alert-circle" class="w-4 h-4"></i> {% trans "Rejection Reason" %}
|
|
</h3>
|
|
<textarea name="rejection_reason" id="rejectionReason" rows="3"
|
|
class="w-full px-4 py-3 border-2 border-red-200 rounded-xl text-sm focus:outline-none focus:border-red-400 focus:ring-2 focus:ring-red-200/40 resize-none"
|
|
placeholder="{% trans 'Please explain why you are rejecting this response...' %}"></textarea>
|
|
</div>
|
|
|
|
<div class="flex gap-3 pt-2">
|
|
<button type="button" onclick="submitReview('approve')"
|
|
class="flex-1 px-4 py-3 bg-green-600 text-white font-semibold rounded-xl hover:bg-green-700 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="check-circle" class="w-5 h-5"></i> {% trans "Approve & Forward to PX Team" %}
|
|
</button>
|
|
<button type="button" onclick="handleReject()"
|
|
class="flex-1 px-4 py-3 bg-red-600 text-white font-semibold rounded-xl hover:bg-red-700 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="x-circle" class="w-5 h-5"></i> {% trans "Reject & Return to Champion" %}
|
|
</button>
|
|
<a href="{% url 'organizations:department_detail' department.pk %}"
|
|
class="px-4 py-3 bg-slate-100 text-slate-700 font-semibold rounded-xl hover:bg-slate-200 transition text-center">
|
|
{% trans "Cancel" %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function submitReview(action) {
|
|
document.getElementById('reviewAction').value = action;
|
|
document.getElementById('managerReviewForm').submit();
|
|
}
|
|
|
|
function handleReject() {
|
|
var box = document.getElementById('rejectionReasonBox');
|
|
box.classList.remove('hidden');
|
|
document.getElementById('rejectionReason').focus();
|
|
box.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
var reasonEl = document.getElementById('rejectionReason');
|
|
reasonEl.setAttribute('required', 'required');
|
|
|
|
var submitBtn = box.closest('form').querySelectorAll('button')[1];
|
|
submitBtn.onclick = function() {
|
|
var reason = reasonEl.value.trim();
|
|
if (!reason) {
|
|
alert('{% trans "Please provide a rejection reason." %}');
|
|
return;
|
|
}
|
|
submitReview('reject');
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|