590 lines
22 KiB
HTML
590 lines
22 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}OR Block Schedule{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.schedule-header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 0.5rem;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.block-card {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 1rem;
|
|
background: white;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.block-card:hover {
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.block-header {
|
|
padding: 1rem 1.5rem;
|
|
border-bottom: 1px solid #dee2e6;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.block-content {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.block-info {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
font-weight: 600;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.info-value {
|
|
color: #495057;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.utilization-bar {
|
|
height: 20px;
|
|
background: #e9ecef;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.utilization-fill {
|
|
height: 100%;
|
|
border-radius: 10px;
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.utilization-text {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 0.75rem;
|
|
font-weight: bold;
|
|
color: #495057;
|
|
}
|
|
|
|
.case-list {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.case-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
background: #f8f9fa;
|
|
border-radius: 0.25rem;
|
|
border-left: 4px solid #007bff;
|
|
}
|
|
|
|
.case-time {
|
|
font-weight: bold;
|
|
color: #007bff;
|
|
margin-right: 1rem;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.case-details {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.case-patient {
|
|
font-weight: 600;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.case-procedure {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.case-status {
|
|
margin-left: auto;
|
|
}
|
|
|
|
.filter-section {
|
|
background: #f8f9fa;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.calendar-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.date-picker {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: #007bff;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #6c757d;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.schedule-header {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.block-info {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.case-item {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.case-time {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.calendar-nav {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div id="content" class="app-content">
|
|
<!-- Page Header -->
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:dashboard' %}">Operating Theatre</a></li>
|
|
<li class="breadcrumb-item active">Block Schedule</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">
|
|
<i class="fas fa-calendar-alt me-2"></i>OR Block Schedule
|
|
</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<div class="btn-group">
|
|
<a href="{% url 'operating_theatre:or_block_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>New Block
|
|
</a>
|
|
<button class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-download me-1"></i>Export
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="exportSchedule('pdf')">
|
|
<i class="fas fa-file-pdf me-2"></i>PDF
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportSchedule('excel')">
|
|
<i class="fas fa-file-excel me-2"></i>Excel
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Schedule Header -->
|
|
<div class="schedule-header">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h2 class="mb-3">{{ current_date|date:"l, F d, Y" }}</h2>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="mb-2">
|
|
<strong>Total Blocks:</strong> {{ stats.total_blocks }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Active Blocks:</strong> {{ stats.active_blocks }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-2">
|
|
<strong>Scheduled Cases:</strong> {{ stats.scheduled_cases }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Available Slots:</strong> {{ stats.available_slots }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-2">
|
|
<strong>Overall Utilization:</strong> {{ stats.utilization_percentage }}%
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Emergency Slots:</strong> {{ stats.emergency_slots }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-end">
|
|
<div class="calendar-nav">
|
|
<a href="?date={{ previous_date|date:'Y-m-d' }}" class="btn btn-outline-light">
|
|
<i class="fas fa-chevron-left"></i>
|
|
</a>
|
|
<div class="date-picker">
|
|
<input type="date" class="form-control" id="datePicker"
|
|
value="{{ current_date|date:'Y-m-d' }}" onchange="changeDate()">
|
|
</div>
|
|
<a href="?date={{ next_date|date:'Y-m-d' }}" class="btn btn-outline-light">
|
|
<i class="fas fa-chevron-right"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="filter-section">
|
|
<form method="get" id="filterForm">
|
|
<input type="hidden" name="date" value="{{ current_date|date:'Y-m-d' }}">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Operating Room</label>
|
|
<select class="form-select" name="room">
|
|
<option value="">All Rooms</option>
|
|
{% for room in operating_rooms %}
|
|
<option value="{{ room.id }}" {% if request.GET.room == room.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ room.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Surgeon</label>
|
|
<select class="form-select" name="surgeon">
|
|
<option value="">All Surgeons</option>
|
|
{% for surgeon in surgeons %}
|
|
<option value="{{ surgeon.id }}" {% if request.GET.surgeon == surgeon.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ surgeon.get_full_name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Block Type</label>
|
|
<select class="form-select" name="block_type">
|
|
<option value="">All Types</option>
|
|
<option value="regular" {% if request.GET.block_type == 'regular' %}selected{% endif %}>Regular</option>
|
|
<option value="emergency" {% if request.GET.block_type == 'emergency' %}selected{% endif %}>Emergency</option>
|
|
<option value="maintenance" {% if request.GET.block_type == 'maintenance' %}selected{% endif %}>Maintenance</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" name="status">
|
|
<option value="">All Status</option>
|
|
<option value="available" {% if request.GET.status == 'available' %}selected{% endif %}>Available</option>
|
|
<option value="booked" {% if request.GET.status == 'booked' %}selected{% endif %}>Booked</option>
|
|
<option value="in_progress" {% if request.GET.status == 'in_progress' %}selected{% endif %}>In Progress</option>
|
|
<option value="completed" {% if request.GET.status == 'completed' %}selected{% endif %}>Completed</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<button type="submit" class="btn btn-primary me-2">
|
|
<i class="fas fa-filter me-1"></i>Apply Filters
|
|
</button>
|
|
<a href="?date={{ current_date|date:'Y-m-d' }}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-times me-1"></i>Clear
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Block Schedule -->
|
|
<div class="row">
|
|
{% for block in blocks %}
|
|
<div class="col-lg-6 col-xl-4">
|
|
<div class="block-card">
|
|
<div class="block-header">
|
|
<div>
|
|
<h5 class="mb-1">{{ block.operating_room.name }}</h5>
|
|
<small class="text-muted">{{ block.start_time|date:"H:i" }} - {{ block.end_time|date:"H:i" }}</small>
|
|
</div>
|
|
<div>
|
|
{% if block.status == 'available' %}
|
|
<span class="badge bg-success">Available</span>
|
|
{% elif block.status == 'booked' %}
|
|
<span class="badge bg-primary">Booked</span>
|
|
{% elif block.status == 'in_progress' %}
|
|
<span class="badge bg-warning">In Progress</span>
|
|
{% elif block.status == 'completed' %}
|
|
<span class="badge bg-secondary">Completed</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="block-content">
|
|
<div class="block-info">
|
|
<div class="info-item">
|
|
<div class="info-label">Assigned Surgeon</div>
|
|
<div class="info-value">
|
|
{% if block.assigned_surgeon %}
|
|
{{ block.assigned_surgeon.get_full_name }}
|
|
{% else %}
|
|
<span class="text-muted">Unassigned</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Block Type</div>
|
|
<div class="info-value">{{ block.get_block_type_display }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Duration</div>
|
|
<div class="info-value">{{ block.duration_hours }} hours</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Utilization</div>
|
|
<div class="info-value">
|
|
<div class="utilization-bar">
|
|
<div class="utilization-fill bg-{% if block.utilization_percentage >= 80 %}success{% elif block.utilization_percentage >= 60 %}warning{% else %}danger{% endif %}"
|
|
style="width: {{ block.utilization_percentage }}%"></div>
|
|
<div class="utilization-text">{{ block.utilization_percentage }}%</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if block.scheduled_cases.exists %}
|
|
<div class="case-list">
|
|
<h6 class="mb-2">
|
|
<i class="fas fa-procedures me-1"></i>Scheduled Cases ({{ block.scheduled_cases.count }})
|
|
</h6>
|
|
{% for case in block.scheduled_cases.all %}
|
|
<div class="case-item">
|
|
<div class="case-time">{{ case.scheduled_start_time|date:"H:i" }}</div>
|
|
<div class="case-details">
|
|
<div class="case-patient">{{ case.patient.get_full_name }}</div>
|
|
<div class="case-procedure">{{ case.procedure_name }}</div>
|
|
</div>
|
|
<div class="case-status">
|
|
{% if case.status == 'scheduled' %}
|
|
<span class="badge bg-primary">Scheduled</span>
|
|
{% elif case.status == 'in_progress' %}
|
|
<span class="badge bg-warning">In Progress</span>
|
|
{% elif case.status == 'completed' %}
|
|
<span class="badge bg-success">Completed</span>
|
|
{% elif case.status == 'cancelled' %}
|
|
<span class="badge bg-danger">Cancelled</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-calendar-plus fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted mb-0">No cases scheduled</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="d-flex justify-content-between mt-3">
|
|
<div class="btn-group">
|
|
<a href="{% url 'operating_theatre:block_detail' block.pk %}"
|
|
class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-eye me-1"></i>View
|
|
</a>
|
|
{% if block.status == 'available' %}
|
|
<a href="{% url 'operating_theatre:block_edit' block.pk %}"
|
|
class="btn btn-outline-secondary btn-sm">
|
|
<i class="fas fa-edit me-1"></i>Edit
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
<div class="btn-group">
|
|
{% if block.status == 'available' %}
|
|
<button class="btn btn-success btn-sm" onclick="addCase('{{ block.pk }}')">
|
|
<i class="fas fa-plus me-1"></i>Add Case
|
|
</button>
|
|
{% endif %}
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle"
|
|
data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="duplicateBlock('{{ block.pk }}')">
|
|
<i class="fas fa-copy me-2"></i>Duplicate
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="printBlock('{{ block.pk }}')">
|
|
<i class="fas fa-print me-2"></i>Print Schedule
|
|
</a>
|
|
</li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li>
|
|
<a class="dropdown-item text-danger" href="#" onclick="cancelBlock('{{ block.pk }}')">
|
|
<i class="fas fa-times me-2"></i>Cancel Block
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No blocks scheduled</h5>
|
|
<p class="text-muted">No OR blocks are scheduled for the selected date and filters.</p>
|
|
<a href="{% url 'operating_theatre:or_block_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create First Block
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="d-flex justify-content-center mt-4">
|
|
<nav>
|
|
<ul class="pagination">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
|
</li>
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
function changeDate() {
|
|
const selectedDate = document.getElementById('datePicker').value;
|
|
window.location.href = `?date=${selectedDate}`;
|
|
}
|
|
|
|
function addCase(blockId) {
|
|
window.location.href = `/operating-theatre/cases/create/?block=${blockId}`;
|
|
}
|
|
|
|
function duplicateBlock(blockId) {
|
|
if (confirm('Duplicate this block for another date?')) {
|
|
window.location.href = `/operating-theatre/blocks/${blockId}/duplicate/`;
|
|
}
|
|
}
|
|
|
|
function printBlock(blockId) {
|
|
window.open(`/operating-theatre/blocks/${blockId}/print/`, '_blank');
|
|
}
|
|
|
|
function cancelBlock(blockId) {
|
|
if (confirm('Cancel this OR block? All scheduled cases will need to be rescheduled.')) {
|
|
$.ajax({
|
|
url: `/operating-theatre/blocks/${blockId}/cancel/`,
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error cancelling block: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Error cancelling block');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function exportSchedule(format) {
|
|
const currentDate = '{{ current_date|date:"Y-m-d" }}';
|
|
window.location.href = `/operating-theatre/blocks/export/?date=${currentDate}&format=${format}`;
|
|
}
|
|
|
|
// Auto-submit form on filter change
|
|
$('#filterForm select').on('change', function() {
|
|
$('#filterForm').submit();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|