Marwan Alwali 2780a2dc7c update
2025-09-16 15:10:57 +03:00

1351 lines
44 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Book Appointment Slot - {{ slot.provider.get_full_name }}{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
<style>
.booking-header {
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
border-radius: 0.5rem;
color: white;
margin-bottom: 2rem;
padding: 2rem;
}
.booking-container {
display: grid;
gap: 2rem;
grid-template-columns: 2fr 1fr;
margin-bottom: 2rem;
}
.booking-form {
background: white;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
padding: 2rem;
}
.form-section {
border-bottom: 2px solid #f8f9fa;
margin-bottom: 2rem;
padding-bottom: 2rem;
}
.form-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-title {
color: #495057;
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1.5rem;
}
.section-icon {
align-items: center;
background: linear-gradient(135deg, #17a2b8, #138496);
border-radius: 50%;
color: white;
display: inline-flex;
font-size: 1rem;
height: 40px;
justify-content: center;
margin-right: 1rem;
width: 40px;
}
.form-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-label {
color: #495057;
font-weight: 600;
margin-bottom: 0.5rem;
}
.form-label.required::after {
color: #dc3545;
content: ' *';
}
.form-control,
.form-select {
border: 2px solid #e9ecef;
border-radius: 0.375rem;
padding: 0.75rem 1rem;
transition: all 0.3s ease;
}
.form-control:focus,
.form-select:focus {
border-color: #17a2b8;
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.25);
}
.patient-search {
position: relative;
}
.patient-results {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
display: none;
max-height: 300px;
overflow-y: auto;
position: absolute;
top: 100%;
width: 100%;
z-index: 1000;
}
.patient-item {
align-items: center;
border-bottom: 1px solid #f8f9fa;
cursor: pointer;
display: flex;
gap: 1rem;
padding: 1rem;
transition: background 0.3s ease;
}
.patient-item:hover {
background: #f8f9fa;
}
.patient-item:last-child {
border-bottom: none;
}
.patient-avatar {
align-items: center;
background: #17a2b8;
border-radius: 50%;
color: white;
display: flex;
font-size: 1rem;
font-weight: 600;
height: 40px;
justify-content: center;
width: 40px;
}
.patient-info {
flex: 1;
}
.patient-name {
color: #495057;
font-size: 1rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.patient-details {
color: #6c757d;
font-size: 0.85rem;
}
.selected-patient {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
margin-bottom: 1rem;
padding: 1rem;
}
.selected-patient-header {
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
.selected-patient-info {
align-items: center;
display: flex;
gap: 1rem;
}
.selected-patient-details {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.detail-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.detail-label {
color: #6c757d;
font-size: 0.8rem;
font-weight: 500;
text-transform: uppercase;
}
.detail-value {
color: #495057;
font-size: 0.95rem;
font-weight: 600;
}
.booking-summary {
background: white;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
padding: 1.5rem;
position: sticky;
top: 2rem;
}
.summary-header {
border-bottom: 2px solid #f8f9fa;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
}
.summary-title {
color: #495057;
font-size: 1.25rem;
font-weight: 600;
margin: 0;
}
.slot-details {
background: #f8f9fa;
border-left: 4px solid #17a2b8;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.slot-date {
color: #495057;
font-size: 1.1rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.slot-time {
background: #17a2b8;
border-radius: 0.25rem;
color: white;
font-family: 'Courier New', monospace;
font-size: 1rem;
font-weight: 600;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
text-align: center;
}
.slot-duration {
color: #6c757d;
font-size: 0.9rem;
text-align: center;
}
.provider-card {
align-items: center;
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.provider-avatar {
align-items: center;
background: #007bff;
border-radius: 50%;
color: white;
display: flex;
font-size: 1.25rem;
font-weight: 600;
height: 50px;
justify-content: center;
width: 50px;
}
.provider-info {
flex: 1;
}
.provider-name {
color: #0066cc;
font-size: 1.1rem;
font-weight: 700;
margin-bottom: 0.25rem;
}
.provider-specialty {
color: #004080;
font-size: 0.9rem;
}
.booking-actions {
display: flex;
flex-direction: column;
gap: 1rem;
}
.btn-book {
background: linear-gradient(135deg, #28a745, #20c997);
border: none;
color: white;
font-weight: 600;
padding: 1rem;
transition: all 0.3s ease;
}
.btn-book:hover {
background: linear-gradient(135deg, #20c997, #17a2b8);
color: white;
transform: translateY(-1px);
}
.btn-book:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.booking-notes {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.notes-title {
color: #856404;
font-weight: 600;
margin-bottom: 0.5rem;
}
.notes-list {
color: #856404;
font-size: 0.9rem;
margin: 0;
padding-left: 1.5rem;
}
.confirmation-section {
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.confirmation-title {
color: #155724;
font-weight: 600;
margin-bottom: 0.75rem;
}
.confirmation-checks {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-check {
align-items: center;
display: flex;
gap: 0.5rem;
}
.form-check-label {
color: #155724;
font-size: 0.9rem;
}
.emergency-contact {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.emergency-title {
color: #721c24;
font-weight: 600;
margin-bottom: 0.5rem;
}
.emergency-info {
color: #721c24;
font-size: 0.9rem;
line-height: 1.4;
}
.booking-progress {
background: #e9ecef;
border-radius: 0.5rem;
height: 8px;
margin-bottom: 1.5rem;
overflow: hidden;
}
.progress-fill {
background: linear-gradient(90deg, #17a2b8, #20c997);
height: 100%;
transition: width 0.3s ease;
width: 0%;
}
.step-indicator {
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
}
.step {
align-items: center;
background: #e9ecef;
border-radius: 50%;
color: #6c757d;
display: flex;
font-weight: 600;
height: 40px;
justify-content: center;
width: 40px;
}
.step.active {
background: #17a2b8;
color: white;
}
.step.completed {
background: #28a745;
color: white;
}
@media (max-width: 768px) {
.booking-header {
padding: 1.5rem;
text-align: center;
}
.booking-container {
grid-template-columns: 1fr;
}
.booking-summary {
position: static;
}
.form-grid {
grid-template-columns: 1fr;
}
.selected-patient-details {
grid-template-columns: 1fr;
}
}
.fade-in {
animation: fadeIn 0.6s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.validation-feedback {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.is-invalid {
border-color: #dc3545;
}
.is-valid {
border-color: #28a745;
}
.loading-overlay {
align-items: center;
background: rgba(255, 255, 255, 0.9);
display: none;
height: 100%;
justify-content: center;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9999;
}
.loading-content {
align-items: center;
display: flex;
flex-direction: column;
gap: 1rem;
}
.spinner {
animation: spin 1s linear infinite;
border: 4px solid #f3f3f3;
border-radius: 50%;
border-top: 4px solid #17a2b8;
height: 50px;
width: 50px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Page Header -->
<div class="booking-header fade-in">
<div class="row align-items-center">
<div class="col-lg-8">
<h1 class="mb-2">
<i class="fas fa-calendar-plus me-3"></i>
Book Appointment Slot
</h1>
<p class="mb-3 opacity-90">
Schedule a new appointment for the selected time slot
</p>
<div class="d-flex flex-wrap gap-3">
<div class="d-flex align-items-center gap-2">
<i class="fas fa-calendar"></i>
<span>{{ slot.date|date:"F d, Y" }}</span>
</div>
<div class="d-flex align-items-center gap-2">
<i class="fas fa-clock"></i>
<span>{{ slot.start_time|time:"g:i A" }} - {{ slot.end_time|time:"g:i A" }}</span>
</div>
<div class="d-flex align-items-center gap-2">
<i class="fas fa-user-md"></i>
<span>{{ slot.provider.get_full_name }}</span>
</div>
</div>
</div>
<div class="col-lg-4 text-lg-end">
<div class="d-flex flex-column gap-2">
<a href="{% url 'appointments:slot_detail' slot.pk %}" class="btn btn-outline-light">
<i class="fas fa-arrow-left me-2"></i>Back to Slot
</a>
<a href="{% url 'appointments:slot_list' %}" class="btn btn-outline-light">
<i class="fas fa-list me-2"></i>All Slots
</a>
</div>
</div>
</div>
</div>
<!-- Progress Indicator -->
<div class="step-indicator fade-in">
<div class="step active" id="step1">1</div>
<div class="step" id="step2">2</div>
<div class="step" id="step3">3</div>
<div class="step" id="step4">4</div>
</div>
<!-- Main Booking Container -->
<div class="booking-container fade-in">
<!-- Booking Form -->
<div class="booking-form">
<form id="bookingForm" method="post">
{% csrf_token %}
<input type="hidden" name="slot_id" value="{{ slot.id }}">
<input type="hidden" name="selected_patient_id" id="selectedPatientId">
<!-- Step 1: Patient Selection -->
<div class="form-section" id="section1">
<h3 class="section-title">
<span class="section-icon">
<i class="fas fa-user"></i>
</span>
Patient Selection
</h3>
<div class="patient-search">
<div class="form-group">
<label class="form-label required">Search Patient</label>
<input type="text" class="form-control" id="patientSearch"
placeholder="Search by name, MRN, or phone number..." required>
<div class="form-text">Type at least 3 characters to search</div>
</div>
<div class="patient-results" id="patientResults">
<!-- Search results will be populated here -->
</div>
</div>
<div class="selected-patient" id="selectedPatient" style="display: none;">
<div class="selected-patient-header">
<h6 class="mb-0">
<i class="fas fa-check-circle text-success me-2"></i>
Selected Patient
</h6>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearPatientSelection()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="selected-patient-info">
<div class="patient-avatar" id="selectedPatientAvatar"></div>
<div>
<div class="patient-name" id="selectedPatientName"></div>
<div class="patient-details" id="selectedPatientMRN"></div>
</div>
</div>
<div class="selected-patient-details" id="selectedPatientDetails">
<!-- Patient details will be populated here -->
</div>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-outline-primary" onclick="createNewPatient()">
<i class="fas fa-user-plus me-2"></i>Create New Patient
</button>
<button type="button" class="btn btn-primary" onclick="nextStep()" disabled id="step1Next">
<i class="fas fa-arrow-right me-2"></i>Next
</button>
</div>
</div>
<!-- Step 2: Appointment Details -->
<div class="form-section" id="section2" style="display: none;">
<h3 class="section-title">
<span class="section-icon">
<i class="fas fa-calendar-check"></i>
</span>
Appointment Details
</h3>
<div class="form-grid">
<div class="form-group">
<label class="form-label required">Appointment Type</label>
<select class="form-select" name="appointment_type" id="appointmentType" required>
<option value="">Select Type</option>
<option value="consultation">Consultation</option>
<option value="follow_up">Follow-up</option>
<option value="procedure">Procedure</option>
<option value="emergency">Emergency</option>
<option value="routine">Routine Check-up</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Priority</label>
<select class="form-select" name="priority" id="priority">
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Chief Complaint</label>
<input type="text" class="form-control" name="chief_complaint" id="chiefComplaint"
placeholder="Brief description of the main concern...">
</div>
<div class="form-group">
<label class="form-label">Referring Provider</label>
<select class="form-select" name="referring_provider" id="referringProvider">
<option value="">None</option>
{% for provider in providers %}
<option value="{{ provider.id }}">{{ provider.get_full_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">Appointment Notes</label>
<textarea class="form-control" name="notes" id="appointmentNotes" rows="3"
placeholder="Additional notes or special instructions..."></textarea>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-secondary" onclick="previousStep()">
<i class="fas fa-arrow-left me-2"></i>Previous
</button>
<button type="button" class="btn btn-primary" onclick="nextStep()">
<i class="fas fa-arrow-right me-2"></i>Next
</button>
</div>
</div>
<!-- Step 3: Contact & Notifications -->
<div class="form-section" id="section3" style="display: none;">
<h3 class="section-title">
<span class="section-icon">
<i class="fas fa-bell"></i>
</span>
Contact & Notifications
</h3>
<div class="form-grid">
<div class="form-group">
<label class="form-label">Preferred Contact Method</label>
<select class="form-select" name="contact_method" id="contactMethod">
<option value="phone">Phone Call</option>
<option value="sms">SMS/Text</option>
<option value="email">Email</option>
<option value="none">No Reminders</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Reminder Timing</label>
<select class="form-select" name="reminder_timing" id="reminderTiming">
<option value="24h">24 hours before</option>
<option value="2h">2 hours before</option>
<option value="1h">1 hour before</option>
<option value="30m">30 minutes before</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Emergency Contact</label>
<input type="text" class="form-control" name="emergency_contact" id="emergencyContact"
placeholder="Emergency contact name and phone...">
</div>
<div class="form-group">
<label class="form-label">Insurance Information</label>
<input type="text" class="form-control" name="insurance_info" id="insuranceInfo"
placeholder="Insurance provider and policy number...">
</div>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="send_confirmation"
id="sendConfirmation" checked>
<label class="form-check-label" for="sendConfirmation">
Send appointment confirmation immediately
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="add_to_calendar"
id="addToCalendar" checked>
<label class="form-check-label" for="addToCalendar">
Add to patient's calendar
</label>
</div>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-secondary" onclick="previousStep()">
<i class="fas fa-arrow-left me-2"></i>Previous
</button>
<button type="button" class="btn btn-primary" onclick="nextStep()">
<i class="fas fa-arrow-right me-2"></i>Next
</button>
</div>
</div>
<!-- Step 4: Confirmation -->
<div class="form-section" id="section4" style="display: none;">
<h3 class="section-title">
<span class="section-icon">
<i class="fas fa-check-circle"></i>
</span>
Confirmation
</h3>
<div class="confirmation-section">
<div class="confirmation-title">
<i class="fas fa-clipboard-check me-2"></i>
Please Confirm the Following
</div>
<div class="confirmation-checks">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirmPatient" required>
<label class="form-check-label" for="confirmPatient">
Patient information is correct and verified
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirmSlot" required>
<label class="form-check-label" for="confirmSlot">
Appointment date and time are correct
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirmProvider" required>
<label class="form-check-label" for="confirmProvider">
Provider and appointment type are appropriate
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirmContact" required>
<label class="form-check-label" for="confirmContact">
Contact information and preferences are accurate
</label>
</div>
</div>
</div>
<div class="booking-notes">
<div class="notes-title">
<i class="fas fa-info-circle me-2"></i>
Important Notes
</div>
<ul class="notes-list">
<li>Please arrive 15 minutes before your appointment time</li>
<li>Bring a valid ID and insurance card</li>
<li>If you need to cancel, please do so at least 24 hours in advance</li>
<li>Late arrivals may result in rescheduling</li>
<li>Confirmation and reminder messages will be sent to your preferred contact method</li>
</ul>
</div>
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-secondary" onclick="previousStep()">
<i class="fas fa-arrow-left me-2"></i>Previous
</button>
<button type="submit" class="btn btn-success btn-lg" id="confirmBooking" disabled>
<i class="fas fa-check me-2"></i>Confirm Booking
</button>
</div>
</div>
</form>
</div>
<!-- Booking Summary -->
<div class="booking-summary">
<div class="summary-header">
<h5 class="summary-title">
<i class="fas fa-clipboard-list text-info me-2"></i>
Booking Summary
</h5>
</div>
<!-- Progress Bar -->
<div class="booking-progress">
<div class="progress-fill" id="progressFill"></div>
</div>
<!-- Slot Details -->
<div class="slot-details">
<div class="slot-date">{{ slot.date|date:"l, F d, Y" }}</div>
<div class="slot-time">{{ slot.start_time|time:"g:i A" }} - {{ slot.end_time|time:"g:i A" }}</div>
<div class="slot-duration">Duration: {{ slot.duration_minutes }} minutes</div>
</div>
<!-- Provider Information -->
<div class="provider-card">
{% if slot.provider.profile_picture %}
<img src="{{ slot.provider.profile_picture.url }}"
class="provider-avatar" alt="{{ slot.provider.get_full_name }}">
{% else %}
<div class="provider-avatar">
{{ slot.provider.first_name.0 }}{{ slot.provider.last_name.0 }}
</div>
{% endif %}
<div class="provider-info">
<div class="provider-name">{{ slot.provider.get_full_name }}</div>
<div class="provider-specialty">{{ slot.provider.specialty|default:"General Practice" }}</div>
</div>
</div>
<!-- Summary Details -->
<div id="summaryDetails">
<div class="detail-item">
<div class="detail-label">Patient</div>
<div class="detail-value" id="summaryPatient">Not selected</div>
</div>
<div class="detail-item">
<div class="detail-label">Appointment Type</div>
<div class="detail-value" id="summaryType">Not specified</div>
</div>
<div class="detail-item">
<div class="detail-label">Priority</div>
<div class="detail-value" id="summaryPriority">Normal</div>
</div>
<div class="detail-item">
<div class="detail-label">Contact Method</div>
<div class="detail-value" id="summaryContact">Phone</div>
</div>
</div>
<!-- Emergency Contact Info -->
<div class="emergency-contact">
<div class="emergency-title">
<i class="fas fa-phone me-2"></i>
Emergency Contact
</div>
<div class="emergency-info">
For urgent matters or to cancel/reschedule:<br>
<strong>{{ hospital_phone|default:"(555) 123-4567" }}</strong><br>
Available 24/7
</div>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-content">
<div class="spinner"></div>
<h5>Processing Booking...</h5>
<p>Please wait while we confirm your appointment</p>
</div>
</div>
<!-- New Patient Modal -->
<div class="modal fade" id="newPatientModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-user-plus me-2"></i>Create New Patient
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="newPatientForm">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">First Name *</label>
<input type="text" class="form-control" name="first_name" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Last Name *</label>
<input type="text" class="form-control" name="last_name" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Date of Birth *</label>
<input type="date" class="form-control" name="date_of_birth" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Gender</label>
<select class="form-select" name="gender">
<option value="">Select Gender</option>
<option value="M">Male</option>
<option value="F">Female</option>
<option value="O">Other</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Phone Number *</label>
<input type="tel" class="form-control" name="phone" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" name="email">
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Address</label>
<textarea class="form-control" name="address" rows="2"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveNewPatient()">
<i class="fas fa-save me-1"></i>Create Patient
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/select2/js/select2.min.js' %}"></script>
<script>
let currentStep = 1;
let selectedPatient = null;
let searchTimeout;
$(document).ready(function() {
setupEventListeners();
updateProgress();
// Initialize Select2
$('#referringProvider').select2({
placeholder: 'Select referring provider...',
allowClear: true
});
});
function setupEventListeners() {
// Patient search
$('#patientSearch').on('input', function() {
clearTimeout(searchTimeout);
const query = $(this).val();
if (query.length >= 3) {
searchTimeout = setTimeout(() => searchPatients(query), 300);
} else {
$('#patientResults').hide();
}
});
// Form validation
$('input, select, textarea').on('change input', function() {
validateCurrentStep();
updateSummary();
});
// Confirmation checkboxes
$('#section4 input[type="checkbox"]').on('change', function() {
const allChecked = $('#section4 input[type="checkbox"]:checked').length ===
$('#section4 input[type="checkbox"]').length;
$('#confirmBooking').prop('disabled', !allChecked);
});
// Form submission
$('#bookingForm').on('submit', function(e) {
e.preventDefault();
submitBooking();
});
}
function searchPatients(query) {
$.ajax({
url: '/api/patients/search/',
data: { q: query },
success: function(data) {
displayPatientResults(data.patients);
},
error: function() {
$('#patientResults').hide();
}
});
}
function displayPatientResults(patients) {
const resultsContainer = $('#patientResults');
resultsContainer.empty();
if (patients.length === 0) {
resultsContainer.append(`
<div class="patient-item">
<div class="patient-info">
<div class="patient-name">No patients found</div>
<div class="patient-details">Try a different search term</div>
</div>
</div>
`);
} else {
patients.forEach(patient => {
const patientItem = $(`
<div class="patient-item" onclick="selectPatient(${patient.id})">
<div class="patient-avatar">
${patient.first_name.charAt(0)}${patient.last_name.charAt(0)}
</div>
<div class="patient-info">
<div class="patient-name">${patient.first_name} ${patient.last_name}</div>
<div class="patient-details">
MRN: ${patient.mrn} | DOB: ${patient.date_of_birth} | Phone: ${patient.phone}
</div>
</div>
</div>
`);
resultsContainer.append(patientItem);
});
}
resultsContainer.show();
}
function selectPatient(patientId) {
$.ajax({
url: `/api/patients/${patientId}/`,
success: function(patient) {
selectedPatient = patient;
displaySelectedPatient(patient);
$('#patientResults').hide();
$('#patientSearch').val('');
$('#selectedPatientId').val(patientId);
validateCurrentStep();
updateSummary();
}
});
}
function displaySelectedPatient(patient) {
$('#selectedPatientAvatar').text(patient.first_name.charAt(0) + patient.last_name.charAt(0));
$('#selectedPatientName').text(`${patient.first_name} ${patient.last_name}`);
$('#selectedPatientMRN').text(`MRN: ${patient.mrn}`);
const detailsContainer = $('#selectedPatientDetails');
detailsContainer.html(`
<div class="detail-item">
<div class="detail-label">Date of Birth</div>
<div class="detail-value">${patient.date_of_birth}</div>
</div>
<div class="detail-item">
<div class="detail-label">Phone</div>
<div class="detail-value">${patient.phone}</div>
</div>
<div class="detail-item">
<div class="detail-label">Email</div>
<div class="detail-value">${patient.email || 'Not provided'}</div>
</div>
<div class="detail-item">
<div class="detail-label">Gender</div>
<div class="detail-value">${patient.gender || 'Not specified'}</div>
</div>
<div class="detail-item">
<div class="detail-label">Insurance</div>
<div class="detail-value">${patient.insurance || 'Not provided'}</div>
</div>
<div class="detail-item">
<div class="detail-label">Emergency Contact</div>
<div class="detail-value">${patient.emergency_contact || 'Not provided'}</div>
</div>
`);
$('#selectedPatient').show();
}
function clearPatientSelection() {
selectedPatient = null;
$('#selectedPatient').hide();
$('#selectedPatientId').val('');
$('#patientSearch').val('');
validateCurrentStep();
updateSummary();
}
function createNewPatient() {
$('#newPatientModal').modal('show');
}
function saveNewPatient() {
const formData = new FormData($('#newPatientForm')[0]);
$.ajax({
url: '/api/patients/create/',
method: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRFToken': $('[name=csrfmiddlewaretoken]').val()
},
success: function(patient) {
$('#newPatientModal').modal('hide');
selectPatient(patient.id);
showAlert('New patient created successfully!', 'success');
},
error: function() {
showAlert('Error creating patient', 'error');
}
});
}
function nextStep() {
if (validateCurrentStep()) {
if (currentStep < 4) {
currentStep++;
showStep(currentStep);
updateProgress();
}
}
}
function previousStep() {
if (currentStep > 1) {
currentStep--;
showStep(currentStep);
updateProgress();
}
}
function showStep(step) {
// Hide all sections
$('.form-section').hide();
// Show current section
$(`#section${step}`).show();
// Update step indicators
$('.step').removeClass('active completed');
for (let i = 1; i < step; i++) {
$(`#step${i}`).addClass('completed');
}
$(`#step${step}`).addClass('active');
}
function updateProgress() {
const progress = (currentStep / 4) * 100;
$('#progressFill').css('width', `${progress}%`);
}
function validateCurrentStep() {
let isValid = true;
switch (currentStep) {
case 1:
isValid = selectedPatient !== null;
$('#step1Next').prop('disabled', !isValid);
break;
case 2:
isValid = $('#appointmentType').val() !== '';
break;
case 3:
// All fields in step 3 are optional
isValid = true;
break;
case 4:
isValid = $('#section4 input[type="checkbox"]:checked').length ===
$('#section4 input[type="checkbox"]').length;
break;
}
return isValid;
}
function updateSummary() {
// Update patient summary
if (selectedPatient) {
$('#summaryPatient').text(`${selectedPatient.first_name} ${selectedPatient.last_name}`);
} else {
$('#summaryPatient').text('Not selected');
}
// Update appointment type
const appointmentType = $('#appointmentType option:selected').text();
$('#summaryType').text(appointmentType || 'Not specified');
// Update priority
const priority = $('#priority option:selected').text();
$('#summaryPriority').text(priority || 'Normal');
// Update contact method
const contactMethod = $('#contactMethod option:selected').text();
$('#summaryContact').text(contactMethod || 'Phone');
}
function submitBooking() {
if (!validateCurrentStep()) {
showAlert('Please complete all required fields', 'warning');
return;
}
$('#loadingOverlay').show();
const formData = new FormData($('#bookingForm')[0]);
$.ajax({
url: '{% url "appointments:slot_book" slot.pk %}',
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
$('#loadingOverlay').hide();
showAlert('Appointment booked successfully!', 'success');
// Redirect to appointment detail or confirmation page
setTimeout(() => {
window.location.href = response.redirect_url || '/appointments/';
}, 2000);
},
error: function(xhr) {
$('#loadingOverlay').hide();
const errorMessage = xhr.responseJSON?.message || 'Error booking appointment';
showAlert(errorMessage, 'error');
}
});
}
function showAlert(message, type) {
const alertClass = type === 'success' ? 'alert-success' :
type === 'warning' ? 'alert-warning' :
type === 'info' ? 'alert-info' : 'alert-danger';
const alertHtml = `
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
$('.container-fluid').prepend(alertHtml);
setTimeout(() => $('.alert').fadeOut(), 5000);
}
// Hide patient results when clicking outside
$(document).on('click', function(e) {
if (!$(e.target).closest('.patient-search').length) {
$('#patientResults').hide();
}
});
// Initialize first step
showStep(1);
</script>
{% endblock %}