545 lines
20 KiB
HTML
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 %}
|