2026-07-11 19:24:28 +03:00

335 lines
17 KiB
HTML

{% load i18n %}
{% load hospital_filters %}
<section id="aiAnalysisContent" class="bg-white rounded-2xl p-4 shadow-sm border border-slate-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-navy flex items-center gap-2">
<i data-lucide="bot" class="w-6 h-6"></i> {% trans "AI Analysis" %}
</h3>
{% if user.is_px_admin or user.is_hospital_admin %}
{% if complaint.is_active_status %}
<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>
{% if complaint.emotion or complaint.short_description_en or complaint.suggested_actions %}
{% trans "Reanalyze" %}
{% else %}
{% trans "Analyze" %}
{% endif %}
</button>
{% endif %}
{% endif %}
</div>
{% if complaint.emotion %}
<div class="bg-gradient-to-r from-light to-blue-50 border border-blue-200 rounded-xl p-3 mb-3">
<h4 class="font-bold text-navy mb-2 flex items-center gap-2">
<i data-lucide="cpu" class="w-4 h-4"></i> {% trans "Emotion Analysis" %}
</h4>
<div class="flex items-center justify-between mb-2">
<span class="px-3 py-1 bg-navy text-white rounded-xl font-bold flex items-center gap-2 text-sm">
<i data-lucide="frown" class="w-4 h-4"></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-1.5 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-xl p-3 mb-3">
<h4 class="font-bold text-navy mb-1 text-sm">{% trans "AI Summary (English)" %}</h4>
<p class="text-slate text-sm">{{ complaint.short_description_en }}</p>
</div>
{% endif %}
{% if complaint.short_description_ar %}
<div class="bg-light/50 border border-slate-200 rounded-xl p-3 mb-3">
<h4 class="font-bold text-navy mb-1 text-sm">{% trans "AI Summary (Arabic)" %}</h4>
<p class="text-slate text-sm" dir="rtl">{{ complaint.short_description_ar }}</p>
</div>
{% endif %}
{% if complaint.suggested_actions %}
<div class="space-y-2">
<h4 class="font-bold text-navy flex items-center gap-2 text-sm">
<i data-lucide="lightbulb" class="w-4 h-4 text-blue-600"></i>
{% trans "Suggested Actions" %}
</h4>
{% for action in complaint.suggested_actions %}
<div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
<div class="flex items-start justify-between gap-3">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1 flex-wrap">
<span class="px-2 py-0.5
{% 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 text-xs font-bold uppercase">
{{ action.priority }}
</span>
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs font-bold">
{{ action.category|replace:"_: "|title }}
</span>
</div>
<p class="text-slate text-sm font-medium">{{ action.action_en }}</p>
{% if action.action_ar %}
<p class="text-slate text-xs mt-1" dir="rtl">{{ action.action_ar }}</p>
{% endif %}
</div>
{% if user.is_px_admin or user.is_hospital_admin or user.is_px_employee %}
{% if complaint.is_active_status %}
<button onclick="createAction('{{ action.action_en|escapejs }}', '{{ action.priority }}', '{{ action.category }}')"
class="inline-flex items-center gap-1 px-3 py-1.5 bg-gradient-to-r from-blue to-navy text-white rounded-lg font-semibold hover:from-navy hover:to-blue transition text-xs shadow whitespace-nowrap">
<i data-lucide="plus-circle" class="w-3.5 h-3.5"></i>
{% trans "Create Action" %}
</button>
{% endif %}
{% 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-xl p-3">
<div class="flex items-start justify-between gap-3 mb-2">
<h4 class="font-bold text-navy flex items-center gap-2 text-sm">
<i data-lucide="lightbulb" class="w-4 h-4 text-blue-600"></i>
{% trans "Suggested Action" %}
</h4>
{% if user.is_px_admin or user.is_hospital_admin or user.is_px_employee %}
{% if complaint.is_active_status %}
<button onclick="createAction('{{ complaint.suggested_action_en|escapejs }}', 'medium', 'process_improvement')"
class="inline-flex items-center gap-1 px-3 py-1.5 bg-gradient-to-r from-blue to-navy text-white rounded-lg font-semibold hover:from-navy hover:to-blue transition text-xs shadow whitespace-nowrap">
<i data-lucide="plus-circle" class="w-3.5 h-3.5"></i>
{% trans "Create Action" %}
</button>
{% endif %}
{% endif %}
</div>
<p class="text-slate text-sm">{{ 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-8">
<i data-lucide="bot" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
<p class="text-slate text-sm">{% trans "No AI analysis available for this complaint" %}</p>
{% if user.is_px_admin or user.is_hospital_admin %}
<p class="text-slate text-xs mt-1">{% trans "Click \"Analyze\" above to run AI analysis" %}</p>
{% endif %}
</div>
{% endif %}
</section>
<script>
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-3"><h3 class="text-xl font-bold text-navy flex items-center gap-2"><i data-lucide="bot" class="w-5 h-5"></i> {% trans "AI Analysis" %}</h3><div class="flex items-center gap-3"><span class="text-xs text-green-600 font-semibold">{% trans "Analysis complete" %}</span><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 "Reanalyze" %}</button></div></div>';
if (data.emotion && data.emotion !== 'neutral') {
html += '<div class="bg-gradient-to-r from-light to-blue-50 border border-blue-200 rounded-xl p-3 mb-3">';
html += '<h4 class="font-bold text-navy mb-2 flex items-center gap-2"><i data-lucide="cpu" class="w-4 h-4"></i> {% trans "Emotion Analysis" %}</h4>';
html += '<div class="flex items-center justify-between mb-2">';
html += '<span class="px-3 py-1 bg-navy text-white rounded-xl font-bold flex items-center gap-2 text-sm"><i data-lucide="frown" class="w-4 h-4"></i> ' + escapeHtml(data.emotion_display) + '</span>';
html += '<span class="text-slate text-sm">{% trans "Confidence" %}: ' + data.emotion_confidence_percent.toFixed(0) + '%</span>';
html += '</div>';
html += '<div><div class="flex justify-between text-xs 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-1.5 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-xl p-3 mb-3">';
html += '<h4 class="font-bold text-navy mb-1 text-sm">{% trans "AI Summary (English)" %}</h4>';
html += '<p class="text-slate text-sm">' + escapeHtml(data.short_description_en) + '</p></div>';
}
if (data.short_description_ar) {
html += '<div class="bg-light/50 border border-slate-200 rounded-xl p-3 mb-3">';
html += '<h4 class="font-bold text-navy mb-1 text-sm">{% trans "AI Summary (Arabic)" %}</h4>';
html += '<p class="text-slate text-sm" dir="rtl">' + escapeHtml(data.short_description_ar) + '</p></div>';
}
if (data.suggested_actions && data.suggested_actions.length > 0) {
html += '<div class="space-y-2"><h4 class="font-bold text-navy flex items-center gap-2 text-sm"><i data-lucide="lightbulb" class="w-4 h-4 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-lg p-3"><div class="flex items-center gap-2 mb-1"><span class="px-2 py-0.5 ' + priClass + ' rounded text-xs font-bold uppercase">' + escapeHtml(action.priority) + '</span><span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs font-bold">' + escapeHtml(action.category) + '</span></div>';
html += '<p class="text-slate text-sm font-medium">' + escapeHtml(action.action_en) + '</p>';
if (action.action_ar) {
html += '<p class="text-slate text-xs mt-1" dir="rtl">' + escapeHtml(action.action_ar) + '</p>';
}
html += '</div>';
});
html += '</div>';
}
content.innerHTML = html;
lucide.createIcons();
} else {
content.innerHTML = '<div class="text-center py-8"><i data-lucide="alert-circle" class="w-12 h-12 mx-auto text-red-400 mb-3"></i><p class="text-slate text-sm">' + escapeHtml(data.error || '{% trans "Analysis failed" %}') + '</p></div>';
lucide.createIcons();
}
})
.catch(error => {
console.error('Error:', error);
content.innerHTML = '<div class="text-center py-8"><i data-lucide="alert-circle" class="w-12 h-12 mx-auto text-red-400 mb-3"></i><p class="text-slate text-sm">' + escapeHtml(error.message || '{% trans "An error occurred" %}') + '</p><button onclick="location.reload()" class="mt-3 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;
}
// 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);
}
function loadAIHelperSuggestions() {
const container = document.getElementById('aiHelperContent');
if (container) {
container.innerHTML = '<div class="text-center py-3"><div class="inline-block w-5 h-5 border-2 border-slate-200 border-t-navy rounded-full animate-spin"></div><p class="text-slate text-xs mt-2">{% trans "Generating suggestions..." %}</p></div>';
}
fetch('/complaints/api/complaints/{{ complaint.pk }}/ai_helper_suggestions/', {
headers: {'X-CSRFToken': getCSRFToken()},
credentials: 'same-origin'
})
.then(r => {
if (!r.ok) {
return r.text().then(text => {
let msg = '{% trans "Failed to generate suggestions" %}';
try { msg = JSON.parse(text).error || msg; } catch (e) {}
throw new Error(msg);
});
}
return r.json();
})
.then(data => {
if (data.success) {
location.reload();
} else {
if (container) {
container.innerHTML = '<div class="bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-700">' + escapeHtml(data.error || '{% trans "Failed to generate suggestions" %}') + '</div>';
}
}
})
.catch(err => {
if (container) {
container.innerHTML = '<div class="bg-red-50 border border-red-200 rounded-lg p-3 text-xs text-red-700">{% trans "An error occurred. Please try again." %}</div>';
}
});
}
</script>