223 lines
9.6 KiB
HTML
223 lines
9.6 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n static %}
|
|
|
|
{% block title %}{% trans "Appointment Calendar" %} - {{ block.super }}{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.fc-event {
|
|
cursor: pointer;
|
|
}
|
|
/* Status-based event colors - customizable via context */
|
|
.fc-event-BOOKED { background-color: {{ status_colors.BOOKED }}; border-color: {{ status_colors.BOOKED }}; }
|
|
.fc-event-CONFIRMED { background-color: {{ status_colors.CONFIRMED }}; border-color: {{ status_colors.CONFIRMED }}; }
|
|
.fc-event-ARRIVED { background-color: {{ status_colors.ARRIVED }}; border-color: {{ status_colors.ARRIVED }}; color: #000; }
|
|
.fc-event-IN_PROGRESS { background-color: {{ status_colors.IN_PROGRESS }}; border-color: {{ status_colors.IN_PROGRESS }}; }
|
|
.fc-event-COMPLETED { background-color: {{ status_colors.COMPLETED }}; border-color: {{ status_colors.COMPLETED }}; }
|
|
.fc-event-CANCELLED { background-color: {{ status_colors.CANCELLED }}; border-color: {{ status_colors.CANCELLED }}; }
|
|
.fc-event-NO_SHOW { background-color: {{ status_colors.NO_SHOW }}; border-color: {{ status_colors.NO_SHOW }}; }
|
|
.fc-event-RESCHEDULED { background-color: {{ status_colors.RESCHEDULED }}; border-color: {{ status_colors.RESCHEDULED }}; color: #000; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1 class="h3 mb-0">
|
|
<i class="fa fa-calendar me-2"></i>{% trans "Appointment Calendar" %}
|
|
</h1>
|
|
<div class="btn-group">
|
|
<a href="{% url 'appointments:appointment-create' %}" class="btn btn-primary">
|
|
<i class="fa fa-plus me-1"></i>{% trans "New Appointment" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{% include "includes/messages.html" %}
|
|
|
|
<!-- Calendar Filters -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">{% trans "Service" %}</label>
|
|
<select id="service-filter" class="form-select">
|
|
<option value="">{% trans "All Services" %}</option>
|
|
{% for service in services %}
|
|
<option value="{{ service }}">{{ service }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{% trans "Provider" %}</label>
|
|
<select id="provider-filter" class="form-select">
|
|
<option value="">{% trans "All Providers" %}</option>
|
|
{% for provider in providers %}
|
|
<option value="{{ provider.id }}">{{ provider.first_name }} {{ provider.last_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{% trans "Status" %}</label>
|
|
<select id="status-filter" class="form-select">
|
|
<option value="">{% trans "All Statuses" %}</option>
|
|
<option value="BOOKED">{% trans "Booked" %}</option>
|
|
<option value="CONFIRMED">{% trans "Confirmed" %}</option>
|
|
<option value="ARRIVED">{% trans "Arrived" %}</option>
|
|
<option value="IN_PROGRESS">{% trans "In Progress" %}</option>
|
|
<option value="COMPLETED">{% trans "Completed" %}</option>
|
|
<option value="CANCELLED">{% trans "Cancelled" %}</option>
|
|
<option value="NO_SHOW">{% trans "No Show" %}</option>
|
|
<option value="RESCHEDULED">{% trans "Rescheduled" %}</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3 d-flex align-items-end">
|
|
<button id="apply-filters" class="btn btn-primary w-100">
|
|
<i class="fa fa-filter me-1"></i>{% trans "Apply Filters" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Calendar -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div id="calendar"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Appointment Detail Modal -->
|
|
<div class="modal fade" id="appointmentModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{% trans "Appointment Details" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="appointment-details">
|
|
<!-- Content loaded via HTMX -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static 'plugins/moment/min/moment.min.js' %}"></script>
|
|
<script src="{% static 'plugins/@fullcalendar/core/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 src="{% static 'plugins/@fullcalendar/bootstrap/index.global.js' %}"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const calendarEl = document.getElementById('calendar');
|
|
const modal = new bootstrap.Modal(document.getElementById('appointmentModal'));
|
|
|
|
// Calendar settings from Django context
|
|
const calendarSettings = {
|
|
initialView: '{{ calendar_settings.initial_view }}',
|
|
slotDuration: '{{ calendar_settings.slot_duration }}',
|
|
slotMinTime: '{{ calendar_settings.slot_min_time }}',
|
|
slotMaxTime: '{{ calendar_settings.slot_max_time }}',
|
|
weekends: {{ calendar_settings.weekends|yesno:"true,false" }},
|
|
allDaySlot: {{ calendar_settings.all_day_slot|yesno:"true,false" }},
|
|
eventTimeFormat: {
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
meridiem: 'short'
|
|
},
|
|
slotLabelFormat: {
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
meridiem: 'short'
|
|
}
|
|
};
|
|
|
|
const calendar = new FullCalendar.Calendar(calendarEl, {
|
|
initialView: calendarSettings.initialView,
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
|
},
|
|
editable: false,
|
|
selectable: true,
|
|
selectMirror: true,
|
|
dayMaxEvents: true,
|
|
weekends: calendarSettings.weekends,
|
|
slotDuration: calendarSettings.slotDuration,
|
|
slotMinTime: calendarSettings.slotMinTime,
|
|
slotMaxTime: calendarSettings.slotMaxTime,
|
|
allDaySlot: calendarSettings.allDaySlot,
|
|
eventTimeFormat: calendarSettings.eventTimeFormat,
|
|
slotLabelFormat: calendarSettings.slotLabelFormat,
|
|
events: function(info, successCallback, failureCallback) {
|
|
const params = new URLSearchParams({
|
|
start: info.startStr,
|
|
end: info.endStr,
|
|
service: document.getElementById('service-filter').value,
|
|
provider: document.getElementById('provider-filter').value,
|
|
status: document.getElementById('status-filter').value
|
|
});
|
|
|
|
fetch(`{% url 'appointments:appointment-events' %}?${params}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const events = data.map(apt => ({
|
|
id: apt.id,
|
|
title: `${apt.patient_name}`,
|
|
start: apt.start_time,
|
|
end: apt.end_time,
|
|
className: `fc-event-${apt.status}`,
|
|
extendedProps: {
|
|
patient: apt.patient_name,
|
|
service: apt.service_name,
|
|
provider: apt.provider_name,
|
|
status: apt.status,
|
|
room: apt.room_name
|
|
}
|
|
}));
|
|
successCallback(events);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading events:', error);
|
|
failureCallback(error);
|
|
});
|
|
},
|
|
eventClick: function(info) {
|
|
// Load appointment details via HTMX
|
|
const detailUrl = "{% url 'appointments:appointment-quick-view' pk='00000000-0000-0000-0000-000000000000' %}".replace('00000000-0000-0000-0000-000000000000', info.event.id);
|
|
htmx.ajax('GET', detailUrl, {
|
|
target: '#appointment-details',
|
|
swap: 'innerHTML'
|
|
}).then(() => {
|
|
modal.show();
|
|
});
|
|
},
|
|
select: function(info) {
|
|
// Redirect to create appointment with pre-filled date
|
|
const startDate = info.startStr;
|
|
window.location.href = `{% url 'appointments:appointment-create' %}?date=${startDate}`;
|
|
}
|
|
});
|
|
|
|
calendar.render();
|
|
|
|
// Apply filters
|
|
document.getElementById('apply-filters').addEventListener('click', function() {
|
|
calendar.refetchEvents();
|
|
});
|
|
|
|
// Auto-refresh every 2 minutes
|
|
setInterval(function() {
|
|
calendar.refetchEvents();
|
|
}, 120000);
|
|
});
|
|
</script>
|
|
{% endblock %}
|