500 lines
23 KiB
HTML
500 lines
23 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}
|
|
{% if object %}Edit Waiting Queue{% else %}Create Waiting Queue{% endif %}
|
|
{% endblock %}
|
|
|
|
{% block css %}
|
|
<!-- Select2 CSS -->
|
|
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
|
<style>
|
|
.help-sidebar {
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
padding: 15px;
|
|
}
|
|
.operating-hours-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
.day-schedule {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 1rem;
|
|
}
|
|
.day-header {
|
|
font-weight: 600;
|
|
margin-bottom: 0.75rem;
|
|
color: #495057;
|
|
}
|
|
.time-inputs {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
}
|
|
.priority-weight-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 0.75rem;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.weight-label {
|
|
flex: 1;
|
|
font-weight: 500;
|
|
}
|
|
.weight-input {
|
|
width: 100px;
|
|
}
|
|
@media (max-width: 768px) {
|
|
.operating-hours-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.time-inputs {
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
.priority-weight-item {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 0.5rem;
|
|
}
|
|
.weight-input {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- begin breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Home</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'appointments:dashboard' %}">Appointments</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'appointments:waiting_queue_list' %}">Waiting Queues</a></li>
|
|
<li class="breadcrumb-item active">
|
|
{% if object %}Edit Queue{% else %}Create Queue{% endif %}
|
|
</li>
|
|
</ol>
|
|
<!-- end breadcrumb -->
|
|
|
|
<!-- begin page-header -->
|
|
<h1 class="page-header">
|
|
{% if object %}Edit Waiting Queue{% else %}Create Waiting Queue{% endif %}
|
|
</h1>
|
|
<!-- end page-header -->
|
|
|
|
<!-- begin row -->
|
|
<div class="row">
|
|
<!-- begin col-8 -->
|
|
<div class="col-xl-8">
|
|
<!-- begin panel -->
|
|
<div class="panel panel-inverse">
|
|
<!-- begin panel-heading -->
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
{% if object %}Edit Queue: {{ object.name }}{% else %}Create New Waiting Queue{% endif %}
|
|
</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
|
</div>
|
|
</div>
|
|
<!-- end panel-heading -->
|
|
|
|
<!-- begin panel-body -->
|
|
<div class="panel-body">
|
|
<form method="post" id="queueForm">
|
|
{% csrf_token %}
|
|
|
|
<!-- Form Errors -->
|
|
{% if form.errors %}
|
|
<div class="alert alert-danger">
|
|
<h5><i class="fas fa-exclamation-circle"></i> Please correct the errors below:</h5>
|
|
<ul class="mb-0">
|
|
{% for field in form %}
|
|
{% for error in field.errors %}
|
|
<li>{{ field.label }}: {{ error }}</li>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
{% for error in form.non_field_errors %}
|
|
<li>{{ error }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Basic Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><i class="fas fa-info-circle me-2"></i>Basic Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="{{ form.name.id_for_label }}" class="form-label">Queue Name <span class="text-danger">*</span></label>
|
|
{{ form.name }}
|
|
{% if form.name.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.name.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.queue_type.id_for_label }}" class="form-label">Queue Type <span class="text-danger">*</span></label>
|
|
{{ form.queue_type }}
|
|
{% if form.queue_type.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.queue_type.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="{{ form.description.id_for_label }}" class="form-label">Description</label>
|
|
{{ form.description }}
|
|
{% if form.description.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.description.errors }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Provide a detailed description of this queue's purpose</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Queue Configuration -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><i class="fas fa-cogs me-2"></i>Queue Configuration</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="{{ form.max_queue_size.id_for_label }}" class="form-label">Maximum Queue Size <span class="text-danger">*</span></label>
|
|
{{ form.max_queue_size }}
|
|
{% if form.max_queue_size.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.max_queue_size.errors }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Maximum number of patients allowed in queue</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.average_service_time_minutes.id_for_label }}" class="form-label">Average Service Time (minutes) <span class="text-danger">*</span></label>
|
|
{{ form.average_service_time_minutes }}
|
|
{% if form.average_service_time_minutes.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.average_service_time_minutes.errors }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Average time to serve each patient</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="form-check mb-2">
|
|
{{ form.is_active }}
|
|
<label class="form-check-label" for="{{ form.is_active.id_for_label }}">
|
|
Queue is active
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check mb-2">
|
|
{{ form.is_accepting_patients }}
|
|
<label class="form-check-label" for="{{ form.is_accepting_patients.id_for_label }}">
|
|
Accepting new patients
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Queue Associations -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><i class="fas fa-link me-2"></i>Queue Associations</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="{{ form.specialty.id_for_label }}" class="form-label">Medical Specialty</label>
|
|
{{ form.specialty }}
|
|
{% if form.specialty.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.specialty.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.location.id_for_label }}" class="form-label">Location</label>
|
|
{{ form.location }}
|
|
{% if form.location.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.location.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="{{ form.providers.id_for_label }}" class="form-label">Assigned Providers</label>
|
|
{{ form.providers }}
|
|
{% if form.providers.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.providers.errors }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Select one or more providers for this queue</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Operating Hours -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><i class="fas fa-clock me-2"></i>Operating Hours</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="operating-hours-grid">
|
|
{% for day in days_of_week %}
|
|
<div class="day-schedule">
|
|
<div class="day-header">{{ day.name }}</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox"
|
|
id="day_{{ day.value }}_enabled"
|
|
name="operating_hours_{{ day.value }}_enabled"
|
|
{% if day.enabled %}checked{% endif %}>
|
|
<label class="form-check-label" for="day_{{ day.value }}_enabled">
|
|
Open
|
|
</label>
|
|
</div>
|
|
<div class="time-inputs" id="times_{{ day.value }}"
|
|
{% if not day.enabled %}style="display: none;"{% endif %}>
|
|
<input type="time" class="form-control form-control-sm"
|
|
name="operating_hours_{{ day.value }}_start"
|
|
value="{{ day.start_time|default:'09:00' }}">
|
|
<span>to</span>
|
|
<input type="time" class="form-control form-control-sm"
|
|
name="operating_hours_{{ day.value }}_end"
|
|
value="{{ day.end_time|default:'17:00' }}">
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Priority Configuration -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><i class="fas fa-sort-amount-up me-2"></i>Priority Configuration</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted mb-3">Configure priority weights for queue ordering (higher values = higher priority)</p>
|
|
|
|
<div class="priority-weight-item">
|
|
<span class="weight-label">Emergency Cases</span>
|
|
<input type="number" class="form-control weight-input"
|
|
name="priority_weight_emergency"
|
|
value="{{ priority_weights.emergency|default:10 }}"
|
|
min="0" max="100" step="0.1">
|
|
</div>
|
|
|
|
<div class="priority-weight-item">
|
|
<span class="weight-label">Urgent Cases</span>
|
|
<input type="number" class="form-control weight-input"
|
|
name="priority_weight_urgent"
|
|
value="{{ priority_weights.urgent|default:5 }}"
|
|
min="0" max="100" step="0.1">
|
|
</div>
|
|
|
|
<div class="priority-weight-item">
|
|
<span class="weight-label">Elderly Patients (65+)</span>
|
|
<input type="number" class="form-control weight-input"
|
|
name="priority_weight_elderly"
|
|
value="{{ priority_weights.elderly|default:2 }}"
|
|
min="0" max="100" step="0.1">
|
|
</div>
|
|
|
|
<div class="priority-weight-item">
|
|
<span class="weight-label">Pregnant Patients</span>
|
|
<input type="number" class="form-control weight-input"
|
|
name="priority_weight_pregnant"
|
|
value="{{ priority_weights.pregnant|default:3 }}"
|
|
min="0" max="100" step="0.1">
|
|
</div>
|
|
|
|
<div class="priority-weight-item">
|
|
<span class="weight-label">Pediatric Patients</span>
|
|
<input type="number" class="form-control weight-input"
|
|
name="priority_weight_pediatric"
|
|
value="{{ priority_weights.pediatric|default:2 }}"
|
|
min="0" max="100" step="0.1">
|
|
</div>
|
|
|
|
<div class="priority-weight-item">
|
|
<span class="weight-label">Regular Appointments</span>
|
|
<input type="number" class="form-control weight-input"
|
|
name="priority_weight_regular"
|
|
value="{{ priority_weights.regular|default:1 }}"
|
|
min="0" max="100" step="0.1">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="d-flex justify-content-between mt-4">
|
|
<a href="{% url 'appointments:waiting_queue_list' %}" class="btn btn-secondary">
|
|
<i class="fas fa-times"></i> Cancel
|
|
</a>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> {% if object %}Update Queue{% else %}Create Queue{% endif %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<!-- end panel-body -->
|
|
</div>
|
|
<!-- end panel -->
|
|
</div>
|
|
<!-- end col-8 -->
|
|
|
|
<!-- begin col-4 -->
|
|
<div class="col-xl-4">
|
|
<!-- Help Sidebar -->
|
|
<div class="help-sidebar">
|
|
<h5><i class="fas fa-info-circle"></i> Help & Guidelines</h5>
|
|
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="card-title mb-0">Queue Setup Tips</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<ul class="mb-0">
|
|
<li>Choose a descriptive queue name</li>
|
|
<li>Set realistic maximum queue size</li>
|
|
<li>Estimate average service time accurately</li>
|
|
<li>Assign appropriate providers</li>
|
|
<li>Configure operating hours for each day</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="card-title mb-0">Queue Types</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<ul class="mb-0">
|
|
<li><strong>Provider:</strong> Queue managed by specific providers</li>
|
|
<li><strong>Specialty:</strong> Queue for specific medical specialty</li>
|
|
<li><strong>Location:</strong> Queue for specific location/department</li>
|
|
<li><strong>Procedure:</strong> Queue for specific procedures</li>
|
|
<li><strong>Emergency:</strong> Priority queue for emergencies</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="card-title mb-0">Priority Weights</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="mb-2">Priority weights determine patient order in queue:</p>
|
|
<ul class="mb-0">
|
|
<li>Higher values = higher priority</li>
|
|
<li>Emergency cases should have highest weight</li>
|
|
<li>Regular appointments have lowest weight</li>
|
|
<li>Adjust based on your facility's needs</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end col-4 -->
|
|
</div>
|
|
<!-- end row -->
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<!-- Select2 JS -->
|
|
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize Select2
|
|
$('#{{ form.providers.id_for_label }}').select2({
|
|
theme: 'bootstrap-5',
|
|
placeholder: 'Select providers...',
|
|
allowClear: true
|
|
});
|
|
|
|
// Operating hours toggle
|
|
$('[id^="day_"][id$="_enabled"]').on('change', function() {
|
|
const dayValue = $(this).attr('id').replace('day_', '').replace('_enabled', '');
|
|
const timesDiv = $('#times_' + dayValue);
|
|
|
|
if ($(this).is(':checked')) {
|
|
timesDiv.show();
|
|
} else {
|
|
timesDiv.hide();
|
|
}
|
|
});
|
|
|
|
// Form validation
|
|
$('#queueForm').on('submit', function(e) {
|
|
let isValid = true;
|
|
const errors = [];
|
|
|
|
// Validate required fields
|
|
if (!$('#{{ form.name.id_for_label }}').val().trim()) {
|
|
errors.push('Queue name is required');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!$('#{{ form.queue_type.id_for_label }}').val()) {
|
|
errors.push('Queue type is required');
|
|
isValid = false;
|
|
}
|
|
|
|
const maxSize = parseInt($('#{{ form.max_queue_size.id_for_label }}').val());
|
|
if (!maxSize || maxSize < 1) {
|
|
errors.push('Maximum queue size must be at least 1');
|
|
isValid = false;
|
|
}
|
|
|
|
const serviceTime = parseInt($('#{{ form.average_service_time_minutes.id_for_label }}').val());
|
|
if (!serviceTime || serviceTime < 1) {
|
|
errors.push('Average service time must be at least 1 minute');
|
|
isValid = false;
|
|
}
|
|
|
|
// Validate operating hours
|
|
let hasOperatingHours = false;
|
|
$('[id^="day_"][id$="_enabled"]:checked').each(function() {
|
|
hasOperatingHours = true;
|
|
const dayValue = $(this).attr('id').replace('day_', '').replace('_enabled', '');
|
|
const startTime = $(`[name="operating_hours_${dayValue}_start"]`).val();
|
|
const endTime = $(`[name="operating_hours_${dayValue}_end"]`).val();
|
|
|
|
if (!startTime || !endTime) {
|
|
errors.push(`Please set operating hours for enabled days`);
|
|
isValid = false;
|
|
return false;
|
|
}
|
|
|
|
if (startTime >= endTime) {
|
|
errors.push(`End time must be after start time for all enabled days`);
|
|
isValid = false;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (!hasOperatingHours) {
|
|
errors.push('Please enable at least one day of operation');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
alert(errors.join('\n'));
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|