335 lines
17 KiB
HTML
335 lines
17 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 "Resolution" %}</h3>
|
|
|
|
{% if complaint.status == 'resolved' or complaint.status == 'closed' %}
|
|
<div class="bg-green-50 border border-green-200 rounded-2xl p-6 mb-6">
|
|
<div class="flex items-center gap-2 mb-4">
|
|
<i data-lucide="check-circle" class="w-6 h-6 text-green-500"></i>
|
|
<h4 class="font-bold text-green-800">{% trans "Complaint Resolved" %}</h4>
|
|
</div>
|
|
{% if complaint.resolution %}
|
|
<p class="text-slate-700 mb-4">{{ complaint.resolution|linebreaks }}</p>
|
|
{% endif %}
|
|
|
|
<!-- Resolution Outcome Display -->
|
|
{% if complaint.resolution_outcome %}
|
|
<div class="mb-4 pt-4 border-t border-green-200">
|
|
<p class="text-sm font-semibold text-green-800 mb-2">{% trans "Resolution Outcome" %}</p>
|
|
<div class="flex items-center gap-2">
|
|
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-bold
|
|
{% if complaint.resolution_outcome == 'patient' %}bg-blue-100 text-blue-800
|
|
{% elif complaint.resolution_outcome == 'hospital' %}bg-green-100 text-green-800
|
|
{% else %}bg-orange-100 text-orange-800{% endif %}">
|
|
{% if complaint.resolution_outcome == 'patient' %}
|
|
<i data-lucide="user" class="w-4 h-4 mr-1.5"></i>{% trans "Patient" %}
|
|
{% elif complaint.resolution_outcome == 'hospital' %}
|
|
<i data-lucide="building-2" class="w-4 h-4 mr-1.5"></i>{% trans "Hospital" %}
|
|
{% else %}
|
|
<i data-lucide="circle" class="w-4 h-4 mr-1.5"></i>{% trans "Other" %}
|
|
{% endif %}
|
|
</span>
|
|
{% if complaint.resolution_outcome == 'other' and complaint.resolution_outcome_other %}
|
|
<span class="text-sm text-slate">({{ complaint.resolution_outcome_other }})</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="text-sm text-slate">
|
|
<p>{% trans "Resolved by:" %} {{ complaint.resolved_by.get_full_name|default:"-" }}</p>
|
|
<p>{% trans "Resolved at:" %} {{ complaint.resolved_at|date:"M d, Y H:i"|default:"-" }}</p>
|
|
{% if complaint.pending_external_set_at %}
|
|
<p class="mt-1 text-amber-600">{% trans "Pending External since:" %} {{ complaint.pending_external_set_at|date:"M d, Y H:i" }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Satisfaction Section -->
|
|
<div class="bg-white border-2 border-slate-200 rounded-2xl p-6 mb-6">
|
|
<div class="flex items-center gap-2 mb-4">
|
|
<i data-lucide="smile" class="w-5 h-5 text-navy"></i>
|
|
<h4 class="font-bold text-navy">{% trans "Patient Satisfaction" %}</h4>
|
|
</div>
|
|
|
|
{% if complaint.satisfaction %}
|
|
<!-- Current satisfaction badge -->
|
|
<div class="flex items-center gap-3 mb-3">
|
|
<span class="inline-flex items-center gap-1.5 px-4 py-2 rounded-xl text-sm font-bold
|
|
{% if complaint.satisfaction == 'satisfied' %}bg-green-100 text-green-800 border border-green-300
|
|
{% elif complaint.satisfaction == 'neutral' %}bg-yellow-100 text-yellow-800 border border-yellow-300
|
|
{% elif complaint.satisfaction == 'dissatisfied' %}bg-red-100 text-red-800 border border-red-300
|
|
{% else %}bg-slate-100 text-slate-600 border border-slate-300{% endif %}">
|
|
{% if complaint.satisfaction == 'satisfied' %}
|
|
<i data-lucide="thumbs-up" class="w-4 h-4"></i>
|
|
{% elif complaint.satisfaction == 'neutral' %}
|
|
<i data-lucide="minus-circle" class="w-4 h-4"></i>
|
|
{% elif complaint.satisfaction == 'dissatisfied' %}
|
|
<i data-lucide="thumbs-down" class="w-4 h-4"></i>
|
|
{% else %}
|
|
<i data-lucide="phone-off" class="w-4 h-4"></i>
|
|
{% endif %}
|
|
{{ complaint.get_satisfaction_display }}
|
|
</span>
|
|
{% if complaint.satisfaction_set_at %}
|
|
<span class="text-[10px] text-slate-400 ml-2">
|
|
{{ complaint.satisfaction_set_at|date:"Y-m-d H:i" }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if can_edit %}
|
|
<form method="post" action="{% url 'complaints:update_satisfaction' pk=complaint.pk %}" class="mt-3">
|
|
{% csrf_token %}
|
|
<p class="text-xs text-slate mb-3">{% trans "Set satisfaction based on patient follow-up call:" %}</p>
|
|
<div class="flex flex-wrap gap-2">
|
|
<button type="submit" name="satisfaction" value="satisfied"
|
|
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-semibold border-2 transition
|
|
{% if complaint.satisfaction == 'satisfied' %}border-green-500 bg-green-50 text-green-700
|
|
{% else %}border-slate-200 text-slate-600 hover:border-green-400 hover:bg-green-50{% endif %}">
|
|
<i data-lucide="thumbs-up" class="w-3.5 h-3.5"></i> {% trans "Satisfied" %}
|
|
</button>
|
|
<button type="submit" name="satisfaction" value="neutral"
|
|
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-semibold border-2 transition
|
|
{% if complaint.satisfaction == 'neutral' %}border-yellow-500 bg-yellow-50 text-yellow-700
|
|
{% else %}border-slate-200 text-slate-600 hover:border-yellow-400 hover:bg-yellow-50{% endif %}">
|
|
<i data-lucide="minus-circle" class="w-3.5 h-3.5"></i> {% trans "Neutral" %}
|
|
</button>
|
|
<button type="submit" name="satisfaction" value="dissatisfied"
|
|
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-semibold border-2 transition
|
|
{% if complaint.satisfaction == 'dissatisfied' %}border-red-500 bg-red-50 text-red-700
|
|
{% else %}border-slate-200 text-slate-600 hover:border-red-400 hover:bg-red-50{% endif %}">
|
|
<i data-lucide="thumbs-down" class="w-3.5 h-3.5"></i> {% trans "Dissatisfied" %}
|
|
</button>
|
|
<button type="submit" name="satisfaction" value="no_response"
|
|
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-semibold border-2 transition
|
|
{% if complaint.satisfaction == 'no_response' %}border-slate-400 bg-slate-50 text-slate-700
|
|
{% else %}border-slate-200 text-slate-600 hover:border-slate-400 hover:bg-slate-50{% endif %}">
|
|
<i data-lucide="phone-off" class="w-3.5 h-3.5"></i> {% trans "No Response" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% elif not complaint.satisfaction %}
|
|
<p class="text-sm text-slate italic">{% trans "No satisfaction feedback recorded yet." %}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-6 mb-6">
|
|
<div class="flex items-center gap-2 mb-4">
|
|
<i data-lucide="clock" class="w-6 h-6 text-yellow-500"></i>
|
|
<h4 class="font-bold text-yellow-800">{% trans "Pending Resolution" %}</h4>
|
|
</div>
|
|
<p class="text-slate mb-4">{% trans "This complaint has not been resolved yet." %}</p>
|
|
</div>
|
|
|
|
{% if can_edit and complaint.is_active_status %}
|
|
{% if complaint.assigned_to == current_user %}
|
|
<form method="post" action="{% url 'complaints:complaint_change_status' pk=complaint.pk %}" id="resolutionForm">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="status" value="resolved">
|
|
|
|
<!-- AI Generate Resolution Button -->
|
|
{% if explanations %}
|
|
<div class="mb-4">
|
|
<button type="button" onclick="generateAIResolution()" id="aiGenerateBtn" class="w-full px-4 py-3 bg-gradient-to-r from-navy to-blue text-white rounded-xl font-semibold hover:opacity-90 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="sparkles" class="w-5 h-5"></i>
|
|
<span>{% trans "Analyze Complaint & Generate Resolution Note" %}</span>
|
|
</button>
|
|
<div id="aiLoading" class="hidden mt-3 text-center">
|
|
<div class="inline-flex items-center gap-2 text-slate">
|
|
<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>
|
|
<span>{% trans "AI is analyzing complaint and explanations..." %}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI Generated Resolutions Selection -->
|
|
<div id="aiResolutionSelection" class="hidden mb-4">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Select AI Generated Resolution" %}</label>
|
|
<div class="space-y-3">
|
|
<!-- English Option -->
|
|
<div class="border border-slate-200 rounded-xl p-4 cursor-pointer hover:border-navy transition" onclick="selectResolution('en')">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<input type="radio" name="resolution_language" value="en" id="resEn" class="w-4 h-4 text-navy">
|
|
<label for="resEn" class="font-semibold text-navy cursor-pointer">{% trans "English" %}</label>
|
|
</div>
|
|
<div id="resolutionEnText" class="text-sm text-slate bg-slate-50 rounded-lg p-3 max-h-32 overflow-y-auto"></div>
|
|
</div>
|
|
|
|
<!-- Arabic Option -->
|
|
<div class="border border-slate-200 rounded-xl p-4 cursor-pointer hover:border-navy transition" onclick="selectResolution('ar')">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<input type="radio" name="resolution_language" value="ar" id="resAr" class="w-4 h-4 text-navy">
|
|
<label for="resAr" class="font-semibold text-navy cursor-pointer">{% trans "Arabic" %}</label>
|
|
</div>
|
|
<div id="resolutionArText" class="text-sm text-slate bg-slate-50 rounded-lg p-3 max-h-32 overflow-y-auto text-right" dir="rtl"></div>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-slate mt-2">{% trans "Click on a card to select. You can edit the selected resolution in the text area below before submitting." %}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Resolution Notes" %}</label>
|
|
<textarea name="resolution" id="resolutionTextarea" rows="6" class="w-full border border-slate-200 rounded-xl p-4 text-sm focus:ring-2 focus:ring-navy/20 outline-none" placeholder="{% trans 'Enter resolution details...' %}"></textarea>
|
|
<div class="mt-2 bg-blue-50 border border-blue-200 rounded-xl p-3 flex items-center gap-2">
|
|
<i data-lucide="mail" class="w-4 h-4 text-blue-500"></i>
|
|
<span class="text-xs text-blue-700 font-medium">{% trans "This resolution will be sent to the patient via SMS and Email upon complaint closure." %}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolution Outcome - Who was in wrong/right -->
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Resolution Outcome" %}</label>
|
|
<p class="text-xs text-slate mb-2">{% trans "Who was in wrong / who was in right?" %}</p>
|
|
<select name="resolution_outcome" id="resolutionOutcome" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none bg-white">
|
|
<option value="">-- {% trans "Select Outcome" %} --</option>
|
|
<option value="patient">{% trans "Patient" %}</option>
|
|
<option value="hospital">{% trans "Hospital" %}</option>
|
|
<option value="other">{% trans "Other — please specify" %}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Other specification field (shown when Other is selected) -->
|
|
<div class="mb-4 hidden" id="otherSpecificationDiv">
|
|
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Please Specify" %}</label>
|
|
<textarea name="resolution_outcome_other" id="resolutionOutcomeOther" 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 'Specify who was in wrong/right...' %}"></textarea>
|
|
</div>
|
|
|
|
<button type="submit" class="w-full px-6 py-3 bg-green-500 text-white rounded-xl font-bold hover:bg-green-600 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="check-circle" class="w-5 h-5"></i> {% trans "Mark as Resolved" %}
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<!-- Show message that activation is required to resolve -->
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-xl p-4 text-center">
|
|
<i data-lucide="lock" class="w-5 h-5 text-yellow-600 mx-auto mb-2"></i>
|
|
<p class="text-sm text-yellow-700 font-medium">{% trans "Activate this complaint to resolve it" %}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endif %}
|
|
</section>
|
|
|
|
<script>
|
|
// Store generated resolutions
|
|
let generatedResolutions = {
|
|
en: '',
|
|
ar: ''
|
|
};
|
|
|
|
function generateAIResolution() {
|
|
const btn = document.getElementById('aiGenerateBtn');
|
|
const loading = document.getElementById('aiLoading');
|
|
const selectionDiv = document.getElementById('aiResolutionSelection');
|
|
|
|
// Get CSRF token from the form's hidden input
|
|
const csrfInput = document.querySelector('#resolutionForm input[name="csrfmiddlewaretoken"]');
|
|
const csrfToken = csrfInput ? csrfInput.value : null;
|
|
|
|
if (!csrfToken) {
|
|
alert('{% trans "CSRF token not found. Please refresh the page and try again." %}');
|
|
return;
|
|
}
|
|
|
|
// Show loading, hide button
|
|
btn.disabled = true;
|
|
btn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
loading.classList.remove('hidden');
|
|
selectionDiv.classList.add('hidden');
|
|
|
|
fetch(`/complaints/api/complaints/{{ complaint.id }}/generate_ai_resolution/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
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 => {
|
|
if (data.success && data.resolution_en && data.resolution_ar) {
|
|
// Store generated resolutions
|
|
generatedResolutions.en = data.resolution_en;
|
|
generatedResolutions.ar = data.resolution_ar;
|
|
|
|
// Display in selection cards
|
|
document.getElementById('resolutionEnText').textContent = data.resolution_en;
|
|
document.getElementById('resolutionArText').textContent = data.resolution_ar;
|
|
|
|
// Show selection div
|
|
selectionDiv.classList.remove('hidden');
|
|
|
|
// Auto-select English by default
|
|
selectResolution('en');
|
|
|
|
// Scroll to selection
|
|
selectionDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
} else {
|
|
alert(data.error || '{% trans "Failed to generate resolution. Please try again." %}');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert(error.message || '{% trans "An error occurred. Please try again." %}');
|
|
})
|
|
.finally(() => {
|
|
btn.disabled = false;
|
|
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
loading.classList.add('hidden');
|
|
});
|
|
}
|
|
|
|
function selectResolution(lang) {
|
|
const textarea = document.getElementById('resolutionTextarea');
|
|
const radioEn = document.getElementById('resEn');
|
|
const radioAr = document.getElementById('resAr');
|
|
|
|
if (lang === 'en') {
|
|
radioEn.checked = true;
|
|
radioAr.checked = false;
|
|
textarea.value = generatedResolutions.en;
|
|
textarea.dir = 'ltr';
|
|
} else {
|
|
radioEn.checked = false;
|
|
radioAr.checked = true;
|
|
textarea.value = generatedResolutions.ar;
|
|
textarea.dir = 'rtl';
|
|
}
|
|
|
|
// Highlight selected card
|
|
const cards = document.querySelectorAll('#aiResolutionSelection > div');
|
|
cards.forEach((card, index) => {
|
|
if ((lang === 'en' && index === 0) || (lang === 'ar' && index === 1)) {
|
|
card.classList.add('border-navy', 'bg-light');
|
|
} else {
|
|
card.classList.remove('border-navy', 'bg-light');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Show/hide "Other" specification field
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const outcomeSelect = document.getElementById('resolutionOutcome');
|
|
const otherDiv = document.getElementById('otherSpecificationDiv');
|
|
|
|
if (outcomeSelect && otherDiv) {
|
|
outcomeSelect.addEventListener('change', function() {
|
|
if (this.value === 'other') {
|
|
otherDiv.classList.remove('hidden');
|
|
} else {
|
|
otherDiv.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|