HH/templates/complaints/partials/explanation_panel.html
2026-02-22 08:35:53 +03:00

340 lines
16 KiB
HTML

{% load i18n %}
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
<h3 class="text-xl font-bold text-navy mb-6">{% trans "Staff Explanations" %}</h3>
{% if explanations %}
<div class="flex items-center justify-between mb-6">
<div></div>
{% if can_edit %}
<a href="{% url 'complaints:request_explanation_form' pk=complaint.id %}" class="px-4 py-2 bg-gradient-to-r from-navy to-blue text-white rounded-xl font-semibold hover:opacity-90 transition inline-flex items-center gap-2">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Request More Explanations" %}
</a>
{% 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.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 %}
<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 %}
<!-- Admin Actions (for submitted explanations) -->
{% if can_edit and exp.is_used and not exp.escalated_to_manager %}
<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-4 py-2 bg-green-600 text-white rounded-lg text-sm 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-4 py-2 bg-red-600 text-white rounded-lg text-sm 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-4 py-2 bg-orange-500 text-white rounded-lg text-sm 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-4 py-2 bg-slate-100 text-slate-400 rounded-lg text-sm 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 %}
<!-- Pending Actions -->
{% if can_edit and not exp.is_used %}
<div class="mt-4 flex gap-2">
<button onclick="resendExplanation('{{ exp.token }}')" class="px-4 py-2 bg-navy text-white rounded-lg text-sm 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>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<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 explanation requests sent yet" %}</p>
{% if can_edit %}
<a href="{% url 'complaints:request_explanation_form' pk=complaint.id %}" class="px-4 py-2 bg-gradient-to-r from-navy to-blue text-white rounded-xl font-semibold hover:opacity-90 transition inline-flex items-center gap-2">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Request Explanation" %}
</a>
{% endif %}
</div>
{% endif %}
</section>
<!-- Escalate Modal -->
<div id="escalateModal" 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="closeEscalateModal()" 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>
// Store the explanation ID for escalation
let escalateExplanationId = null;
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 showEscalateModal(explanationId) {
console.log('showEscalateModal called, raw explanationId:', explanationId, 'type:', typeof explanationId);
// Validate 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;
}
// Store in module-level variable
escalateExplanationId = explanationId;
// Also store in hidden input as backup
const input = document.getElementById('escalateExplanationId');
if (input) {
input.value = explanationId;
}
console.log('Stored explanationId:', escalateExplanationId);
document.getElementById('escalateModal').classList.remove('hidden');
lucide.createIcons();
}
function closeEscalateModal() {
document.getElementById('escalateModal').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('escalateModal').addEventListener('click', function(e) {
if (e.target === this) {
closeEscalateModal();
}
});
// 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);
showEscalateModal(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>