558 lines
21 KiB
HTML
558 lines
21 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}OR Equipment Management{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.equipment-header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 0.5rem;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.equipment-card {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 1rem;
|
|
background: white;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.equipment-card:hover {
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.equipment-image {
|
|
width: 100%;
|
|
height: 200px;
|
|
object-fit: cover;
|
|
border-radius: 0.375rem 0.375rem 0 0;
|
|
}
|
|
|
|
.equipment-content {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.equipment-info {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 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;
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.status-available { background-color: #28a745; }
|
|
.status-in-use { background-color: #007bff; }
|
|
.status-maintenance { background-color: #ffc107; }
|
|
.status-out-of-order { background-color: #dc3545; }
|
|
.status-reserved { background-color: #6f42c1; }
|
|
|
|
.maintenance-alert {
|
|
background: #fff3cd;
|
|
border: 1px solid #ffeaa7;
|
|
border-radius: 0.25rem;
|
|
padding: 0.75rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.filter-section {
|
|
background: #f8f9fa;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.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;
|
|
}
|
|
|
|
.equipment-actions {
|
|
display: flex;
|
|
justify-content: between;
|
|
gap: 0.5rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.equipment-header {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.equipment-info {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.equipment-actions {
|
|
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">Equipment</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">
|
|
<i class="fas fa-tools me-2"></i>OR Equipment Management
|
|
</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<div class="btn-group">
|
|
<a href="{% url 'operating_theatre:equipment_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Add Equipment
|
|
</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="exportEquipment('pdf')">
|
|
<i class="fas fa-file-pdf me-2"></i>PDF
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportEquipment('excel')">
|
|
<i class="fas fa-file-excel me-2"></i>Excel
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Equipment Header -->
|
|
<div class="equipment-header">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h2 class="mb-3">Equipment Inventory</h2>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="mb-2">
|
|
<strong>Total Equipment:</strong> {{ stats.total_equipment }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Available:</strong> {{ stats.available_equipment }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-2">
|
|
<strong>In Use:</strong> {{ stats.in_use_equipment }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Maintenance:</strong> {{ stats.maintenance_equipment }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-2">
|
|
<strong>Out of Order:</strong> {{ stats.out_of_order_equipment }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Utilization:</strong> {{ stats.utilization_percentage }}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-end">
|
|
<div class="mb-3">
|
|
<button class="btn btn-outline-light" onclick="scheduleMaintenanceCheck()">
|
|
<i class="fas fa-wrench me-1"></i>Schedule Maintenance
|
|
</button>
|
|
</div>
|
|
<div class="mb-2">
|
|
<button class="btn btn-outline-light" onclick="generateQRCodes()">
|
|
<i class="fas fa-qrcode me-1"></i>Generate QR Codes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Maintenance Alerts -->
|
|
{% if maintenance_alerts %}
|
|
<div class="maintenance-alert">
|
|
<h6 class="mb-2">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>Maintenance Alerts
|
|
</h6>
|
|
<ul class="mb-0">
|
|
{% for alert in maintenance_alerts %}
|
|
<li>{{ alert.equipment_name }} - {{ alert.message }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Statistics -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-number text-success">{{ stats.available_equipment }}</div>
|
|
<div class="stat-label">Available</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number text-primary">{{ stats.in_use_equipment }}</div>
|
|
<div class="stat-label">In Use</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number text-warning">{{ stats.maintenance_equipment }}</div>
|
|
<div class="stat-label">Maintenance</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number text-danger">{{ stats.out_of_order_equipment }}</div>
|
|
<div class="stat-label">Out of Order</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="filter-section">
|
|
<form method="get" id="filterForm">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Category</label>
|
|
<select class="form-select" name="category">
|
|
<option value="">All Categories</option>
|
|
{% for category in equipment_categories %}
|
|
<option value="{{ category.0 }}" {% if request.GET.category == category.0 %}selected{% endif %}>
|
|
{{ category.1 }}
|
|
</option>
|
|
{% endfor %}
|
|
</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="in_use" {% if request.GET.status == 'in_use' %}selected{% endif %}>In Use</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>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Location</label>
|
|
<select class="form-select" name="location">
|
|
<option value="">All Locations</option>
|
|
{% for room in operating_rooms %}
|
|
<option value="{{ room.id }}" {% if request.GET.location == 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">Search</label>
|
|
<input type="text" class="form-control" name="search"
|
|
value="{{ request.GET.search }}" placeholder="Search equipment...">
|
|
</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="{% url 'operating_theatre:equipment_list' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-times me-1"></i>Clear
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Equipment Grid -->
|
|
<div class="row">
|
|
{% for equipment in equipment_list %}
|
|
<div class="col-lg-4 col-md-6">
|
|
<div class="equipment-card">
|
|
{% if equipment.image %}
|
|
<img src="{{ equipment.image.url }}" alt="{{ equipment.name }}" class="equipment-image">
|
|
{% else %}
|
|
<div class="equipment-image bg-light d-flex align-items-center justify-content-center">
|
|
<i class="fas fa-tools fa-3x text-muted"></i>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="equipment-content">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<h5 class="mb-1">{{ equipment.name }}</h5>
|
|
<div>
|
|
<span class="status-indicator status-{{ equipment.status }}"></span>
|
|
{% if equipment.status == 'available' %}
|
|
<span class="badge bg-success">Available</span>
|
|
{% elif equipment.status == 'in_use' %}
|
|
<span class="badge bg-primary">In Use</span>
|
|
{% elif equipment.status == 'maintenance' %}
|
|
<span class="badge bg-warning">Maintenance</span>
|
|
{% elif equipment.status == 'out_of_order' %}
|
|
<span class="badge bg-danger">Out of Order</span>
|
|
{% elif equipment.status == 'reserved' %}
|
|
<span class="badge bg-info">Reserved</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="equipment-info">
|
|
<div class="info-item">
|
|
<div class="info-label">Serial Number</div>
|
|
<div class="info-value">{{ equipment.serial_number }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Category</div>
|
|
<div class="info-value">{{ equipment.get_category_display }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Manufacturer</div>
|
|
<div class="info-value">{{ equipment.manufacturer }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Model</div>
|
|
<div class="info-value">{{ equipment.model }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Location</div>
|
|
<div class="info-value">
|
|
{% if equipment.current_location %}
|
|
{{ equipment.current_location.name }}
|
|
{% else %}
|
|
<span class="text-muted">Not assigned</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Last Maintenance</div>
|
|
<div class="info-value">
|
|
{% if equipment.last_maintenance_date %}
|
|
{{ equipment.last_maintenance_date|date:"M d, Y" }}
|
|
{% else %}
|
|
<span class="text-muted">Never</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if equipment.next_maintenance_due %}
|
|
<div class="mb-2">
|
|
<small class="text-muted">
|
|
<i class="fas fa-calendar-alt me-1"></i>
|
|
Next maintenance: {{ equipment.next_maintenance_due|date:"M d, Y" }}
|
|
</small>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="equipment-actions">
|
|
<div class="btn-group flex-fill">
|
|
<a href="{% url 'operating_theatre:equipment_detail' equipment.pk %}"
|
|
class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-eye me-1"></i>View
|
|
</a>
|
|
<a href="{% url 'operating_theatre:equipment_edit' equipment.pk %}"
|
|
class="btn btn-outline-secondary btn-sm">
|
|
<i class="fas fa-edit me-1"></i>Edit
|
|
</a>
|
|
</div>
|
|
<div class="btn-group">
|
|
{% if equipment.status == 'available' %}
|
|
<button class="btn btn-success btn-sm" onclick="reserveEquipment('{{ equipment.pk }}')">
|
|
<i class="fas fa-lock me-1"></i>Reserve
|
|
</button>
|
|
{% elif equipment.status == 'reserved' %}
|
|
<button class="btn btn-warning btn-sm" onclick="releaseEquipment('{{ equipment.pk }}')">
|
|
<i class="fas fa-unlock me-1"></i>Release
|
|
</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="scheduleMaintenance('{{ equipment.pk }}')">
|
|
<i class="fas fa-wrench me-2"></i>Schedule Maintenance
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="generateQR('{{ equipment.pk }}')">
|
|
<i class="fas fa-qrcode me-2"></i>Generate QR Code
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="viewHistory('{{ equipment.pk }}')">
|
|
<i class="fas fa-history me-2"></i>View History
|
|
</a>
|
|
</li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li>
|
|
<a class="dropdown-item text-danger" href="#" onclick="reportIssue('{{ equipment.pk }}')">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>Report Issue
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-tools fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No equipment found</h5>
|
|
<p class="text-muted">No equipment matches the selected filters.</p>
|
|
<a href="{% url 'operating_theatre:equipment_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Add First Equipment
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
{% include 'partial/pagination.html' %}
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
function reserveEquipment(equipmentId) {
|
|
if (confirm('Reserve this equipment?')) {
|
|
$.ajax({
|
|
url: `/operating-theatre/equipment/${equipmentId}/reserve/`,
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error reserving equipment: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Error reserving equipment');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function releaseEquipment(equipmentId) {
|
|
if (confirm('Release this equipment reservation?')) {
|
|
$.ajax({
|
|
url: `/operating-theatre/equipment/${equipmentId}/release/`,
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error releasing equipment: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Error releasing equipment');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function scheduleMaintenance(equipmentId) {
|
|
window.location.href = `/operating-theatre/equipment/${equipmentId}/maintenance/schedule/`;
|
|
}
|
|
|
|
function generateQR(equipmentId) {
|
|
window.open(`/operating-theatre/equipment/${equipmentId}/qr-code/`, '_blank');
|
|
}
|
|
|
|
function viewHistory(equipmentId) {
|
|
window.location.href = `/operating-theatre/equipment/${equipmentId}/history/`;
|
|
}
|
|
|
|
function reportIssue(equipmentId) {
|
|
window.location.href = `/operating-theatre/equipment/${equipmentId}/report-issue/`;
|
|
}
|
|
|
|
function scheduleMaintenanceCheck() {
|
|
window.location.href = '/operating-theatre/maintenance/schedule/';
|
|
}
|
|
|
|
function generateQRCodes() {
|
|
window.open('/operating-theatre/equipment/qr-codes/bulk/', '_blank');
|
|
}
|
|
|
|
function exportEquipment(format) {
|
|
window.location.href = `/operating-theatre/equipment/export/?format=${format}`;
|
|
}
|
|
|
|
// Auto-submit form on filter change
|
|
$('#filterForm select').on('change', function() {
|
|
$('#filterForm').submit();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|