517 lines
19 KiB
HTML
517 lines
19 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Surgical Notes{% 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" />
|
|
<style>
|
|
.stats-card {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 0.5rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-item {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 50px;
|
|
height: 50px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto 1rem;
|
|
color: white;
|
|
}
|
|
|
|
.stat-icon.primary { background: #007bff; }
|
|
.stat-icon.success { background: #28a745; }
|
|
.stat-icon.warning { background: #ffc107; }
|
|
.stat-icon.info { background: #17a2b8; }
|
|
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: #495057;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #6c757d;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.filter-section {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.note-status {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.status-draft { background: #f8f9fa; color: #6c757d; }
|
|
.status-in-progress { background: #fff3cd; color: #856404; }
|
|
.status-completed { background: #d4edda; color: #155724; }
|
|
.status-signed { background: #d1ecf1; color: #0c5460; }
|
|
.status-amended { background: #f8d7da; color: #721c24; }
|
|
|
|
.priority-badge {
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.priority-low { background: #d4edda; color: #155724; }
|
|
.priority-medium { background: #fff3cd; color: #856404; }
|
|
.priority-high { background: #f8d7da; color: #721c24; }
|
|
.priority-critical { background: #f5c6cb; color: #721c24; }
|
|
|
|
@media (max-width: 768px) {
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.stat-item {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 1.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div id="content" class="app-content">
|
|
<!-- Page Header -->
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:dashboard' %}">Operating Theatre</a></li>
|
|
<li class="breadcrumb-item active">Surgical Notes</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">
|
|
<i class="fas fa-file-medical me-2"></i>Surgical Notes Management
|
|
</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<a href="{% url 'operating_theatre:surgical_note_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create Note
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="stats-grid">
|
|
<div class="stat-item">
|
|
<div class="stat-icon primary">
|
|
<i class="fas fa-file-medical"></i>
|
|
</div>
|
|
<div class="stat-number" id="total-notes">{{ stats.total_notes|default:0 }}</div>
|
|
<div class="stat-label">Total Notes</div>
|
|
</div>
|
|
|
|
<div class="stat-item">
|
|
<div class="stat-icon warning">
|
|
<i class="fas fa-edit"></i>
|
|
</div>
|
|
<div class="stat-number" id="draft-notes">{{ stats.draft_notes|default:0 }}</div>
|
|
<div class="stat-label">Draft Notes</div>
|
|
</div>
|
|
|
|
<div class="stat-item">
|
|
<div class="stat-icon success">
|
|
<i class="fas fa-check-circle"></i>
|
|
</div>
|
|
<div class="stat-number" id="completed-notes">{{ stats.completed_notes|default:0 }}</div>
|
|
<div class="stat-label">Completed</div>
|
|
</div>
|
|
|
|
<div class="stat-item">
|
|
<div class="stat-icon info">
|
|
<i class="fas fa-signature"></i>
|
|
</div>
|
|
<div class="stat-number" id="signed-notes">{{ stats.signed_notes|default:0 }}</div>
|
|
<div class="stat-label">Signed</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="filter-section">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" id="status-filter">
|
|
<option value="">All Statuses</option>
|
|
<option value="draft">Draft</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="signed">Signed</option>
|
|
<option value="amended">Amended</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Surgeon</label>
|
|
<select class="form-select" id="surgeon-filter">
|
|
<option value="">All Surgeons</option>
|
|
{% for surgeon in surgeons %}
|
|
<option value="{{ surgeon.id }}">{{ surgeon.get_full_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<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="week">This Week</option>
|
|
<option value="month">This Month</option>
|
|
<option value="custom">Custom Range</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Priority</label>
|
|
<select class="form-select" id="priority-filter">
|
|
<option value="">All Priorities</option>
|
|
<option value="low">Low</option>
|
|
<option value="medium">Medium</option>
|
|
<option value="high">High</option>
|
|
<option value="critical">Critical</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-3" id="custom-date-range" style="display: none;">
|
|
<div class="col-md-6">
|
|
<label class="form-label">From Date</label>
|
|
<input type="date" class="form-control" id="from-date">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">To Date</label>
|
|
<input type="date" class="form-control" id="to-date">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-3">
|
|
<div class="col-md-8">
|
|
<label class="form-label">Search</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="search-input"
|
|
placeholder="Search by patient name, procedure, or note content...">
|
|
<button class="btn btn-outline-secondary" type="button" id="search-btn">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 d-flex align-items-end">
|
|
<button class="btn btn-outline-primary me-2" onclick="applyFilters()">
|
|
<i class="fas fa-filter me-1"></i>Apply
|
|
</button>
|
|
<button class="btn btn-outline-secondary" onclick="clearFilters()">
|
|
<i class="fas fa-times me-1"></i>Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes Table -->
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-list me-2"></i>Surgical Notes
|
|
</h5>
|
|
<div class="btn-group">
|
|
<button class="btn btn-outline-success btn-sm" onclick="exportNotes('excel')">
|
|
<i class="fas fa-file-excel me-1"></i>Excel
|
|
</button>
|
|
<button class="btn btn-outline-danger btn-sm" onclick="exportNotes('pdf')">
|
|
<i class="fas fa-file-pdf me-1"></i>PDF
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="printNotes()">
|
|
<i class="fas fa-print me-1"></i>Print
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table id="notesTable" class="table table-striped table-bordered align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th width="5%">
|
|
<input type="checkbox" id="select-all">
|
|
</th>
|
|
<th>Patient</th>
|
|
<th>Procedure</th>
|
|
<th>Surgeon</th>
|
|
<th>Date</th>
|
|
<th>Status</th>
|
|
<th>Priority</th>
|
|
<th>Last Modified</th>
|
|
<th width="15%">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for note in notes %}
|
|
<tr>
|
|
<td>
|
|
<input type="checkbox" class="note-checkbox" value="{{ note.id }}">
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar avatar-sm me-2">
|
|
<i class="fas fa-user-circle fa-2x text-muted"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold">{{ note.patient.get_full_name }}</div>
|
|
<small class="text-muted">ID: {{ note.patient.patient_id }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">{{ note.procedure_name }}</div>
|
|
<small class="text-muted">{{ note.procedure_code|default:"" }}</small>
|
|
</td>
|
|
<td>
|
|
<div>{{ note.surgeon.get_full_name }}</div>
|
|
<small class="text-muted">{{ note.surgeon.specialization|default:"" }}</small>
|
|
</td>
|
|
<td>
|
|
<div>{{ note.surgery_date|date:"M d, Y" }}</div>
|
|
<small class="text-muted">{{ note.surgery_date|time:"g:i A" }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="note-status status-{{ note.status }}">
|
|
{{ note.get_status_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="priority-badge priority-{{ note.priority }}">
|
|
{{ note.get_priority_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div>{{ note.updated_at|date:"M d, Y" }}</div>
|
|
<small class="text-muted">{{ note.updated_at|time:"g:i A" }}</small>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'operating_theatre:surgical_note_detail' note.pk %}"
|
|
class="btn btn-outline-primary" title="View">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
{% if note.status != 'signed' %}
|
|
<a href="{% url 'operating_theatre:surgical_note_edit' note.pk %}"
|
|
class="btn btn-outline-warning" title="Edit">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
{% endif %}
|
|
<button class="btn btn-outline-success"
|
|
onclick="printNote('{{ note.pk }}')" title="Print">
|
|
<i class="fas fa-print"></i>
|
|
</button>
|
|
{% if note.status != 'signed' %}
|
|
<a href="{% url 'operating_theatre:surgical_note_delete' note.pk %}"
|
|
class="btn btn-outline-danger" title="Delete">
|
|
<i class="fas fa-trash"></i>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="9" class="text-center py-4">
|
|
<i class="fas fa-file-medical fa-3x text-muted mb-3"></i>
|
|
<p class="text-muted">No surgical notes found</p>
|
|
<a href="{% url 'operating_theatre:surgical_note_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create First Note
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</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>
|
|
$(document).ready(function() {
|
|
// Initialize DataTable
|
|
const table = $('#notesTable').DataTable({
|
|
responsive: true,
|
|
pageLength: 25,
|
|
order: [[4, 'desc']], // Sort by date descending
|
|
columnDefs: [
|
|
{ orderable: false, targets: [0, 8] } // Disable sorting for checkbox and actions
|
|
],
|
|
language: {
|
|
search: "",
|
|
searchPlaceholder: "Search notes...",
|
|
lengthMenu: "Show _MENU_ notes per page",
|
|
info: "Showing _START_ to _END_ of _TOTAL_ notes",
|
|
infoEmpty: "No notes available",
|
|
infoFiltered: "(filtered from _MAX_ total notes)"
|
|
}
|
|
});
|
|
|
|
// Custom search
|
|
$('#search-input').on('keyup', function() {
|
|
table.search(this.value).draw();
|
|
});
|
|
|
|
// Select all checkbox
|
|
$('#select-all').on('change', function() {
|
|
$('.note-checkbox').prop('checked', this.checked);
|
|
});
|
|
|
|
// Date filter change
|
|
$('#date-filter').on('change', function() {
|
|
if ($(this).val() === 'custom') {
|
|
$('#custom-date-range').show();
|
|
} else {
|
|
$('#custom-date-range').hide();
|
|
}
|
|
});
|
|
|
|
// Load statistics
|
|
loadStats();
|
|
});
|
|
|
|
function loadStats() {
|
|
$.ajax({
|
|
url: '{% url "operating_theatre:surgical_note_stats" %}',
|
|
success: function(data) {
|
|
$('#total-notes').text(data.total_notes);
|
|
$('#draft-notes').text(data.draft_notes);
|
|
$('#completed-notes').text(data.completed_notes);
|
|
$('#signed-notes').text(data.signed_notes);
|
|
}
|
|
});
|
|
}
|
|
|
|
function applyFilters() {
|
|
const filters = {
|
|
status: $('#status-filter').val(),
|
|
surgeon: $('#surgeon-filter').val(),
|
|
date_range: $('#date-filter').val(),
|
|
priority: $('#priority-filter').val(),
|
|
from_date: $('#from-date').val(),
|
|
to_date: $('#to-date').val(),
|
|
search: $('#search-input').val()
|
|
};
|
|
|
|
// Apply filters via AJAX
|
|
$.ajax({
|
|
url: '{% url "operating_theatre:surgical_note_list" %}',
|
|
data: filters,
|
|
success: function(response) {
|
|
// Reload table data
|
|
location.reload();
|
|
}
|
|
});
|
|
}
|
|
|
|
function clearFilters() {
|
|
$('#status-filter').val('');
|
|
$('#surgeon-filter').val('');
|
|
$('#date-filter').val('');
|
|
$('#priority-filter').val('');
|
|
$('#from-date').val('');
|
|
$('#to-date').val('');
|
|
$('#search-input').val('');
|
|
$('#custom-date-range').hide();
|
|
|
|
// Clear DataTable search
|
|
$('#notesTable').DataTable().search('').draw();
|
|
}
|
|
|
|
function exportNotes(format) {
|
|
const selectedNotes = $('.note-checkbox:checked').map(function() {
|
|
return this.value;
|
|
}).get();
|
|
|
|
const params = new URLSearchParams({
|
|
format: format,
|
|
notes: selectedNotes.join(',')
|
|
});
|
|
|
|
window.open(`{% url "operating_theatre:surgical_note_export" %}?${params}`, '_blank');
|
|
}
|
|
|
|
function printNotes() {
|
|
const selectedNotes = $('.note-checkbox:checked').map(function() {
|
|
return this.value;
|
|
}).get();
|
|
|
|
if (selectedNotes.length === 0) {
|
|
alert('Please select notes to print');
|
|
return;
|
|
}
|
|
|
|
const params = new URLSearchParams({
|
|
notes: selectedNotes.join(',')
|
|
});
|
|
|
|
window.open(`{% url "operating_theatre:surgical_note_print" %}?${params}`, '_blank');
|
|
}
|
|
|
|
function printNote(noteId) {
|
|
window.open(`{% url "operating_theatre:surgical_note_print" %}?notes=${noteId}`, '_blank');
|
|
}
|
|
|
|
// Keyboard shortcuts
|
|
$(document).keydown(function(e) {
|
|
// Ctrl+N for new note
|
|
if (e.ctrlKey && e.keyCode === 78) {
|
|
e.preventDefault();
|
|
window.location.href = '{% url "operating_theatre:surgical_note_create" %}';
|
|
}
|
|
|
|
// Ctrl+F for search
|
|
if (e.ctrlKey && e.keyCode === 70) {
|
|
e.preventDefault();
|
|
$('#search-input').focus();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|