hospital-management/templates/patients/notes/patient_note_form.html
2025-08-12 13:33:25 +03:00

663 lines
28 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}{% if object %}Edit{% else %}Add{% endif %} Patient Note - Patients{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/summernote/dist/summernote-lite.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 'patients:patient_list' %}">Patients</a></li>
{% if object %}
<li class="breadcrumb-item"><a href="{% url 'patients:patient_detail' object.patient.pk %}">{{ object.patient.get_full_name }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'patients:patient_note_detail' object.pk %}">{{ object.title|truncatechars:20 }}</a></li>
<li class="breadcrumb-item active">Edit</li>
{% else %}
<li class="breadcrumb-item"><a href="{% url 'patients:patient_note_list' %}">Notes</a></li>
<li class="breadcrumb-item active">Add Note</li>
{% endif %}
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
{% if object %}Edit Patient Note{% else %}Add Patient Note{% endif %}
<small>{% if object %}{{ object.title }}{% else %}New Clinical Note{% endif %}</small>
</h1>
<!-- END page-header -->
<div class="row">
<div class="col-xl-8">
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Note Information</h4>
<div class="panel-heading-btn">
<button type="button" class="btn btn-xs btn-info me-2" onclick="saveDraft()">
<i class="fa fa-save"></i> Save Draft
</button>
<button type="button" class="btn btn-xs btn-secondary me-2" onclick="useTemplate()">
<i class="fa fa-file-text"></i> Use Template
</button>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
</div>
</div>
<div class="panel-body">
<form method="post" id="note-form" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.patient }}
<label for="{{ form.patient.id_for_label }}">Patient <span class="text-danger">*</span></label>
{% if form.patient.errors %}
<div class="invalid-feedback d-block">{{ form.patient.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
{{ form.category }}
<label for="{{ form.category.id_for_label }}">Category <span class="text-danger">*</span></label>
{% if form.category.errors %}
<div class="invalid-feedback d-block">{{ form.category.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-8">
<div class="form-floating">
{{ form.title }}
<label for="{{ form.title.id_for_label }}">Note Title <span class="text-danger">*</span></label>
{% if form.title.errors %}
<div class="invalid-feedback d-block">{{ form.title.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="form-floating">
{{ form.priority }}
<label for="{{ form.priority.id_for_label }}">Priority <span class="text-danger">*</span></label>
{% if form.priority.errors %}
<div class="invalid-feedback d-block">{{ form.priority.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.content.id_for_label }}" class="form-label">Note Content <span class="text-danger">*</span></label>
{{ form.content }}
{% if form.content.errors %}
<div class="invalid-feedback d-block">{{ form.content.errors.0 }}</div>
{% endif %}
<div class="form-text">Use the rich text editor to format your note. You can add lists, links, and formatting.</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.follow_up_date }}
<label for="{{ form.follow_up_date.id_for_label }}">Follow-up Date</label>
{% if form.follow_up_date.errors %}
<div class="invalid-feedback d-block">{{ form.follow_up_date.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-check mt-3">
{{ form.is_confidential }}
<label class="form-check-label" for="{{ form.is_confidential.id_for_label }}">
<i class="fa fa-lock me-1"></i>Confidential Note
</label>
{% if form.is_confidential.errors %}
<div class="invalid-feedback d-block">{{ form.is_confidential.errors.0 }}</div>
{% endif %}
<div class="form-text">Confidential notes have restricted access</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.follow_up_notes.id_for_label }}" class="form-label">Follow-up Instructions</label>
{{ form.follow_up_notes }}
{% if form.follow_up_notes.errors %}
<div class="invalid-feedback d-block">{{ form.follow_up_notes.errors.0 }}</div>
{% endif %}
<div class="form-text">Specific instructions for follow-up actions or appointments.</div>
</div>
<div class="mb-3">
<label for="{{ form.tags.id_for_label }}" class="form-label">Tags</label>
{{ form.tags }}
{% if form.tags.errors %}
<div class="invalid-feedback d-block">{{ form.tags.errors.0 }}</div>
{% endif %}
<div class="form-text">Add tags to help categorize and search for this note (comma-separated).</div>
</div>
<!-- Attachments -->
<div class="mb-3">
<label class="form-label">Attachments</label>
<div class="border rounded p-3">
<div class="row">
<div class="col-md-8">
<input type="file" class="form-control" id="attachment-input" multiple accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.txt">
</div>
<div class="col-md-4">
<button type="button" class="btn btn-outline-primary w-100" onclick="addAttachment()">
<i class="fa fa-plus me-2"></i>Add File
</button>
</div>
</div>
<div id="attachment-list" class="mt-3">
{% if object.attachments.exists %}
{% for attachment in object.attachments.all %}
<div class="d-flex align-items-center justify-content-between p-2 border rounded mb-2">
<div class="d-flex align-items-center">
<i class="fa fa-file me-2"></i>
<span>{{ attachment.name }}</span>
<small class="text-muted ms-2">({{ attachment.file_size|filesizeformat }})</small>
</div>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeAttachment('{{ attachment.pk }}')">
<i class="fa fa-trash"></i>
</button>
</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="form-text">Supported formats: PDF, DOC, DOCX, JPG, JPEG, PNG, TXT. Max size: 10MB per file.</div>
</div>
<!-- Form Actions -->
<div class="d-flex justify-content-between">
<div>
<a href="{% if object %}{% url 'patients:patient_note_detail' object.pk %}{% else %}{% url 'patients:patient_note_list' %}{% endif %}" class="btn btn-secondary">
<i class="fa fa-arrow-left me-2"></i>Cancel
</a>
</div>
<div>
<button type="button" class="btn btn-info me-2" onclick="saveDraft()">
<i class="fa fa-save me-2"></i>Save Draft
</button>
<button type="submit" class="btn btn-primary">
<i class="fa fa-check me-2"></i>{% if object %}Update{% else %}Create{% endif %} Note
</button>
</div>
</div>
</form>
</div>
</div>
<!-- END panel -->
</div>
<div class="col-xl-4">
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Help & Guidelines</h4>
</div>
<div class="panel-body">
<div class="alert alert-info">
<h6 class="alert-heading">Note Writing Tips</h6>
<ul class="mb-0 small">
<li>Be clear and concise in your documentation</li>
<li>Use appropriate medical terminology</li>
<li>Include relevant dates and times</li>
<li>Mark urgent items with high priority</li>
<li>Add follow-up dates for actionable items</li>
<li>Use confidential flag for sensitive information</li>
</ul>
</div>
<div class="card border-primary mb-3">
<div class="card-header bg-primary text-white">
<h6 class="card-title mb-0">Required Fields</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0 small">
<li><i class="fa fa-check text-success me-2"></i>Patient</li>
<li><i class="fa fa-check text-success me-2"></i>Note Title</li>
<li><i class="fa fa-check text-success me-2"></i>Category</li>
<li><i class="fa fa-check text-success me-2"></i>Priority</li>
<li><i class="fa fa-check text-success me-2"></i>Note Content</li>
</ul>
</div>
</div>
<div class="card border-warning mb-3">
<div class="card-header bg-warning text-dark">
<h6 class="card-title mb-0">Priority Guidelines</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0 small">
<li><span class="badge bg-danger me-2">URGENT</span>Immediate attention required</li>
<li><span class="badge bg-warning me-2">HIGH</span>Important, address soon</li>
<li><span class="badge bg-success me-2">NORMAL</span>Standard priority</li>
<li><span class="badge bg-secondary me-2">LOW</span>For reference only</li>
</ul>
</div>
</div>
<div class="card border-success">
<div class="card-header bg-success text-white">
<h6 class="card-title mb-0">Quick Actions</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="useTemplate()">
<i class="fa fa-file-text me-2"></i>Use Template
</button>
<button type="button" class="btn btn-outline-info btn-sm" onclick="insertDateTime()">
<i class="fa fa-clock me-2"></i>Insert Date/Time
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertSignature()">
<i class="fa fa-signature me-2"></i>Insert Signature
</button>
</div>
</div>
</div>
</div>
</div>
<!-- END panel -->
{% if object %}
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Current Patient</h4>
</div>
<div class="panel-body">
<div class="text-center mb-3">
<div class="w-60px h-60px bg-primary rounded-circle d-flex align-items-center justify-content-center mx-auto mb-2">
<i class="fa fa-user text-white"></i>
</div>
<h6 class="mb-1">{{ object.patient.get_full_name }}</h6>
<small class="text-muted">{{ object.patient.patient_id }}</small>
</div>
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold" width="60">DOB:</td>
<td>{{ object.patient.date_of_birth|date:"M d, Y" }}</td>
</tr>
<tr>
<td class="fw-bold">Age:</td>
<td>{{ object.patient.age }} years</td>
</tr>
<tr>
<td class="fw-bold">Gender:</td>
<td>{{ object.patient.get_gender_display }}</td>
</tr>
</table>
<div class="d-grid">
<a href="{% url 'patients:patient_detail' object.patient.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fa fa-user me-2"></i>View Patient
</a>
</div>
</div>
</div>
<!-- END panel -->
{% endif %}
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Form Status</h4>
</div>
<div class="panel-body">
<div id="form-status">
<div class="alert alert-secondary">
<i class="fa fa-info-circle me-2"></i>
<span id="status-text">Form not saved</span>
</div>
</div>
<div class="small text-muted">
<div>Last saved: <span id="last-saved">Never</span></div>
<div>Auto-save: <span class="text-success">Enabled</span></div>
<div>Word count: <span id="word-count">0</span></div>
</div>
</div>
</div>
<!-- END panel -->
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Recent Notes</h4>
</div>
<div class="panel-body">
{% for note in recent_notes %}
<div class="d-flex align-items-center mb-2 p-2 border rounded">
<div class="me-2">
<span class="badge bg-{% if note.priority == 'URGENT' %}danger{% elif note.priority == 'HIGH' %}warning{% else %}secondary{% endif %}">
{{ note.get_priority_display|slice:":1" }}
</span>
</div>
<div class="flex-grow-1">
<div class="fw-bold small">{{ note.title|truncatechars:25 }}</div>
<small class="text-muted">{{ note.created_at|date:"M d" }}</small>
</div>
</div>
{% empty %}
<div class="text-center text-muted">
<i class="fa fa-sticky-note fa-2x mb-2"></i>
<div>No recent notes</div>
</div>
{% endfor %}
</div>
</div>
<!-- END panel -->
</div>
</div>
<!-- Template Modal -->
<div class="modal fade" id="templateModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Select Note Template</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4">
<div class="list-group" id="template-categories">
<button type="button" class="list-group-item list-group-item-action active" data-category="clinical">
Clinical Notes
</button>
<button type="button" class="list-group-item list-group-item-action" data-category="administrative">
Administrative
</button>
<button type="button" class="list-group-item list-group-item-action" data-category="follow-up">
Follow-up
</button>
</div>
</div>
<div class="col-md-8">
<div id="template-list">
<!-- Templates will be loaded here -->
</div>
</div>
</div>
</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="applyTemplate()" disabled id="apply-template-btn">
<i class="fa fa-check me-2"></i>Apply Template
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/summernote/dist/summernote-lite.min.js' %}"></script>
<script>
var selectedTemplate = null;
var autoSaveTimer;
$(document).ready(function() {
// Initialize Summernote
$('#{{ form.content.id_for_label }}').summernote({
height: 300,
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link']],
['view', ['fullscreen', 'codeview', 'help']]
],
callbacks: {
onChange: function(contents, $editable) {
updateWordCount(contents);
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(function() {
saveDraft();
}, 5000);
}
}
});
// Form validation
$('#note-form').on('submit', function(e) {
if (!validateForm()) {
e.preventDefault();
}
});
// Auto-save functionality
$('#note-form input, #note-form select, #note-form textarea').on('change input', function() {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(function() {
saveDraft();
}, 5000);
});
// Priority change handler
$('#{{ form.priority.id_for_label }}').on('change', function() {
var priority = $(this).val();
if (priority === 'URGENT') {
toastr.warning('Urgent priority selected. This note will require immediate attention.');
}
});
// Category change handler
$('#{{ form.category.id_for_label }}').on('change', function() {
var category = $(this).val();
if (category === 'CLINICAL') {
$('#{{ form.is_confidential.id_for_label }}').prop('checked', true);
}
});
// Initial word count
updateWordCount($('#{{ form.content.id_for_label }}').summernote('code'));
});
function validateForm() {
var isValid = true;
var errors = [];
// Required field validation
var requiredFields = ['{{ form.patient.id_for_label }}', '{{ form.title.id_for_label }}', '{{ form.category.id_for_label }}', '{{ form.priority.id_for_label }}'];
requiredFields.forEach(function(fieldId) {
var field = $('#' + fieldId);
if (!field.val()) {
field.addClass('is-invalid');
errors.push(field.closest('.form-floating').find('label').text().replace(' *', '') + ' is required');
isValid = false;
} else {
field.removeClass('is-invalid');
}
});
// Content validation
var content = $('#{{ form.content.id_for_label }}').summernote('code');
if (!content || content.trim() === '' || content === '<p><br></p>') {
$('#{{ form.content.id_for_label }}').addClass('is-invalid');
errors.push('Note content is required');
isValid = false;
} else {
$('#{{ form.content.id_for_label }}').removeClass('is-invalid');
}
if (!isValid) {
toastr.error('Please fix the following errors:\n' + errors.join('\n'));
}
return isValid;
}
function updateWordCount(content) {
var text = $(content).text();
var wordCount = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;
$('#word-count').text(wordCount);
}
function saveDraft() {
var formData = $('#note-form').serialize();
var content = $('#{{ form.content.id_for_label }}').summernote('code');
formData += '&content=' + encodeURIComponent(content);
$.ajax({
url: '{% url "patients:save_note_draft" %}',
method: 'POST',
data: formData,
success: function(response) {
updateFormStatus('Draft saved', 'success');
$('#last-saved').text(new Date().toLocaleTimeString());
},
error: function() {
updateFormStatus('Failed to save draft', 'danger');
}
});
}
function updateFormStatus(message, type) {
var alertClass = 'alert-' + type;
$('#form-status').html('<div class="alert ' + alertClass + '"><i class="fa fa-info-circle me-2"></i>' + message + '</div>');
}
function useTemplate() {
$('#templateModal').modal('show');
loadTemplates('clinical');
}
function loadTemplates(category) {
$.ajax({
url: '{% url "patients:get_note_templates" %}',
method: 'GET',
data: { category: category },
success: function(response) {
$('#template-list').html(response.html);
}
});
}
function selectTemplate(templateId) {
selectedTemplate = templateId;
$('.template-item').removeClass('selected');
$('[data-template="' + templateId + '"]').addClass('selected');
$('#apply-template-btn').prop('disabled', false);
}
function applyTemplate() {
if (!selectedTemplate) return;
$.ajax({
url: '{% url "patients:get_note_template" %}',
method: 'GET',
data: { template_id: selectedTemplate },
success: function(response) {
$('#{{ form.title.id_for_label }}').val(response.title);
$('#{{ form.category.id_for_label }}').val(response.category);
$('#{{ form.priority.id_for_label }}').val(response.priority);
$('#{{ form.content.id_for_label }}').summernote('code', response.content);
$('#templateModal').modal('hide');
toastr.success('Template applied successfully');
}
});
}
function insertDateTime() {
var now = new Date();
var dateTime = now.toLocaleString();
var currentContent = $('#{{ form.content.id_for_label }}').summernote('code');
$('#{{ form.content.id_for_label }}').summernote('code', currentContent + '<p><strong>Date/Time:</strong> ' + dateTime + '</p>');
}
function insertSignature() {
var signature = '<p><strong>{{ user.get_full_name }}</strong><br>{{ user.profile.title|default:"Healthcare Provider" }}</p>';
var currentContent = $('#{{ form.content.id_for_label }}').summernote('code');
$('#{{ form.content.id_for_label }}').summernote('code', currentContent + signature);
}
function addAttachment() {
var input = $('#attachment-input')[0];
if (input.files.length === 0) {
toastr.warning('Please select files to attach');
return;
}
// Add files to attachment list (this would normally upload via AJAX)
for (var i = 0; i < input.files.length; i++) {
var file = input.files[i];
var attachmentHtml = '<div class="d-flex align-items-center justify-content-between p-2 border rounded mb-2">' +
'<div class="d-flex align-items-center">' +
'<i class="fa fa-file me-2"></i>' +
'<span>' + file.name + '</span>' +
'<small class="text-muted ms-2">(' + formatFileSize(file.size) + ')</small>' +
'</div>' +
'<button type="button" class="btn btn-sm btn-outline-danger" onclick="$(this).closest(\'.d-flex\').remove()">' +
'<i class="fa fa-trash"></i>' +
'</button>' +
'</div>';
$('#attachment-list').append(attachmentHtml);
}
input.value = '';
toastr.success('Files added to attachment list');
}
function removeAttachment(attachmentId) {
if (confirm('Remove this attachment?')) {
// This would normally make an AJAX call to remove the attachment
$('[onclick*="' + attachmentId + '"]').closest('.d-flex').remove();
toastr.success('Attachment removed');
}
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
var k = 1024;
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Template category selection
$(document).on('click', '[data-category]', function() {
var category = $(this).data('category');
$('.list-group-item').removeClass('active');
$(this).addClass('active');
loadTemplates(category);
});
</script>
<style>
.template-item {
cursor: pointer;
transition: all 0.2s;
}
.template-item:hover {
background-color: #f8f9fa;
}
.template-item.selected {
background-color: #e3f2fd;
border-color: #2196f3;
}
.note-content {
min-height: 200px;
}
</style>
{% endblock %}