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

545 lines
20 KiB
HTML

{% extends "layouts/public_base.html" %}
{% load i18n %}
{% block title %}{% trans "Submit a Complaint" %} - PX360{% endblock %}
{% block extra_css %}
<style>
.complaint-form {
max-width: 900px;
margin: 0 auto;
padding: 1rem 0;
}
.form-section {
background: white;
border-radius: 1.5rem;
padding: 2rem;
margin-bottom: 1.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
}
.form-section:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.form-section h3 {
color: #005696;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #eef6fb;
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 1.25rem;
font-weight: 700;
}
.form-control {
width: 100%;
padding: 0.875rem 1.125rem;
border: 2px solid #cbd5e1;
border-radius: 0.875rem;
font-size: 0.95rem;
transition: all 0.2s;
background: white;
color: #1e293b;
font-family: 'Inter', sans-serif;
}
.form-control:focus {
outline: none;
border-color: #007bbd;
box-shadow: 0 0 0 4px rgba(0, 123, 189, 0.1);
transform: translateY(-1px);
}
.form-label {
display: block;
margin-bottom: 0.625rem;
font-weight: 600;
color: #1e293b;
font-size: 0.9rem;
}
.form-text {
display: block;
margin-top: 0.5rem;
font-size: 0.825rem;
color: #64748b;
}
.required-mark {
color: #ef4444;
}
.alert-box {
padding: 1.5rem 1.75rem;
border-radius: 1.25rem;
margin-bottom: 2rem;
}
.alert-box.info {
background: linear-gradient(135deg, #eef6fb 0%, #e0f2fe 100%);
color: #005696;
border: 2px solid #bae6fd;
}
.location-hierarchy-help {
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
border-left: 4px solid #f59e0b;
padding: 1.25rem;
margin: 1.25rem 0;
border-radius: 1rem;
font-size: 0.9rem;
color: #4b5563;
}
[dir="rtl"] .location-hierarchy-help {
border-left: none;
border-right: 4px solid #f59e0b;
}
.btn-submit {
background: linear-gradient(135deg, #005696 0%, #007bbd 100%);
border: none;
padding: 1rem 2.5rem;
border-radius: 1rem;
color: white;
font-weight: 700;
font-size: 1.05rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 8px 16px rgba(0, 86, 150, 0.25);
display: inline-flex;
align-items: center;
gap: 0.75rem;
}
.btn-submit:hover {
transform: translateY(-3px);
box-shadow: 0 12px 24px rgba(0, 86, 150, 0.35);
}
.btn-submit:disabled {
background: #94a3b8;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.spinner {
display: inline-block;
width: 1.125rem;
height: 1.125rem;
border: 2px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Loading state for dropdowns */
.select-loading {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'%3E%3Cpath fill='%23005696' d='M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8z' opacity='.25'/%3E%3Cpath fill='%23005696' d='M12 2a10 10 0 0 1 10 10h-2a8 8 0 0 0-8-8z'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='1s' repeatCount='indefinite'/%3E%3C/path%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 1.25rem;
}
</style>
{% endblock %}
{% block content %}
<div class="complaint-form animate-fade-in">
<!-- Info Box -->
<div class="alert-box info">
<h4 class="font-bold text-base mb-2 flex items-center gap-2">
<i data-lucide="info" class="w-5 h-5"></i> {% trans "About This Form" %}
</h4>
<p class="m-0 text-sm leading-relaxed">
{% trans "Use this form to submit a complaint about your experience at one of our hospitals. We will review your complaint and get back to you as soon as possible." %}
</p>
</div>
<form id="public_complaint_form" method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- Complainant Information -->
<div class="form-section">
<h3>
<i data-lucide="user" class="w-6 h-6"></i> {% trans "Complainant Information" %}
</h3>
<div class="mb-5">
<label for="id_complainant_name" class="form-label">
{% trans "Complainant Name" %} <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="id_complainant_name" name="complainant_name"
maxlength="200" placeholder="{% trans 'Your full name' %}" required>
<small class="form-text">{% trans "Please provide your full name." %}</small>
</div>
<div class="mb-5">
<label for="id_relation_to_patient" class="form-label">
{% trans "Relation to Patient" %} <span class="required-mark">*</span>
</label>
<select class="form-control" id="id_relation_to_patient" name="relation_to_patient" required>
<option value="">{% trans "Select Relation" %}</option>
<option value="patient">{% trans "Patient" %}</option>
<option value="relative">{% trans "Relative" %}</option>
<option value="friend">{% trans "Friend" %}</option>
<option value="other">{% trans "Other" %}</option>
</select>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<div>
<label for="id_email" class="form-label">{% trans "Email Address" %}</label>
<input type="email" class="form-control" id="id_email" name="email" placeholder="your@email.com">
</div>
<div>
<label for="id_mobile_number" class="form-label">
{% trans "Mobile Number" %} <span class="required-mark">*</span>
</label>
<input type="tel" class="form-control" id="id_mobile_number" name="mobile_number"
maxlength="20" placeholder="05xxxxxxxx" required>
</div>
</div>
</div>
<!-- Patient Information -->
<div class="form-section">
<h3>
<i data-lucide="user-injured" class="w-6 h-6"></i> {% trans "Patient Information" %}
</h3>
<div class="mb-5">
<label for="id_patient_name" class="form-label">
{% trans "Patient Name" %} <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="id_patient_name" name="patient_name"
maxlength="200" placeholder="{% trans 'Name of patient involved' %}" required>
</div>
<div class="mb-5">
<label for="id_national_id" class="form-label">
{% trans "National ID / Iqama No." %} <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="id_national_id" name="national_id"
maxlength="20" placeholder="{% trans '10-digit National ID or Iqama number' %}" required>
</div>
<div class="mb-5">
<label for="id_incident_date" class="form-label">
{% trans "Incident Date" %} <span class="required-mark">*</span>
</label>
<input type="date" class="form-control" id="id_incident_date" name="incident_date" required>
</div>
</div>
<!-- Complaint Details -->
<div class="form-section">
<h3>
<i data-lucide="file-text" class="w-6 h-6"></i> {% trans "Complaint Details" %}
</h3>
<div class="mb-5">
<label for="id_hospital" class="form-label">
{% trans "Hospital" %} <span class="required-mark">*</span>
</label>
<select class="form-control" id="id_hospital" name="hospital" required>
<option value="">{% trans "Select Hospital" %}</option>
{% for hospital in hospitals %}
<option value="{{ hospital.id }}">{{ hospital.name }}</option>
{% endfor %}
</select>
</div>
<!-- Location Hierarchy Help -->
<div class="location-hierarchy-help">
<strong class="flex items-center gap-2 mb-2">
<i data-lucide="map-pin" class="w-4 h-4"></i> {% trans "Location Selection" %}
</strong>
<p class="m-0 text-sm leading-relaxed">
{% trans "Select the location where the incident occurred. Start with the general area, then section and subsection if applicable." %}
</p>
</div>
<!-- Location Dropdown -->
<div class="mb-5">
<label for="id_location" class="form-label">
{% trans "Location" %} <span class="required-mark">*</span>
</label>
<select class="form-control" id="id_location" name="location" required>
<option value="">{% trans "Select Location" %}</option>
</select>
</div>
<!-- Main Section Dropdown -->
<div class="mb-5" id="main_section_container" style="display: none;">
<label for="id_main_section" class="form-label">
{% trans "Main Section" %} <span class="required-mark">*</span>
</label>
<select class="form-control" id="id_main_section" name="main_section" required>
<option value="">{% trans "Select Main Section" %}</option>
</select>
</div>
<!-- Subsection Dropdown -->
<div class="mb-5" id="subsection_container" style="display: none;">
<label for="id_subsection" class="form-label">
{% trans "Subsection" %} <span class="required-mark">*</span>
</label>
<select class="form-control" id="id_subsection" name="subsection" required>
<option value="">{% trans "Select Subsection" %}</option>
</select>
</div>
<!-- Description -->
<div class="mb-5">
<label for="id_description" class="form-label">
{% trans "Complaint Description" %} <span class="required-mark">*</span>
</label>
<textarea class="form-control" id="id_complaint_details" name="complaint_details" rows="6" required
placeholder="{% trans 'Please describe your complaint in detail...' %}"></textarea>
</div>
<!-- Attachments -->
<div class="mb-5">
<label for="id_attachments" class="form-label">{% trans "Attachments" %}</label>
<div class="border-2 border-dashed border-slate-300 rounded-xl p-6 text-center cursor-pointer hover:border-blue hover:bg-light transition"
onclick="document.getElementById('id_attachments').click()">
<i data-lucide="upload-cloud" class="w-10 h-10 text-blue-500 mx-auto mb-2"></i>
<p class="text-navy text-sm font-semibold">{% trans "Click to upload files" %}</p>
<p class="text-slate text-xs mt-1">{% trans "Images, PDF, Word (max 10MB each)" %}</p>
</div>
<input type="file" class="hidden" id="id_attachments" name="attachments" multiple
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx" onchange="updateFileList()">
<div id="fileList" class="mt-3 space-y-2"></div>
</div>
</div>
<!-- Submit Button -->
<div class="text-center pt-6">
<button type="submit" class="btn-submit" id="submit_btn">
<i data-lucide="send" class="w-5 h-5"></i>
{% trans "Submit Complaint" %}
</button>
</div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM Content Loaded - Initializing complaint form');
if (typeof lucide !== 'undefined') {
lucide.createIcons();
} else {
console.warn('Lucide not loaded');
}
// Load locations on page load
loadLocations();
// Location change -> load main sections
document.getElementById('id_location').addEventListener('change', function() {
const locationId = this.value;
loadMainSections(locationId);
});
// Main section change -> load subsections
document.getElementById('id_main_section').addEventListener('change', function() {
const locationId = document.getElementById('id_location').value;
const mainSectionId = this.value;
loadSubsections(locationId, mainSectionId);
});
function loadLocations() {
const locationSelect = document.getElementById('id_location');
if (!locationSelect) {
console.error('Location select element not found!');
return;
}
locationSelect.classList.add('select-loading');
console.log('Loading locations...');
fetch('/organizations/dropdowns/locations/')
.then(r => {
console.log('Locations response:', r.status);
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
})
.then(data => {
console.log('Locations data:', data);
locationSelect.innerHTML = '<option value="">{% trans "Select Location" %}</option>';
if (Array.isArray(data) && data.length > 0) {
data.forEach(loc => {
const opt = document.createElement('option');
opt.value = loc.id;
opt.textContent = loc.name;
locationSelect.appendChild(opt);
});
} else {
console.warn('No locations returned or data is not an array');
}
})
.catch(err => {
console.error('Failed to load locations:', err);
locationSelect.innerHTML = '<option value="">{% trans "Error loading locations" %}</option>';
})
.finally(() => locationSelect.classList.remove('select-loading'));
}
function loadMainSections(locationId) {
const mainSectionSelect = document.getElementById('id_main_section');
const mainSectionContainer = document.getElementById('main_section_container');
const subsectionContainer = document.getElementById('subsection_container');
mainSectionSelect.innerHTML = '<option value="">{% trans "Select Main Section" %}</option>';
document.getElementById('id_subsection').innerHTML = '<option value="">{% trans "Select Subsection" %}</option>';
if (!locationId) {
mainSectionContainer.style.display = 'none';
subsectionContainer.style.display = 'none';
return;
}
mainSectionSelect.classList.add('select-loading');
fetch('{% url "organizations:ajax_main_sections" %}?location_id=' + locationId)
.then(r => r.json())
.then(data => {
const sections = data.sections || [];
sections.forEach(section => {
const opt = document.createElement('option');
opt.value = section.id;
opt.textContent = section.name;
mainSectionSelect.appendChild(opt);
});
mainSectionContainer.style.display = sections.length ? 'block' : 'none';
if (sections.length === 0) {
subsectionContainer.style.display = 'none';
}
})
.catch(err => console.error('Failed to load main sections:', err))
.finally(() => mainSectionSelect.classList.remove('select-loading'));
}
function loadSubsections(locationId, mainSectionId) {
const subsectionSelect = document.getElementById('id_subsection');
const subsectionContainer = document.getElementById('subsection_container');
subsectionSelect.innerHTML = '<option value="">{% trans "Select Subsection" %}</option>';
if (!locationId || !mainSectionId) {
subsectionContainer.style.display = 'none';
return;
}
subsectionSelect.classList.add('select-loading');
fetch('{% url "organizations:ajax_subsections" %}?location_id=' + locationId + '&main_section_id=' + mainSectionId)
.then(r => r.json())
.then(data => {
const subsections = data.subsections || [];
subsections.forEach(sub => {
const opt = document.createElement('option');
opt.value = sub.internal_id || sub.id;
opt.textContent = sub.name;
subsectionSelect.appendChild(opt);
});
subsectionContainer.style.display = subsections.length ? 'block' : 'none';
})
.catch(err => console.error('Failed to load subsections:', err))
.finally(() => subsectionSelect.classList.remove('select-loading'));
}
function updateFileList() {
const input = document.getElementById('id_attachments');
const list = document.getElementById('fileList');
list.innerHTML = '';
if (input.files.length > 0) {
const div = document.createElement('div');
div.className = 'space-y-2';
for (let f of input.files) {
const size = (f.size / 1024 / 1024).toFixed(2);
div.innerHTML += `
<div class="flex items-center gap-3 text-sm text-navy p-3 bg-slate-50 rounded-xl">
<i data-lucide="file" class="w-5 h-5 text-slate-500"></i>
<span class="flex-1 truncate font-medium">${f.name}</span>
<span class="text-slate text-xs">${size} MB</span>
</div>
`;
}
list.appendChild(div);
lucide.createIcons();
}
}
// Form submission
document.getElementById('public_complaint_form').addEventListener('submit', function(e) {
e.preventDefault();
const btn = document.getElementById('submit_btn');
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner"></span>{% trans "Submitting..." %}';
const formData = new FormData(this);
fetch('{% url "complaints:public_complaint_submit" %}', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name="csrfmiddlewaretoken"]').value
}
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: '{% trans "Submitted Successfully!" %}',
text: '{% trans "Your reference number is: " %}' + data.reference_number,
confirmButtonColor: '#005696'
});
this.reset();
// Reset dropdowns
document.getElementById('main_section_container').style.display = 'none';
document.getElementById('subsection_container').style.display = 'none';
} else {
Swal.fire({
icon: 'error',
title: '{% trans "Error" %}',
text: data.message || '{% trans "Failed to submit. Please try again." %}'
});
}
})
.catch(err => {
console.error('Submit error:', err);
Swal.fire({
icon: 'error',
title: '{% trans "Error" %}',
text: '{% trans "Failed to submit. Please try again." %}'
});
})
.finally(() => {
btn.disabled = false;
btn.innerHTML = originalHTML;
});
});
});
</script>
{% endblock %}