507 lines
24 KiB
HTML
507 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Operating Rooms - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="fas fa-procedures me-2"></i>Operating Rooms
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<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">Operating Rooms</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% url 'operating_theatre:operating_room_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Add Operating Room
|
|
</a>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-cog me-2"></i>Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="exportData('csv')">
|
|
<i class="fas fa-file-csv me-2"></i>Export CSV
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportData('pdf')">
|
|
<i class="fas fa-file-pdf me-2"></i>Export PDF
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkStatusUpdate()">
|
|
<i class="fas fa-edit me-2"></i>Bulk Status Update
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4" hx-get="{% url 'operating_theatre:operating_theatre_stats' %}" hx-trigger="load, every 30s">
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-primary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="h2 mb-1">{{ total_rooms|default:0 }}</div>
|
|
<div class="small">Total Rooms</div>
|
|
</div>
|
|
<div class="fa-3x opacity-50">
|
|
<i class="fas fa-procedures"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-success text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="h2 mb-1">{{ available_rooms|default:0 }}</div>
|
|
<div class="small">Available</div>
|
|
</div>
|
|
<div class="fa-3x opacity-50">
|
|
<i class="fas fa-check-circle"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-warning text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="h2 mb-1">{{ occupied_rooms|default:0 }}</div>
|
|
<div class="small">Occupied</div>
|
|
</div>
|
|
<div class="fa-3x opacity-50">
|
|
<i class="fas fa-user-md"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-danger text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="h2 mb-1">{{ maintenance_rooms|default:0 }}</div>
|
|
<div class="small">Maintenance</div>
|
|
</div>
|
|
<div class="fa-3x opacity-50">
|
|
<i class="fas fa-tools"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters and Search -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3" id="filterForm">
|
|
<div class="col-md-3">
|
|
<label for="search" class="form-label">Search</label>
|
|
<input type="text" class="form-control" id="search" name="search"
|
|
value="{{ request.GET.search }}" placeholder="Room number, name...">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="status" class="form-label">Status</label>
|
|
<select class="form-select" id="status" name="status">
|
|
<option value="">All Statuses</option>
|
|
<option value="AVAILABLE" {% if request.GET.status == 'AVAILABLE' %}selected{% endif %}>Available</option>
|
|
<option value="OCCUPIED" {% if request.GET.status == 'OCCUPIED' %}selected{% endif %}>Occupied</option>
|
|
<option value="CLEANING" {% if request.GET.status == 'CLEANING' %}selected{% endif %}>Cleaning</option>
|
|
<option value="MAINTENANCE" {% if request.GET.status == 'MAINTENANCE' %}selected{% endif %}>Maintenance</option>
|
|
<option value="OUT_OF_ORDER" {% if request.GET.status == 'OUT_OF_ORDER' %}selected{% endif %}>Out of Order</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="room_type" class="form-label">Type</label>
|
|
<select class="form-select" id="room_type" name="room_type">
|
|
<option value="">All Types</option>
|
|
<option value="GENERAL" {% if request.GET.room_type == 'GENERAL' %}selected{% endif %}>General Surgery</option>
|
|
<option value="CARDIAC" {% if request.GET.room_type == 'CARDIAC' %}selected{% endif %}>Cardiac Surgery</option>
|
|
<option value="NEURO" {% if request.GET.room_type == 'NEURO' %}selected{% endif %}>Neurosurgery</option>
|
|
<option value="ORTHOPEDIC" {% if request.GET.room_type == 'ORTHOPEDIC' %}selected{% endif %}>Orthopedic</option>
|
|
<option value="TRAUMA" {% if request.GET.room_type == 'TRAUMA' %}selected{% endif %}>Trauma</option>
|
|
<option value="EMERGENCY" {% if request.GET.room_type == 'EMERGENCY' %}selected{% endif %}>Emergency</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="floor" class="form-label">Floor</label>
|
|
<select class="form-select" id="floor" name="floor">
|
|
<option value="">All Floors</option>
|
|
{% for floor in available_floors %}
|
|
<option value="{{ floor }}" {% if request.GET.floor == floor|stringformat:"s" %}selected{% endif %}>Floor {{ floor }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label"> </label>
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-search me-2"></i>Filter
|
|
</button>
|
|
<a href="{% url 'operating_theatre:operating_room_list' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-times me-2"></i>Clear
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Operating Rooms List -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-list me-2"></i>Operating Rooms
|
|
<span class="badge bg-secondary ms-2">{{ page_obj.paginator.count }} total</span>
|
|
</h5>
|
|
<div class="d-flex gap-2">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="autoRefresh">
|
|
<label class="form-check-label" for="autoRefresh">Auto Refresh</label>
|
|
</div>
|
|
<button class="btn btn-outline-primary btn-sm" onclick="refreshData()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if object_list %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>
|
|
<input type="checkbox" id="selectAll" class="form-check-input">
|
|
</th>
|
|
<th>Room</th>
|
|
<th>Type</th>
|
|
<th>Status</th>
|
|
<th>Current Case</th>
|
|
<th>Capabilities</th>
|
|
<th>Floor</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for room in object_list %}
|
|
<tr>
|
|
<td>
|
|
<input type="checkbox" class="form-check-input room-checkbox" value="{{ room.pk }}">
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-procedures text-white"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">{{ room.room_number }}</div>
|
|
<small class="text-muted">{{ room.room_name }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ room.get_room_type_display }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if room.status == 'AVAILABLE' %}success{% elif room.status == 'OCCUPIED' %}warning{% elif room.status == 'MAINTENANCE' %}danger{% else %}secondary{% endif %}">
|
|
{{ room.get_status_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if room.current_case %}
|
|
<div class="small">
|
|
<div class="fw-semibold">{{ room.current_case.primary_procedure|truncatechars:30 }}</div>
|
|
<div class="text-muted">{{ room.current_case.patient.get_full_name }}</div>
|
|
</div>
|
|
{% else %}
|
|
<span class="text-muted">No active case</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="d-flex gap-1">
|
|
{% if room.supports_robotic %}
|
|
<span class="badge bg-primary" title="Robotic Surgery">R</span>
|
|
{% endif %}
|
|
{% if room.supports_laparoscopic %}
|
|
<span class="badge bg-info" title="Laparoscopic">L</span>
|
|
{% endif %}
|
|
{% if room.has_c_arm %}
|
|
<span class="badge bg-warning" title="C-Arm">C</span>
|
|
{% endif %}
|
|
{% if room.supports_microscopy %}
|
|
<span class="badge bg-success" title="Microscopy">M</span>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">Floor {{ room.floor_number }}</span>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="{% url 'operating_theatre:operating_room_detail' room.pk %}"
|
|
class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'operating_theatre:operating_room_update' room.pk %}"
|
|
class="btn btn-outline-secondary btn-sm" title="Edit">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split"
|
|
data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="updateRoomStatus({{ room.pk }}, 'AVAILABLE')">
|
|
<i class="fas fa-check-circle me-2 text-success"></i>Set Available
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="updateRoomStatus({{ room.pk }}, 'CLEANING')">
|
|
<i class="fas fa-broom me-2 text-info"></i>Set Cleaning
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="updateRoomStatus({{ room.pk }}, 'MAINTENANCE')">
|
|
<i class="fas fa-tools me-2 text-warning"></i>Set Maintenance
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-danger" href="{% url 'operating_theatre:operating_room_delete' room.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="card-footer">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="text-muted">
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} rooms
|
|
</div>
|
|
<nav>
|
|
<ul class="pagination pagination-sm mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Last</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-procedures fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Operating Rooms Found</h5>
|
|
<p class="text-muted">No operating rooms match your current filters.</p>
|
|
<a href="{% url 'operating_theatre:operating_room_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Add First Operating Room
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Auto-refresh functionality
|
|
let autoRefreshInterval;
|
|
document.getElementById('autoRefresh').addEventListener('change', function() {
|
|
if (this.checked) {
|
|
autoRefreshInterval = setInterval(refreshData, 30000);
|
|
} else {
|
|
clearInterval(autoRefreshInterval);
|
|
}
|
|
});
|
|
|
|
function refreshData() {
|
|
htmx.trigger('[hx-get]', 'refresh');
|
|
location.reload();
|
|
}
|
|
|
|
// Select all functionality
|
|
document.getElementById('selectAll').addEventListener('change', function() {
|
|
const checkboxes = document.querySelectorAll('.room-checkbox');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = this.checked;
|
|
});
|
|
});
|
|
|
|
// Export functionality
|
|
function exportData(format) {
|
|
const selectedRooms = Array.from(document.querySelectorAll('.room-checkbox:checked')).map(cb => cb.value);
|
|
const params = new URLSearchParams(window.location.search);
|
|
params.set('export', format);
|
|
if (selectedRooms.length > 0) {
|
|
params.set('selected', selectedRooms.join(','));
|
|
}
|
|
window.open(`${window.location.pathname}?${params.toString()}`);
|
|
}
|
|
|
|
// Bulk status update
|
|
function bulkStatusUpdate() {
|
|
const selectedRooms = Array.from(document.querySelectorAll('.room-checkbox:checked')).map(cb => cb.value);
|
|
if (selectedRooms.length === 0) {
|
|
alert('Please select at least one room.');
|
|
return;
|
|
}
|
|
|
|
const status = prompt('Enter new status (AVAILABLE, CLEANING, MAINTENANCE, OUT_OF_ORDER):');
|
|
if (status) {
|
|
// Implementation for bulk status update
|
|
console.log('Bulk update rooms:', selectedRooms, 'to status:', status);
|
|
}
|
|
}
|
|
|
|
// Update room status
|
|
function updateRoomStatus(roomId, status) {
|
|
if (confirm(`Are you sure you want to set this room to ${status}?`)) {
|
|
fetch(`{% url 'operating_theatre:update_room_status' 0 %}`.replace('0', roomId), {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
body: JSON.stringify({status: status})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating room status: ' + data.error);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error updating room status');
|
|
});
|
|
}
|
|
}
|
|
|
|
// Real-time search
|
|
document.getElementById('search').addEventListener('input', function() {
|
|
clearTimeout(this.searchTimeout);
|
|
this.searchTimeout = setTimeout(() => {
|
|
document.getElementById('filterForm').submit();
|
|
}, 500);
|
|
});
|
|
|
|
// Keyboard shortcuts
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.ctrlKey || e.metaKey) {
|
|
switch(e.key) {
|
|
case 'n':
|
|
e.preventDefault();
|
|
window.location.href = "{% url 'operating_theatre:operating_room_create' %}";
|
|
break;
|
|
case 'r':
|
|
e.preventDefault();
|
|
refreshData();
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.bg-gradient {
|
|
background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
|
}
|
|
|
|
.opacity-50 {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-1px);
|
|
transition: transform 0.2s ease-in-out;
|
|
}
|
|
|
|
.table th {
|
|
border-top: none;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.badge {
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.d-flex.justify-content-between {
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.table-responsive {
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.fa-3x {
|
|
font-size: 2em;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|