hospital-management/templates/emr/encounter_list.html
2025-08-12 13:33:25 +03:00

491 lines
22 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Encounter Management - {{ 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-notes-medical me-2"></i>Encounter Management
</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'emr:dashboard' %}">EMR</a></li>
<li class="breadcrumb-item active">Encounters</li>
</ol>
</nav>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-2"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportEncounters('csv')">
<i class="fas fa-file-csv me-2"></i>Export as CSV
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEncounters('excel')">
<i class="fas fa-file-excel me-2"></i>Export as Excel
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEncounters('pdf')">
<i class="fas fa-file-pdf me-2"></i>Export as PDF
</a></li>
</ul>
<button type="button" class="btn btn-primary" onclick="createNewEncounter()">
<i class="fas fa-plus me-2"></i>New Encounter
</button>
</div>
</div>
<!-- Filters Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filters
</h5>
</div>
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-2">
<label for="encounter_type" class="form-label">Type</label>
<select name="encounter_type" id="encounter_type" class="form-select">
<option value="">All Types</option>
{% for value, label in encounter_types %}
<option value="{{ value }}" {% if request.GET.encounter_type == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label for="status" class="form-label">Status</label>
<select name="status" id="status" class="form-select">
<option value="">All Status</option>
{% for value, label in encounter_statuses %}
<option value="{{ value }}" {% if request.GET.status == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label for="date_from" class="form-label">From Date</label>
<input type="date" name="date_from" id="date_from" class="form-control"
value="{{ request.GET.date_from }}">
</div>
<div class="col-md-2">
<label for="date_to" class="form-label">To Date</label>
<input type="date" name="date_to" id="date_to" class="form-control"
value="{{ request.GET.date_to }}">
</div>
<div class="col-md-3">
<label for="search" class="form-label">Search</label>
<input type="text" name="search" id="search" class="form-control"
placeholder="Search patients, providers, complaints..."
value="{{ request.GET.search }}">
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-search me-1"></i>Filter
</button>
<a href="{% url 'emr:encounter_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times"></i>
</a>
</div>
</form>
</div>
</div>
<!-- Encounters List -->
<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-list me-2"></i>Encounters
{% if encounters %}
<span class="badge bg-secondary ms-2">{{ encounters|length }}</span>
{% endif %}
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="refreshEncounters()">
<i class="fas fa-sync-alt me-1"></i>Refresh
</button>
<button type="button" class="btn btn-outline-secondary" onclick="toggleBulkActions()">
<i class="fas fa-check-square me-1"></i>Bulk Actions
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<!-- Bulk Actions Bar (Hidden by default) -->
<div id="bulk-actions-bar" class="bg-light border-bottom p-3" style="display: none;">
<div class="d-flex justify-content-between align-items-center">
<div>
<span id="selected-count">0</span> encounter(s) selected
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" onclick="bulkUpdateStatus()">
<i class="fas fa-edit me-1"></i>Update Status
</button>
<button type="button" class="btn btn-outline-success" onclick="bulkExport()">
<i class="fas fa-download me-1"></i>Export Selected
</button>
<button type="button" class="btn btn-outline-secondary" onclick="clearSelection()">
<i class="fas fa-times me-1"></i>Clear
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th width="40">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="select-all">
</div>
</th>
<th>Patient</th>
<th>Type</th>
<th>Provider</th>
<th>Date/Time</th>
<th>Status</th>
<th>Chief Complaint</th>
<th width="120">Actions</th>
</tr>
</thead>
<tbody>
{% for encounter in encounters %}
<tr>
<td>
<div class="form-check">
<input class="form-check-input encounter-checkbox" type="checkbox"
value="{{ encounter.id }}">
</div>
</td>
<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">
{{ encounter.patient.first_name.0 }}{{ encounter.patient.last_name.0 }}
</span>
</div>
<div>
<div class="fw-semibold">{{ encounter.patient.get_full_name }}</div>
<small class="text-muted">MRN: {{ encounter.patient.mrn }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-primary">{{ encounter.get_encounter_type_display }}</span>
</td>
<td>{{ encounter.provider.get_full_name }}</td>
<td>
<div>{{ encounter.start_datetime|date:"M d, Y" }}</div>
<small class="text-muted">{{ encounter.start_datetime|time:"H:i" }}</small>
</td>
<td>
<span class="badge bg-{% if encounter.status == 'FINISHED' %}success{% elif encounter.status == 'CANCELLED' %}danger{% elif encounter.status == 'IN_PROGRESS' %}warning{% else %}secondary{% endif %}">
{{ encounter.get_status_display }}
</span>
</td>
<td>
<div class="text-truncate" style="max-width: 200px;" title="{{ encounter.chief_complaint }}">
{{ encounter.chief_complaint|default:"Not specified" }}
</div>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{% url 'emr:encounter_detail' encounter.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-edit me-2"></i>Edit Encounter
</a></li>
<li><a class="dropdown-item" href="{% url 'emr:add_vital_signs' encounter.id %}">
<i class="fas fa-heartbeat me-2"></i>Add Vital Signs
</a></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-notes-medical me-2"></i>Add Clinical Note
</a></li>
<li><a class="dropdown-item" href="{% url 'emr:add_problem' encounter.patient.id %}">
<i class="fas fa-exclamation-triangle me-2"></i>Add Problem
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-print me-2"></i>Print Summary
</a></li>
<li><a class="dropdown-item text-danger" href="#">
<i class="fas fa-trash me-2"></i>Delete
</a></li>
</ul>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-5">
<i class="fas fa-notes-medical fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No encounters found</h5>
<p class="text-muted">No encounters match your current filters.</p>
<button type="button" class="btn btn-primary" onclick="createNewEncounter()">
<i class="fas fa-plus me-2"></i>Create First Encounter
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted small">
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} encounters
</div>
<nav aria-label="Encounters pagination">
<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.encounter_type %}&encounter_type={{ request.GET.encounter_type }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.encounter_type %}&encounter_type={{ request.GET.encounter_type }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% 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.encounter_type %}&encounter_type={{ request.GET.encounter_type }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.encounter_type %}&encounter_type={{ request.GET.encounter_type }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.date_from %}&date_from={{ request.GET.date_from }}{% endif %}{% if request.GET.date_to %}&date_to={{ request.GET.date_to }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% endif %}
</div>
</div>
<script>
// Encounter list functionality
document.addEventListener('DOMContentLoaded', function() {
// Initialize bulk selection
const selectAllCheckbox = document.getElementById('select-all');
const encounterCheckboxes = document.querySelectorAll('.encounter-checkbox');
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', function() {
encounterCheckboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateSelectedCount();
});
}
encounterCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
updateSelectedCount();
// Update select all checkbox state
const checkedCount = document.querySelectorAll('.encounter-checkbox:checked').length;
if (selectAllCheckbox) {
selectAllCheckbox.checked = checkedCount === encounterCheckboxes.length;
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < encounterCheckboxes.length;
}
});
});
});
function updateSelectedCount() {
const checkedCount = document.querySelectorAll('.encounter-checkbox:checked').length;
const selectedCountElement = document.getElementById('selected-count');
if (selectedCountElement) {
selectedCountElement.textContent = checkedCount;
}
// Show/hide bulk actions bar
const bulkActionsBar = document.getElementById('bulk-actions-bar');
if (bulkActionsBar) {
bulkActionsBar.style.display = checkedCount > 0 ? 'block' : 'none';
}
}
function toggleBulkActions() {
const bulkActionsBar = document.getElementById('bulk-actions-bar');
const isVisible = bulkActionsBar.style.display !== 'none';
if (isVisible) {
clearSelection();
} else {
bulkActionsBar.style.display = 'block';
}
}
function clearSelection() {
document.querySelectorAll('.encounter-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
}
updateSelectedCount();
}
function refreshEncounters() {
location.reload();
}
function createNewEncounter() {
// Implementation would depend on your encounter creation workflow
alert('New encounter creation functionality would be implemented here');
}
function exportEncounters(format) {
const form = document.querySelector('form');
const formData = new FormData(form);
const params = new URLSearchParams(formData);
params.append('format', format);
window.open(`{% url 'emr:encounter_list' %}export/?${params}`, '_blank');
}
function bulkUpdateStatus() {
const selectedIds = Array.from(document.querySelectorAll('.encounter-checkbox:checked'))
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert('Please select encounters to update');
return;
}
const newStatus = prompt('Enter new status (PLANNED, ARRIVED, TRIAGED, IN_PROGRESS, ON_HOLD, FINISHED, CANCELLED):');
if (newStatus) {
// Implementation for bulk status update
console.log('Updating encounters:', selectedIds, 'to status:', newStatus);
alert('Bulk status update functionality would be implemented here');
}
}
function bulkExport() {
const selectedIds = Array.from(document.querySelectorAll('.encounter-checkbox:checked'))
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert('Please select encounters to export');
return;
}
const params = new URLSearchParams();
selectedIds.forEach(id => params.append('ids', id));
window.open(`{% url 'emr:encounter_list' %}export/?${params}`, '_blank');
}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
return;
}
switch (e.key) {
case 'n':
e.preventDefault();
createNewEncounter();
break;
case 'r':
e.preventDefault();
refreshEncounters();
break;
case '/':
e.preventDefault();
document.getElementById('search').focus();
break;
case 'a':
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.click();
}
}
break;
}
});
</script>
<style>
.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));
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.form-check-input:indeterminate {
background-color: #0d6efd;
border-color: #0d6efd;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}
#bulk-actions-bar {
border-bottom: 1px solid #dee2e6;
}
@media (max-width: 768px) {
.btn-group-sm .btn {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.table-responsive {
font-size: 0.875rem;
}
.d-flex.justify-content-between {
flex-direction: column;
gap: 1rem;
}
}
</style>
{% endblock %}