323 lines
15 KiB
HTML
323 lines
15 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Appointment Calendar{% endblock %}
|
|
|
|
{% block css %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="container-fluid">
|
|
<div class="row justify-content-center">
|
|
<div class="col-xl-12">
|
|
<ul class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'appointments:appointment_list' %}">Appointments</a></li>
|
|
<li class="breadcrumb-item active">Calendar</li>
|
|
</ul>
|
|
|
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
|
<h1 class="page-header mb-0">Appointment Calendar</h1>
|
|
<div>
|
|
<a href="#" class="btn btn-success">
|
|
<i class="fa fa-plus me-2"></i>New Appointment
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xl-9">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div id="calendar"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3">
|
|
<!-- Filters -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Filters</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="filterForm">
|
|
<div class="mb-3">
|
|
<label class="form-label">Provider</label>
|
|
<select name="provider" class="form-select" id="providerFilter">
|
|
<option value="">All Providers</option>
|
|
{% for provider in appointments.provider.all %}
|
|
<option value="{{ provider.id }}">{{ provider.first_name }} {{ provider.last_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Department</label>
|
|
<select name="department" class="form-select" id="departmentFilter">
|
|
<option value="">All Departments</option>
|
|
{% for dept in departments %}
|
|
<option value="{{ dept.id }}">{{ dept.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Status</label>
|
|
<select name="status" class="form-select" id="statusFilter">
|
|
<option value="">All Statuses</option>
|
|
<option value="scheduled">Scheduled</option>
|
|
<option value="confirmed">Confirmed</option>
|
|
<option value="checked_in">Checked In</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="cancelled">Cancelled</option>
|
|
<option value="no_show">No Show</option>
|
|
</select>
|
|
</div>
|
|
<button type="button" class="btn btn-primary" onclick="applyFilters()">
|
|
<i class="fa fa-filter me-2"></i>Apply Filters
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legend -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Status Legend</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="w-20px h-20px bg-info rounded me-2"></div>
|
|
<span>Scheduled</span>
|
|
</div>
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="w-20px h-20px bg-success rounded me-2"></div>
|
|
<span>Confirmed</span>
|
|
</div>
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="w-20px h-20px bg-primary rounded me-2"></div>
|
|
<span>Checked In</span>
|
|
</div>
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="w-20px h-20px bg-warning rounded me-2"></div>
|
|
<span>In Progress</span>
|
|
</div>
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="w-20px h-20px bg-dark rounded me-2"></div>
|
|
<span>Completed</span>
|
|
</div>
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="w-20px h-20px bg-danger rounded me-2"></div>
|
|
<span>Cancelled</span>
|
|
</div>
|
|
<div class="d-flex align-items-center">
|
|
<div class="w-20px h-20px bg-secondary rounded me-2"></div>
|
|
<span>No Show</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Today's Appointments -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Today's Appointments</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% for appointment in appointments %}
|
|
<div class="d-flex align-items-center mb-3 p-2 border rounded">
|
|
<div class="flex-grow-1">
|
|
<div class="fw-bold">{{ appointment.appointment_time|time:"g:i A" }}</div>
|
|
<div class="small">{{ appointment.patient.first_name }} {{ appointment.patient.last_name }}</div>
|
|
<div class="small text-muted">{{ appointment.provider.first_name }} {{ appointment.provider.last_name }}</div>
|
|
</div>
|
|
<div>
|
|
{% if appointment.status == 'SCHEDULED' %}
|
|
<span class="badge bg-info">Scheduled</span>
|
|
{% elif appointment.status == 'CONFIRMED' %}
|
|
<span class="badge bg-success">Confirmed</span>
|
|
{% elif appointment.status == 'CHECKED_IN' %}
|
|
<span class="badge bg-primary">Checked In</span>
|
|
{% elif appointment.status == 'IN_PROGRESS' %}
|
|
<span class="badge bg-warning">In Progress</span>
|
|
{% elif appointment.status == 'COMPLETED' %}
|
|
<span class="badge bg-dark">Completed</span>
|
|
{% elif appointment.status == 'CANCELLED' %}
|
|
<span class="badge bg-danger">Cancelled</span>
|
|
{% elif appointment.status == 'NO_SHOW' %}
|
|
<span class="badge bg-secondary">No Show</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<p class="text-muted text-center">No appointments today</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Appointment Detail Modal -->
|
|
<div class="modal fade" id="appointmentModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Appointment Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="appointmentModalBody">
|
|
<!-- Content will be loaded dynamically -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<a href="#" class="btn btn-primary" id="editAppointmentBtn">Edit</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static 'plugins/@fullcalendar/core/index.global.js' %}"></script>
|
|
<script src="{% static 'plugins/@fullcalendar/bootstrap/index.global.js' %}"></script>
|
|
<script src="{% static 'plugins/@fullcalendar/daygrid/index.global.js' %}"></script>
|
|
<script src="{% static 'plugins/@fullcalendar/timegrid/index.global.js' %}"></script>
|
|
<script src="{% static 'plugins/@fullcalendar/interaction/index.global.js' %}"></script>
|
|
<script src="{% static 'plugins/@fullcalendar/list/index.global.js' %}"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var calendarEl = document.getElementById('calendar');
|
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
|
initialView: 'dayGridMonth',
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
|
},
|
|
events: function(fetchInfo, successCallback, failureCallback) {
|
|
// Get filter values
|
|
var provider = document.getElementById('providerFilter').value;
|
|
var department = document.getElementById('departmentFilter').value;
|
|
var status = document.getElementById('statusFilter').value;
|
|
|
|
// Build URL with filters
|
|
var url = '{% url "appointments:calendar_appointments" %}?format=json';
|
|
if (provider) url += '&provider=' + provider;
|
|
if (department) url += '&department=' + department;
|
|
if (status) url += '&status=' + status;
|
|
url += '&start=' + fetchInfo.startStr + '&end=' + fetchInfo.endStr;
|
|
|
|
fetch(url)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
var events = data.map(function(appointment) {
|
|
var color = getStatusColor(appointment.status);
|
|
return {
|
|
id: appointment.id,
|
|
title: appointment.patient_name + ' - ' + appointment.provider_name,
|
|
start: appointment.appointment_datetime,
|
|
backgroundColor: color,
|
|
borderColor: color,
|
|
extendedProps: appointment
|
|
};
|
|
});
|
|
successCallback(events);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading appointments:', error);
|
|
failureCallback(error);
|
|
});
|
|
},
|
|
eventClick: function(info) {
|
|
showAppointmentDetails(info.event.extendedProps);
|
|
}
|
|
});
|
|
|
|
calendar.render();
|
|
|
|
// Store calendar reference for filter updates
|
|
window.appointmentCalendar = calendar;
|
|
});
|
|
|
|
function getStatusColor(status) {
|
|
var colors = {
|
|
'scheduled': '#17a2b8',
|
|
'confirmed': '#28a745',
|
|
'checked_in': '#007bff',
|
|
'in_progress': '#ffc107',
|
|
'completed': '#343a40',
|
|
'cancelled': '#dc3545',
|
|
'no_show': '#6c757d'
|
|
};
|
|
return colors[status] || '#17a2b8';
|
|
}
|
|
|
|
function applyFilters() {
|
|
if (window.appointmentCalendar) {
|
|
window.appointmentCalendar.refetchEvents();
|
|
}
|
|
}
|
|
|
|
function showAppointmentDetails(appointment) {
|
|
var modalBody = document.getElementById('appointmentModalBody');
|
|
var editBtn = document.getElementById('editAppointmentBtn');
|
|
|
|
modalBody.innerHTML = `
|
|
<div class="row mb-2">
|
|
<div class="col-4"><strong>Patient:</strong></div>
|
|
<div class="col-8">${appointment.patient_name}</div>
|
|
</div>
|
|
<div class="row mb-2">
|
|
<div class="col-4"><strong>Provider:</strong></div>
|
|
<div class="col-8">${appointment.provider_name}</div>
|
|
</div>
|
|
<div class="row mb-2">
|
|
<div class="col-4"><strong>Department:</strong></div>
|
|
<div class="col-8">${appointment.department_name}</div>
|
|
</div>
|
|
<div class="row mb-2">
|
|
<div class="col-4"><strong>Date/Time:</strong></div>
|
|
<div class="col-8">${new Date(appointment.appointment_datetime).toLocaleString()}</div>
|
|
</div>
|
|
<div class="row mb-2">
|
|
<div class="col-4"><strong>Type:</strong></div>
|
|
<div class="col-8">${appointment.appointment_type}</div>
|
|
</div>
|
|
<div class="row mb-2">
|
|
<div class="col-4"><strong>Status:</strong></div>
|
|
<div class="col-8"><span class="badge bg-${getStatusBadgeClass(appointment.status)}">${appointment.status}</span></div>
|
|
</div>
|
|
${appointment.notes ? `
|
|
<div class="row">
|
|
<div class="col-4"><strong>Notes:</strong></div>
|
|
<div class="col-8">${appointment.notes}</div>
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
|
|
editBtn.href = `/appointments/${appointment.id}/update/`;
|
|
|
|
var modal = new bootstrap.Modal(document.getElementById('appointmentModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function getStatusBadgeClass(status) {
|
|
var classes = {
|
|
'scheduled': 'info',
|
|
'confirmed': 'success',
|
|
'checked_in': 'primary',
|
|
'in_progress': 'warning',
|
|
'completed': 'dark',
|
|
'cancelled': 'danger',
|
|
'no_show': 'secondary'
|
|
};
|
|
return classes[status] || 'info';
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|