2025-11-02 14:35:35 +03:00

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 %}