1351 lines
44 KiB
HTML
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 %}
|
|
|