Marwan Alwali 43901b5bda update
2025-09-09 01:15:48 +03:00

1187 lines
39 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Patients Dashboard{% endblock %}
{% block css %}
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<style>
.dashboard-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
overflow: hidden;
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--card-color);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
color: white;
font-size: 1.5rem;
}
.stat-number {
font-size: 2.5rem;
font-weight: bold;
color: #495057;
margin-bottom: 0.5rem;
line-height: 1;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-change {
font-size: 0.75rem;
margin-top: 0.5rem;
font-weight: 600;
}
.stat-change.positive {
color: #28a745;
}
.stat-change.negative {
color: #dc3545;
}
.stat-change.neutral {
color: #6c757d;
}
.dashboard-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
margin-bottom: 2rem;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
display: flex;
justify-content: between;
align-items: center;
}
.section-content {
padding: 1.5rem;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.action-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
text-align: center;
transition: all 0.2s;
cursor: pointer;
text-decoration: none;
color: inherit;
}
.action-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
text-decoration: none;
color: inherit;
}
.action-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
color: white;
font-size: 1.25rem;
}
.action-title {
font-weight: 600;
margin-bottom: 0.5rem;
color: #495057;
}
.action-description {
font-size: 0.875rem;
color: #6c757d;
line-height: 1.4;
}
.chart-container {
position: relative;
height: 300px;
margin-bottom: 1rem;
}
.recent-activity {
max-height: 400px;
overflow-y: auto;
}
.activity-item {
display: flex;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #f1f3f4;
transition: background-color 0.2s;
}
.activity-item:hover {
background: #f8f9fa;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-weight: 600;
font-size: 0.875rem;
}
.activity-content {
flex: 1;
}
.activity-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.activity-description {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.25rem;
}
.activity-time {
font-size: 0.75rem;
color: #adb5bd;
}
.activity-type {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.type-admission { background: #d4edda; color: #155724; }
.type-discharge { background: #f8d7da; color: #721c24; }
.type-appointment { background: #d1ecf1; color: #0c5460; }
.type-emergency { background: #f5c6cb; color: #721c24; }
.type-update { background: #fff3cd; color: #856404; }
.alerts-section {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 2rem;
}
.alert-item {
display: flex;
align-items: center;
padding: 0.75rem;
background: white;
border: 1px solid #ffeaa7;
border-radius: 0.25rem;
margin-bottom: 0.75rem;
}
.alert-item:last-child {
margin-bottom: 0;
}
.alert-icon {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-size: 0.875rem;
}
.alert-critical { background: #dc3545; color: white; }
.alert-warning { background: #ffc107; color: #212529; }
.alert-info { background: #17a2b8; color: white; }
.patient-status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.status-item {
text-align: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 0.375rem;
border: 1px solid #dee2e6;
}
.status-number {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.25rem;
}
.status-label {
font-size: 0.75rem;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
}
.search-widget {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.search-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.search-tab {
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
background: #f8f9fa;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
font-weight: 600;
}
.search-tab.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.search-content {
display: none;
}
.search-content.active {
display: block;
}
@media (max-width: 768px) {
.dashboard-header {
padding: 1.5rem;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.stat-card {
padding: 1rem;
}
.stat-number {
font-size: 2rem;
}
.quick-actions {
grid-template-columns: 1fr;
}
.section-content {
padding: 1rem;
}
.patient-status-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media print {
.quick-actions, .search-widget, .btn {
display: none !important;
}
.dashboard-section {
break-inside: avoid;
margin-bottom: 1rem;
}
.section-header {
background: none;
border-bottom: 2px solid #000;
color: #000;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Dashboard Header -->
<div class="dashboard-header">
<div class="row align-items-center">
<div class="col-md-8">
<h1 class="mb-2">
<i class="fas fa-users me-3"></i>Patients Dashboard
</h1>
<p class="mb-0 fs-5">Comprehensive patient management and analytics</p>
</div>
<div class="col-md-4 text-md-end">
<div class="text-white-50 mb-1">Last Updated</div>
<div class="h5 mb-0">{% now "M d, Y g:i A" %}</div>
</div>
</div>
</div>
<!-- Key Statistics -->
<div class="stats-grid">
<div class="stat-card" style="--card-color: #007bff;">
<div class="stat-icon" style="background: #007bff;">
<i class="fas fa-users"></i>
</div>
<div class="stat-number" id="total-patients">{{ stats.total_patients|default:0 }}</div>
<div class="stat-label">Total Patients</div>
<div class="stat-change positive">
<i class="fas fa-arrow-up me-1"></i>+{{ stats.new_patients_today|default:0 }} today
</div>
</div>
<div class="stat-card" style="--card-color: #28a745;">
<div class="stat-icon" style="background: #28a745;">
<i class="fas fa-user-check"></i>
</div>
<div class="stat-number" id="active-patients">{{ stats.active_patients|default:0 }}</div>
<div class="stat-label">Active Patients</div>
<div class="stat-change positive">
<i class="fas fa-arrow-up me-1"></i>{{ stats.active_change|default:0 }}% this week
</div>
</div>
<div class="stat-card" style="--card-color: #ffc107;">
<div class="stat-icon" style="background: #ffc107;">
<i class="fas fa-bed"></i>
</div>
<div class="stat-number" id="admitted-patients">{{ stats.admitted_patients|default:0 }}</div>
<div class="stat-label">Currently Admitted</div>
<div class="stat-change neutral">
<i class="fas fa-minus me-1"></i>{{ stats.admission_change|default:0 }} from yesterday
</div>
</div>
<div class="stat-card" style="--card-color: #dc3545;">
<div class="stat-icon" style="background: #dc3545;">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="stat-number" id="critical-patients">{{ stats.critical_patients|default:0 }}</div>
<div class="stat-label">Critical Condition</div>
<div class="stat-change negative">
<i class="fas fa-arrow-down me-1"></i>{{ stats.critical_change|default:0 }} from last hour
</div>
</div>
<div class="stat-card" style="--card-color: #17a2b8;">
<div class="stat-icon" style="background: #17a2b8;">
<i class="fas fa-calendar-check"></i>
</div>
<div class="stat-number" id="appointments-today">{{ stats.appointments_today|default:0 }}</div>
<div class="stat-label">Appointments Today</div>
<div class="stat-change positive">
<i class="fas fa-clock me-1"></i>{{ stats.completed_appointments|default:0 }} completed
</div>
</div>
<div class="stat-card" style="--card-color: #6f42c1;">
<div class="stat-icon" style="background: #6f42c1;">
<i class="fas fa-user-plus"></i>
</div>
<div class="stat-number" id="new-registrations">{{ stats.new_registrations|default:0 }}</div>
<div class="stat-label">New Registrations</div>
<div class="stat-change positive">
<i class="fas fa-calendar me-1"></i>This week
</div>
</div>
</div>
<!-- Alerts Section -->
{% if alerts %}
<div class="alerts-section">
<h6 class="mb-3">
<i class="fas fa-bell me-2"></i>Patient Alerts & Notifications
</h6>
{% for alert in alerts %}
<div class="alert-item">
<div class="alert-icon alert-{{ alert.priority }}">
<i class="fas fa-{{ alert.icon }}"></i>
</div>
<div class="flex-grow-1">
<div class="fw-bold">{{ alert.title }}</div>
<div class="text-muted small">{{ alert.message }}</div>
</div>
<div class="text-muted small">{{ alert.created_at|timesince }} ago</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Quick Actions -->
<div class="quick-actions">
<a href="{% url 'patients:patient_registration' %}" class="action-card">
<div class="action-icon" style="background: #007bff;">
<i class="fas fa-user-plus"></i>
</div>
<div class="action-title">Register New Patient</div>
<div class="action-description">Add a new patient to the system with complete registration</div>
</a>
<a href="{% url 'patients:patient_list' %}" class="action-card">
<div class="action-icon" style="background: #28a745;">
<i class="fas fa-list"></i>
</div>
<div class="action-title">View All Patients</div>
<div class="action-description">Browse and manage all registered patients</div>
</a>
<a href="{% url 'appointments:appointment_create' %}" class="action-card">
<div class="action-icon" style="background: #17a2b8;">
<i class="fas fa-calendar-plus"></i>
</div>
<div class="action-title">Schedule Appointment</div>
<div class="action-description">Book new appointments for existing patients</div>
</a>
<a href="{% url 'patients:emergency_admission' %}" class="action-card">
<div class="action-icon" style="background: #dc3545;">
<i class="fas fa-ambulance"></i>
</div>
<div class="action-title">Emergency Admission</div>
<div class="action-description">Quick admission for emergency cases</div>
</a>
<a href="{% url 'patients:discharge_management' %}" class="action-card">
<div class="action-icon" style="background: #ffc107;">
<i class="fas fa-sign-out-alt"></i>
</div>
<div class="action-title">Discharge Management</div>
<div class="action-description">Process patient discharges and follow-ups</div>
</a>
<a href="{% url 'patients:reports' %}" class="action-card">
<div class="action-icon" style="background: #6f42c1;">
<i class="fas fa-chart-bar"></i>
</div>
<div class="action-title">Patient Reports</div>
<div class="action-description">Generate comprehensive patient analytics</div>
</a>
</div>
<!-- Search Widget -->
<div class="search-widget">
<h6 class="mb-3">
<i class="fas fa-search me-2"></i>Quick Patient Search
</h6>
<div class="search-tabs">
<div class="search-tab active" onclick="switchSearchTab('name')">By Name</div>
<div class="search-tab" onclick="switchSearchTab('id')">By ID</div>
<div class="search-tab" onclick="switchSearchTab('phone')">By Phone</div>
<div class="search-tab" onclick="switchSearchTab('advanced')">Advanced</div>
</div>
<div class="search-content active" id="search-name">
<div class="input-group">
<input type="text" class="form-control" placeholder="Enter patient name..." id="search-name-input">
<button class="btn btn-primary" onclick="searchPatients('name')">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="search-content" id="search-id">
<div class="input-group">
<input type="text" class="form-control" placeholder="Enter patient ID..." id="search-id-input">
<button class="btn btn-primary" onclick="searchPatients('id')">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="search-content" id="search-phone">
<div class="input-group">
<input type="text" class="form-control" placeholder="Enter phone number..." id="search-phone-input">
<button class="btn btn-primary" onclick="searchPatients('phone')">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="search-content" id="search-advanced">
<div class="row">
<div class="col-md-4">
<input type="text" class="form-control mb-2" placeholder="First Name" id="search-fname">
</div>
<div class="col-md-4">
<input type="text" class="form-control mb-2" placeholder="Last Name" id="search-lname">
</div>
<div class="col-md-4">
<input type="date" class="form-control mb-2" placeholder="Date of Birth" id="search-dob">
</div>
</div>
<button class="btn btn-primary" onclick="searchPatients('advanced')">
<i class="fas fa-search me-1"></i>Advanced Search
</button>
</div>
<div id="search-results" class="mt-3" style="display: none;">
<!-- Search results will be populated here -->
</div>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Patient Status Overview -->
<div class="dashboard-section">
<div class="section-header">
<div>
<i class="fas fa-chart-pie me-2"></i>Patient Status Overview
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary" onclick="refreshCharts()">
<i class="fas fa-sync"></i>
</button>
<button class="btn btn-outline-secondary" onclick="exportChart('status')">
<i class="fas fa-download"></i>
</button>
</div>
</div>
<div class="section-content">
<div class="patient-status-grid">
<div class="status-item">
<div class="status-number text-success">{{ stats.outpatients|default:0 }}</div>
<div class="status-label">Outpatients</div>
</div>
<div class="status-item">
<div class="status-number text-primary">{{ stats.inpatients|default:0 }}</div>
<div class="status-label">Inpatients</div>
</div>
<div class="status-item">
<div class="status-number text-warning">{{ stats.emergency|default:0 }}</div>
<div class="status-label">Emergency</div>
</div>
<div class="status-item">
<div class="status-number text-info">{{ stats.icu|default:0 }}</div>
<div class="status-label">ICU</div>
</div>
<div class="status-item">
<div class="status-number text-secondary">{{ stats.discharged_today|default:0 }}</div>
<div class="status-label">Discharged Today</div>
</div>
<div class="status-item">
<div class="status-number text-danger">{{ stats.readmissions|default:0 }}</div>
<div class="status-label">Readmissions</div>
</div>
</div>
<div class="chart-container">
<canvas id="patientStatusChart"></canvas>
</div>
</div>
</div>
<!-- Admission Trends -->
<div class="dashboard-section">
<div class="section-header">
<div>
<i class="fas fa-chart-line me-2"></i>Admission Trends (Last 30 Days)
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary" onclick="changeTrendPeriod('7')">7D</button>
<button class="btn btn-outline-secondary active" onclick="changeTrendPeriod('30')">30D</button>
<button class="btn btn-outline-secondary" onclick="changeTrendPeriod('90')">90D</button>
</div>
</div>
<div class="section-content">
<div class="chart-container">
<canvas id="admissionTrendsChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Recent Activity -->
<div class="dashboard-section">
<div class="section-header">
<div>
<i class="fas fa-clock me-2"></i>Recent Activity
</div>
<a href="{% url 'patients:activity_log' %}" class="btn btn-outline-primary btn-sm">
View All
</a>
</div>
<div class="section-content p-0">
<div class="recent-activity">
{% for activity in recent_activities %}
<div class="activity-item">
<div class="activity-avatar">
{{ activity.patient.first_name.0|default:"P" }}{{ activity.patient.last_name.0|default:"" }}
</div>
<div class="activity-content">
<div class="activity-title">{{ activity.patient.get_full_name }}</div>
<div class="activity-description">{{ activity.description }}</div>
<div class="activity-time">{{ activity.created_at|timesince }} ago</div>
</div>
<div class="activity-type type-{{ activity.activity_type }}">
{{ activity.get_activity_type_display }}
</div>
</div>
{% empty %}
<div class="text-center py-4 text-muted">
<i class="fas fa-clock fa-2x mb-2"></i>
<p>No recent activity</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Today's Appointments -->
<div class="dashboard-section">
<div class="section-header">
<div>
<i class="fas fa-calendar-day me-2"></i>Today's Appointments
</div>
<a href="{% url 'appointments:appointment_list' %}" class="btn btn-outline-primary btn-sm">
View All
</a>
</div>
<div class="section-content">
{% for appointment in todays_appointments %}
<div class="d-flex align-items-center mb-3">
<div class="me-3">
<div class="fw-bold text-primary">{{ appointment.scheduled_time|time:"g:i A" }}</div>
<small class="text-muted">{{ appointment.duration }} min</small>
</div>
<div class="flex-grow-1">
<div class="fw-bold">{{ appointment.patient.get_full_name }}</div>
<small class="text-muted">{{ appointment.appointment_type }} - {{ appointment.doctor.get_full_name }}</small>
</div>
<div>
<span class="badge bg-{{ appointment.status_color }}">
{{ appointment.get_status_display }}
</span>
</div>
</div>
{% empty %}
<div class="text-center py-3 text-muted">
<i class="fas fa-calendar fa-2x mb-2"></i>
<p>No appointments scheduled for today</p>
</div>
{% endfor %}
</div>
</div>
<!-- Critical Patients -->
<div class="dashboard-section">
<div class="section-header">
<div>
<i class="fas fa-exclamation-triangle me-2 text-danger"></i>Critical Patients
</div>
<a href="{% url 'patients:critical_list' %}" class="btn btn-outline-danger btn-sm">
View All
</a>
</div>
<div class="section-content">
{% for patient in critical_patients %}
<div class="d-flex align-items-center mb-3">
<div class="activity-avatar bg-danger me-3">
{{ patient.first_name.0 }}{{ patient.last_name.0 }}
</div>
<div class="flex-grow-1">
<div class="fw-bold">{{ patient.get_full_name }}</div>
<small class="text-muted">{{ patient.current_condition }}</small>
<div class="small text-danger">
<i class="fas fa-map-marker-alt me-1"></i>{{ patient.current_location }}
</div>
</div>
<div>
<a href="{% url 'patients:patient_detail' patient.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye"></i>
</a>
</div>
</div>
{% empty %}
<div class="text-center py-3 text-muted">
<i class="fas fa-heart fa-2x mb-2 text-success"></i>
<p>No critical patients at this time</p>
</div>
{% endfor %}
</div>
</div>
<!-- Quick Stats -->
<div class="dashboard-section">
<div class="section-header">
<i class="fas fa-tachometer-alt me-2"></i>Quick Statistics
</div>
<div class="section-content">
<div class="row text-center">
<div class="col-6 mb-3">
<div class="h4 text-primary mb-1">{{ stats.avg_stay_duration|default:0 }}</div>
<small class="text-muted">Avg Stay (days)</small>
</div>
<div class="col-6 mb-3">
<div class="h4 text-success mb-1">{{ stats.satisfaction_rate|default:0 }}%</div>
<small class="text-muted">Satisfaction Rate</small>
</div>
<div class="col-6 mb-3">
<div class="h4 text-warning mb-1">{{ stats.readmission_rate|default:0 }}%</div>
<small class="text-muted">Readmission Rate</small>
</div>
<div class="col-6 mb-3">
<div class="h4 text-info mb-1">{{ stats.bed_occupancy|default:0 }}%</div>
<small class="text-muted">Bed Occupancy</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugins/chart.js/dist/chart.js' %}"></script>
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize charts
initializePatientStatusChart();
initializeAdmissionTrendsChart();
// Auto-refresh data every 5 minutes
setInterval(refreshDashboardData, 300000);
// Initialize search functionality
initializeSearch();
});
function initializePatientStatusChart() {
const ctx = document.getElementById('patientStatusChart').getContext('2d');
const data = {
labels: ['Outpatients', 'Inpatients', 'Emergency', 'ICU', 'Discharged', 'Readmissions'],
datasets: [{
data: [
{{ stats.outpatients|default:0 }},
{{ stats.inpatients|default:0 }},
{{ stats.emergency|default:0 }},
{{ stats.icu|default:0 }},
{{ stats.discharged_today|default:0 }},
{{ stats.readmissions|default:0 }}
],
backgroundColor: [
'#28a745',
'#007bff',
'#ffc107',
'#17a2b8',
'#6c757d',
'#dc3545'
],
borderWidth: 2,
borderColor: '#fff'
}]
};
new Chart(ctx, {
type: 'doughnut',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
}
}
}
});
}
function initializeAdmissionTrendsChart() {
const ctx = document.getElementById('admissionTrendsChart').getContext('2d');
// Generate sample data for the last 30 days
const labels = [];
const admissionData = [];
const dischargeData = [];
for (let i = 29; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
labels.push(date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }));
// Sample data - replace with actual data from backend
admissionData.push(Math.floor(Math.random() * 20) + 5);
dischargeData.push(Math.floor(Math.random() * 15) + 3);
}
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Admissions',
data: admissionData,
borderColor: '#007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
}, {
label: 'Discharges',
data: dischargeData,
borderColor: '#28a745',
backgroundColor: 'rgba(40, 167, 69, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top'
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 5
}
},
x: {
ticks: {
maxTicksLimit: 10
}
}
}
}
});
}
function refreshDashboardData() {
fetch('/patients/dashboard/data/', {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
// Update statistics
document.getElementById('total-patients').textContent = data.total_patients || 0;
document.getElementById('active-patients').textContent = data.active_patients || 0;
document.getElementById('admitted-patients').textContent = data.admitted_patients || 0;
document.getElementById('critical-patients').textContent = data.critical_patients || 0;
document.getElementById('appointments-today').textContent = data.appointments_today || 0;
document.getElementById('new-registrations').textContent = data.new_registrations || 0;
// Show update indicator
showUpdateIndicator();
})
.catch(error => {
console.error('Error refreshing dashboard data:', error);
});
}
function showUpdateIndicator() {
const indicator = document.createElement('div');
indicator.className = 'alert alert-success alert-dismissible fade show position-fixed';
indicator.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 250px;';
indicator.innerHTML = `
<i class="fas fa-sync me-2"></i>Dashboard updated
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(indicator);
setTimeout(() => {
if (indicator.parentNode) {
indicator.remove();
}
}, 3000);
}
function refreshCharts() {
// Refresh chart data
location.reload();
}
function exportChart(chartType) {
// Export chart functionality
const canvas = document.getElementById(chartType === 'status' ? 'patientStatusChart' : 'admissionTrendsChart');
const url = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = `${chartType}-chart.png`;
link.href = url;
link.click();
}
function changeTrendPeriod(days) {
// Update trend chart for different periods
document.querySelectorAll('.btn-group .btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// Fetch new data for the selected period
fetch(`/patients/dashboard/trends/?days=${days}`, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
// Update chart with new data
// Implementation depends on your backend API
})
.catch(error => {
console.error('Error updating trend data:', error);
});
}
function switchSearchTab(tabType) {
// Hide all search contents
document.querySelectorAll('.search-content').forEach(content => {
content.classList.remove('active');
});
// Remove active class from all tabs
document.querySelectorAll('.search-tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected content and activate tab
document.getElementById(`search-${tabType}`).classList.add('active');
event.target.classList.add('active');
// Hide search results
document.getElementById('search-results').style.display = 'none';
}
function initializeSearch() {
// Add enter key support for search inputs
document.querySelectorAll('.search-content input').forEach(input => {
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const searchType = this.closest('.search-content').id.replace('search-', '');
searchPatients(searchType);
}
});
});
}
function searchPatients(searchType) {
let searchData = {};
switch (searchType) {
case 'name':
searchData.name = document.getElementById('search-name-input').value;
break;
case 'id':
searchData.patient_id = document.getElementById('search-id-input').value;
break;
case 'phone':
searchData.phone = document.getElementById('search-phone-input').value;
break;
case 'advanced':
searchData.first_name = document.getElementById('search-fname').value;
searchData.last_name = document.getElementById('search-lname').value;
searchData.date_of_birth = document.getElementById('search-dob').value;
break;
}
// Show loading state
const resultsDiv = document.getElementById('search-results');
resultsDiv.style.display = 'block';
resultsDiv.innerHTML = '<div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i> Searching...</div>';
fetch('/patients/search/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify(searchData)
})
.then(response => response.json())
.then(data => {
displaySearchResults(data.results);
})
.catch(error => {
resultsDiv.innerHTML = '<div class="alert alert-danger">Error performing search</div>';
});
}
function displaySearchResults(results) {
const resultsDiv = document.getElementById('search-results');
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="alert alert-info">No patients found matching your search criteria</div>';
return;
}
let html = '<div class="list-group">';
results.forEach(patient => {
html += `
<a href="/patients/${patient.id}/" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">${patient.full_name}</h6>
<small class="text-muted">ID: ${patient.patient_id}</small>
</div>
<p class="mb-1">${patient.date_of_birth}${patient.gender}</p>
<small class="text-muted">${patient.phone || 'No phone'}${patient.email || 'No email'}</small>
</a>
`;
});
html += '</div>';
resultsDiv.innerHTML = html;
}
// Real-time updates for critical alerts
function checkCriticalAlerts() {
fetch('/patients/critical-alerts/', {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.alerts && data.alerts.length > 0) {
showCriticalAlert(data.alerts[0]);
}
})
.catch(error => {
console.error('Error checking critical alerts:', error);
});
}
function showCriticalAlert(alert) {
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger alert-dismissible fade show position-fixed';
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 350px;';
alertDiv.innerHTML = `
<h6><i class="fas fa-exclamation-triangle me-2"></i>Critical Alert</h6>
<p class="mb-1"><strong>${alert.patient_name}</strong></p>
<p class="mb-0">${alert.message}</p>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
// Auto-remove after 10 seconds
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 10000);
}
// Check for critical alerts every 2 minutes
setInterval(checkCriticalAlerts, 120000);
</script>
{% endblock %}