489 lines
21 KiB
HTML
489 lines
21 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Patient Management Dashboard{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<link href="{% static 'assets/plugins/jvectormap-next/jquery-jvectormap.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
<style>
|
|
.metric-card {
|
|
transition: transform 0.2s;
|
|
}
|
|
.metric-card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
.patient-avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: bold;
|
|
color: white;
|
|
}
|
|
.priority-high { border-left: 4px solid #dc3545; }
|
|
.priority-medium { border-left: 4px solid #ffc107; }
|
|
.priority-low { border-left: 4px solid #28a745; }
|
|
.chart-container {
|
|
position: relative;
|
|
height: 300px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- BEGIN breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Home</a></li>
|
|
<li class="breadcrumb-item active">Patient Management</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
<i class="fas fa-users text-primary me-2"></i>
|
|
Patient Management Dashboard
|
|
<small class="text-muted ms-2">Comprehensive overview of patient care and management</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<!-- BEGIN statistics cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card bg-primary text-white metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<h6 class="card-title mb-1">Total Patients</h6>
|
|
<h3 class="mb-0" id="total-patients">{{ stats.total_patients|default:0 }}</h3>
|
|
<small class="opacity-75">Active registrations</small>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-users fa-2x opacity-75"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card bg-success text-white metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<h6 class="card-title mb-1">Active Inpatients</h6>
|
|
<h3 class="mb-0" id="active-inpatients">{{ stats.active_inpatients|default:0 }}</h3>
|
|
<small class="opacity-75">Currently admitted</small>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-bed fa-2x opacity-75"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card bg-warning text-white metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<h6 class="card-title mb-1">Today's Visits</h6>
|
|
<h3 class="mb-0" id="todays-visits">{{ stats.todays_visits|default:0 }}</h3>
|
|
<small class="opacity-75">Outpatient visits</small>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-calendar-check fa-2x opacity-75"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card bg-info text-white metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<h6 class="card-title mb-1">Critical Alerts</h6>
|
|
<h3 class="mb-0" id="critical-alerts">{{ stats.critical_alerts|default:0 }}</h3>
|
|
<small class="opacity-75">Require attention</small>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-exclamation-triangle fa-2x opacity-75"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END statistics cards -->
|
|
|
|
<!-- BEGIN quick actions -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-12">
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-bolt me-2"></i>Quick Actions
|
|
</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-md-2 col-sm-4 col-6 mb-3">
|
|
<a href="{% url 'patients:patient_create' %}" class="btn btn-outline-primary w-100 py-3">
|
|
<i class="fas fa-user-plus fa-2x mb-2"></i>
|
|
<div>Register Patient</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 col-6 mb-3">
|
|
<a href="{% url 'patients:patient_search' %}" class="btn btn-outline-info w-100 py-3">
|
|
<i class="fas fa-search fa-2x mb-2"></i>
|
|
<div>Search Patients</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 col-6 mb-3">
|
|
<a href="{% url 'appointments:appointment_create' %}" class="btn btn-outline-success w-100 py-3">
|
|
<i class="fas fa-calendar-plus fa-2x mb-2"></i>
|
|
<div>Schedule Visit</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 col-6 mb-3">
|
|
<a href="{% url 'inpatients:admission_create' %}" class="btn btn-outline-warning w-100 py-3">
|
|
<i class="fas fa-hospital fa-2x mb-2"></i>
|
|
<div>Admit Patient</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 col-6 mb-3">
|
|
<a href="{% url 'patients:emergency_registration' %}" class="btn btn-outline-danger w-100 py-3">
|
|
<i class="fas fa-ambulance fa-2x mb-2"></i>
|
|
<div>Emergency Reg.</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 col-6 mb-3">
|
|
<a href="{% url 'patients:patient_reports' %}" class="btn btn-outline-secondary w-100 py-3">
|
|
<i class="fas fa-chart-bar fa-2x mb-2"></i>
|
|
<div>Reports</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END quick actions -->
|
|
|
|
<!-- BEGIN main content -->
|
|
<div class="row">
|
|
<!-- LEFT COLUMN -->
|
|
<div class="col-xl-8">
|
|
<!-- Recent Admissions -->
|
|
<div class="panel panel-inverse mb-4">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-hospital-user me-2"></i>Recent Admissions
|
|
</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="{% url 'inpatients:admission_list' %}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-eye me-1"></i>View All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Patient</th>
|
|
<th>Room</th>
|
|
<th>Admission Date</th>
|
|
<th>Attending Physician</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for admission in recent_admissions %}
|
|
<tr class="{% if admission.priority == 'HIGH' %}priority-high{% elif admission.priority == 'MEDIUM' %}priority-medium{% else %}priority-low{% endif %}">
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="patient-avatar bg-primary me-2">
|
|
{{ admission.patient.first_name|first }}{{ admission.patient.last_name|first }}
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold">{{ admission.patient.get_full_name }}</div>
|
|
<small class="text-muted">MRN: {{ admission.patient.mrn }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ admission.room.number }}</span>
|
|
<small class="d-block text-muted">{{ admission.room.ward.name }}</small>
|
|
</td>
|
|
<td>{{ admission.admission_date|date:"M d, Y H:i" }}</td>
|
|
<td>{{ admission.attending_physician.get_full_name }}</td>
|
|
<td>
|
|
{% if admission.status == 'ACTIVE' %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% elif admission.status == 'PENDING' %}
|
|
<span class="badge bg-warning">Pending</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ admission.get_status_display }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group" role="group">
|
|
<a href="{% url 'patients:patient_detail' admission.patient.pk %}"
|
|
class="btn btn-outline-primary btn-sm" title="View Patient">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'inpatients:admission_detail' admission.pk %}"
|
|
class="btn btn-outline-info btn-sm" title="View Admission">
|
|
<i class="fas fa-hospital"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="6" class="text-center py-4 text-muted">
|
|
<i class="fas fa-inbox fa-2x mb-2"></i>
|
|
<p class="mb-0">No recent admissions</p>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Patient Activity Chart -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-chart-line me-2"></i>Patient Activity Trends
|
|
</h4>
|
|
<div class="panel-heading-btn">
|
|
<select class="form-select form-select-sm" id="chart-period">
|
|
<option value="7">Last 7 days</option>
|
|
<option value="30" selected>Last 30 days</option>
|
|
<option value="90">Last 90 days</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="chart-container">
|
|
<canvas id="patientActivityChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RIGHT COLUMN -->
|
|
<div class="col-xl-4">
|
|
<!-- Critical Alerts -->
|
|
<div class="panel panel-inverse mb-4">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>Critical Alerts
|
|
</h4>
|
|
<div class="panel-heading-btn">
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="refreshAlerts()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body" style="max-height: 300px; overflow-y: auto;">
|
|
{% for alert in critical_alerts %}
|
|
<div class="alert alert-{% if alert.severity == 'CRITICAL' %}danger{% elif alert.severity == 'HIGH' %}warning{% else %}info{% endif %} alert-dismissible fade show mb-2">
|
|
<div class="d-flex align-items-start">
|
|
<div class="flex-grow-1">
|
|
<h6 class="alert-heading mb-1">{{ alert.title }}</h6>
|
|
<p class="mb-1">{{ alert.message }}</p>
|
|
<small class="text-muted">
|
|
<i class="fas fa-clock me-1"></i>{{ alert.created_at|timesince }} ago
|
|
</small>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-check-circle fa-2x mb-2"></i>
|
|
<p class="mb-0">No critical alerts</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Today's Schedule -->
|
|
<div class="panel panel-inverse mb-4">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-calendar-day me-2"></i>Today's Schedule
|
|
</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="{% url 'appointments:appointment_list' %}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-calendar me-1"></i>Full Calendar
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body" style="max-height: 300px; overflow-y: auto;">
|
|
{% for appointment in todays_appointments %}
|
|
<div class="d-flex align-items-center mb-3 pb-3 border-bottom">
|
|
<div class="patient-avatar bg-primary me-3">
|
|
{{ appointment.patient.first_name|first }}{{ appointment.patient.last_name|first }}
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<div class="fw-bold">{{ appointment.patient.get_full_name }}</div>
|
|
<small class="text-muted">{{ appointment.get_appointment_type_display }}</small>
|
|
<div class="text-primary">
|
|
<i class="fas fa-clock me-1"></i>{{ appointment.appointment_time|time:"g:i A" }}
|
|
</div>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-{% if appointment.status == 'CONFIRMED' %}success{% elif appointment.status == 'PENDING' %}warning{% else %}secondary{% endif %}">
|
|
{{ appointment.get_status_display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-calendar-times fa-2x mb-2"></i>
|
|
<p class="mb-0">No appointments scheduled for today</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Department Occupancy -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-hospital me-2"></i>Department Occupancy
|
|
</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
{% for dept in department_occupancy %}
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="fw-bold">{{ dept.name }}</span>
|
|
<span class="text-muted">{{ dept.occupied }}/{{ dept.capacity }}</span>
|
|
</div>
|
|
<div class="progress" style="height: 8px;">
|
|
<div class="progress-bar bg-{% if dept.occupancy_rate >= 90 %}danger{% elif dept.occupancy_rate >= 75 %}warning{% else %}success{% endif %}"
|
|
style="width: {{ dept.occupancy_rate }}%"></div>
|
|
</div>
|
|
<small class="text-muted">{{ dept.occupancy_rate }}% occupancy</small>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-building fa-2x mb-2"></i>
|
|
<p class="mb-0">No department data available</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END main content -->
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="{% static 'assets/plugins/chart.js/dist/chart.min.js' %}"></script>
|
|
<script src="{% static 'assets/plugins/datatables.net/js/dataTables.min.js' %}"></script>
|
|
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize Patient Activity Chart
|
|
const ctx = document.getElementById('patientActivityChart').getContext('2d');
|
|
const patientActivityChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: {{ chart_labels|safe }},
|
|
datasets: [{
|
|
label: 'New Registrations',
|
|
data: {{ new_registrations_data|safe }},
|
|
borderColor: 'rgb(75, 192, 192)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
|
tension: 0.1
|
|
}, {
|
|
label: 'Admissions',
|
|
data: {{ admissions_data|safe }},
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
tension: 0.1
|
|
}, {
|
|
label: 'Discharges',
|
|
data: {{ discharges_data|safe }},
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
|
tension: 0.1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: 'Patient Activity Over Time'
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Chart period change handler
|
|
$('#chart-period').change(function() {
|
|
const period = $(this).val();
|
|
// AJAX call to update chart data
|
|
$.get('{% url "patients:dashboard_chart_data" %}', {period: period}, function(data) {
|
|
patientActivityChart.data.labels = data.labels;
|
|
patientActivityChart.data.datasets[0].data = data.new_registrations;
|
|
patientActivityChart.data.datasets[1].data = data.admissions;
|
|
patientActivityChart.data.datasets[2].data = data.discharges;
|
|
patientActivityChart.update();
|
|
});
|
|
});
|
|
|
|
// Auto-refresh stats every 30 seconds
|
|
setInterval(function() {
|
|
refreshStats();
|
|
}, 30000);
|
|
|
|
// Auto-refresh alerts every 60 seconds
|
|
setInterval(function() {
|
|
refreshAlerts();
|
|
}, 60000);
|
|
});
|
|
|
|
function refreshStats() {
|
|
$.get('{% url "patients:dashboard_stats" %}', function(data) {
|
|
$('#total-patients').text(data.total_patients);
|
|
$('#active-inpatients').text(data.active_inpatients);
|
|
$('#todays-visits').text(data.todays_visits);
|
|
$('#critical-alerts').text(data.critical_alerts);
|
|
});
|
|
}
|
|
|
|
function refreshAlerts() {
|
|
// Refresh critical alerts section
|
|
$.get('{% url "patients:dashboard_alerts" %}', function(data) {
|
|
// Update alerts section with new data
|
|
// Implementation would depend on your specific alert system
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|