678 lines
24 KiB
HTML
678 lines
24 KiB
HTML
{% extends "layouts/public_base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Submit a Complaint" %}{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.complaint-form {
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
padding: 2rem 0;
|
|
}
|
|
|
|
.form-section {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.form-section h3 {
|
|
color: #2c3e50;
|
|
margin-bottom: 1.5rem;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 2px solid #3498db;
|
|
}
|
|
|
|
.alert-box {
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.alert-box.success {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.alert-box.info {
|
|
background-color: #cce5ff;
|
|
color: #004085;
|
|
border: 1px solid #b8daff;
|
|
}
|
|
|
|
.required-mark {
|
|
color: #dc3545;
|
|
}
|
|
|
|
.btn-submit {
|
|
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
|
|
border: none;
|
|
padding: 1rem 2rem;
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.btn-submit:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
|
|
}
|
|
|
|
.btn-submit:disabled {
|
|
background: #6c757d;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
border: 2px solid rgba(255,255,255,0.3);
|
|
border-radius: 50%;
|
|
border-top-color: white;
|
|
animation: spin 1s ease-in-out infinite;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
[dir="rtl"] .spinner {
|
|
margin-right: 0;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.location-hierarchy-help {
|
|
background-color: #f8f9fa;
|
|
border-left: 4px solid #3498db;
|
|
padding: 1rem;
|
|
margin-top: 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.95rem;
|
|
color: #495057;
|
|
}
|
|
|
|
[dir="rtl"] .location-hierarchy-help {
|
|
border-left: none;
|
|
border-right: 4px solid #3498db;
|
|
}
|
|
|
|
.loading-spinner {
|
|
display: inline-block;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
border: 2px solid #3498db;
|
|
border-radius: 50%;
|
|
border-top-color: transparent;
|
|
animation: spin 0.8s linear infinite;
|
|
margin-left: 0.5rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="complaint-form">
|
|
<div class="alert-box info">
|
|
<h4 style="margin-top: 0;">
|
|
<i class="fas fa-info-circle"></i> {% trans "About This Form" %}
|
|
</h4>
|
|
<p style="margin-bottom: 0;">
|
|
{% 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">
|
|
{% csrf_token %}
|
|
|
|
<!-- Complainant Information Section -->
|
|
<div class="form-section">
|
|
<h3><i class="fas fa-user"></i> {% trans "Complainant Information" %}</h3>
|
|
|
|
<div class="form-group">
|
|
<label for="id_complainant_name">
|
|
{% 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 text-muted">
|
|
{% trans "Please provide your full name." %}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-group mt-3">
|
|
<label for="id_relation_to_patient">
|
|
{% 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>
|
|
</select>
|
|
<small class="form-text text-muted">
|
|
{% trans "Select your relationship to the patient." %}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="row mt-3">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="id_email">
|
|
{% trans "Email Address" %} <span class="text-muted">(Optional)</span>
|
|
</label>
|
|
<input type="email"
|
|
class="form-control"
|
|
id="id_email"
|
|
name="email"
|
|
placeholder="your@email.com">
|
|
<small class="form-text text-muted">
|
|
{% trans "We will use this to contact you about your complaint." %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="id_mobile_number">
|
|
{% 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>
|
|
<small class="form-text text-muted">
|
|
{% trans "We may contact you by phone if needed." %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Patient Information Section -->
|
|
<div class="form-section">
|
|
<h3><i class="fas fa-user-injured"></i> {% trans "Patient Information" %}</h3>
|
|
|
|
<div class="form-group">
|
|
<label for="id_patient_name">
|
|
{% 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>
|
|
<small class="form-text text-muted">
|
|
{% trans "Provide the full name of the patient." %}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-group mt-3">
|
|
<label for="id_national_id">
|
|
{% 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 'Saudi National ID or Iqama number (10 digits)' %}"
|
|
required>
|
|
<small class="form-text text-muted">
|
|
{% trans "Enter 10-digit National ID or Iqama number." %}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-group mt-3">
|
|
<label for="id_incident_date">
|
|
{% trans "Incident Date" %} <span class="required-mark">*</span>
|
|
</label>
|
|
<input type="date"
|
|
class="form-control"
|
|
id="id_incident_date"
|
|
name="incident_date"
|
|
required>
|
|
<small class="form-text text-muted">
|
|
{% trans "Date when the incident occurred." %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Complaint Details Section -->
|
|
<div class="form-section">
|
|
<h3><i class="fas fa-file-alt"></i> {% trans "Complaint Details" %}</h3>
|
|
|
|
<div class="form-group">
|
|
<label for="id_hospital">
|
|
{% 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 %}
|
|
{% get_current_language as LANGUAGE_CODE %}
|
|
<option value="{{ hospital.id }}">
|
|
{% if LANGUAGE_CODE == 'ar' and hospital.name_ar %}{{ hospital.name_ar }}{% else %}{{ hospital.name }}{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Location Hierarchy -->
|
|
<div class="mt-4">
|
|
<div class="location-hierarchy-help">
|
|
<strong><i class="fas fa-map-marker-alt"></i> {% trans "Location Hierarchy" %}</strong>
|
|
<p class="mb-0 mt-2">
|
|
{% trans "Please select the location, section, and subsection where the incident occurred. Start by selecting a location, then choose the appropriate section and subsection." %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Level 1: Location -->
|
|
<div class="form-group mt-3">
|
|
<label for="id_location">
|
|
{% trans "Area/Location" %} <span class="required-mark">*</span>
|
|
</label>
|
|
<select class="form-control"
|
|
id="id_location"
|
|
name="location"
|
|
required>
|
|
<option value="">{% trans "Select Location" %}</option>
|
|
</select>
|
|
<small class="form-text text-muted">
|
|
{% trans "Select the general location (e.g., Outpatient, Inpatient, Emergency, etc.)" %}
|
|
</small>
|
|
</div>
|
|
|
|
<!-- Level 2: Section -->
|
|
<div class="form-group mt-3" id="main_section_container" style="display: none;">
|
|
<label for="id_main_section">
|
|
{% trans "Main Section/ Department" %} <span class="required-mark">*</span>
|
|
</label>
|
|
<select class="form-control"
|
|
id="id_main_section"
|
|
name="main_section"
|
|
required>
|
|
<option value="">{% trans "Select Section" %}</option>
|
|
</select>
|
|
<small class="form-text text-muted">
|
|
{% trans "Select the specific section within the chosen location" %}
|
|
</small>
|
|
</div>
|
|
|
|
<!-- Level 3: Subsection -->
|
|
<div class="form-group mt-3" id="subsection_container" style="display: none;">
|
|
<label for="id_subsection">
|
|
{% trans "Sub-Section" %} <span class="required-mark">*</span>
|
|
</label>
|
|
<select class="form-control"
|
|
id="id_subsection"
|
|
name="subsection"
|
|
required>
|
|
<option value="">{% trans "Select Subsection" %}</option>
|
|
</select>
|
|
<small class="form-text text-muted">
|
|
{% trans "Select the most specific area related to your complaint" %}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-group mt-3">
|
|
<label for="id_staff_name">
|
|
{% trans "Staff Involved" %} <span class="text-muted">(Optional)</span>
|
|
</label>
|
|
<input type="text"
|
|
class="form-control"
|
|
id="id_staff_name"
|
|
name="staff_name"
|
|
maxlength="200"
|
|
placeholder="{% trans 'Name of staff member involved (if known)' %}">
|
|
<small class="form-text text-muted">
|
|
{% trans "If you know the name of the staff member involved, please provide it here." %}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-group mt-3">
|
|
<label for="id_complaint_details">
|
|
{% trans "Complaint Details" %} <span class="required-mark">*</span>
|
|
</label>
|
|
<textarea class="form-control"
|
|
id="id_complaint_details"
|
|
name="complaint_details"
|
|
rows="6"
|
|
placeholder="{% trans 'Please describe your complaint in detail. Include dates, names of staff involved, and any other relevant information.' %}"
|
|
required></textarea>
|
|
</div>
|
|
|
|
<div class="form-group mt-3">
|
|
<label for="id_expected_result">
|
|
{% trans "Expected Complaint Result" %} <span class="text-muted">(Optional)</span>
|
|
</label>
|
|
<textarea class="form-control"
|
|
id="id_expected_result"
|
|
name="expected_result"
|
|
rows="3"
|
|
placeholder="{% trans 'What do you expect as a resolution?' %}"></textarea>
|
|
<small class="form-text text-muted">
|
|
{% trans "Describe what you expect as a resolution to your complaint." %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Section -->
|
|
<div class="form-section">
|
|
<div class="alert-box info">
|
|
<p style="margin-bottom: 0;">
|
|
<i class="fas fa-clock"></i>
|
|
<strong>{% trans "Response Time:" %}</strong>
|
|
{% trans "We typically respond to complaints within 24-48 hours depending on severity." %}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<button type="submit" class="btn btn-submit btn-lg" id="submit_btn">
|
|
<i class="fas fa-paper-plane"></i> {% trans "Submit Complaint" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Modal for success -->
|
|
<div class="modal fade" id="successModal" tabindex="-1" role="dialog" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-body text-center" style="padding: 3rem;">
|
|
<i class="fas fa-check-circle" style="font-size: 5rem; color: #28a745; margin-bottom: 1.5rem;"></i>
|
|
<h3 style="margin-bottom: 1rem;">{% trans "Complaint Submitted Successfully!" %}</h3>
|
|
<p class="lead" style="margin-bottom: 1rem;">
|
|
{% trans "Your complaint has been received and is being reviewed." %}
|
|
</p>
|
|
<p>
|
|
<strong>{% trans "Reference Number:" %}</strong>
|
|
<span id="complaint_reference" style="font-size: 1.5rem; color: #3498db;"></span>
|
|
</p>
|
|
<p class="text-muted">
|
|
{% trans "Please save this reference number for your records." %}
|
|
</p>
|
|
<a href="{% url 'complaints:public_complaint_submit' %}" class="btn btn-primary">
|
|
{% trans "Submit Another Complaint" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
console.log("Public complaint form initialized");
|
|
|
|
// Get CSRF token
|
|
function getCSRFToken() {
|
|
// Try to get from cookie first
|
|
const cookieValue = document.cookie
|
|
.split('; ')
|
|
.find(row => row.startsWith('csrftoken='))
|
|
?.split('=')[1];
|
|
|
|
if (cookieValue) {
|
|
return cookieValue;
|
|
}
|
|
|
|
// Fallback to hidden input
|
|
return $('[name="csrfmiddlewaretoken"]').val();
|
|
}
|
|
|
|
// Set default incident date to today
|
|
const today = new Date().toISOString().split('T')[0];
|
|
$('#id_incident_date').val(today);
|
|
|
|
// Load all locations on page load
|
|
function loadLocations() {
|
|
console.log('Loading locations...');
|
|
|
|
$.ajax({
|
|
url: '/organizations/dropdowns/locations/',
|
|
type: 'GET',
|
|
success: function(response) {
|
|
console.log('Locations loaded:', response);
|
|
|
|
const locationSelect = $('#id_location');
|
|
locationSelect.find('option:not(:first)').remove();
|
|
|
|
response.forEach(function(location) {
|
|
locationSelect.append($('<option>', {
|
|
value: location.id,
|
|
text: location.name
|
|
}));
|
|
});
|
|
|
|
console.log('Total locations added:', response.length);
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Failed to load locations:', error);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '{% trans "Error" %}',
|
|
text: '{% trans "Failed to load locations. Please refresh the page." %}'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load sections based on selected location
|
|
function loadSections(locationId) {
|
|
console.log('Loading sections for location:', locationId);
|
|
|
|
if (!locationId) {
|
|
$('#main_section_container').hide();
|
|
$('#subsection_container').hide();
|
|
$('#id_main_section').find('option:not(:first)').remove();
|
|
$('#id_subsection').find('option:not(:first)').remove();
|
|
$('#id_main_section').prop('required', false);
|
|
$('#id_subsection').prop('required', false);
|
|
return;
|
|
}
|
|
|
|
const sectionSelect = $('#id_main_section');
|
|
sectionSelect.find('option:not(:first)').remove();
|
|
sectionSelect.prop('disabled', true);
|
|
|
|
$.ajax({
|
|
url: '/organizations/dropdowns/main-sections/',
|
|
type: 'GET',
|
|
success: function(response) {
|
|
console.log('Sections loaded:', response);
|
|
|
|
response.forEach(function(section) {
|
|
sectionSelect.append($('<option>', {
|
|
value: section.id,
|
|
text: section.name
|
|
}));
|
|
});
|
|
|
|
sectionSelect.prop('disabled', false);
|
|
|
|
if (response.length > 0) {
|
|
$('#main_section_container').show();
|
|
$('#id_main_section').prop('required', true);
|
|
} else {
|
|
$('#main_section_container').hide();
|
|
$('#id_main_section').prop('required', false);
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Failed to load sections:', error);
|
|
sectionSelect.prop('disabled', false);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '{% trans "Error" %}',
|
|
text: '{% trans "Failed to load sections. Please try again." %}'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load subsections based on selected location and section
|
|
function loadSubsections(locationId, sectionId) {
|
|
console.log('Loading subsections for location:', locationId, 'section:', sectionId);
|
|
|
|
if (!sectionId) {
|
|
$('#subsection_container').hide();
|
|
$('#id_subsection').find('option:not(:first)').remove();
|
|
$('#id_subsection').prop('required', false);
|
|
return;
|
|
}
|
|
|
|
const subsectionSelect = $('#id_subsection');
|
|
subsectionSelect.find('option:not(:first)').remove();
|
|
subsectionSelect.prop('disabled', true);
|
|
|
|
$.ajax({
|
|
url: '/organizations/dropdowns/subsections/',
|
|
type: 'GET',
|
|
data: {
|
|
location: locationId,
|
|
main_section: sectionId
|
|
},
|
|
success: function(response) {
|
|
console.log('Subsections loaded:', response);
|
|
|
|
response.forEach(function(subsection) {
|
|
subsectionSelect.append($('<option>', {
|
|
value: subsection.id,
|
|
text: subsection.name
|
|
}));
|
|
});
|
|
|
|
subsectionSelect.prop('disabled', false);
|
|
|
|
if (response.length > 0) {
|
|
$('#subsection_container').show();
|
|
$('#id_subsection').prop('required', true);
|
|
} else {
|
|
$('#subsection_container').hide();
|
|
$('#id_subsection').prop('required', false);
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Failed to load subsections:', error);
|
|
subsectionSelect.prop('disabled', false);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '{% trans "Error" %}',
|
|
text: '{% trans "Failed to load subsections. Please try again." %}'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle location change
|
|
$('#id_location').on('change', function() {
|
|
const locationId = $(this).val();
|
|
loadSections(locationId);
|
|
// Clear lower-level dropdowns
|
|
$('#id_main_section').val('');
|
|
$('#id_subsection').val('');
|
|
});
|
|
|
|
// Handle section change
|
|
$('#id_main_section').on('change', function() {
|
|
const sectionId = $(this).val();
|
|
const locationId = $('#id_location').val();
|
|
loadSubsections(locationId, sectionId);
|
|
// Clear lower-level dropdowns
|
|
$('#id_subsection').val('');
|
|
});
|
|
|
|
// Form submission
|
|
$('#public_complaint_form').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const submitBtn = $('#submit_btn');
|
|
const originalText = submitBtn.html();
|
|
|
|
submitBtn.prop('disabled', true).html(
|
|
'<span class="spinner"></span> {% trans "Submitting..." %}'
|
|
);
|
|
|
|
const formData = new FormData(this);
|
|
|
|
$.ajax({
|
|
url: '{% url "complaints:public_complaint_submit" %}',
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
headers: {
|
|
'X-CSRFToken': getCSRFToken()
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#complaint_reference').text(response.reference_number);
|
|
$('#successModal').modal('show');
|
|
|
|
// Reset form
|
|
document.getElementById('public_complaint_form').reset();
|
|
// Reset incident date to today
|
|
$('#id_incident_date').val(today);
|
|
// Hide all location hierarchy containers
|
|
$('#main_section_container').hide();
|
|
$('#subsection_container').hide();
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '{% trans "Error" %}',
|
|
text: response.message || '{% trans "Failed to submit complaint. Please try again." %}'
|
|
});
|
|
}
|
|
},
|
|
error: function(xhr) {
|
|
let errorMessage = '{% trans "Failed to submit complaint. Please try again." %}';
|
|
|
|
if (xhr.responseJSON && xhr.responseJSON.errors) {
|
|
errorMessage = xhr.responseJSON.errors.join('\n');
|
|
}
|
|
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '{% trans "Error" %}',
|
|
text: errorMessage
|
|
});
|
|
},
|
|
complete: function() {
|
|
submitBtn.prop('disabled', false).html(originalText);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Load locations on page initialization
|
|
console.log('Document ready - loading locations');
|
|
loadLocations();
|
|
});
|
|
</script>
|
|
{% endblock %} |