2025-08-12 13:33:25 +03:00

565 lines
24 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Encounters - EMR{% endblock %}
{% block css %}
<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" />
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'emr:dashboard' %}">EMR</a></li>
<li class="breadcrumb-item active">Encounters</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Patient Encounters
<small>Electronic Medical Records</small>
</h1>
<!-- END page-header -->
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Encounter Management</h4>
<div class="panel-heading-btn">
<a href="{% url 'emr:encounter_create' %}" class="btn btn-xs btn-success me-2">
<i class="fa fa-plus"></i> New Encounter
</a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</div>
</div>
<div class="panel-body">
<!-- Filters and Search -->
<div class="row mb-3">
<div class="col-md-3">
<label class="form-label">Search</label>
<div class="input-group">
<input type="text" class="form-control" id="search-input" placeholder="Patient name, MRN, or provider...">
<button class="btn btn-outline-secondary" type="button" id="search-btn">
<i class="fa fa-search"></i>
</button>
</div>
</div>
<div class="col-md-2">
<label class="form-label">Status</label>
<select class="form-select" id="status-filter">
<option value="">All Statuses</option>
<option value="PLANNED">Planned</option>
<option value="ARRIVED">Arrived</option>
<option value="TRIAGED">Triaged</option>
<option value="IN_PROGRESS">In Progress</option>
<option value="ON_HOLD">On Hold</option>
<option value="FINISHED">Finished</option>
<option value="CANCELLED">Cancelled</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Type</label>
<select class="form-select" id="type-filter">
<option value="">All Types</option>
<option value="INPATIENT">Inpatient</option>
<option value="OUTPATIENT">Outpatient</option>
<option value="EMERGENCY">Emergency</option>
<option value="URGENT_CARE">Urgent Care</option>
<option value="TELEMEDICINE">Telemedicine</option>
<option value="CONSULTATION">Consultation</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Date Range</label>
<select class="form-select" id="date-filter">
<option value="">All Dates</option>
<option value="today">Today</option>
<option value="yesterday">Yesterday</option>
<option value="this_week">This Week</option>
<option value="last_week">Last Week</option>
<option value="this_month">This Month</option>
<option value="custom">Custom Range</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Provider</label>
<select class="form-select select2" id="provider-filter">
<option value="">All Providers</option>
<!-- Options will be loaded dynamically -->
</select>
</div>
</div>
<!-- Custom Date Range (hidden by default) -->
<div class="row mb-3" id="custom-date-range" style="display: none;">
<div class="col-md-3">
<label class="form-label">Start Date</label>
<input type="date" class="form-control" id="start-date">
</div>
<div class="col-md-3">
<label class="form-label">End Date</label>
<input type="date" class="form-control" id="end-date">
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<button class="btn btn-primary d-block" id="apply-date-filter">Apply</button>
</div>
</div>
<!-- Action Buttons -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" id="bulk-actions-btn" disabled>
<i class="fa fa-tasks me-2"></i>Bulk Actions
</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" id="bulk-dropdown" disabled>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="bulkUpdateStatus('FINISHED')">
<i class="fa fa-check me-2"></i>Mark as Finished
</a></li>
<li><a class="dropdown-item" href="#" onclick="bulkUpdateStatus('CANCELLED')">
<i class="fa fa-times me-2"></i>Cancel Selected
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="bulkExport()">
<i class="fa fa-download me-2"></i>Export Selected
</a></li>
</ul>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" onclick="refreshTable()">
<i class="fa fa-refresh me-2"></i>Refresh
</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fa fa-download me-2"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportData('csv')">
<i class="fa fa-file-csv me-2"></i>Export as CSV
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportData('excel')">
<i class="fa fa-file-excel me-2"></i>Export as Excel
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportData('pdf')">
<i class="fa fa-file-pdf me-2"></i>Export as PDF
</a></li>
</ul>
</div>
</div>
<!-- Encounters Table -->
<div class="table-responsive">
<table id="encounters-table" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th width="30">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="select-all">
</div>
</th>
<th>Patient</th>
<th>Provider</th>
<th>Type</th>
<th>Status</th>
<th>Start Date/Time</th>
<th>Duration</th>
<th>Location</th>
<th>Priority</th>
<th>Documentation</th>
<th width="120">Actions</th>
</tr>
</thead>
<tbody>
<!-- Data will be loaded via AJAX -->
</tbody>
</table>
</div>
</div>
</div>
<!-- END panel -->
<!-- Quick View Modal -->
<div class="modal fade" id="encounterQuickViewModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Encounter Quick View</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="encounter-quick-view-content">
<!-- Content will be loaded dynamically -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<a href="#" class="btn btn-primary" id="view-full-encounter">View Full Details</a>
</div>
</div>
</div>
</div>
<!-- Status Update Modal -->
<div class="modal fade" id="statusUpdateModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Update Encounter Status</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="status-update-form">
<input type="hidden" id="encounter-id" name="encounter_id">
<div class="mb-3">
<label class="form-label">New Status</label>
<select class="form-select" name="status" required>
<option value="">Select Status</option>
<option value="PLANNED">Planned</option>
<option value="ARRIVED">Arrived</option>
<option value="TRIAGED">Triaged</option>
<option value="IN_PROGRESS">In Progress</option>
<option value="ON_HOLD">On Hold</option>
<option value="FINISHED">Finished</option>
<option value="CANCELLED">Cancelled</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Notes (Optional)</label>
<textarea class="form-control" name="notes" rows="3" placeholder="Reason for status change..."></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="updateEncounterStatus()">Update Status</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'assets/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script>
$(document).ready(function() {
var table;
// Initialize DataTable
table = $('#encounters-table').DataTable({
processing: true,
serverSide: true,
ajax: {
url: '{% url "emr:encounter_list_api" %}',
data: function(d) {
d.search_value = $('#search-input').val();
d.status = $('#status-filter').val();
d.encounter_type = $('#type-filter').val();
d.date_range = $('#date-filter').val();
d.start_date = $('#start-date').val();
d.end_date = $('#end-date').val();
d.provider = $('#provider-filter').val();
}
},
columns: [
{
data: 'id',
orderable: false,
render: function(data, type, row) {
return '<div class="form-check"><input class="form-check-input encounter-checkbox" type="checkbox" value="' + data + '"></div>';
}
},
{
data: 'patient',
render: function(data, type, row) {
return '<div class="d-flex align-items-center">' +
'<div class="flex-fill">' +
'<div class="fw-bold">' + data.name + '</div>' +
'<div class="text-muted small">' + data.mrn + '</div>' +
'</div></div>';
}
},
{
data: 'provider',
render: function(data, type, row) {
return '<div>' +
'<div class="fw-bold">Dr. ' + data.name + '</div>' +
'<div class="text-muted small">' + (data.specialty || 'General') + '</div>' +
'</div>';
}
},
{
data: 'encounter_type',
render: function(data, type, row) {
var badgeClass = {
'INPATIENT': 'bg-primary',
'OUTPATIENT': 'bg-success',
'EMERGENCY': 'bg-danger',
'URGENT_CARE': 'bg-warning',
'TELEMEDICINE': 'bg-info',
'CONSULTATION': 'bg-secondary'
};
return '<span class="badge ' + (badgeClass[data] || 'bg-secondary') + '">' + row.encounter_type_display + '</span>';
}
},
{
data: 'status',
render: function(data, type, row) {
var badgeClass = {
'PLANNED': 'bg-secondary',
'ARRIVED': 'bg-info',
'TRIAGED': 'bg-warning',
'IN_PROGRESS': 'bg-primary',
'ON_HOLD': 'bg-warning',
'FINISHED': 'bg-success',
'CANCELLED': 'bg-danger'
};
return '<span class="badge ' + (badgeClass[data] || 'bg-secondary') + '">' + row.status_display + '</span>';
}
},
{
data: 'start_datetime',
render: function(data, type, row) {
var date = new Date(data);
return '<div>' +
'<div>' + date.toLocaleDateString() + '</div>' +
'<div class="text-muted small">' + date.toLocaleTimeString() + '</div>' +
'</div>';
}
},
{
data: 'duration',
render: function(data, type, row) {
if (data) {
return data;
} else if (row.is_active) {
return '<span class="text-primary">Ongoing</span>';
}
return '<span class="text-muted">-</span>';
}
},
{
data: 'location',
render: function(data, type, row) {
if (data) {
return '<div>' +
'<div>' + data + '</div>' +
(row.room_number ? '<div class="text-muted small">Room ' + row.room_number + '</div>' : '') +
'</div>';
}
return '<span class="text-muted">Not specified</span>';
}
},
{
data: 'priority',
render: function(data, type, row) {
var badgeClass = {
'ROUTINE': 'bg-secondary',
'URGENT': 'bg-warning',
'ASAP': 'bg-warning',
'STAT': 'bg-danger',
'EMERGENCY': 'bg-danger'
};
return '<span class="badge ' + (badgeClass[data] || 'bg-secondary') + '">' + row.priority_display + '</span>';
}
},
{
data: 'documentation_complete',
render: function(data, type, row) {
if (data) {
return '<i class="fa fa-check-circle text-success" title="Complete"></i>';
} else if (row.status === 'FINISHED') {
return '<i class="fa fa-exclamation-triangle text-warning" title="Pending"></i>';
}
return '<i class="fa fa-clock text-muted" title="In Progress"></i>';
}
},
{
data: 'id',
orderable: false,
render: function(data, type, row) {
return '<div class="btn-group btn-group-sm">' +
'<button class="btn btn-outline-primary" onclick="viewEncounter(' + data + ')" title="View">' +
'<i class="fa fa-eye"></i></button>' +
'<button class="btn btn-outline-secondary" onclick="editEncounter(' + data + ')" title="Edit">' +
'<i class="fa fa-edit"></i></button>' +
'<button class="btn btn-outline-info" onclick="updateStatus(' + data + ')" title="Update Status">' +
'<i class="fa fa-tasks"></i></button>' +
'</div>';
}
}
],
order: [[5, 'desc']],
pageLength: 25,
responsive: true,
language: {
processing: '<div class="d-flex justify-content-center"><div class="spinner-border" role="status"></div></div>'
}
});
// Initialize Select2
$('.select2').select2({
theme: 'bootstrap-5',
width: '100%'
});
// Load providers for filter
loadProviders();
// Filter event handlers
$('#search-input, #status-filter, #type-filter, #date-filter, #provider-filter').on('change keyup', function() {
if ($(this).attr('id') === 'date-filter' && $(this).val() === 'custom') {
$('#custom-date-range').show();
} else if ($(this).attr('id') === 'date-filter') {
$('#custom-date-range').hide();
}
table.draw();
});
$('#apply-date-filter').on('click', function() {
table.draw();
});
// Select all checkbox
$('#select-all').on('change', function() {
$('.encounter-checkbox').prop('checked', this.checked);
updateBulkActions();
});
// Individual checkbox change
$(document).on('change', '.encounter-checkbox', function() {
updateBulkActions();
});
// Auto-refresh every 30 seconds
setInterval(function() {
table.ajax.reload(null, false);
}, 30000);
});
function loadProviders() {
$.ajax({
url: '{% url "users:provider_list_api" %}',
success: function(data) {
var select = $('#provider-filter');
data.forEach(function(provider) {
select.append('<option value="' + provider.id + '">Dr. ' + provider.name + '</option>');
});
}
});
}
function updateBulkActions() {
var checkedCount = $('.encounter-checkbox:checked').length;
$('#bulk-actions-btn, #bulk-dropdown').prop('disabled', checkedCount === 0);
}
function viewEncounter(encounterId) {
window.location.href = '{% url "emr:encounter_detail" 0 %}'.replace('0', encounterId);
}
function editEncounter(encounterId) {
window.location.href = '{% url "emr:encounter_update" 0 %}'.replace('0', encounterId);
}
function updateStatus(encounterId) {
$('#encounter-id').val(encounterId);
$('#statusUpdateModal').modal('show');
}
function updateEncounterStatus() {
var formData = $('#status-update-form').serialize();
$.ajax({
url: '{% url "emr:encounter_update_status" %}',
method: 'POST',
data: formData + '&csrfmiddlewaretoken=' + '{{ csrf_token }}',
success: function(response) {
if (response.success) {
toastr.success('Encounter status updated successfully');
$('#statusUpdateModal').modal('hide');
$('#encounters-table').DataTable().ajax.reload();
} else {
toastr.error('Failed to update encounter status');
}
},
error: function() {
toastr.error('An error occurred while updating the status');
}
});
}
function bulkUpdateStatus(status) {
var selectedIds = $('.encounter-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedIds.length === 0) {
toastr.warning('Please select encounters to update');
return;
}
if (confirm('Are you sure you want to update ' + selectedIds.length + ' encounter(s)?')) {
$.ajax({
url: '{% url "emr:encounter_bulk_update" %}',
method: 'POST',
data: {
'encounter_ids': selectedIds,
'status': status,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
toastr.success(response.message);
$('#encounters-table').DataTable().ajax.reload();
$('#select-all').prop('checked', false);
updateBulkActions();
} else {
toastr.error('Failed to update encounters');
}
},
error: function() {
toastr.error('An error occurred during bulk update');
}
});
}
}
function bulkExport() {
var selectedIds = $('.encounter-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedIds.length === 0) {
toastr.warning('Please select encounters to export');
return;
}
window.location.href = '{% url "emr:encounter_export" %}?ids=' + selectedIds.join(',');
}
function exportData(format) {
window.location.href = '{% url "emr:encounter_export" %}?format=' + format;
}
function refreshTable() {
$('#encounters-table').DataTable().ajax.reload();
toastr.info('Table refreshed');
}
</script>
{% endblock %}