599 lines
32 KiB
HTML
599 lines
32 KiB
HTML
{% load i18n %}
|
|
<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 "Send To Department" %}</h3>
|
|
|
|
<!-- Department Response Status (always shown when departments were sent) -->
|
|
{% if complaint.sent_to_any_department %}
|
|
<div class="space-y-3 mb-4">
|
|
{% for idept in complaint.involved_departments.all %}
|
|
{% if idept.sent %}
|
|
<div class="border border-slate-200 rounded-xl p-3">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="flex items-center gap-2">
|
|
<i data-lucide="building-2" class="w-4 h-4 text-navy"></i>
|
|
<span class="font-semibold text-navy text-sm">{{ idept.department.get_localized_name }}</span>
|
|
{% if idept.is_primary %}
|
|
<span class="px-1.5 py-0.5 bg-navy/10 text-navy rounded text-[9px] font-bold">{% trans "Primary" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
<span class="text-[10px] text-slate">{{ idept.forwarded_at|date:"M d, Y" }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
{% if not idept.response_submitted %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-100 text-amber-700 uppercase">
|
|
<i data-lucide="clock" class="w-3 h-3"></i> {% trans "Awaiting Response" %}
|
|
</span>
|
|
{% elif idept.manager_review_status == 'pending' %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-100 text-amber-700 uppercase">
|
|
<i data-lucide="shield" class="w-3 h-3"></i> {% trans "Pending Manager Review" %}
|
|
</span>
|
|
{% elif idept.manager_review_status == 'rejected' %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-red-100 text-red-700 uppercase">
|
|
<i data-lucide="x-circle" class="w-3 h-3"></i> {% trans "Rejected by Manager" %}
|
|
</span>
|
|
{% elif idept.manager_review_status == 'approved' and idept.acceptance_status == 'pending' %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-green-100 text-green-700 uppercase">
|
|
<i data-lucide="check-circle" class="w-3 h-3"></i> {% trans "Approved — Awaiting PX Review" %}
|
|
</span>
|
|
{% elif idept.acceptance_status == 'acceptable' %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-green-100 text-green-700 uppercase">
|
|
<i data-lucide="check-circle" class="w-3 h-3"></i> {% trans "Accepted" %}
|
|
</span>
|
|
{% elif idept.acceptance_status == 'not_acceptable' %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold bg-red-100 text-red-700 uppercase">
|
|
<i data-lucide="x-circle" class="w-3 h-3"></i> {% trans "Rejected by PX" %}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if idept.response_notes %}
|
|
<div class="mt-2 bg-slate-50 rounded-lg p-2">
|
|
<p class="text-[10px] text-slate-400 uppercase font-bold mb-0.5">{% trans "Response" %}</p>
|
|
<p class="text-xs text-slate-600">{{ idept.response_notes|truncatechars:150 }}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% if idept.manager_reviews.exists %}
|
|
<div class="mt-2 bg-blue-50 rounded-lg p-2 border border-blue-100">
|
|
<p class="text-[10px] text-blue-600 uppercase font-bold mb-1.5 flex items-center gap-1">
|
|
<i data-lucide="shield-check" class="w-3 h-3"></i> {% trans "Manager Review Answers" %}
|
|
</p>
|
|
{% for review in idept.manager_reviews.all %}
|
|
<div class="space-y-1.5">
|
|
{% for answer in review.answers.all %}
|
|
<div class="flex items-start gap-2 text-xs">
|
|
<span class="text-slate-500 font-semibold min-w-[140px]">{{ answer.question.text_ar|default:answer.question.text_en }}:</span>
|
|
<span class="font-bold
|
|
{% if answer.text_value == 'yes' %}text-red-600
|
|
{% elif answer.text_value == 'no' %}text-green-600
|
|
{% else %}text-navy{% endif %}">
|
|
{% if answer.text_value == 'yes' %}{% trans "Yes" %}{% elif answer.text_value == 'no' %}{% trans "No" %}{% else %}{{ answer.text_value }}{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<button type="button" onclick="showSendModal('{{ complaint.id }}', 'complaint')" class="px-3 py-1.5 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-semibold hover:opacity-90 transition inline-flex items-center gap-2 mb-4">
|
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Send to Another Department" %}
|
|
</button>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{% if explanations %}
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div></div>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<button type="button" onclick="showSendModal('{{ complaint.id }}', 'complaint')" class="px-3 py-1.5 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-semibold hover:opacity-90 transition inline-flex items-center gap-2">
|
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Send to Department" %}
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="space-y-6">
|
|
{% for exp in explanations %}
|
|
<div class="bg-white border border-slate-200 rounded-xl p-5 {% if exp.escalated_to_manager %}border-l-4 border-l-orange-500{% endif %}">
|
|
<!-- Header: Staff Info + Status -->
|
|
<div class="flex justify-between items-start mb-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-navy rounded-full flex items-center justify-center text-white font-bold text-sm">
|
|
{{ exp.staff.first_name|first }}{{ exp.staff.last_name|first }}
|
|
</div>
|
|
<div>
|
|
<h4 class="font-bold text-navy">{{ exp.staff }}</h4>
|
|
<div class="flex items-center gap-2 text-xs text-slate">
|
|
<span>{{ exp.staff.employee_id }}</span>
|
|
{% if exp.staff.department %}
|
|
<span>•</span>
|
|
<span>{{ exp.staff.department.get_localized_name }}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col items-end gap-1">
|
|
<!-- Submission Status -->
|
|
<span class="px-3 py-1 {% if exp.is_used %}bg-green-100 text-green-600{% else %}bg-yellow-100 text-yellow-600{% endif %} rounded-lg text-xs font-bold">
|
|
{% if exp.is_used %}{% trans "Submitted" %}{% else %}{% trans "Pending" %}{% endif %}
|
|
</span>
|
|
<!-- Acceptance Status (only for submitted) -->
|
|
{% if exp.is_used %}
|
|
<span class="px-3 py-1 rounded-lg text-xs font-bold
|
|
{% if exp.acceptance_status == 'acceptable' %}bg-blue-100 text-blue-600
|
|
{% elif exp.acceptance_status == 'not_acceptable' %}bg-red-100 text-red-600
|
|
{% else %}bg-slate-100 text-slate-600{% endif %}">
|
|
{{ exp.get_acceptance_status_display }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Escalation Notice -->
|
|
{% if exp.escalated_to_manager %}
|
|
<div class="bg-orange-50 border border-orange-200 rounded-lg p-3 mb-4">
|
|
<div class="flex items-center gap-2 text-orange-700 text-sm">
|
|
<i data-lucide="arrow-up-circle" class="w-4 h-4"></i>
|
|
<span class="font-semibold">{% trans "Escalated to Manager" %}</span>
|
|
<span class="text-slate">({{ exp.escalated_to_manager.staff.get_full_name }})</span>
|
|
</div>
|
|
{% if exp.acceptance_notes %}
|
|
<p class="text-xs text-slate mt-1 ml-6">{{ exp.acceptance_notes }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Explanation Content -->
|
|
{% if exp.is_used and exp.explanation %}
|
|
{% with linked_dept=exp.linked_involved_department %}
|
|
{% if linked_dept and linked_dept.manager_review_status == 'pending' %}
|
|
<div class="rounded-lg p-3 mb-4 border bg-amber-50 border-amber-200">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<i data-lucide="clock" class="w-4 h-4 text-amber-500"></i>
|
|
<span class="text-sm font-semibold text-amber-700">{% trans "Pending Manager Review" %}</span>
|
|
</div>
|
|
<p class="text-xs text-slate-500">{% trans "Awaiting department manager approval." %}</p>
|
|
</div>
|
|
{% elif linked_dept and linked_dept.manager_review_status == 'rejected' %}
|
|
<div class="rounded-lg p-3 mb-4 border bg-red-50 border-red-200">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<i data-lucide="x-circle" class="w-4 h-4 text-red-500"></i>
|
|
<span class="text-sm font-semibold text-red-700">{% trans "Rejected by Manager" %}</span>
|
|
</div>
|
|
<p class="text-xs text-slate-500">{% trans "Champion needs to re-submit." %}</p>
|
|
</div>
|
|
{% else %}
|
|
<div class="bg-slate-50 rounded-xl p-4 mb-4">
|
|
<p class="text-slate text-sm">{{ exp.explanation }}</p>
|
|
{% if exp.attachment_count > 0 %}
|
|
<div class="mt-3 pt-3 border-t border-slate-200 flex items-center gap-2 text-xs text-slate">
|
|
<i data-lucide="paperclip" class="w-4 h-4"></i>
|
|
<span>{{ exp.attachment_count }} {% trans "attachment(s)" %}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% endif %}
|
|
|
|
<!-- Admin Actions (for submitted explanations) -->
|
|
{% if can_manage_actions and complaint.is_active_status and exp.is_used and not exp.escalated_to_manager %}
|
|
{% with linked_dept=exp.linked_involved_department %}
|
|
{% if not linked_dept or linked_dept.manager_review_status == 'approved' %}
|
|
<div class="mt-4 pt-4 border-t border-slate-100">
|
|
{% if exp.acceptance_status == 'pending' %}
|
|
<div class="flex flex-wrap gap-2">
|
|
<button onclick="markExplanation('{{ exp.id|stringformat:"s" }}', 'acceptable')" class="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-semibold hover:bg-green-700 transition inline-flex items-center gap-2">
|
|
<i data-lucide="check-circle" class="w-4 h-4"></i> {% trans "Acceptable" %}
|
|
</button>
|
|
<button onclick="markExplanation('{{ exp.id|stringformat:"s" }}', 'not_acceptable')" class="px-3 py-1.5 bg-red-600 text-white rounded-lg text-xs font-semibold hover:bg-red-700 transition inline-flex items-center gap-2">
|
|
<i data-lucide="x-circle" class="w-4 h-4"></i> {% trans "Not Acceptable" %}
|
|
</button>
|
|
{% if exp.staff.report_to %}
|
|
<button type="button" data-escalate-explanation-id="{{ exp.id }}" class="escalate-btn px-3 py-1.5 bg-orange-500 text-white rounded-lg text-xs font-semibold hover:bg-orange-600 transition inline-flex items-center gap-2">
|
|
<i data-lucide="arrow-up-circle" class="w-4 h-4"></i> {% trans "Not Acceptable & Escalate" %}
|
|
</button>
|
|
{% else %}
|
|
<span class="px-3 py-1.5 bg-slate-100 text-slate-400 rounded-lg text-xs font-semibold inline-flex items-center gap-2 cursor-not-allowed" title="{% trans 'Cannot escalate: Staff has no manager assigned' %}">
|
|
<i data-lucide="arrow-up-circle" class="w-4 h-4"></i> {% trans "No Manager to Escalate" %}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="flex items-center gap-2 text-sm">
|
|
<span class="text-slate">{% trans "Reviewed by:" %}</span>
|
|
<span class="font-semibold text-navy">{{ exp.accepted_by.get_full_name }}</span>
|
|
<span class="text-slate">{% trans "on" %}</span>
|
|
<span class="font-semibold text-navy">{{ exp.accepted_at|date:"Y-m-d H:i" }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% endif %}
|
|
|
|
{% for inv in exp.investigation.all %}
|
|
<div class="mt-4 pt-4 border-t border-slate-100">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<i data-lucide="search" class="w-4 h-4 text-navy"></i>
|
|
<span class="text-sm font-bold text-navy">{% trans "Feedback" %}</span>
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase
|
|
{% if inv.status == 'questions_sent' %}bg-amber-100 text-amber-700
|
|
{% elif inv.status == 'answers_received' %}bg-blue-100 text-blue-700
|
|
{% elif inv.status == 'reply_submitted' %}bg-green-100 text-green-700{% endif %}">
|
|
{{ inv.get_status_display }}
|
|
</span>
|
|
<span class="text-xs text-slate ml-auto">{{ inv.questions.count }} {% trans "questions" %}</span>
|
|
</div>
|
|
|
|
<div class="space-y-1.5 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>
|
|
|
|
<!-- Investigation Questions & Answers -->
|
|
{% if inv.questions.exists %}
|
|
<div class="mt-3 space-y-3">
|
|
{% for question in inv.questions.all %}
|
|
<div class="bg-slate-50 rounded-lg p-3 border border-slate-200">
|
|
<div class="flex items-start gap-2 mb-2">
|
|
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-navy text-white text-[10px] font-bold flex-shrink-0 mt-0.5">{{ forloop.counter }}</span>
|
|
<p class="text-sm font-semibold text-slate-700">{{ question.question_text }}</p>
|
|
</div>
|
|
<div class="ml-7 space-y-1.5">
|
|
{% for resp in inv.responses.all %}
|
|
{% if resp.is_completed %}
|
|
{% with answer=resp.answers.all %}
|
|
{% for a in answer %}
|
|
{% if a.question_id == question.id and a.answer_text %}
|
|
<div class="flex items-start gap-1.5 text-xs">
|
|
<i data-lucide="corner-up-right" class="w-3 h-3 text-slate-400 flex-shrink-0 mt-0.5"></i>
|
|
<span class="text-slate-500 font-medium">{{ resp.staff.get_full_name }}:</span>
|
|
<span class="text-slate-600">{{ a.answer_text|truncatewords:40 }}</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endwith %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if inv.status == 'answers_received' and inv.explanation.token %}
|
|
<a href="/complaints/{{ 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 %}
|
|
|
|
{% if inv.status == 'reply_submitted' and inv.final_reply %}
|
|
<div class="bg-slate-50 rounded-lg p-3 mt-2">
|
|
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Final Reply" %}</p>
|
|
<p class="text-sm text-slate-700">{{ inv.final_reply|truncatewords:30 }}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if inv.status == 'reply_submitted' and inv.negligence_finding %}
|
|
<div class="mt-2 flex flex-wrap gap-2">
|
|
{% if inv.negligence_finding == 'yes' %}
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-red-100 text-red-700">{% trans "Negligence" %}</span>
|
|
{% endif %}
|
|
{% if inv.policy_issue_finding == 'yes' %}
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-orange-100 text-orange-700">{% trans "Policy Issue" %}</span>
|
|
{% endif %}
|
|
{% if inv.requires_improvement_project == 'yes' %}
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-100 text-amber-700">{% trans "Improvement Project" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if inv.status == 'reply_submitted' and inv.requires_improvement_project == 'yes' and inv.improvement_project_note %}
|
|
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3 mt-2">
|
|
<p class="text-xs font-bold text-amber-700 uppercase mb-1">{% trans "Improvement Project Recommended" %}</p>
|
|
<p class="text-sm text-amber-800">{{ inv.improvement_project_note }}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Pending Actions -->
|
|
{% if can_manage_actions and complaint.is_active_status and not exp.is_used %}
|
|
<div class="mt-4 pt-4 border-t border-slate-100">
|
|
<div class="flex flex-wrap gap-2">
|
|
{% if not exp.reminder_sent_at %}
|
|
<button onclick="sendExplanationReminder('{{ exp.id }}', 'first')" class="px-3 py-1.5 bg-amber-500 text-white rounded-lg text-xs font-semibold hover:bg-amber-600 transition inline-flex items-center gap-2">
|
|
<i data-lucide="bell" class="w-4 h-4"></i> {% trans "Send Reminder" %}
|
|
</button>
|
|
{% elif not exp.second_reminder_sent_at %}
|
|
<button onclick="sendExplanationReminder('{{ exp.id }}', 'second')" class="px-3 py-1.5 bg-orange-500 text-white rounded-lg text-xs font-semibold hover:bg-orange-600 transition inline-flex items-center gap-2">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4"></i> {% trans "Send Second Reminder" %}
|
|
</button>
|
|
{% else %}
|
|
<span class="px-3 py-1.5 bg-slate-100 text-slate-400 rounded-lg text-xs font-semibold inline-flex items-center gap-2">
|
|
<i data-lucide="bell-check" class="w-4 h-4"></i> {% trans "Reminders Sent" %}
|
|
</span>
|
|
{% endif %}
|
|
<button onclick="resendExplanation('{{ exp.token }}')" class="px-3 py-1.5 bg-navy text-white rounded-lg text-xs font-semibold hover:bg-blue transition inline-flex items-center gap-2">
|
|
<i data-lucide="refresh-cw" class="w-4 h-4"></i> {% trans "Resend Link" %}
|
|
</button>
|
|
</div>
|
|
{% if exp.reminder_sent_at %}
|
|
<div class="mt-2 text-xs text-slate flex flex-wrap gap-x-4 gap-y-1">
|
|
<span>{% trans "1st reminder:" %} {{ exp.reminder_sent_at|date:"Y-m-d H:i" }}</span>
|
|
{% if exp.second_reminder_sent_at %}
|
|
<span>{% trans "2nd reminder:" %} {{ exp.second_reminder_sent_at|date:"Y-m-d H:i" }}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
{% if not complaint.sent_to_any_department %}
|
|
<div class="text-center py-12">
|
|
<i data-lucide="message-square" class="w-16 h-16 mx-auto text-slate-300 mb-4"></i>
|
|
<p class="text-slate mb-4">{% trans "No requests sent yet" %}</p>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<button type="button" onclick="showSendModal('{{ complaint.id }}', 'complaint')" class="px-3 py-1.5 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-semibold hover:opacity-90 transition inline-flex items-center gap-2">
|
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Send to Department" %}
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</section>
|
|
|
|
<!-- Escalate Modal -->
|
|
<div id="explanationEscalateModal" 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-md mx-4 shadow-2xl">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
|
<i data-lucide="arrow-up-circle" class="w-5 h-5 text-orange-500"></i>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-navy">{% trans "Escalate to Manager" %}</h3>
|
|
</div>
|
|
<p class="text-slate mb-4 text-sm">{% trans "This will mark the explanation as not acceptable and send a request to the staff's manager for an explanation." %}</p>
|
|
<form id="escalateForm" onsubmit="handleEscalate(event)">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="escalateExplanationId" name="explanation_id">
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Notes (Optional)" %}</label>
|
|
<textarea name="notes" id="escalateNotes" 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="flex gap-3">
|
|
<button type="button" onclick="closeExplanationEscalateModal()" 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-orange-500 text-white rounded-xl font-semibold hover:bg-orange-600 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="arrow-up-circle" class="w-4 h-4"></i> {% trans "Escalate" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let escalateExplanationId = null;
|
|
|
|
function sendExplanationReminder(explanationId, reminderType) {
|
|
const label = reminderType === 'first' ? '{% trans "first" %}' : '{% trans "second" %}';
|
|
if (!confirm('{% trans "Are you sure you want to send the" %} ' + label + ' {% trans "reminder?" %}')) {
|
|
return;
|
|
}
|
|
|
|
const csrfToken = document.querySelector('#escalateForm input[name="csrfmiddlewaretoken"]')?.value || getCookie('csrftoken');
|
|
|
|
fetch(`/complaints/api/complaints/{{ complaint.id }}/send_explanation_reminder/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
explanation_id: explanationId,
|
|
reminder_type: reminderType
|
|
})
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
location.reload();
|
|
} else {
|
|
response.json().then(data => {
|
|
alert(data.error || '{% trans "Failed to send reminder. Please try again." %}');
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('{% trans "An error occurred. Please try again." %}');
|
|
});
|
|
}
|
|
|
|
function resendExplanation(token) {
|
|
if (!confirm('{% trans "Are you sure you want to resend the explanation request?" %}')) {
|
|
return;
|
|
}
|
|
|
|
fetch('/complaints/{{ complaint.id }}/resend-explanation/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': getCookie('csrftoken'),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
alert('{% trans "Explanation request resent successfully!" %}');
|
|
} else {
|
|
alert('{% trans "Failed to resend explanation request. Please try again." %}');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('{% trans "An error occurred. Please try again." %}');
|
|
});
|
|
}
|
|
|
|
function markExplanation(explanationId, status) {
|
|
const actionText = status === 'acceptable' ? '{% trans "mark as acceptable" %}' : '{% trans "mark as not acceptable" %}';
|
|
if (!confirm('{% trans "Are you sure you want to" %} ' + actionText + '?')) {
|
|
return;
|
|
}
|
|
|
|
// Get CSRF token from the form's hidden input
|
|
const csrfToken = document.querySelector('#escalateForm input[name="csrfmiddlewaretoken"]')?.value || getCookie('csrftoken');
|
|
|
|
fetch(`/complaints/api/complaints/{{ complaint.id }}/review_explanation/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
explanation_id: explanationId,
|
|
acceptance_status: status
|
|
})
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
location.reload();
|
|
} else {
|
|
alert('{% trans "Failed to update explanation status. Please try again." %}');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('{% trans "An error occurred. Please try again." %}');
|
|
});
|
|
}
|
|
|
|
function showExplanationEscalateModal(explanationId) {
|
|
console.log('showExplanationEscalateModal called, raw explanationId:', explanationId, 'type:', typeof explanationId);
|
|
|
|
if (!explanationId || explanationId === 'None' || explanationId === '' || explanationId === 'undefined') {
|
|
console.error('Invalid explanationId:', explanationId);
|
|
alert('{% trans "Error: Invalid explanation ID. Please refresh the page and try again." %}');
|
|
return;
|
|
}
|
|
|
|
escalateExplanationId = explanationId;
|
|
|
|
const input = document.getElementById('escalateExplanationId');
|
|
if (input) {
|
|
input.value = explanationId;
|
|
}
|
|
|
|
console.log('Stored explanationId:', escalateExplanationId);
|
|
document.getElementById('explanationEscalateModal').classList.remove('hidden');
|
|
lucide.createIcons();
|
|
}
|
|
|
|
function closeExplanationEscalateModal() {
|
|
document.getElementById('explanationEscalateModal').classList.add('hidden');
|
|
document.getElementById('escalateNotes').value = '';
|
|
document.getElementById('escalateExplanationId').value = '';
|
|
escalateExplanationId = null;
|
|
}
|
|
|
|
function handleEscalate(event) {
|
|
event.preventDefault();
|
|
|
|
// Get explanationId from module variable first, then fallback to input
|
|
let explanationId = escalateExplanationId;
|
|
if (!explanationId) {
|
|
explanationId = document.getElementById('escalateExplanationId').value;
|
|
}
|
|
|
|
const notes = document.getElementById('escalateNotes').value;
|
|
// Get CSRF token from the form's hidden input
|
|
const csrfToken = document.querySelector('#escalateForm input[name="csrfmiddlewaretoken"]').value;
|
|
|
|
// Debug: Log values being sent
|
|
console.log('Escalating explanation:', { explanationId, notes });
|
|
|
|
if (!explanationId || explanationId === 'None') {
|
|
alert('{% trans "Error: No explanation selected. Please try again." %}');
|
|
closeEscalateModal();
|
|
return;
|
|
}
|
|
|
|
fetch(`/complaints/api/complaints/{{ complaint.id }}/escalate_explanation/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
explanation_id: escalateExplanationId,
|
|
acceptance_notes: notes
|
|
})
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
location.reload();
|
|
} else {
|
|
response.json().then(data => {
|
|
alert(data.error || '{% trans "Failed to escalate. Please try again." %}');
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('{% trans "An error occurred. Please try again." %}');
|
|
});
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
document.getElementById('explanationEscalateModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
closeExplanationEscalateModal();
|
|
}
|
|
});
|
|
|
|
// Add click event listeners to all escalate buttons (using data attributes)
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('.escalate-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const explanationId = this.getAttribute('data-escalate-explanation-id');
|
|
escalateExplanationId = explanationId;
|
|
console.log('Escalate button clicked, data-explanation-id:', explanationId);
|
|
showExplanationEscalateModal(explanationId);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Helper function to get CSRF token
|
|
function getCookie(name) {
|
|
let cookieValue = null;
|
|
if (document.cookie && document.cookie !== '') {
|
|
const cookies = document.cookie.split(';');
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
const cookie = cookies[i].trim();
|
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
</script>
|