HH/templates/complaints/investigation_questions.html
2026-07-05 14:49:45 +03:00

335 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE|default:'en' }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ complaint.reference_number }} - {% trans "Collect Feedback" %}</title>
<link rel="stylesheet" href="{% static 'dist/css/tailwind.css' %}">
<link rel="stylesheet" href="{% static 'vendor/fonts/noto-kufi-arabic/arabic.css' %}">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body { font-family: 'Inter', 'Noto Kufi Arabic', sans-serif; }
[dir="rtl"] body, [dir="rtl"] input, [dir="rtl"] textarea, [dir="rtl"] select, [dir="rtl"] button {
font-family: 'Noto Kufi Arabic', sans-serif;
}
.btn-add {
display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem;
background: #eef6fb; color: #005696; border: 2px dashed #93c5fd; border-radius: 0.75rem;
font-weight: 600; cursor: pointer; transition: all 0.2s; font-size: 0.875rem;
}
.btn-add:hover { background: #dbeaf6; border-color: #005696; }
.btn-remove {
width: 2rem; height: 2rem; display: flex; align-items: center; justify-content: center;
background: #fee2e2; color: #dc2626; border: none; border-radius: 0.5rem; cursor: pointer;
font-size: 1.25rem; transition: all 0.2s; flex-shrink: 0;
}
.btn-remove:hover { background: #fecaca; }
</style>
</head>
<body class="bg-slate-50 min-h-screen py-8 px-4">
<div class="w-full max-w-3xl mx-auto">
{% if complaint.department %}
<a href="{% url 'organizations:department_detail' complaint.department.id %}" class="inline-flex items-center gap-2 text-sm text-slate hover:text-navy transition mb-4">&larr; {% trans "Back to Department" %}</a>
{% endif %}
<!-- Logo -->
<div class="text-center mb-6">
<img src="{% static 'img/hh-logo.png' %}" alt="Al Hammadi Hospital" class="h-16 w-auto mx-auto inline-block">
</div>
<!-- Complaint Card -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 mb-6">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-bold text-navy">{{ complaint.reference_number }}</span>
</div>
<h1 class="text-xl font-bold text-navy mb-4">{{ complaint.title }}</h1>
<div class="border-t border-slate-100 pt-4">
<p class="text-xs font-bold text-slate uppercase mb-2">{% trans "Description" %}</p>
<p class="text-base leading-relaxed text-slate">{{ complaint.description }}</p>
</div>
</div>
{% if success %}
<!-- Success State -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-8 text-center">
<div class="w-16 h-16 mx-auto bg-green-100 rounded-full flex items-center justify-center mb-4">
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
</div>
<h2 class="text-lg font-bold text-navy mb-1">{% trans "Feedback Requests Sent" %}</h2>
<p class="text-sm text-slate">{{ success }}</p>
<p class="text-xs text-slate/60 mt-3">{% trans "You will be notified when all staff members respond." %}</p>
</div>
{% else %}
<!-- Feedback Form -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
{% if error %}
<div class="bg-red-50 border border-red-200 rounded-xl p-4 mb-4">
<p class="text-sm text-red-700 font-medium">{{ error }}</p>
</div>
{% endif %}
<form method="post" class="space-y-6">
{% csrf_token %}
<div>
<p class="text-sm font-semibold text-navy mb-2">{% trans "Select staff and write their questions" %}</p>
<p class="text-xs text-slate mb-4">{% trans "Each staff member receives their own questions. You can write different questions for each person." %}</p>
{% if not accused_staff %}
<!-- Empty state: no PX-team pre-selected staff -->
<div class="bg-blue-50 border border-blue-200 rounded-xl p-3 mb-4 flex items-start gap-2">
<i data-lucide="info" class="w-4 h-4 text-blue-600 mt-0.5 shrink-0"></i>
<p class="text-sm text-blue-700">{% trans "No staff pre-selected by PX-team. Search and add staff members to investigate." %}</p>
</div>
{% endif %}
<!-- Staff search box -->
<div class="bg-slate-50 rounded-xl p-4 mb-4">
<label class="block text-sm font-semibold text-navy mb-2">{% trans "Add staff member" %}</label>
<div class="relative">
<input type="text" id="staffSearchInput" autocomplete="off"
class="w-full px-4 py-2 pl-10 border-2 border-slate-200 rounded-xl text-sm focus:outline-none focus:border-navy"
placeholder="{% trans 'Search by name or employee ID...' %}">
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-3 text-slate/60 pointer-events-none"></i>
<div id="staffSearchResults" class="hidden absolute z-10 mt-1 w-full bg-white border border-slate-200 rounded-xl shadow-lg max-h-72 overflow-auto"></div>
</div>
<p class="text-xs text-slate/60 mt-2">{% trans "Search returns active staff from the complaint's hospital." %}</p>
</div>
<!-- Staff cards (pre-existing + dynamically added) -->
<div id="staffCardsContainer" class="space-y-4">
{% for s in accused_staff %}
<div class="staff-card border-2 border-slate-200 rounded-xl p-4" data-staff-id="{{ s.staff_id }}">
<label class="flex items-center gap-3 cursor-pointer mb-3">
<input type="checkbox" name="accused_staff[]" value="{{ s.staff_id }}" class="include-staff w-5 h-5 rounded accent-navy" onchange="toggleStaffCard(this)">
<span class="font-semibold text-sm text-slate">{{ s.staff.get_full_name }}</span>
{% if s.staff.job_title %}<span class="text-xs text-slate/60">{{ s.staff.job_title }}</span>{% endif %}
<button type="button" class="btn-remove-staff ml-auto text-slate/60 hover:text-red-600 transition" onclick="removeStaffCard(this)" title="{% trans 'Remove' %}">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</label>
<div class="questions-block pl-8">
<div class="questions-container space-y-2" id="q-{{ s.staff_id }}">
<div class="question-row flex items-start gap-2">
<span class="q-num mt-2 text-sm font-bold text-slate/40">1.</span>
<select name="question_types__{{ s.staff_id }}[]" class="px-2 py-2 border-2 border-slate-200 rounded-lg text-xs text-slate focus:outline-none focus:border-navy shrink-0" onchange="syncQuestionType(this)">
<option value="text">{% trans "Text" %}</option>
<option value="yes_no">{% trans "Yes/No" %}</option>
</select>
<input type="text" name="questions__{{ s.staff_id }}[]" class="q-input w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:outline-none focus:border-navy" placeholder="{% trans 'Enter a question...' %}" onkeydown="handleQuestionKeydown(event, '{{ s.staff_id }}')" required>
<button type="button" class="btn-remove mt-1" onclick="removeQuestion(this)" title="{% trans 'Remove' %}">&times;</button>
</div>
</div>
<button type="button" onclick="addQuestion('{{ s.staff_id }}')" class="btn-add mt-2">
+ {% trans "Add Question" %}
</button>
</div>
</div>
{% endfor %}
</div>
</div>
<button type="submit" onclick="return confirmSend(event)" class="w-full flex items-center justify-center gap-2 px-4 py-3 bg-navy text-white rounded-xl font-bold text-sm hover:bg-blue transition">
<i data-lucide="send" class="w-4 h-4"></i>
{% trans "Send Feedback Requests" %}
</button>
</form>
</div>
{% endif %}
</div>
<script>
lucide.createIcons();
// Apply initial visual state for unchecked staff cards (dim the question blocks)
document.querySelectorAll('.include-staff').forEach(function (cb) {
if (!cb.checked) toggleStaffCard(cb);
});
const qCounts = {};
document.querySelectorAll('.questions-container').forEach(function (c) {
qCounts[c.id] = c.children.length;
});
function toggleStaffCard(checkbox) {
var card = checkbox.closest('.staff-card');
var block = card.querySelector('.questions-block');
block.style.opacity = checkbox.checked ? '1' : '0.4';
block.style.pointerEvents = checkbox.checked ? 'auto' : 'none';
block.querySelectorAll('input[type="text"]').forEach(function (i) { i.required = checkbox.checked; });
}
function confirmSend(event) {
var checked = document.querySelectorAll('.include-staff:checked');
var totalQuestions = document.querySelectorAll('.q-input').length;
if (checked.length === 0) {
alert('{% trans "Please select at least one staff member." %}');
event.preventDefault();
return false;
}
var names = [];
checked.forEach(function (cb) {
var nameEl = cb.closest('.staff-card').querySelector('.font-semibold');
if (nameEl) names.push(nameEl.textContent.trim());
});
return confirm(
'{% trans "Send feedback requests to the following staff?" %}\n\n' +
names.join('\n') + '\n\n' +
'{% trans "Each will receive an email and SMS with their questions. This action cannot be undone." %}'
);
}
function addQuestion(staffId) {
var container = document.getElementById('q-' + staffId);
if (!container) return;
qCounts[container.id] = (qCounts[container.id] || 0) + 1;
var row = document.createElement('div');
row.className = 'question-row flex items-start gap-2';
row.innerHTML = '<span class="q-num mt-2 text-sm font-bold text-slate/40">' + qCounts[container.id] + '.</span>' +
'<select name="question_types__' + staffId + '[]" class="px-2 py-2 border-2 border-slate-200 rounded-lg text-xs text-slate focus:outline-none focus:border-navy shrink-0" onchange="syncQuestionType(this)">' +
'<option value="text">Text</option><option value="yes_no">Yes/No</option></select>' +
'<input type="text" name="questions__' + staffId + '[]" class="q-input w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:outline-none focus:border-navy" placeholder="Enter a question..." onkeydown="handleQuestionKeydown(event, \'' + staffId + '\')" required>' +
'<button type="button" class="btn-remove mt-1" onclick="removeQuestion(this)" title="Remove">&times;</button>';
container.appendChild(row);
var inputs = row.querySelectorAll('input[type="text"]');
if (inputs.length > 0) inputs[0].focus();
}
function syncQuestionType(select) {
var input = select.parentElement.querySelector('.q-input');
if (!input) return;
if (select.value === 'yes_no') {
input.placeholder = 'Enter a yes/no question...';
} else {
input.placeholder = 'Enter a question...';
}
}
function handleQuestionKeydown(event, staffId) {
if (event.key === 'Enter') { event.preventDefault(); addQuestion(staffId); }
}
function removeQuestion(btn) {
var container = btn.closest('.questions-container');
if (container && container.children.length > 1) {
btn.closest('.question-row').remove();
renumber(container);
}
}
function renumber(container) {
var rows = container.querySelectorAll('.question-row');
rows.forEach(function (row, i) { row.querySelector('.q-num').textContent = (i + 1) + '.'; });
qCounts[container.id] = rows.length;
}
// ─── Staff search + dynamic card creation ───
var searchTimer = null;
var searchInput = document.getElementById('staffSearchInput');
var searchResults = document.getElementById('staffSearchResults');
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];
});
}
if (searchInput) {
searchInput.addEventListener('input', function () {
clearTimeout(searchTimer);
var q = this.value.trim();
if (q.length < 2) { searchResults.classList.add('hidden'); return; }
searchTimer = setTimeout(function () { doStaffSearch(q); }, 300);
});
// Hide results when clicking outside
document.addEventListener('click', function (e) {
if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
searchResults.classList.add('hidden');
}
});
}
function doStaffSearch(q) {
fetch('{{ search_url }}?q=' + encodeURIComponent(q))
.then(function (r) { return r.json(); })
.then(function (data) {
if (!data.results || !data.results.length) {
searchResults.innerHTML = '<div class="p-3 text-sm text-slate/60 italic">{% trans "No matches." %}</div>';
} else {
searchResults.innerHTML = data.results.map(function (s) {
var alreadyInList = document.querySelector('.staff-card[data-staff-id="' + s.id + '"]');
var disabled = s.already_added || alreadyInList;
var onclickAttr = disabled ? '' : 'onclick="addStaffCard(\'' + s.id + '\', \'' + escapeHtml(s.name).replace(/'/g, "\\'") + '\', \'' + escapeHtml(s.department_name).replace(/'/g, "\\'") + '\')"';
return '<div class="px-3 py-2 flex items-center justify-between ' + (disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer hover:bg-slate-50') + '" ' + onclickAttr + '>' +
'<span class="text-sm">' + escapeHtml(s.name) + (s.employee_id ? ' <span class="text-xs text-slate/40">(' + escapeHtml(s.employee_id) + ')</span>' : '') + ' <span class="text-xs text-slate/60">' + escapeHtml(s.department_name) + '</span></span>' +
(disabled ? '<span class="text-[10px] font-bold text-slate/60">{% trans "ADDED" %}</span>' : '') +
'</div>';
}).join('');
}
searchResults.classList.remove('hidden');
})
.catch(function () {
searchResults.innerHTML = '<div class="p-3 text-sm text-red-600 italic">{% trans "Search failed. Please try again." %}</div>';
searchResults.classList.remove('hidden');
});
}
function addStaffCard(id, name, dept) {
// Avoid duplicates: bail if a card with this staff_id already exists
if (document.querySelector('.staff-card[data-staff-id="' + id + '"]')) return;
var container = document.getElementById('staffCardsContainer');
var card = document.createElement('div');
card.className = 'staff-card border-2 border-slate-200 rounded-xl p-4';
card.dataset.staffId = id;
card.innerHTML =
'<label class="flex items-center gap-3 cursor-pointer mb-3">' +
'<input type="checkbox" name="accused_staff[]" value="' + id + '" class="include-staff w-5 h-5 rounded accent-navy" checked onchange="toggleStaffCard(this)">' +
'<span class="font-semibold text-sm text-slate">' + escapeHtml(name) + '</span>' +
'<span class="text-xs text-slate/60">' + escapeHtml(dept) + '</span>' +
'<button type="button" class="btn-remove-staff ml-auto text-slate/60 hover:text-red-600 transition" onclick="removeStaffCard(this)" title="{% trans "Remove" %}">' +
'<i data-lucide="x" class="w-4 h-4"></i>' +
'</button>' +
'</label>' +
'<div class="questions-block pl-8">' +
'<div class="questions-container space-y-2" id="q-' + id + '">' +
'<div class="question-row flex items-start gap-2">' +
'<span class="q-num mt-2 text-sm font-bold text-slate/40">1.</span>' +
'<select name="question_types__' + id + '[]" class="px-2 py-2 border-2 border-slate-200 rounded-lg text-xs text-slate focus:outline-none focus:border-navy shrink-0" onchange="syncQuestionType(this)">' +
'<option value="text">{% trans "Text" %}</option><option value="yes_no">{% trans "Yes/No" %}</option>' +
'</select>' +
'<input type="text" name="questions__' + id + '[]" class="q-input w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:outline-none focus:border-navy" placeholder="{% trans "Enter a question..." %}" onkeydown="handleQuestionKeydown(event, \'' + id + '\')" required>' +
'<button type="button" class="btn-remove mt-1" onclick="removeQuestion(this)" title="{% trans "Remove" %}">&times;</button>' +
'</div>' +
'</div>' +
'<button type="button" onclick="addQuestion(\'' + id + '\')" class="btn-add mt-2">+ {% trans "Add Question" %}</button>' +
'</div>';
container.appendChild(card);
// Reset search input + hide dropdown
searchInput.value = '';
searchResults.classList.add('hidden');
// Register q-count for the new card so addQuestion() works
qCounts['q-' + id] = 1;
// Re-render icons (for the × button)
lucide.createIcons();
}
function removeStaffCard(btn) {
if (confirm('{% trans "Remove this staff member from the investigation?" %}')) {
var card = btn.closest('.staff-card');
if (card) card.remove();
}
}
</script>
</body>
</html>