515 lines
23 KiB
HTML
515 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if object %}Edit Report - {{ object.report_id }}{% else %}Create Radiology Report{% endif %}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'radiology:dashboard' %}">Radiology</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'radiology:radiology_report_list' %}">Reports</a></li>
|
|
{% if object %}
|
|
<li class="breadcrumb-item"><a href="{% url 'radiology:radiology_report_detail' object.pk %}">{{ object.report_id }}</a></li>
|
|
<li class="breadcrumb-item active">Edit</li>
|
|
{% else %}
|
|
<li class="breadcrumb-item active">Create</li>
|
|
{% endif %}
|
|
</ol>
|
|
<h1 class="page-header mb-0">
|
|
{% if object %}Edit Radiology Report{% else %}Create Radiology Report{% endif %}
|
|
</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<button type="button" class="btn btn-outline-secondary me-2" onclick="saveDraft()">
|
|
<i class="fas fa-save me-2"></i>Save Draft
|
|
</button>
|
|
{% if object %}
|
|
<a href="{% url 'radiology:radiology_report_detail' object.pk %}" class="btn btn-secondary">
|
|
<i class="fas fa-times me-2"></i>Cancel
|
|
</a>
|
|
{% else %}
|
|
<a href="{% url 'radiology:radiology_report_list' %}" class="btn btn-secondary">
|
|
<i class="fas fa-times me-2"></i>Cancel
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="reportForm" novalidate>
|
|
{% csrf_token %}
|
|
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- Study Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-x-ray me-2"></i>
|
|
Study Information
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Study <span class="text-danger">*</span></label>
|
|
{{ form.study }}
|
|
{% if form.study.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.study.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Report ID</label>
|
|
{{ form.report_id }}
|
|
{% if form.report_id.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.report_id.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Auto-generated if left blank</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Patient Information Display -->
|
|
<div id="patientInfo" style="display: none;">
|
|
<div class="alert alert-info">
|
|
<h6><i class="fas fa-user me-2"></i>Patient Information</h6>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<p class="mb-1"><strong>Name:</strong> <span id="patientName"></span></p>
|
|
<p class="mb-1"><strong>DOB:</strong> <span id="patientDOB"></span></p>
|
|
<p class="mb-0"><strong>Gender:</strong> <span id="patientGender"></span></p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<p class="mb-1"><strong>MRN:</strong> <span id="patientMRN"></span></p>
|
|
<p class="mb-1"><strong>Study Date:</strong> <span id="studyDate"></span></p>
|
|
<p class="mb-0"><strong>Modality:</strong> <span id="studyModality"></span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Report Content -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-file-medical me-2"></i>
|
|
Report Content
|
|
</h4>
|
|
<div class="card-toolbar">
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary" onclick="insertTemplate('normal')">
|
|
<i class="fas fa-file-alt me-1"></i>Normal Template
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="insertTemplate('abnormal')">
|
|
<i class="fas fa-exclamation-triangle me-1"></i>Abnormal Template
|
|
</button>
|
|
<button type="button" class="btn btn-outline-info" onclick="insertTemplate('comparison')">
|
|
<i class="fas fa-balance-scale me-1"></i>Comparison Template
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Clinical History</label>
|
|
{{ form.clinical_history }}
|
|
{% if form.clinical_history.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.clinical_history.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Patient's clinical background and reason for study</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Technique</label>
|
|
{{ form.technique }}
|
|
{% if form.technique.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.technique.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Technical parameters and methodology</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Findings <span class="text-danger">*</span></label>
|
|
{{ form.findings }}
|
|
{% if form.findings.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.findings.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Detailed description of imaging findings</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Impression <span class="text-danger">*</span></label>
|
|
{{ form.impression }}
|
|
{% if form.impression.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.impression.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Clinical interpretation and diagnosis</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Recommendations</label>
|
|
{{ form.recommendations }}
|
|
{% if form.recommendations.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.recommendations.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Follow-up recommendations and additional studies</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Critical Findings -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-exclamation-triangle text-danger me-2"></i>
|
|
Critical Findings
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
{{ form.has_critical_findings }}
|
|
<label class="form-check-label" for="{{ form.has_critical_findings.id_for_label }}">
|
|
This report contains critical findings
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Critical Finding Type</label>
|
|
{{ form.critical_finding_type }}
|
|
{% if form.critical_finding_type.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.critical_finding_type.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3" id="criticalFindingsDetails" style="display: none;">
|
|
<label class="form-label">Critical Findings Details</label>
|
|
{{ form.critical_findings_details }}
|
|
{% if form.critical_findings_details.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.critical_findings_details.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Detailed description of critical findings requiring immediate attention</div>
|
|
</div>
|
|
|
|
<div class="mb-3" id="notificationDetails" style="display: none;">
|
|
<label class="form-label">Notification Time</label>
|
|
{{ form.critical_notification_time }}
|
|
{% if form.critical_notification_time.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.critical_notification_time.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">Time when referring physician was notified</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- Report Status -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Report Status</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Status</label>
|
|
{{ form.status }}
|
|
{% if form.status.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.status.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Priority</label>
|
|
{{ form.priority }}
|
|
{% if form.priority.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.priority.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Radiologist</label>
|
|
{{ form.radiologist }}
|
|
{% if form.radiologist.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.radiologist.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Dictated Date</label>
|
|
{{ form.dictated_date }}
|
|
{% if form.dictated_date.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.dictated_date.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quality Assurance -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Quality Assurance</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
{{ form.peer_reviewed }}
|
|
<label class="form-check-label" for="{{ form.peer_reviewed.id_for_label }}">
|
|
Peer reviewed
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3" id="peerReviewDetails" style="display: none;">
|
|
<label class="form-label">Peer Reviewer</label>
|
|
{{ form.peer_reviewer }}
|
|
{% if form.peer_reviewer.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.peer_reviewer.errors.0 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Quality Score</label>
|
|
{{ form.quality_score }}
|
|
{% if form.quality_score.errors %}
|
|
<div class="invalid-feedback d-block">{{ form.quality_score.errors.0 }}</div>
|
|
{% endif %}
|
|
<div class="form-text">1-5 scale (5 = excellent)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">Actions</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" name="action" value="save_draft" class="btn btn-outline-secondary">
|
|
<i class="fas fa-save me-2"></i>Save as Draft
|
|
</button>
|
|
<button type="submit" name="action" value="submit_review" class="btn btn-outline-warning">
|
|
<i class="fas fa-eye me-2"></i>Submit for Review
|
|
</button>
|
|
<button type="submit" name="action" value="finalize" class="btn btn-success">
|
|
<i class="fas fa-check me-2"></i>Finalize Report
|
|
</button>
|
|
{% if object %}
|
|
<button type="button" class="btn btn-outline-info" onclick="previewReport()">
|
|
<i class="fas fa-print me-2"></i>Preview
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="text-center">
|
|
<small class="text-muted">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
Auto-save every 30 seconds
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Report Templates 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">Report Templates</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="templateList">
|
|
<button type="button" class="list-group-item list-group-item-action active" data-template="chest_xray">
|
|
Chest X-Ray Normal
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" data-template="ct_head">
|
|
CT Head Normal
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" data-template="mri_brain">
|
|
MRI Brain Normal
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<div id="templatePreview">
|
|
<h6>Chest X-Ray Normal Template</h6>
|
|
<p><strong>Technique:</strong> PA and lateral chest radiographs</p>
|
|
<p><strong>Findings:</strong> The lungs are clear bilaterally. No focal consolidation, pleural effusion, or pneumothorax. The cardiac silhouette is normal in size and configuration. The mediastinal contours are unremarkable.</p>
|
|
<p><strong>Impression:</strong> Normal chest radiograph.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" onclick="useTemplate()">Use Template</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Study selection change handler
|
|
const studySelect = document.querySelector('select[name="study"]');
|
|
if (studySelect) {
|
|
studySelect.addEventListener('change', function() {
|
|
if (this.value) {
|
|
loadStudyInfo(this.value);
|
|
} else {
|
|
document.getElementById('patientInfo').style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Critical findings checkbox handler
|
|
const criticalCheckbox = document.querySelector('input[name="has_critical_findings"]');
|
|
if (criticalCheckbox) {
|
|
criticalCheckbox.addEventListener('change', function() {
|
|
const details = document.getElementById('criticalFindingsDetails');
|
|
const notification = document.getElementById('notificationDetails');
|
|
|
|
if (this.checked) {
|
|
details.style.display = 'block';
|
|
notification.style.display = 'block';
|
|
} else {
|
|
details.style.display = 'none';
|
|
notification.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Peer review checkbox handler
|
|
const peerReviewCheckbox = document.querySelector('input[name="peer_reviewed"]');
|
|
if (peerReviewCheckbox) {
|
|
peerReviewCheckbox.addEventListener('change', function() {
|
|
const details = document.getElementById('peerReviewDetails');
|
|
details.style.display = this.checked ? 'block' : 'none';
|
|
});
|
|
}
|
|
|
|
// Auto-save functionality
|
|
let autoSaveTimer;
|
|
const form = document.getElementById('reportForm');
|
|
const formInputs = form.querySelectorAll('input, textarea, select');
|
|
|
|
formInputs.forEach(input => {
|
|
input.addEventListener('input', function() {
|
|
clearTimeout(autoSaveTimer);
|
|
autoSaveTimer = setTimeout(autoSave, 30000); // 30 seconds
|
|
});
|
|
});
|
|
|
|
// Character counting for text areas
|
|
const textAreas = document.querySelectorAll('textarea');
|
|
textAreas.forEach(textarea => {
|
|
const maxLength = textarea.getAttribute('maxlength');
|
|
if (maxLength) {
|
|
const counter = document.createElement('div');
|
|
counter.className = 'form-text text-end';
|
|
counter.innerHTML = `<span class="char-count">0</span>/${maxLength} characters`;
|
|
textarea.parentNode.appendChild(counter);
|
|
|
|
textarea.addEventListener('input', function() {
|
|
const count = this.value.length;
|
|
const span = counter.querySelector('.char-count');
|
|
span.textContent = count;
|
|
span.className = count > maxLength * 0.9 ? 'text-warning' : '';
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
function loadStudyInfo(studyId) {
|
|
// Simulate loading study information
|
|
fetch(`/api/studies/${studyId}/`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('patientName').textContent = data.patient.name;
|
|
document.getElementById('patientDOB').textContent = data.patient.dob;
|
|
document.getElementById('patientGender').textContent = data.patient.gender;
|
|
document.getElementById('patientMRN').textContent = data.patient.mrn;
|
|
document.getElementById('studyDate').textContent = data.study_date;
|
|
document.getElementById('studyModality').textContent = data.modality;
|
|
document.getElementById('patientInfo').style.display = 'block';
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading study info:', error);
|
|
});
|
|
}
|
|
|
|
function saveDraft() {
|
|
const form = document.getElementById('reportForm');
|
|
const formData = new FormData(form);
|
|
formData.append('action', 'save_draft');
|
|
|
|
fetch(form.action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('Draft saved successfully', 'success');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error saving draft', 'error');
|
|
});
|
|
}
|
|
|
|
function autoSave() {
|
|
saveDraft();
|
|
}
|
|
|
|
function insertTemplate(type) {
|
|
const modal = new bootstrap.Modal(document.getElementById('templateModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function useTemplate() {
|
|
// Implement template insertion logic
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('templateModal'));
|
|
modal.hide();
|
|
}
|
|
|
|
function previewReport() {
|
|
const reportId = '{{ object.pk }}';
|
|
window.open(`/radiology/reports/${reportId}/preview/`, '_blank');
|
|
}
|
|
|
|
function showNotification(message, type) {
|
|
// Implement notification system
|
|
console.log(`${type}: ${message}`);
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|