557 lines
25 KiB
HTML
557 lines
25 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Inpatient Management Dashboard - {{ 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-bed me-2"></i>Inpatient Management
|
|
</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 active">Inpatients</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshDashboard()">
|
|
<i class="fas fa-sync-alt me-2"></i>Refresh
|
|
</button>
|
|
<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>Quick Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'inpatients:admission_list' %}?action=new">
|
|
<i class="fas fa-user-plus me-2"></i>New Admission
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'inpatients:bed_management' %}">
|
|
<i class="fas fa-bed me-2"></i>Manage Beds
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'inpatients:transfer_management' %}">
|
|
<i class="fas fa-exchange-alt me-2"></i>Transfer Patient
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#">
|
|
<i class="fas fa-chart-bar me-2"></i>Generate Report
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Key Metrics -->
|
|
<div class="row mb-4" id="ward-stats" hx-get="{% url 'inpatients:ward_stats' %}" hx-trigger="load, every 30s">
|
|
{% include 'inpatients/partials/ward_stats.html' %}
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Main Content -->
|
|
<div class="col-lg-8">
|
|
<!-- Bed Occupancy Overview -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-hospital me-2"></i>Bed Occupancy Overview
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshBedGrid()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<a href="{% url 'inpatients:bed_management' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-cog me-1"></i>Manage
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="bed-grid" hx-get="{% url 'inpatients:bed_grid' %}" hx-trigger="load, every 60s">
|
|
{% include 'inpatients/partials/bed_grid.html' %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Admissions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-user-plus me-2"></i>Recent Admissions
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshAdmissions()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<a href="{% url 'inpatients:admission_list' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-list me-1"></i>View All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Patient</th>
|
|
<th>Ward/Bed</th>
|
|
<th>Admission Date</th>
|
|
<th>Attending Physician</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for admission in recent_admissions %}
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-sm bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center me-2">
|
|
<span class="text-white small fw-bold">
|
|
{{ admission.patient.first_name.0 }}{{ admission.patient.last_name.0 }}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">{{ admission.patient.get_full_name }}</div>
|
|
<small class="text-muted">{{ admission.patient.patient_id }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div>{{ admission.ward.name }}</div>
|
|
<small class="text-muted">Bed {{ admission.bed.bed_number }}</small>
|
|
</td>
|
|
<td>
|
|
<div>{{ admission.admission_date|date:"M d, Y" }}</div>
|
|
<small class="text-muted">{{ admission.admission_date|timesince }} ago</small>
|
|
</td>
|
|
<td>
|
|
{% if admission.attending_physician %}
|
|
{{ admission.attending_physician.get_full_name }}
|
|
{% else %}
|
|
<span class="text-muted">Not assigned</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if admission.status == 'ADMITTED' %}success{% elif admission.status == 'PENDING' %}warning{% else %}secondary{% endif %}">
|
|
{{ admission.get_status_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'inpatients:admission_detail' admission.pk %}"
|
|
class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<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="#">
|
|
<i class="fas fa-exchange-alt me-2"></i>Transfer
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#">
|
|
<i class="fas fa-sign-out-alt me-2"></i>Discharge
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#">
|
|
<i class="fas fa-notes-medical me-2"></i>Add Note
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="6" class="text-center py-4">
|
|
<i class="fas fa-user-plus fa-2x text-muted mb-2"></i>
|
|
<h6 class="text-muted">No recent admissions</h6>
|
|
<p class="text-muted">Recent patient admissions will appear here</p>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Today's Schedule -->
|
|
<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-calendar-day me-2"></i>Today's Schedule
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshSchedule()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<a href="{% url 'inpatients:surgery_schedule' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-calendar me-1"></i>Full Schedule
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="text-muted mb-3">Scheduled Surgeries</h6>
|
|
{% for surgery in todays_surgeries %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="bg-info bg-gradient rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-scalpel text-white"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">{{ surgery.patient.get_full_name }}</h6>
|
|
<small class="text-muted">{{ surgery.procedure_name }} - {{ surgery.scheduled_time|time:"H:i" }}</small>
|
|
</div>
|
|
<span class="badge bg-{% if surgery.status == 'SCHEDULED' %}primary{% elif surgery.status == 'IN_PROGRESS' %}warning{% elif surgery.status == 'COMPLETED' %}success{% else %}secondary{% endif %}">
|
|
{{ surgery.get_status_display }}
|
|
</span>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-scalpel fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted mb-0">No surgeries scheduled for today</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6 class="text-muted mb-3">Pending Discharges</h6>
|
|
{% for discharge in pending_discharges %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-sign-out-alt text-white"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">{{ discharge.patient.get_full_name }}</h6>
|
|
<small class="text-muted">{{ discharge.ward.name }} - Bed {{ discharge.bed.bed_number }}</small>
|
|
</div>
|
|
<span class="badge bg-warning">Pending</span>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-sign-out-alt fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted mb-0">No pending discharges</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Quick Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bolt me-2"></i>Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'inpatients:admission_list' %}?action=new" class="btn btn-primary">
|
|
<i class="fas fa-user-plus me-2"></i>New Admission
|
|
</a>
|
|
<a href="{% url 'inpatients:bed_management' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-bed me-2"></i>Bed Management
|
|
</a>
|
|
<a href="{% url 'inpatients:transfer_management' %}" class="btn btn-outline-info">
|
|
<i class="fas fa-exchange-alt me-2"></i>Transfer Patient
|
|
</a>
|
|
<a href="{% url 'inpatients:ward_list' %}" class="btn btn-outline-success">
|
|
<i class="fas fa-hospital me-2"></i>Ward Management
|
|
</a>
|
|
<a href="{% url 'inpatients:surgery_schedule' %}" class="btn btn-outline-warning">
|
|
<i class="fas fa-calendar-alt me-2"></i>Surgery Schedule
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="printDashboard()">
|
|
<i class="fas fa-print me-2"></i>Print Report
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ward Summary -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-hospital me-2"></i>Ward Summary
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="list-group list-group-flush">
|
|
{% for ward in wards %}
|
|
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
|
|
<div>
|
|
<h6 class="mb-1">{{ ward.name }}</h6>
|
|
<small class="text-muted">{{ ward.get_ward_type_display }}</small>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-primary">{{ ward.o }}/{{ ward.total_beds }}</span>
|
|
<br><small class="text-muted">{{ ward.occupancy_rate|floatformat:0 }}%</small>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-hospital fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted mb-0">No wards configured</p>
|
|
<a href="{% url 'inpatients:ward_list' %}" class="btn btn-sm btn-outline-primary mt-2">
|
|
<i class="fas fa-plus me-1"></i>Add Ward
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alerts and Notifications -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bell me-2"></i>Alerts & Notifications
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="list-group list-group-flush">
|
|
{% if critical_alerts %}
|
|
{% for alert in critical_alerts %}
|
|
<div class="list-group-item d-flex align-items-center px-0 border-start border-danger border-3">
|
|
<div class="bg-danger bg-gradient rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 32px; height: 32px;">
|
|
<i class="fas fa-exclamation text-white small"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">{{ alert.title }}</h6>
|
|
<small class="text-muted">{{ alert.message }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
{% if warning_alerts %}
|
|
{% for alert in warning_alerts %}
|
|
<div class="list-group-item d-flex align-items-center px-0 border-start border-warning border-3">
|
|
<div class="bg-warning bg-gradient rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 32px; height: 32px;">
|
|
<i class="fas fa-exclamation-triangle text-white small"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">{{ alert.title }}</h6>
|
|
<small class="text-muted">{{ alert.message }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
{% if not critical_alerts and not warning_alerts %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-check-circle fa-2x text-success mb-2"></i>
|
|
<h6 class="text-success">All Clear</h6>
|
|
<p class="text-muted mb-0">No active alerts</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-clock me-2"></i>Recent Activity
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="timeline">
|
|
{% for activity in recent_activities %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-{% if activity.type == 'admission' %}success{% elif activity.type == 'transfer' %}info{% elif activity.type == 'discharge' %}warning{% else %}secondary{% endif %}"></div>
|
|
<div class="timeline-content">
|
|
<h6 class="timeline-title">{{ activity.title }}</h6>
|
|
<p class="timeline-text">{{ activity.description }}</p>
|
|
<small class="text-muted">{{ activity.timestamp|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-clock fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted mb-0">No recent activity</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Inpatient dashboard functionality
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Auto-refresh dashboard every 5 minutes
|
|
setInterval(refreshDashboard, 300000);
|
|
|
|
// Auto-refresh bed grid every 2 minutes
|
|
setInterval(refreshBedGrid, 120000);
|
|
});
|
|
|
|
function refreshDashboard() {
|
|
// Refresh statistics
|
|
fetch('{% url "inpatients:ward_stats" %}')
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
document.getElementById('ward-stats').innerHTML = html;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error refreshing dashboard:', error);
|
|
});
|
|
}
|
|
|
|
function refreshBedGrid() {
|
|
// Refresh bed grid
|
|
fetch('{% url "inpatients:bed_grid" %}')
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
document.getElementById('bed-grid').innerHTML = html;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error refreshing bed grid:', error);
|
|
});
|
|
}
|
|
|
|
function refreshAdmissions() {
|
|
// Refresh recent admissions
|
|
location.reload();
|
|
}
|
|
|
|
function refreshSchedule() {
|
|
// Refresh today's schedule
|
|
location.reload();
|
|
}
|
|
|
|
function printDashboard() {
|
|
window.print();
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-bottom: 1.5rem;
|
|
}
|
|
|
|
.timeline-item:not(:last-child)::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -1.5rem;
|
|
top: 1rem;
|
|
width: 2px;
|
|
height: calc(100% - 0.5rem);
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.timeline-marker {
|
|
position: absolute;
|
|
left: -1.75rem;
|
|
top: 0.25rem;
|
|
width: 0.5rem;
|
|
height: 0.5rem;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.timeline-content {
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.timeline-title {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.timeline-text {
|
|
font-size: 0.8rem;
|
|
color: #6c757d;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.avatar-sm {
|
|
width: 32px;
|
|
height: 32px;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.bg-gradient {
|
|
background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
|
}
|
|
|
|
.list-group-item {
|
|
border: none;
|
|
padding: 0.75rem 0;
|
|
}
|
|
|
|
.list-group-item:first-child {
|
|
border-top: none;
|
|
}
|
|
|
|
.list-group-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.border-3 {
|
|
border-width: 3px !important;
|
|
}
|
|
|
|
@media print {
|
|
.btn-group,
|
|
.card:last-child {
|
|
display: none !important;
|
|
}
|
|
|
|
.container-fluid {
|
|
padding: 0 !important;
|
|
}
|
|
|
|
.card {
|
|
border: 1px solid #000 !important;
|
|
break-inside: avoid;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.d-flex.justify-content-between {
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.timeline {
|
|
padding-left: 1rem;
|
|
}
|
|
|
|
.timeline-marker {
|
|
left: -1.25rem;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|