2026-04-08 17:13:35 +03:00

338 lines
17 KiB
HTML

{% load i18n %}
{% load hospital_filters %}
<section id="aiAnalysisContent" class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-navy flex items-center gap-2">
<i data-lucide="bot" class="w-6 h-6"></i> {% trans "AI Analysis" %}
</h3>
{% if not complaint.emotion and not complaint.short_description_en and not complaint.suggested_actions %}
{% if user.is_px_admin or user.is_hospital_admin %}
<button id="reanalyzeBtn" onclick="reanalyzeComplaintAI()" class="inline-flex items-center gap-1 px-3 py-1.5 bg-white border border-slate-200 rounded-lg text-xs font-semibold text-navy hover:bg-slate-50 transition">
<i data-lucide="refresh-cw" class="w-3 h-3"></i>
{% trans "Analyze" %}
</button>
{% endif %}
{% endif %}
</div>
{% if complaint.emotion %}
<div class="bg-gradient-to-r from-light to-blue-50 border border-blue-200 rounded-2xl p-6 mb-6">
<h4 class="font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="cpu" class="w-5 h-5"></i> {% trans "Emotion Analysis" %}
</h4>
<div class="flex items-center justify-between mb-4">
<span class="px-4 py-2 bg-navy text-white rounded-xl font-bold flex items-center gap-2">
<i data-lucide="frown" class="w-5 h-5"></i> {{ complaint.get_emotion_display }}
</span>
<span class="text-slate">{% trans "Confidence" %}: {{ complaint.emotion_confidence_percent|floatformat:0 }}%</span>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-slate">{% trans "Intensity" %}</span>
<span class="font-semibold">{{ complaint.emotion_intensity|floatformat:2 }} / 1.0</span>
</div>
<div class="h-2 bg-slate-200 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-navy to-blue rounded-full" style="width: {{ complaint.emotion_intensity_percent }}%"></div>
</div>
</div>
</div>
{% endif %}
{% if complaint.short_description_en %}
<div class="bg-light/50 border border-slate-200 rounded-2xl p-6 mb-6">
<h4 class="font-bold text-navy mb-3">{% trans "AI Summary (English)" %}</h4>
<p class="text-slate">{{ complaint.short_description_en }}</p>
</div>
{% endif %}
{% if complaint.short_description_ar %}
<div class="bg-light/50 border border-slate-200 rounded-2xl p-6 mb-6">
<h4 class="font-bold text-navy mb-3">{% trans "AI Summary (Arabic)" %}</h4>
<p class="text-slate" dir="rtl">{{ complaint.short_description_ar }}</p>
</div>
{% endif %}
{% if complaint.suggested_actions %}
<div class="space-y-4">
<h4 class="font-bold text-navy flex items-center gap-2">
<i data-lucide="lightbulb" class="w-5 h-5 text-blue-600"></i>
{% trans "Suggested Actions" %}
</h4>
{% for action in complaint.suggested_actions %}
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4">
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<div class="flex items-center gap-2 mb-2 flex-wrap">
<span class="px-2 py-1
{% if action.priority == 'high' %}bg-red-100 text-red-700
{% elif action.priority == 'medium' %}bg-orange-100 text-orange-700
{% else %}bg-green-100 text-green-700{% endif %}
rounded-lg text-xs font-bold uppercase">
<i data-lucide="flag" class="w-3 h-3 inline mr-1"></i>
{{ action.priority }}
</span>
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded-lg text-xs font-bold">
<i data-lucide="folder" class="w-3 h-3 inline mr-1"></i>
{{ action.category|replace:"_: "|title }}
</span>
</div>
<p class="text-slate font-medium">{{ action.action_en }}</p>
{% if action.action_ar %}
<p class="text-slate text-sm mt-2" dir="rtl">{{ action.action_ar }}</p>
{% endif %}
</div>
{% if user.is_px_admin or user.is_hospital_admin or user.is_px_coordinator %}
<button onclick="createAction('{{ action.action_en|escapejs }}', '{{ action.priority }}', '{{ action.category }}')"
class="inline-flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-blue to-navy text-white rounded-lg font-semibold hover:from-navy hover:to-blue transition text-sm shadow-lg shadow-blue-200 whitespace-nowrap">
<i data-lucide="plus-circle" class="w-4 h-4"></i>
{% trans "Create Action" %}
</button>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% elif complaint.suggested_action_en %}
<!-- Fallback for old single-action format -->
<div class="bg-blue-50 border border-blue-200 rounded-2xl p-6">
<div class="flex items-start justify-between gap-4 mb-3">
<h4 class="font-bold text-navy flex items-center gap-2">
<i data-lucide="lightbulb" class="w-5 h-5 text-blue-600"></i>
{% trans "Suggested Action" %}
</h4>
{% if user.is_px_admin or user.is_hospital_admin or user.is_px_coordinator %}
<button onclick="createAction('{{ complaint.suggested_action_en|escapejs }}', 'medium', 'process_improvement')"
class="inline-flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-blue to-navy text-white rounded-lg font-semibold hover:from-navy hover:to-blue transition text-sm shadow-lg shadow-blue-200">
<i data-lucide="plus-circle" class="w-4 h-4"></i>
{% trans "Create Action" %}
</button>
{% endif %}
</div>
<p class="text-slate">{{ complaint.suggested_action_en }}</p>
</div>
{% endif %}
{% if not complaint.emotion and not complaint.short_description_en and not complaint.suggested_actions %}
<div id="aiEmptyState" class="text-center py-12">
<i data-lucide="bot" class="w-16 h-16 mx-auto text-slate-300 mb-4"></i>
<p class="text-slate mb-4">{% trans "No AI analysis available for this complaint" %}</p>
{% if user.is_px_admin or user.is_hospital_admin %}
<button onclick="reanalyzeComplaintAI()" class="inline-flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-blue to-navy text-white rounded-lg font-semibold hover:from-navy hover:to-blue transition text-sm shadow-lg">
<i data-lucide="sparkles" class="w-4 h-4"></i>
{% trans "Run AI Analysis" %}
</button>
{% endif %}
</div>
{% endif %}
</section>
<script>
function getCSRFToken() {
const metaTag = document.querySelector('[name=csrfmiddlewaretoken]');
if (metaTag) {
return metaTag.value;
}
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, 9) === ('csrftoken' + '=')) {
cookieValue = decodeURIComponent(cookie.substring(9));
break;
}
}
}
return cookieValue;
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function reanalyzeComplaintAI() {
const btn = document.getElementById('reanalyzeBtn');
const content = document.getElementById('aiAnalysisContent');
const emptyState = document.getElementById('aiEmptyState');
if (btn) {
btn.disabled = true;
btn.innerHTML = '<span class="spinner spinner-dark"></span> {% trans "Analyzing..." %}';
}
if (content) {
content.innerHTML = '<div class="text-center py-12"><span class="spinner spinner-dark" style="width:2rem;height:2rem;border-width:3px;display:inline-block;border:3px solid rgba(0,86,150,0.2);border-top-color:#005696;border-radius:50%;animation:spin 0.6s linear infinite;"></span><p class="text-slate text-sm mt-3">{% trans "Analyzing complaint with AI, this may take a moment..." %}</p></div><style>@keyframes spin{to{transform:rotate(360deg)}}</style>';
}
fetch('/complaints/api/complaints/{{ complaint.id }}/reanalyze_ai/', {
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 => {
if (data.success) {
let html = '<div class="flex items-center justify-between mb-6"><h3 class="text-xl font-bold text-navy flex items-center gap-2"><i data-lucide="bot" class="w-6 h-6"></i> {% trans "AI Analysis" %}</h3><span class="text-xs text-green-600 font-semibold">{% trans "Analysis complete" %}</span></div>';
if (data.emotion && data.emotion !== 'neutral') {
html += '<div class="bg-gradient-to-r from-light to-blue-50 border border-blue-200 rounded-2xl p-6 mb-6">';
html += '<h4 class="font-bold text-navy mb-4 flex items-center gap-2"><i data-lucide="cpu" class="w-5 h-5"></i> {% trans "Emotion Analysis" %}</h4>';
html += '<div class="flex items-center justify-between mb-4">';
html += '<span class="px-4 py-2 bg-navy text-white rounded-xl font-bold flex items-center gap-2"><i data-lucide="frown" class="w-5 h-5"></i> ' + escapeHtml(data.emotion_display) + '</span>';
html += '<span class="text-slate">{% trans "Confidence" %}: ' + data.emotion_confidence_percent.toFixed(0) + '%</span>';
html += '</div>';
html += '<div><div class="flex justify-between text-sm mb-1"><span class="text-slate">{% trans "Intensity" %}</span><span class="font-semibold">' + data.emotion_intensity.toFixed(2) + ' / 1.0</span></div>';
html += '<div class="h-2 bg-slate-200 rounded-full overflow-hidden"><div class="h-full bg-gradient-to-r from-navy to-blue rounded-full" style="width: ' + data.emotion_intensity_percent + '%"></div></div></div>';
html += '</div>';
}
if (data.short_description_en) {
html += '<div class="bg-light/50 border border-slate-200 rounded-2xl p-6 mb-6">';
html += '<h4 class="font-bold text-navy mb-3">{% trans "AI Summary (English)" %}</h4>';
html += '<p class="text-slate">' + escapeHtml(data.short_description_en) + '</p></div>';
}
if (data.short_description_ar) {
html += '<div class="bg-light/50 border border-slate-200 rounded-2xl p-6 mb-6">';
html += '<h4 class="font-bold text-navy mb-3">{% trans "AI Summary (Arabic)" %}</h4>';
html += '<p class="text-slate" dir="rtl">' + escapeHtml(data.short_description_ar) + '</p></div>';
}
if (data.suggested_actions && data.suggested_actions.length > 0) {
html += '<div class="space-y-4"><h4 class="font-bold text-navy flex items-center gap-2"><i data-lucide="lightbulb" class="w-5 h-5 text-blue-600"></i> {% trans "Suggested Actions" %}</h4>';
data.suggested_actions.forEach(function(action) {
const priClass = action.priority === 'high' ? 'bg-red-100 text-red-700' : action.priority === 'medium' ? 'bg-orange-100 text-orange-700' : 'bg-green-100 text-green-700';
html += '<div class="bg-blue-50 border border-blue-200 rounded-xl p-4"><div class="flex items-center gap-2 mb-2"><span class="px-2 py-1 ' + priClass + ' rounded-lg text-xs font-bold uppercase"><i data-lucide="flag" class="w-3 h-3 inline mr-1"></i>' + escapeHtml(action.priority) + '</span><span class="px-2 py-1 bg-blue-100 text-blue-700 rounded-lg text-xs font-bold">' + escapeHtml(action.category) + '</span></div>';
html += '<p class="text-slate font-medium">' + escapeHtml(action.action_en) + '</p>';
if (action.action_ar) {
html += '<p class="text-slate text-sm mt-2" dir="rtl">' + escapeHtml(action.action_ar) + '</p>';
}
html += '</div>';
});
html += '</div>';
}
content.innerHTML = html;
lucide.createIcons();
} else {
content.innerHTML = '<div class="text-center py-12"><i data-lucide="alert-circle" class="w-16 h-16 mx-auto text-red-400 mb-4"></i><p class="text-slate">' + escapeHtml(data.error || '{% trans "Analysis failed" %}') + '</p></div>';
lucide.createIcons();
}
})
.catch(error => {
console.error('Error:', error);
content.innerHTML = '<div class="text-center py-12"><i data-lucide="alert-circle" class="w-16 h-16 mx-auto text-red-400 mb-4"></i><p class="text-slate">' + escapeHtml(error.message || '{% trans "An error occurred" %}') + '</p><button onclick="location.reload()" class="mt-4 px-4 py-2 bg-slate-100 rounded-lg text-sm text-slate hover:bg-slate-200">{% trans "Refresh Page" %}</button></div>';
lucide.createIcons();
});
}
function createAction(actionText, priority, category) {
// Show confirmation
if (!confirm('{% trans "Are you sure you want to create a PX Action from this suggestion?" %}')) {
return;
}
// Get CSRF token from meta tag (more reliable)
function getCSRFToken() {
const metaTag = document.querySelector('[name=csrfmiddlewaretoken]');
if (metaTag) {
return metaTag.value;
}
// Fallback to cookie
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;
}
// Create action via AJAX
const csrftoken = getCSRFToken();
if (!csrftoken) {
showAlert('error', '{% trans "CSRF token not found. Please refresh the page." %}');
return;
}
fetch('{% url "actions:action_create_from_ai" complaint.id %}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': csrftoken,
},
body: new URLSearchParams({
'csrfmiddlewaretoken': csrftoken,
'action': actionText,
'priority': priority,
'category': category
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show success message
showAlert('success', '{% trans "PX Action created successfully!" %}');
// Reload page after short delay to show new action
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
// Show error
showAlert('error', data.error || '{% trans "Failed to create action" %}');
}
})
.catch(error => {
console.error('Error:', error);
showAlert('error', '{% trans "An error occurred while creating the action" %}');
});
}
function showAlert(type, message) {
const alert = document.createElement('div');
const bgColor = type === 'success' ? 'bg-emerald-500' : 'bg-red-500';
const icon = type === 'success' ? 'check-circle' : 'alert-circle';
alert.className = `${bgColor} text-white px-6 py-4 rounded-xl shadow-xl z-50 flex items-center gap-3 fixed top-4 right-4`;
alert.innerHTML = `
<i data-lucide="${icon}" class="w-5 h-5"></i>
<span class="font-semibold">${message}</span>
<button type="button" onclick="this.parentElement.remove()" class="text-white/80 hover:text-white">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
`;
document.body.appendChild(alert);
lucide.createIcons();
setTimeout(() => {
alert.remove();
}, 5000);
}
</script>