584 lines
26 KiB
HTML
584 lines
26 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Patient Profiles{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'patients:patient_list' %}">Patients</a></li>
|
|
<li class="breadcrumb-item active">Profiles</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">Patient Profiles</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<div class="btn-group">
|
|
<a href="{% url 'patients:patient_registration' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Register Patient
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary 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="#" onclick="exportPatients()">
|
|
<i class="fas fa-download me-2"></i>Export Patients
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="importPatients()">
|
|
<i class="fas fa-upload me-2"></i>Import Patients
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkUpdate()">
|
|
<i class="fas fa-edit me-2"></i>Bulk Update
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="generateReport()">
|
|
<i class="fas fa-chart-bar me-2"></i>Generate Report
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card bg-primary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<div class="fs-4 fw-bold">{{ total_patients|default:0 }}</div>
|
|
<div>Total Patients</div>
|
|
</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">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<div class="fs-4 fw-bold">{{ active_patients|default:0 }}</div>
|
|
<div>Active Patients</div>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-user-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">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<div class="fs-4 fw-bold">{{ new_today|default:0 }}</div>
|
|
<div>Registered Today</div>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-user-plus 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">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<div class="fs-4 fw-bold">{{ incomplete_profiles|default:0 }}</div>
|
|
<div>Incomplete Profiles</div>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-exclamation-triangle fa-2x opacity-75"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters and Search -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label for="search" class="form-label">Search</label>
|
|
<input type="text"
|
|
class="form-control"
|
|
id="search"
|
|
name="search"
|
|
value="{{ request.GET.search }}"
|
|
placeholder="Search patients...">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="gender" class="form-label">Gender</label>
|
|
<select class="form-select" id="gender" name="gender">
|
|
<option value="">All Genders</option>
|
|
<option value="male" {% if request.GET.gender == 'male' %}selected{% endif %}>Male</option>
|
|
<option value="female" {% if request.GET.gender == 'female' %}selected{% endif %}>Female</option>
|
|
<option value="other" {% if request.GET.gender == 'other' %}selected{% endif %}>Other</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="age_range" class="form-label">Age Range</label>
|
|
<select class="form-select" id="age_range" name="age_range">
|
|
<option value="">All Ages</option>
|
|
<option value="0-18" {% if request.GET.age_range == '0-18' %}selected{% endif %}>0-18</option>
|
|
<option value="19-35" {% if request.GET.age_range == '19-35' %}selected{% endif %}>19-35</option>
|
|
<option value="36-50" {% if request.GET.age_range == '36-50' %}selected{% endif %}>36-50</option>
|
|
<option value="51-65" {% if request.GET.age_range == '51-65' %}selected{% endif %}>51-65</option>
|
|
<option value="65+" {% if request.GET.age_range == '65+' %}selected{% endif %}>65+</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="status" class="form-label">Status</label>
|
|
<select class="form-select" id="status" name="status">
|
|
<option value="">All Status</option>
|
|
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>Active</option>
|
|
<option value="inactive" {% if request.GET.status == 'inactive' %}selected{% endif %}>Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="registration_date" class="form-label">Registration</label>
|
|
<select class="form-select" id="registration_date" name="registration_date">
|
|
<option value="">All Dates</option>
|
|
<option value="today" {% if request.GET.registration_date == 'today' %}selected{% endif %}>Today</option>
|
|
<option value="week" {% if request.GET.registration_date == 'week' %}selected{% endif %}>This Week</option>
|
|
<option value="month" {% if request.GET.registration_date == 'month' %}selected{% endif %}>This Month</option>
|
|
<option value="year" {% if request.GET.registration_date == 'year' %}selected{% endif %}>This Year</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<label class="form-label"> </label>
|
|
<div class="d-grid">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Patients Table -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex align-items-center">
|
|
<h4 class="card-title mb-0">
|
|
<i class="fas fa-list me-2"></i>
|
|
Patient Profiles
|
|
{% if object_list %}
|
|
<span class="badge bg-secondary ms-2">{{ object_list|length }}</span>
|
|
{% endif %}
|
|
</h4>
|
|
<div class="ms-auto">
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="selectAll()">
|
|
<i class="fas fa-check-square me-1"></i>Select All
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="clearSelection()">
|
|
<i class="fas fa-square me-1"></i>Clear
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="refreshTable()">
|
|
<i class="fas fa-sync me-1"></i>Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if object_list %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th width="40">
|
|
<input type="checkbox" id="selectAllCheckbox" onchange="toggleAll(this)">
|
|
</th>
|
|
<th>Patient</th>
|
|
<th>MRN</th>
|
|
<th>Age/Gender</th>
|
|
<th>Contact</th>
|
|
<th>Primary Care</th>
|
|
<th>Registration</th>
|
|
<th>Status</th>
|
|
<th width="120">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for patient in object_list %}
|
|
<tr>
|
|
<td>
|
|
<input type="checkbox" class="patient-checkbox" value="{{ patient.pk }}">
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<div class="fw-bold">
|
|
<a href="{% url 'patients:patient_detail' patient.pk %}" class="text-decoration-none">
|
|
{{ patient.get_full_name }}
|
|
</a>
|
|
</div>
|
|
{% if patient.date_of_birth %}
|
|
<div class="small text-muted">DOB: {{ patient.date_of_birth|date:"M d, Y" }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-primary">{{ patient.medical_record_number }}</span>
|
|
{% if patient.ssn %}
|
|
<div class="small text-muted">SSN: ***-**-{{ patient.ssn|slice:"-4:" }}</div>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">{{ patient.age }} years</div>
|
|
<div class="small text-muted">
|
|
<i class="fas fa-{% if patient.gender == 'male' %}mars text-primary{% elif patient.gender == 'female' %}venus text-danger{% else %}genderless text-secondary{% endif %} me-1"></i>
|
|
{{ patient.get_gender_display }}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{% if patient.phone_number %}
|
|
<div class="fw-bold">{{ patient.phone_number }}</div>
|
|
{% endif %}
|
|
{% if patient.email %}
|
|
<div class="small text-muted">{{ patient.email|truncatechars:25 }}</div>
|
|
{% endif %}
|
|
{% if not patient.phone_number and not patient.email %}
|
|
<span class="text-muted">No contact info</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if patient.primary_care_physician %}
|
|
<div class="fw-bold">{{ patient.primary_care_physician.get_full_name }}</div>
|
|
<div class="small text-muted">{{ patient.primary_care_physician.department|default:"General" }}</div>
|
|
{% else %}
|
|
<span class="text-muted">Not assigned</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">{{ patient.created_at|date:"M d, Y" }}</div>
|
|
<div class="small text-muted">{{ patient.created_at|date:"g:i A" }}</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if patient.is_active %}success{% else %}secondary{% endif %}">
|
|
{% if patient.is_active %}Active{% else %}Inactive{% endif %}
|
|
</span>
|
|
{% if patient.blood_type %}
|
|
<div class="small text-danger mt-1">
|
|
<i class="fas fa-tint me-1"></i>{{ patient.blood_type }}
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'patients:patient_detail' patient.pk %}"
|
|
class="btn btn-outline-primary"
|
|
title="View Profile">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'patients:patient_form' patient.pk %}"
|
|
class="btn btn-outline-secondary"
|
|
title="Edit">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<button type="button"
|
|
class="btn btn-outline-info"
|
|
onclick="quickActions({{ patient.pk }})"
|
|
title="Quick Actions">
|
|
<i class="fas fa-bolt"></i>
|
|
</button>
|
|
<a href="{% url 'patients:patient_confirm_delete' patient.pk %}"
|
|
class="btn btn-outline-danger"
|
|
title="Delete">
|
|
<i class="fas fa-trash"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="card-footer">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} patients
|
|
</div>
|
|
<nav>
|
|
<ul class="pagination pagination-sm mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ page_obj.number }}</span>
|
|
</li>
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">Last</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Bulk Actions -->
|
|
<div class="card-footer bg-light" id="bulkActions" style="display: none;">
|
|
<div class="d-flex align-items-center">
|
|
<div class="me-3">
|
|
<span id="selectedCount">0</span> patient(s) selected
|
|
</div>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-success" onclick="bulkActivate()">
|
|
<i class="fas fa-check me-1"></i>Activate
|
|
</button>
|
|
<button type="button" class="btn btn-warning" onclick="bulkDeactivate()">
|
|
<i class="fas fa-pause me-1"></i>Deactivate
|
|
</button>
|
|
<button type="button" class="btn btn-info" onclick="bulkAssignPhysician()">
|
|
<i class="fas fa-user-md me-1"></i>Assign Physician
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="bulkExport()">
|
|
<i class="fas fa-download me-1"></i>Export
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="bulkPrint()">
|
|
<i class="fas fa-print me-1"></i>Print
|
|
</button>
|
|
<button type="button" class="btn btn-danger" onclick="bulkDelete()">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-users fa-3x text-muted mb-3"></i>
|
|
<h5>No Patients Found</h5>
|
|
<p class="text-muted">No patients match your current filters.</p>
|
|
<a href="{% url 'patients:patient_registration' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Register First Patient
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions Modal -->
|
|
<div class="modal fade" id="quickActionsModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Quick Actions</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="d-grid gap-2">
|
|
<button type="button" class="btn btn-primary" onclick="scheduleAppointment()">
|
|
<i class="fas fa-calendar-plus me-2"></i>Schedule Appointment
|
|
</button>
|
|
<button type="button" class="btn btn-success" onclick="newEncounter()">
|
|
<i class="fas fa-stethoscope me-2"></i>New Encounter
|
|
</button>
|
|
<button type="button" class="btn btn-info" onclick="orderLabTest()">
|
|
<i class="fas fa-flask me-2"></i>Order Lab Test
|
|
</button>
|
|
<button type="button" class="btn btn-warning" onclick="newPrescription()">
|
|
<i class="fas fa-pills me-2"></i>New Prescription
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="addNote()">
|
|
<i class="fas fa-sticky-note me-2"></i>Add Note
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentPatientId = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
updateBulkActions();
|
|
});
|
|
|
|
function toggleAll(checkbox) {
|
|
const checkboxes = document.querySelectorAll('.patient-checkbox');
|
|
checkboxes.forEach(cb => cb.checked = checkbox.checked);
|
|
updateBulkActions();
|
|
}
|
|
|
|
function selectAll() {
|
|
const checkboxes = document.querySelectorAll('.patient-checkbox');
|
|
checkboxes.forEach(cb => cb.checked = true);
|
|
document.getElementById('selectAllCheckbox').checked = true;
|
|
updateBulkActions();
|
|
}
|
|
|
|
function clearSelection() {
|
|
const checkboxes = document.querySelectorAll('.patient-checkbox');
|
|
checkboxes.forEach(cb => cb.checked = false);
|
|
document.getElementById('selectAllCheckbox').checked = false;
|
|
updateBulkActions();
|
|
}
|
|
|
|
function updateBulkActions() {
|
|
const checkboxes = document.querySelectorAll('.patient-checkbox:checked');
|
|
const count = checkboxes.length;
|
|
const bulkActions = document.getElementById('bulkActions');
|
|
const selectedCount = document.getElementById('selectedCount');
|
|
|
|
if (count > 0) {
|
|
bulkActions.style.display = 'block';
|
|
selectedCount.textContent = count;
|
|
} else {
|
|
bulkActions.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Add event listeners to checkboxes
|
|
document.addEventListener('change', function(e) {
|
|
if (e.target.classList.contains('patient-checkbox')) {
|
|
updateBulkActions();
|
|
}
|
|
});
|
|
|
|
function getSelectedPatients() {
|
|
const checkboxes = document.querySelectorAll('.patient-checkbox:checked');
|
|
return Array.from(checkboxes).map(cb => cb.value);
|
|
}
|
|
|
|
function quickActions(patientId) {
|
|
currentPatientId = patientId;
|
|
const modal = new bootstrap.Modal(document.getElementById('quickActionsModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function scheduleAppointment() {
|
|
if (currentPatientId) {
|
|
window.location.href = `{% url 'appointments:appointment_form' %}?patient=${currentPatientId}`;
|
|
}
|
|
}
|
|
|
|
function newEncounter() {
|
|
if (currentPatientId) {
|
|
window.location.href = `{% url 'emr:encounter_form' %}?patient=${currentPatientId}`;
|
|
}
|
|
}
|
|
|
|
function orderLabTest() {
|
|
if (currentPatientId) {
|
|
window.location.href = `{% url 'laboratory:lab_order_form' %}?patient=${currentPatientId}`;
|
|
}
|
|
}
|
|
|
|
function newPrescription() {
|
|
if (currentPatientId) {
|
|
window.location.href = `{% url 'pharmacy:prescription_form' %}?patient=${currentPatientId}`;
|
|
}
|
|
}
|
|
|
|
function addNote() {
|
|
if (currentPatientId) {
|
|
window.location.href = `{% url 'patients:patient_note_form' %}?patient=${currentPatientId}`;
|
|
}
|
|
}
|
|
|
|
function bulkActivate() {
|
|
const selected = getSelectedPatients();
|
|
if (selected.length === 0) return;
|
|
|
|
if (confirm(`Activate ${selected.length} selected patient(s)?`)) {
|
|
console.log('Activating patients:', selected);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function bulkDeactivate() {
|
|
const selected = getSelectedPatients();
|
|
if (selected.length === 0) return;
|
|
|
|
if (confirm(`Deactivate ${selected.length} selected patient(s)?`)) {
|
|
console.log('Deactivating patients:', selected);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function bulkAssignPhysician() {
|
|
const selected = getSelectedPatients();
|
|
if (selected.length === 0) return;
|
|
|
|
// In a real implementation, this would open a physician selection modal
|
|
alert('Bulk physician assignment functionality would be implemented here.');
|
|
}
|
|
|
|
function bulkExport() {
|
|
const selected = getSelectedPatients();
|
|
if (selected.length === 0) return;
|
|
|
|
console.log('Exporting patients:', selected);
|
|
alert('Export functionality would be implemented here.');
|
|
}
|
|
|
|
function bulkPrint() {
|
|
const selected = getSelectedPatients();
|
|
if (selected.length === 0) return;
|
|
|
|
console.log('Printing patients:', selected);
|
|
alert('Print functionality would be implemented here.');
|
|
}
|
|
|
|
function bulkDelete() {
|
|
const selected = getSelectedPatients();
|
|
if (selected.length === 0) return;
|
|
|
|
if (confirm(`Are you sure you want to delete ${selected.length} selected patient(s)? This action cannot be undone.`)) {
|
|
console.log('Deleting patients:', selected);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function refreshTable() {
|
|
location.reload();
|
|
}
|
|
|
|
function exportPatients() {
|
|
alert('Export all patients functionality would be implemented here.');
|
|
}
|
|
|
|
function importPatients() {
|
|
alert('Import patients functionality would be implemented here.');
|
|
}
|
|
|
|
function bulkUpdate() {
|
|
alert('Bulk update functionality would be implemented here.');
|
|
}
|
|
|
|
function generateReport() {
|
|
alert('Generate report functionality would be implemented here.');
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|