565 lines
24 KiB
HTML
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"> </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 %}
|
|
|