Marwan Alwali 4ca3f7159a update
2025-09-22 01:37:55 +03:00

1225 lines
31 KiB
Plaintext

{% extends 'base.html' %}
{% load static custom_filters %}
{% block title %}Bed Management - Hospital HMS{% endblock %}
{% block css %}
<style>
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
--warning-gradient: linear-gradient(135deg, #fcb045 0%, #fd1d1d 100%);
--info-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--danger-gradient: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
--maintenance-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--cleaning-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--shadow-light: 0 2px 10px rgba(0,0,0,0.1);
--shadow-medium: 0 4px 20px rgba(0,0,0,0.15);
--shadow-heavy: 0 8px 30px rgba(0,0,0,0.2);
--border-radius: 12px;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.management-header {
background: var(--primary-gradient);
color: white;
padding: 2rem;
border-radius: var(--border-radius);
margin-bottom: 2rem;
box-shadow: var(--shadow-medium);
position: relative;
overflow: hidden;
}
.management-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 20s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-light);
border: 1px solid rgba(0,0,0,0.05);
transition: var(--transition);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: var(--primary-gradient);
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-heavy);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 1rem;
position: relative;
}
.stat-icon.total { background: var(--primary-gradient); color: white; }
.stat-icon.available { background: var(--success-gradient); color: white; }
.stat-icon.occupied { background: var(--danger-gradient); color: white; }
.stat-icon.maintenance { background: var(--warning-gradient); color: white; }
.stat-number {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 0.9rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #6c757d;
margin-bottom: 0.5rem;
}
.stat-trend {
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 0.25rem;
}
.trend-up { color: #28a745; }
.trend-down { color: #dc3545; }
.main-content {
display: grid;
grid-template-columns: 1fr 320px;
gap: 2rem;
}
.filters-section {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-light);
margin-bottom: 2rem;
border: 1px solid rgba(0,0,0,0.05);
}
.filters-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-label {
font-size: 0.85rem;
font-weight: 600;
color: #495057;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.filter-input {
padding: 0.5rem;
border: 1px solid #dee2e6;
border-radius: 6px;
font-size: 0.9rem;
transition: var(--transition);
}
.filter-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.search-input {
position: relative;
}
.search-input i {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
}
.ward-section {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: var(--shadow-light);
border: 1px solid rgba(0,0,0,0.05);
transition: var(--transition);
}
.ward-section:hover {
box-shadow: var(--shadow-medium);
}
.ward-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #f0f0f0;
}
.ward-title {
font-size: 1.25rem;
font-weight: 600;
color: #495057;
margin: 0;
}
.ward-stats {
display: flex;
gap: 1rem;
align-items: center;
}
.ward-stat {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: #6c757d;
}
.beds-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
}
.bed-card {
background: white;
border-radius: 10px;
padding: 1rem;
box-shadow: var(--shadow-light);
border: 2px solid transparent;
transition: var(--transition);
cursor: pointer;
position: relative;
overflow: hidden;
}
.bed-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--primary-gradient);
}
.bed-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-medium);
}
.bed-card.selected {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.bed-card.available {
background: linear-gradient(135deg, #f8fff8 0%, #e8f5e8 100%);
border-left: 4px solid #28a745;
}
.bed-card.available::before {
background: var(--success-gradient);
}
.bed-card.occupied {
background: linear-gradient(135deg, #fff8f8 0%, #feeaea 100%);
border-left: 4px solid #dc3545;
}
.bed-card.occupied::before {
background: var(--danger-gradient);
}
.bed-card.maintenance {
background: linear-gradient(135deg, #fffbf0 0%, #fef3c7 100%);
border-left: 4px solid #f59e0b;
}
.bed-card.maintenance::before {
background: var(--warning-gradient);
}
.bed-card.cleaning {
background: linear-gradient(135deg, #f0fdff 0%, #cffafe 100%);
border-left: 4px solid #06b6d4;
}
.bed-card.cleaning::before {
background: var(--cleaning-gradient);
}
.bed-card.blocked {
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
border-left: 4px solid #ef4444;
}
.bed-card.out_of_order {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
border-left: 4px solid #6b7280;
}
.bed-icon-container {
text-align: center;
margin-bottom: 1rem;
}
.bed-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
margin-bottom: 0.5rem;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.bed-card.available .bed-icon { background: var(--success-gradient); color: white; }
.bed-card.occupied .bed-icon { background: var(--danger-gradient); color: white; }
.bed-card.maintenance .bed-icon { background: var(--warning-gradient); color: white; }
.bed-card.cleaning .bed-icon { background: var(--cleaning-gradient); color: white; }
.bed-card.blocked .bed-icon { background: var(--danger-gradient); color: white; }
.bed-card.out_of_order .bed-icon { background: #f3f4f6; color: #6b7280; }
.bed-number {
font-size: 1.1rem;
font-weight: 700;
color: #1f2937;
text-align: center;
margin-bottom: 0.25rem;
}
.bed-room {
font-size: 0.8rem;
color: #6b7280;
text-align: center;
margin-bottom: 0.5rem;
}
.bed-patient {
font-size: 0.85rem;
color: #374151;
text-align: center;
margin-bottom: 0.25rem;
font-weight: 500;
}
.bed-time {
font-size: 0.75rem;
color: #9ca3af;
text-align: center;
}
.bed-status {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.bed-card.available .bed-status { background: #dcfce7; color: #166534; }
.bed-card.occupied .bed-status { background: #fee2e2; color: #991b1b; }
.bed-card.maintenance .bed-status { background: #fef3c7; color: #92400e; }
.bed-card.cleaning .bed-status { background: #cffafe; color: #0e7490; }
.bed-card.blocked .bed-status { background: #fee2e2; color: #991b1b; }
.bed-card.out_of_order .bed-status { background: #f3f4f6; color: #374151; }
.bed-actions {
position: absolute;
bottom: 0.5rem;
left: 0.5rem;
right: 0.5rem;
opacity: 0;
transition: var(--transition);
}
.bed-card:hover .bed-actions {
opacity: 1;
}
.action-buttons {
display: flex;
gap: 0.25rem;
justify-content: center;
}
.action-btn {
width: 28px;
height: 28px;
border-radius: 6px;
border: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
cursor: pointer;
transition: var(--transition);
}
.action-btn:hover {
transform: scale(1.1);
}
.action-btn.view { background: #e3f2fd; color: #1976d2; }
.action-btn.edit { background: #f3e5f5; color: #7b1fa2; }
.action-btn.assign { background: #e8f5e8; color: #388e3c; }
.action-btn.discharge { background: #fff3e0; color: #f57c00; }
.action-btn.more { background: #f5f5f5; color: #616161; }
.sidebar {
position: sticky;
top: 2rem;
}
.sidebar-panel {
background: white;
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-light);
border: 1px solid rgba(0,0,0,0.05);
margin-bottom: 1.5rem;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid #f0f0f0;
}
.panel-title {
font-size: 1rem;
font-weight: 600;
color: #1f2937;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.panel-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
}
.panel-icon.primary { background: var(--primary-gradient); color: white; }
.panel-icon.success { background: var(--success-gradient); color: white; }
.panel-icon.warning { background: var(--warning-gradient); color: white; }
.occupancy-display {
text-align: center;
margin-bottom: 1rem;
}
.occupancy-percentage {
font-size: 2rem;
font-weight: 700;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.occupancy-label {
font-size: 0.9rem;
color: #6b7280;
margin-bottom: 1rem;
}
.occupancy-bar {
height: 12px;
background: #e5e7eb;
border-radius: 6px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.occupancy-fill {
height: 100%;
background: var(--primary-gradient);
border-radius: 6px;
transition: width 0.5s ease;
}
.occupancy-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
font-size: 0.8rem;
color: #6b7280;
}
.ward-breakdown {
space-y: 0.75rem;
}
.ward-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-radius: 6px;
background: #f9fafb;
margin-bottom: 0.5rem;
}
.ward-name {
font-weight: 500;
color: #374151;
}
.ward-occupancy {
display: flex;
align-items: center;
gap: 0.5rem;
}
.ward-bar {
width: 60px;
height: 6px;
background: #e5e7eb;
border-radius: 3px;
overflow: hidden;
}
.ward-fill {
height: 100%;
border-radius: 3px;
transition: width 0.3s ease;
}
.ward-percentage {
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
min-width: 35px;
text-align: right;
}
.quick-actions {
display: grid;
gap: 0.75rem;
}
.quick-btn {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: white;
color: #374151;
text-decoration: none;
transition: var(--transition);
font-weight: 500;
}
.quick-btn:hover {
border-color: #667eea;
background: #f8faff;
color: #667eea;
transform: translateX(2px);
}
.quick-btn i {
font-size: 1.1rem;
width: 20px;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 1024px) {
.main-content {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
}
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.beds-grid {
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.filters-grid {
grid-template-columns: 1fr;
}
.ward-stats {
flex-direction: column;
gap: 0.5rem;
align-items: flex-start;
}
}
@media (max-width: 576px) {
.beds-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.management-header {
padding: 1.5rem;
}
.stat-card {
padding: 1rem;
}
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div>
<h1 class="h2">
<i class="fas fa-bed me-2"></i>Bed<span class="fw-light">Management</span>
</h1>
<p class="text-muted">Real-time bed occupancy and management dashboard</p>
</div>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a href="{% url 'inpatients:bed_create' %}" class="btn btn-primary btn-action">
<i class="fas fa-plus me-1"></i>Add Bed
</a>
<button class="btn btn-outline-info btn-action" onclick="viewFloorPlan()">
<i class="fas fa-map me-1"></i>Floor Plan
</button>
<button class="btn btn-outline-success btn-action" onclick="generateReport()">
<i class="fas fa-chart-bar me-1"></i>Reports
</button>
</div>
</div>
</div>
<!-- Management Header -->
<div class="management-header">
<div class="row">
<div class="col-lg-8">
<h3 class="mb-2">Hospital Bed Management System</h3>
<p class="mb-0 opacity-75">Monitor and manage bed occupancy across all wards in real-time</p>
</div>
<div class="col-lg-4 text-end">
<div class="d-flex align-items-center justify-content-end gap-3">
<div>
<div class="fw-bold fs-4">{{ total_beds }}</div>
<small class="text-white-50">Total Beds</small>
</div>
<div class="vr bg-white opacity-25"></div>
<div>
<div class="fw-bold fs-4 text-success">{{ available_beds }}</div>
<small class="text-white-50">Available</small>
</div>
<div class="vr bg-white opacity-25"></div>
<div>
<div class="fw-bold fs-4 text-danger">{{ occupied_beds }}</div>
<small class="text-white-50">Occupied</small>
</div>
</div>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon total">
<i class="fas fa-bed"></i>
</div>
<div class="stat-number">{{ total_beds }}</div>
<div class="stat-label">Total Beds</div>
<div class="stat-trend">
<i class="fas fa-arrow-up trend-up"></i>
<span>100% Capacity</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon available">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-number">{{ available_beds }}</div>
<div class="stat-label">Available Beds</div>
<div class="stat-trend">
<i class="fas fa-arrow-{% if available_beds > total_beds|div:2 %}up trend-up{% else %}down trend-down{% endif %}"></i>
<span>{{ available_beds|div:total_beds|mul:100|floatformat:0 }}% Free</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon occupied">
<i class="fas fa-users"></i>
</div>
<div class="stat-number">{{ occupied_beds }}</div>
<div class="stat-label">Occupied Beds</div>
<div class="stat-trend">
<i class="fas fa-arrow-{% if occupied_beds > total_beds|div:2 %}up trend-up{% else %}down trend-down{% endif %}"></i>
<span>{{ occupied_beds|div:total_beds|mul:100|floatformat:0 }}% Utilized</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon maintenance">
<i class="fas fa-tools"></i>
</div>
<div class="stat-number">{{ maintenance_beds }}</div>
<div class="stat-label">Maintenance</div>
<div class="stat-trend">
<i class="fas fa-arrow-{% if maintenance_beds > 0 %}up trend-down{% else %}down trend-up{% endif %}"></i>
<span>{{ maintenance_beds|div:total_beds|mul:100|floatformat:0 }}% Down</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Beds Section -->
<div class="beds-section">
<!-- Filters -->
<div class="filters-section">
<div class="filters-grid">
<div class="filter-group">
<label class="filter-label">Ward</label>
<select class="filter-input" id="wardFilter">
<option value="">All Wards</option>
{% for ward in wards %}
<option value="{{ ward.id }}">{{ ward.name }}</option>
{% endfor %}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Status</label>
<select class="filter-input" id="statusFilter">
<option value="">All Statuses</option>
<option value="AVAILABLE">Available</option>
<option value="OCCUPIED">Occupied</option>
<option value="MAINTENANCE">Maintenance</option>
<option value="BLOCKED">Blocked</option>
<option value="CLEANING">Cleaning</option>
<option value="OUT_OF_ORDER">Out of Order</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Bed Type</label>
<select class="filter-input" id="bedTypeFilter">
<option value="">All Types</option>
<option value="STANDARD">Standard</option>
<option value="ICU">ICU</option>
<option value="ISOLATION">Isolation</option>
<option value="PEDIATRIC">Pediatric</option>
<option value="MATERNITY">Maternity</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">Search</label>
<div class="search-input">
<input type="text" class="filter-input" placeholder="Search beds..." id="bedSearch" />
<i class="fas fa-search"></i>
</div>
</div>
</div>
</div>
<!-- Ward Sections -->
{% for ward in wards %}
<div class="ward-section" data-ward="{{ ward.id }}">
<div class="ward-header">
<h5 class="ward-title">
<i class="fas fa-hospital me-2 text-primary"></i>{{ ward.name }}
</h5>
<div class="ward-stats">
<div class="ward-stat">
<i class="fas fa-bed me-1"></i>
<span>{{ ward.beds.count }} beds</span>
</div>
<div class="ward-stat">
<i class="fas fa-chart-pie me-1"></i>
<span>{{ ward.occupancy_rate|floatformat:0 }}% occupied</span>
</div>
</div>
</div>
<div class="beds-grid">
{% for bed in ward.beds.all %}
<div class="bed-card {{ bed.status|lower }}"
data-bed-id="{{ bed.id }}"
data-status="{{ bed.status|lower }}"
data-type="{{ bed.bed_type }}"
onclick="selectBed('{{ bed.id }}')">
<div class="bed-icon-container">
<div class="bed-icon">
<i class="fas fa-bed"></i>
</div>
<div class="bed-number">{{ bed.bed_number }}</div>
<div class="bed-room">Room {{ bed.room_number }}</div>
</div>
{% if bed.current_admission %}
<div class="bed-patient">{{ bed.current_admission.patient.get_full_name }}</div>
<div class="bed-time">{{ bed.occupied_since|timesince }} ago</div>
{% endif %}
<div class="bed-status">
{{ bed.get_status_display }}
</div>
<div class="bed-actions">
<div class="action-buttons">
<a href="{% url 'inpatients:bed_detail' bed.id %}" class="action-btn view" title="View Details">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'inpatients:bed_update' bed.id %}" class="action-btn edit" title="Edit Bed">
<i class="fas fa-edit"></i>
</a>
{% if bed.status == 'AVAILABLE' %}
<a href="{% url 'inpatients:admission_create' %}" class="action-btn assign" title="Assign Patient">
<i class="fas fa-user-plus"></i>
</a>
{% elif bed.current_admission %}
<a href="{% url 'inpatients:discharge_patient' bed.current_admission.id %}" class="action-btn discharge" title="Discharge">
<i class="fas fa-sign-out-alt"></i>
</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Sidebar -->
<div class="sidebar">
<!-- Occupancy Overview -->
<div class="sidebar-panel">
<div class="panel-header">
<h6 class="panel-title">
<div class="panel-icon primary">
<i class="fas fa-chart-pie"></i>
</div>
Occupancy Overview
</h6>
</div>
<div class="occupancy-display">
<div class="occupancy-percentage">{{ occupancy_rate|floatformat:0 }}%</div>
<div class="occupancy-label">Bed Utilization Rate</div>
<div class="occupancy-bar">
<div class="occupancy-fill" style="width: {{ occupancy_rate }}%"></div>
</div>
<div class="occupancy-stats">
<div>Occupied: {{ occupied_beds }}</div>
<div>Available: {{ available_beds }}</div>
</div>
</div>
</div>
<!-- Ward Breakdown -->
<div class="sidebar-panel">
<div class="panel-header">
<h6 class="panel-title">
<div class="panel-icon success">
<i class="fas fa-building"></i>
</div>
Ward Breakdown
</h6>
</div>
<div class="ward-breakdown">
{% for ward in wards %}
<div class="ward-item">
<div class="ward-name">{{ ward.name }}</div>
<div class="ward-occupancy">
<div class="ward-bar">
<div class="ward-fill" style="width: {{ ward.occupancy_rate }}%; background: {% if ward.occupancy_rate >= 90 %}#dc3545{% elif ward.occupancy_rate >= 75 %}#fd7e14{% elif ward.occupancy_rate >= 50 %}#0dcaf0{% else %}#198754{% endif %}"></div>
</div>
<div class="ward-percentage">{{ ward.occupancy_rate|floatformat:0 }}%</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Quick Actions -->
<div class="sidebar-panel">
<div class="panel-header">
<h6 class="panel-title">
<span class="panel-icon warning">
<i class="fas fa-bolt"></i>
</span>
Quick Actions
</h6>
</div>
<div class="quick-actions">
<a href="#" class="quick-btn" onclick="bulkUpdate()">
<i class="fas fa-edit"></i>
<span>Bulk Update</span>
</a>
<a href="#" class="quick-btn" onclick="exportData()">
<i class="fas fa-download"></i>
<span>Export Data</span>
</a>
<a href="#" class="quick-btn" onclick="scheduleMaintenance()">
<i class="fas fa-tools"></i>
<span>Schedule Maintenance</span>
</a>
<a href="#" class="quick-btn" onclick="viewAlerts()">
<i class="fas fa-bell"></i>
<span>View Alerts</span>
</a>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay" style="display: none;">
<div class="loading-spinner"></div>
</div>
<!-- Bed Details Modal -->
<div class="modal fade" id="bedDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Bed Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="bedDetailsContent">
<!-- Content loaded dynamically -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="editCurrentBed()">Edit Bed</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
setupEventHandlers();
setupFilters();
updateBedStatuses();
// Auto-refresh every 30 seconds
setInterval(updateBedStatuses, 30000);
});
function setupEventHandlers() {
// Filter handlers
$('#wardFilter, #statusFilter, #bedTypeFilter').on('change', function() {
filterBeds();
});
$('#bedSearch').on('input', function() {
filterBeds();
});
}
function setupFilters() {
$('.bed-card').each(function() {
$(this).data('original-display', $(this).css('display'));
});
}
function selectBed(bedId) {
// Remove previous selection
$('.bed-card').removeClass('selected');
// Add selection
$(`.bed-card[data-bed-id="${bedId}"]`).addClass('selected');
// Store selected bed
window.selectedBedId = bedId;
}
function editBed(bedId) {
window.location.href = `{% url 'inpatients:bed_update' 0 %}`.replace('0', bedId);
}
function editCurrentBed() {
if (window.selectedBedId) {
editBed(window.selectedBedId);
}
}
function viewBedDetails(bedId) {
$('#loadingOverlay').show();
$.get('', {bed_id: bedId}, function(data) {
$('#loadingOverlay').hide();
if (data.success) {
$('#bedDetailsContent').html(data.html);
$('#bedDetailsModal').modal('show');
window.selectedBedId = bedId;
} else {
showNotification('Failed to load bed details', 'error');
}
}).fail(function() {
$('#loadingOverlay').hide();
showNotification('Network error occurred', 'error');
});
}
function filterBeds() {
const wardFilter = $('#wardFilter').val();
const statusFilter = $('#statusFilter').val();
const typeFilter = $('#bedTypeFilter').val();
const searchTerm = $('#bedSearch').val().toLowerCase();
$('.bed-card').each(function() {
const card = $(this);
const bedId = card.data('bed-id');
const status = card.data('status');
const type = card.data('type');
const text = card.text().toLowerCase();
const wardId = card.closest('.ward-section').data('ward');
const matchesWard = !wardFilter || wardId == wardFilter;
const matchesStatus = !statusFilter || status === statusFilter.toLowerCase();
const matchesType = !typeFilter || type === typeFilter;
const matchesSearch = !searchTerm || text.includes(searchTerm);
if (matchesWard && matchesStatus && matchesType && matchesSearch) {
card.show();
} else {
card.hide();
}
});
// Hide empty ward sections
$('.ward-section').each(function() {
const section = $(this);
const visibleBeds = section.find('.bed-card:visible').length;
if (visibleBeds === 0) {
section.hide();
} else {
section.show();
}
});
}
function updateBedStatuses() {
$.get('', {action: 'update_status'}, function(data) {
if (data.success && data.beds) {
data.beds.forEach(function(bed) {
const bedCard = $(`.bed-card[data-bed-id="${bed.id}"]`);
if (bedCard.length) {
// Update status classes
bedCard.removeClass('available occupied maintenance cleaning blocked out_of_order')
.addClass(bed.status.toLowerCase());
// Update status badge
bedCard.find('.bed-status').text(bed.status_display);
// Update patient info if changed
if (bed.patient_name) {
bedCard.find('.bed-patient').text(bed.patient_name);
bedCard.find('.bed-time').text(bed.occupied_duration);
} else {
bedCard.find('.bed-patient').text('');
bedCard.find('.bed-time').text('');
}
}
});
// Update statistics
if (data.stats) {
$('.stat-number').eq(0).text(data.stats.total_beds);
$('.stat-number').eq(1).text(data.stats.available_beds);
$('.stat-number').eq(2).text(data.stats.occupied_beds);
$('.stat-number').eq(3).text(data.stats.maintenance_beds);
// Update occupancy rate
const occupancyRate = data.stats.occupancy_rate;
$('.occupancy-percentage').text(occupancyRate + '%');
$('.occupancy-fill').css('width', occupancyRate + '%');
$('.occupancy-stats').html(`
<div>Occupied: ${data.stats.occupied_beds}</div>
<div>Available: ${data.stats.available_beds}</div>
`);
}
}
}).fail(function() {
console.log('Failed to update bed statuses');
});
}
function showNotification(message, type = 'info') {
// Simple notification - you can replace with a proper notification library
const colors = {
success: '#28a745',
error: '#dc3545',
warning: '#ffc107',
info: '#17a2b8'
};
const notification = $(`
<div style="
position: fixed;
top: 20px;
right: 20px;
background: ${colors[type]};
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-weight: 500;
">
${message}
</div>
`);
$('body').append(notification);
setTimeout(() => {
notification.fadeOut(() => notification.remove());
}, 3000);
}
// Placeholder functions for buttons
function viewFloorPlan() {
showNotification('Floor plan view coming soon!', 'info');
}
function generateReport() {
showNotification('Report generation started...', 'success');
// Simulate report generation
setTimeout(() => {
showNotification('Report generated successfully!', 'success');
}, 2000);
}
function bulkUpdate() {
showNotification('Bulk update feature coming soon!', 'info');
}
function exportData() {
showNotification('Data export started...', 'success');
// Simulate export
setTimeout(() => {
showNotification('Data exported successfully!', 'success');
}, 1500);
}
function scheduleMaintenance() {
showNotification('Maintenance scheduling opened', 'info');
}
function viewAlerts() {
showNotification('No active alerts at this time', 'success');
}
</script>
{% endblock %}