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

997 lines
32 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Slot Details - {{ slot.provider.get_full_name }} - {{ slot.date|date:"M d, Y" }}{% endblock %}
{% block css %}
<style>
.slot-detail-header {
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
border-radius: 0.5rem;
color: white;
margin-bottom: 2rem;
padding: 2rem;
}
.slot-info-grid {
display: grid;
gap: 2rem;
grid-template-columns: 2fr 1fr;
margin-bottom: 2rem;
}
.info-card {
background: white;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
padding: 1.5rem;
}
.info-card-header {
align-items: center;
border-bottom: 2px solid #f8f9fa;
display: flex;
justify-content: space-between;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
}
.info-card-title {
color: #495057;
font-size: 1.25rem;
font-weight: 600;
margin: 0;
}
.info-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.info-item {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.info-label {
color: #6c757d;
font-size: 0.85rem;
font-weight: 500;
text-transform: uppercase;
}
.info-value {
color: #495057;
font-size: 1rem;
font-weight: 600;
}
.status-badge {
align-items: center;
border-radius: 1rem;
display: inline-flex;
font-size: 0.75rem;
font-weight: 600;
gap: 0.25rem;
padding: 0.5rem 1rem;
text-transform: uppercase;
}
.status-badge.available {
background: #d4edda;
color: #155724;
}
.status-badge.booked {
background: #cce5ff;
color: #004085;
}
.status-badge.blocked {
background: #f8d7da;
color: #721c24;
}
.status-badge.expired {
background: #e2e3e5;
color: #383d41;
}
.provider-profile {
align-items: center;
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.provider-avatar {
align-items: center;
background: #007bff;
border-radius: 50%;
color: white;
display: flex;
font-size: 1.5rem;
font-weight: 600;
height: 80px;
justify-content: center;
width: 80px;
}
.provider-details {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.provider-name {
color: #495057;
font-size: 1.25rem;
font-weight: 700;
}
.provider-specialty {
color: #6c757d;
font-size: 1rem;
}
.provider-contact {
color: #6c757d;
font-size: 0.9rem;
}
.appointment-details {
background: #f8f9fa;
border-left: 4px solid #007bff;
border-radius: 0.375rem;
margin-bottom: 2rem;
padding: 1.5rem;
}
.appointment-patient {
align-items: center;
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.patient-avatar {
align-items: center;
background: #28a745;
border-radius: 50%;
color: white;
display: flex;
font-size: 1rem;
font-weight: 600;
height: 50px;
justify-content: center;
width: 50px;
}
.patient-info {
display: flex;
flex-direction: column;
}
.patient-name {
color: #495057;
font-size: 1.1rem;
font-weight: 600;
}
.patient-mrn {
color: #6c757d;
font-size: 0.9rem;
}
.timeline-section {
background: white;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
margin-bottom: 2rem;
padding: 1.5rem;
}
.timeline {
position: relative;
}
.timeline::before {
background: #dee2e6;
content: '';
height: 100%;
left: 20px;
position: absolute;
top: 0;
width: 2px;
}
.timeline-item {
margin-bottom: 2rem;
padding-left: 3rem;
position: relative;
}
.timeline-item::before {
align-items: center;
background: white;
border: 3px solid #007bff;
border-radius: 50%;
color: #007bff;
content: attr(data-icon);
display: flex;
font-family: 'Font Awesome 5 Free';
font-size: 0.8rem;
font-weight: 900;
height: 40px;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 40px;
}
.timeline-time {
color: #6c757d;
font-size: 0.85rem;
font-weight: 500;
margin-bottom: 0.5rem;
}
.timeline-content {
color: #495057;
line-height: 1.5;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: 2rem;
}
.btn-action {
align-items: center;
display: flex;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
}
.recurring-info {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.recurring-pattern {
color: #0066cc;
font-weight: 600;
margin-bottom: 0.5rem;
}
.recurring-details {
color: #004080;
font-size: 0.9rem;
line-height: 1.4;
}
.notes-section {
background: #fff8e1;
border: 1px solid #ffecb3;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.notes-title {
color: #f57f17;
font-weight: 600;
margin-bottom: 0.5rem;
}
.notes-content {
color: #f57f17;
line-height: 1.4;
}
.conflict-warning {
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
.conflict-title {
color: #c62828;
font-weight: 600;
margin-bottom: 0.5rem;
}
.conflict-list {
color: #c62828;
margin: 0;
padding-left: 1.5rem;
}
@media (max-width: 768px) {
.slot-info-grid {
grid-template-columns: 1fr;
}
.slot-detail-header {
padding: 1.5rem;
text-align: center;
}
.action-buttons {
flex-direction: column;
}
.btn-action {
justify-content: center;
width: 100%;
}
.info-grid {
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); }
}
.slot-time-display {
background: #f8f9fa;
border-radius: 0.375rem;
color: #495057;
font-family: 'Courier New', monospace;
font-size: 1.1rem;
font-weight: 600;
padding: 0.75rem 1rem;
text-align: center;
}
.duration-badge {
background: #e9ecef;
border-radius: 0.25rem;
color: #495057;
font-size: 0.85rem;
font-weight: 500;
padding: 0.25rem 0.75rem;
}
.availability-indicator {
align-items: center;
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.availability-dot {
border-radius: 50%;
height: 12px;
width: 12px;
}
.availability-dot.available { background: #28a745; }
.availability-dot.booked { background: #007bff; }
.availability-dot.blocked { background: #dc3545; }
.availability-dot.expired { background: #6c757d; }
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Page Header -->
<div class="slot-detail-header fade-in">
<div class="row align-items-center">
<div class="col-lg-8">
<h1 class="mb-2">
<i class="fas fa-clock me-3"></i>
Appointment Slot Details
</h1>
<p class="mb-3 opacity-90">
Detailed information and management for appointment slot
</p>
<div class="d-flex flex-wrap gap-3">
<div class="availability-indicator">
<div class="availability-dot {{ slot.status|lower }}"></div>
<span class="status-badge {{ slot.status|lower }}">
<i class="fas fa-circle"></i>
{{ slot.get_status_display }}
</span>
</div>
<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>
</div>
<div class="col-lg-4 text-lg-end">
<div class="d-flex flex-column gap-2">
{% if slot.status == 'AVAILABLE' %}
<button class="btn btn-light btn-lg" onclick="bookSlot()">
<i class="fas fa-calendar-plus me-2"></i>Book Appointment
</button>
{% elif slot.status == 'BOOKED' %}
<button class="btn btn-outline-light" onclick="viewAppointment()">
<i class="fas fa-eye me-2"></i>View Appointment
</button>
{% endif %}
<a href="{% url 'appointments:slot_update' slot.pk %}" class="btn btn-outline-light">
<i class="fas fa-edit me-2"></i>Edit Slot
</a>
</div>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div class="slot-info-grid fade-in">
<!-- Left Column - Main Information -->
<div>
<!-- Slot Information -->
<div class="info-card">
<div class="info-card-header">
<h4 class="info-card-title">
<i class="fas fa-info-circle text-primary me-2"></i>
Slot Information
</h4>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'appointments:slot_update' slot.pk %}">
<i class="fas fa-edit me-2"></i>Edit Slot
</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateSlot()">
<i class="fas fa-copy me-2"></i>Duplicate Slot
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="{% url 'appointments:slot_delete' slot.pk %}">
<i class="fas fa-trash me-2"></i>Delete Slot
</a></li>
</ul>
</div>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Date</div>
<div class="info-value">{{ slot.date|date:"F d, Y" }}</div>
</div>
<div class="info-item">
<div class="info-label">Time</div>
<div class="slot-time-display">
{{ slot.start_time|time:"g:i A" }} - {{ slot.end_time|time:"g:i A" }}
</div>
</div>
<div class="info-item">
<div class="info-label">Duration</div>
<div class="info-value">
<span class="duration-badge">{{ slot.duration_minutes }} minutes</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Appointment Type</div>
<div class="info-value">
<span class="badge bg-light text-dark">{{ slot.appointment_type|default:"General" }}</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-value">{{ slot.created_at|date:"M d, Y g:i A" }}</div>
</div>
<div class="info-item">
<div class="info-label">Last Modified</div>
<div class="info-value">{{ slot.updated_at|date:"M d, Y g:i A" }}</div>
</div>
</div>
</div>
<!-- Provider Information -->
<div class="info-card">
<div class="info-card-header">
<h4 class="info-card-title">
<i class="fas fa-user-md text-success me-2"></i>
Provider Information
</h4>
<a href="{% url 'hr:employee_detail' slot.provider.pk %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-external-link-alt me-1"></i>View Profile
</a>
</div>
<div class="provider-profile">
{% 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-details">
<div class="provider-name">{{ slot.provider.get_full_name }}</div>
<div class="provider-specialty">{{ slot.provider.specialty|default:"General Practice" }}</div>
<div class="provider-contact">{{ slot.provider.email }}</div>
{% if slot.provider.phone %}
<div class="provider-contact">{{ slot.provider.phone }}</div>
{% endif %}
</div>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Department</div>
<div class="info-value">{{ slot.provider.department|default:"Not assigned" }}</div>
</div>
<div class="info-item">
<div class="info-label">License Number</div>
<div class="info-value">{{ slot.provider.license_number|default:"N/A" }}</div>
</div>
<div class="info-item">
<div class="info-label">Years of Experience</div>
<div class="info-value">{{ slot.provider.years_experience|default:"N/A" }}</div>
</div>
<div class="info-item">
<div class="info-label">Languages</div>
<div class="info-value">{{ slot.provider.languages|default:"English" }}</div>
</div>
</div>
</div>
<!-- Appointment Details (if booked) -->
{% if slot.appointment %}
<div class="appointment-details">
<h5 class="mb-3">
<i class="fas fa-calendar-check text-primary me-2"></i>
Appointment Details
</h5>
<div class="appointment-patient">
{% if slot.appointment.patient.profile_picture %}
<img src="{{ slot.appointment.patient.profile_picture.url }}"
class="patient-avatar" alt="{{ slot.appointment.patient.get_full_name }}">
{% else %}
<div class="patient-avatar">
{{ slot.appointment.patient.first_name.0 }}{{ slot.appointment.patient.last_name.0 }}
</div>
{% endif %}
<div class="patient-info">
<div class="patient-name">{{ slot.appointment.patient.get_full_name }}</div>
<div class="patient-mrn">MRN: {{ slot.appointment.patient.mrn }}</div>
</div>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Appointment Type</div>
<div class="info-value">{{ slot.appointment.appointment_type|default:"Consultation" }}</div>
</div>
<div class="info-item">
<div class="info-label">Status</div>
<div class="info-value">
<span class="badge bg-primary">{{ slot.appointment.get_status_display }}</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Booked On</div>
<div class="info-value">{{ slot.appointment.created_at|date:"M d, Y g:i A" }}</div>
</div>
<div class="info-item">
<div class="info-label">Booked By</div>
<div class="info-value">{{ slot.appointment.created_by.get_full_name|default:"System" }}</div>
</div>
</div>
{% if slot.appointment.notes %}
<div class="mt-3">
<div class="info-label">Appointment Notes</div>
<div class="info-value">{{ slot.appointment.notes }}</div>
</div>
{% endif %}
</div>
{% endif %}
<!-- Recurring Information -->
{% if slot.is_recurring %}
<div class="recurring-info">
<div class="recurring-pattern">
<i class="fas fa-repeat me-2"></i>
Recurring Appointment Slot
</div>
<div class="recurring-details">
<strong>Pattern:</strong> {{ slot.recurrence_pattern|default:"Weekly" }}<br>
<strong>Frequency:</strong> Every {{ slot.recurrence_interval|default:1 }} {{ slot.recurrence_unit|default:"week" }}(s)<br>
{% if slot.recurrence_end_date %}
<strong>Ends:</strong> {{ slot.recurrence_end_date|date:"F d, Y" }}
{% else %}
<strong>Ends:</strong> No end date
{% endif %}
</div>
</div>
{% endif %}
<!-- Notes Section -->
{% if slot.notes %}
<div class="notes-section">
<div class="notes-title">
<i class="fas fa-sticky-note me-2"></i>
Slot Notes
</div>
<div class="notes-content">{{ slot.notes }}</div>
</div>
{% endif %}
<!-- Conflict Warnings -->
{% if slot.conflicts %}
<div class="conflict-warning">
<div class="conflict-title">
<i class="fas fa-exclamation-triangle me-2"></i>
Schedule Conflicts Detected
</div>
<ul class="conflict-list">
{% for conflict in slot.conflicts %}
<li>{{ conflict.description }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<!-- Right Column - Timeline and Actions -->
<div>
<!-- Activity Timeline -->
<div class="timeline-section">
<div class="info-card-header">
<h4 class="info-card-title">
<i class="fas fa-history text-info me-2"></i>
Activity Timeline
</h4>
</div>
<div class="timeline">
<div class="timeline-item" data-icon="">
<div class="timeline-time">{{ slot.created_at|date:"M d, Y g:i A" }}</div>
<div class="timeline-content">
<strong>Slot Created</strong><br>
Created by {{ slot.created_by.get_full_name|default:"System" }}
</div>
</div>
{% if slot.appointment %}
<div class="timeline-item" data-icon="">
<div class="timeline-time">{{ slot.appointment.created_at|date:"M d, Y g:i A" }}</div>
<div class="timeline-content">
<strong>Appointment Booked</strong><br>
Booked for {{ slot.appointment.patient.get_full_name }}
</div>
</div>
{% endif %}
{% if slot.updated_at != slot.created_at %}
<div class="timeline-item" data-icon="">
<div class="timeline-time">{{ slot.updated_at|date:"M d, Y g:i A" }}</div>
<div class="timeline-content">
<strong>Slot Modified</strong><br>
Last updated by {{ slot.updated_by.get_full_name|default:"System" }}
</div>
</div>
{% endif %}
{% for activity in slot.activity_log %}
<div class="timeline-item" data-icon="{{ activity.icon }}">
<div class="timeline-time">{{ activity.timestamp|date:"M d, Y g:i A" }}</div>
<div class="timeline-content">
<strong>{{ activity.title }}</strong><br>
{{ activity.description }}
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Quick Stats -->
<div class="info-card">
<div class="info-card-header">
<h4 class="info-card-title">
<i class="fas fa-chart-bar text-warning me-2"></i>
Quick Stats
</h4>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Provider's Slots Today</div>
<div class="info-value">{{ provider_slots_today|default:0 }}</div>
</div>
<div class="info-item">
<div class="info-label">Utilization Rate</div>
<div class="info-value">{{ utilization_rate|default:0 }}%</div>
</div>
<div class="info-item">
<div class="info-label">Average Duration</div>
<div class="info-value">{{ avg_duration|default:30 }} min</div>
</div>
<div class="info-item">
<div class="info-label">No-Show Rate</div>
<div class="info-value">{{ no_show_rate|default:0 }}%</div>
</div>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons fade-in">
{% if slot.status == 'AVAILABLE' %}
<button class="btn btn-primary btn-action" onclick="bookSlot()">
<i class="fas fa-calendar-plus"></i>Book Appointment
</button>
<button class="btn btn-warning btn-action" onclick="blockSlot()">
<i class="fas fa-ban"></i>Block Slot
</button>
{% elif slot.status == 'BOOKED' %}
<a href="{% url 'appointments:appointment_detail' slot.appointment.pk %}" class="btn btn-primary btn-action">
<i class="fas fa-eye"></i>View Appointment
</a>
<button class="btn btn-warning btn-action" onclick="rescheduleAppointment()">
<i class="fas fa-calendar-alt"></i>Reschedule
</button>
<button class="btn btn-danger btn-action" onclick="cancelAppointment()">
<i class="fas fa-times"></i>Cancel Appointment
</button>
{% elif slot.status == 'BLOCKED' %}
<button class="btn btn-success btn-action" onclick="unblockSlot()">
<i class="fas fa-check"></i>Unblock Slot
</button>
{% endif %}
<a href="{% url 'appointments:slot_update' slot.pk %}" class="btn btn-secondary btn-action">
<i class="fas fa-edit"></i>Edit Slot
</a>
<button class="btn btn-info btn-action" onclick="duplicateSlot()">
<i class="fas fa-copy"></i>Duplicate Slot
</button>
<button class="btn btn-outline-secondary btn-action" onclick="printSlot()">
<i class="fas fa-print"></i>Print Details
</button>
<a href="{% url 'appointments:slot_list' %}" class="btn btn-outline-secondary btn-action">
<i class="fas fa-arrow-left"></i>Back to Slots
</a>
</div>
</div>
<!-- Book Appointment Modal -->
<div class="modal fade" id="bookAppointmentModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-calendar-plus me-2"></i>Book Appointment
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="bookAppointmentForm">
<div class="mb-3">
<label class="form-label">Patient *</label>
<select class="form-select" id="patientSelect" required>
<option value="">Select Patient</option>
{% for patient in patients %}
<option value="{{ patient.id }}">{{ patient.get_full_name }} ({{ patient.mrn }})</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Appointment Type</label>
<select class="form-select" id="appointmentType">
<option value="consultation">Consultation</option>
<option value="follow_up">Follow-up</option>
<option value="procedure">Procedure</option>
<option value="emergency">Emergency</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
<textarea class="form-control" id="appointmentNotes" rows="3"
placeholder="Additional notes for the appointment..."></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="confirmBooking()">
<i class="fas fa-check me-1"></i>Book Appointment
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function bookSlot() {
$('#bookAppointmentModal').modal('show');
}
function confirmBooking() {
const formData = {
patient_id: $('#patientSelect').val(),
appointment_type: $('#appointmentType').val(),
notes: $('#appointmentNotes').val()
};
if (!formData.patient_id) {
alert('Please select a patient');
return;
}
$.ajax({
url: '{% url "appointments:slot_book" slot.pk %}',
method: 'POST',
data: formData,
headers: {
'X-CSRFToken': $('[name=csrfmiddlewaretoken]').val()
},
success: function(response) {
$('#bookAppointmentModal').modal('hide');
showAlert('Appointment booked successfully!', 'success');
setTimeout(() => location.reload(), 1500);
},
error: function() {
showAlert('Error booking appointment', 'error');
}
});
}
function blockSlot() {
if (confirm('Are you sure you want to block this slot?')) {
$.ajax({
url: '{% url "appointments:slot_block" slot.pk %}',
method: 'POST',
headers: {
'X-CSRFToken': $('[name=csrfmiddlewaretoken]').val()
},
success: function() {
showAlert('Slot blocked successfully!', 'success');
setTimeout(() => location.reload(), 1500);
},
error: function() {
showAlert('Error blocking slot', 'error');
}
});
}
}
function unblockSlot() {
if (confirm('Are you sure you want to unblock this slot?')) {
$.ajax({
url: '{% url "appointments:slot_unblock" slot.pk %}',
method: 'POST',
headers: {
'X-CSRFToken': $('[name=csrfmiddlewaretoken]').val()
},
success: function() {
showAlert('Slot unblocked successfully!', 'success');
setTimeout(() => location.reload(), 1500);
},
error: function() {
showAlert('Error unblocking slot', 'error');
}
});
}
}
function duplicateSlot() {
window.location.href = '{% url "appointments:slot_create" %}?duplicate={{ slot.pk }}';
}
function rescheduleAppointment() {
window.location.href = '{% url "appointments:appointment_reschedule" slot.appointment.pk %}';
}
function cancelAppointment() {
if (confirm('Are you sure you want to cancel this appointment?')) {
$.ajax({
url: '{% url "appointments:appointment_cancel" slot.appointment.pk %}',
method: 'POST',
headers: {
'X-CSRFToken': $('[name=csrfmiddlewaretoken]').val()
},
success: function() {
showAlert('Appointment cancelled successfully!', 'success');
setTimeout(() => location.reload(), 1500);
},
error: function() {
showAlert('Error cancelling appointment', 'error');
}
});
}
}
function viewAppointment() {
window.location.href = '{% url "appointments:appointment_detail" slot.appointment.pk %}';
}
function printSlot() {
window.print();
}
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);
}
// Auto-complete for patient search
$('#patientSelect').select2({
ajax: {
url: '/api/patients/search/',
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term
};
},
processResults: function (data) {
return {
results: data.patients.map(patient => ({
id: patient.id,
text: `${patient.name} (${patient.mrn})`
}))
};
}
},
minimumInputLength: 2,
dropdownParent: $('#bookAppointmentModal')
});
</script>
{% endblock %}