1163 lines
43 KiB
HTML
1163 lines
43 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}{% if template.pk %}Edit{% else %}Create{% endif %} Surgical Note Template{% endblock %}
|
||
|
||
{% block css %}
|
||
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
||
<link href="{% static 'plugins/summernote/summernote-bs5.min.css' %}" rel="stylesheet" />
|
||
<style>
|
||
.template-form-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border-radius: 0.5rem;
|
||
padding: 2rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.form-section {
|
||
background: white;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.375rem;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.section-header {
|
||
background: #f8f9fa;
|
||
border-bottom: 1px solid #dee2e6;
|
||
padding: 1rem 1.5rem;
|
||
font-weight: 600;
|
||
color: #495057;
|
||
display: flex;
|
||
justify-content: between;
|
||
align-items: center;
|
||
}
|
||
|
||
.section-content {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.required-field::after {
|
||
content: " *";
|
||
color: #dc3545;
|
||
}
|
||
|
||
.template-editor {
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.375rem;
|
||
min-height: 400px;
|
||
}
|
||
|
||
.variables-panel {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.375rem;
|
||
padding: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.variable-item {
|
||
display: flex;
|
||
justify-content: between;
|
||
align-items: center;
|
||
padding: 0.5rem;
|
||
background: white;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.25rem;
|
||
margin-bottom: 0.5rem;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.variable-item:hover {
|
||
background: #e3f2fd;
|
||
}
|
||
|
||
.variable-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.variable-code {
|
||
font-family: 'Courier New', monospace;
|
||
background: #e9ecef;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 0.25rem;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.variable-description {
|
||
font-size: 0.875rem;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.preview-section {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.375rem;
|
||
padding: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.preview-content {
|
||
background: white;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.25rem;
|
||
padding: 1.5rem;
|
||
font-family: 'Times New Roman', serif;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.auto-save-indicator {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
background: #28a745;
|
||
color: white;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 0.25rem;
|
||
font-size: 0.875rem;
|
||
z-index: 1050;
|
||
display: none;
|
||
}
|
||
|
||
.field-help {
|
||
font-size: 0.875rem;
|
||
color: #6c757d;
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
.tag-input {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.375rem;
|
||
min-height: 45px;
|
||
align-items: center;
|
||
}
|
||
|
||
.tag-item {
|
||
background: #007bff;
|
||
color: white;
|
||
padding: 0.25rem 0.75rem;
|
||
border-radius: 0.25rem;
|
||
font-size: 0.875rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.tag-remove {
|
||
cursor: pointer;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.tag-input input {
|
||
border: none;
|
||
outline: none;
|
||
flex: 1;
|
||
min-width: 100px;
|
||
padding: 0.25rem;
|
||
}
|
||
|
||
.editor-toolbar {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-bottom: none;
|
||
border-radius: 0.375rem 0.375rem 0 0;
|
||
padding: 0.75rem 1rem;
|
||
display: flex;
|
||
justify-content: between;
|
||
align-items: center;
|
||
}
|
||
|
||
.toolbar-group {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.toolbar-btn {
|
||
padding: 0.375rem 0.75rem;
|
||
border: 1px solid #dee2e6;
|
||
background: white;
|
||
border-radius: 0.25rem;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.toolbar-btn:hover {
|
||
background: #e9ecef;
|
||
}
|
||
|
||
.toolbar-btn.active {
|
||
background: #007bff;
|
||
color: white;
|
||
border-color: #007bff;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.template-form-header {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.section-content {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.variables-panel {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.editor-toolbar {
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.toolbar-group {
|
||
justify-content: center;
|
||
}
|
||
}
|
||
|
||
@media print {
|
||
.variables-panel, .editor-toolbar, .btn, .form-actions {
|
||
display: none !important;
|
||
}
|
||
|
||
.form-section {
|
||
border: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.section-header {
|
||
background: none;
|
||
border-bottom: 2px solid #000;
|
||
color: #000;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div id="content" class="app-content">
|
||
<!-- Auto-save indicator -->
|
||
<div class="auto-save-indicator" id="auto-save-indicator">
|
||
<i class="fas fa-check me-1"></i>Auto-saved
|
||
</div>
|
||
|
||
<!-- 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"><a href="{% url 'operating_theatre:surgical_note_template_list' %}">Templates</a></li>
|
||
<li class="breadcrumb-item active">{% if template.pk %}Edit{% else %}Create{% endif %} Template</li>
|
||
</ol>
|
||
<h1 class="page-header mb-0">
|
||
<i class="fas fa-clipboard-list me-2"></i>
|
||
{% if template.pk %}Edit Template{% else %}Create Template{% 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-1"></i>Save Draft
|
||
</button>
|
||
<button type="button" class="btn btn-outline-info me-2" onclick="previewTemplate()">
|
||
<i class="fas fa-eye me-1"></i>Preview
|
||
</button>
|
||
<a href="{% url 'operating_theatre:surgical_note_template_list' %}" class="btn btn-outline-primary">
|
||
<i class="fas fa-arrow-left me-1"></i>Back to List
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-lg-8">
|
||
<form method="post" id="template-form" novalidate>
|
||
{% csrf_token %}
|
||
|
||
<!-- Basic Information -->
|
||
<div class="form-section">
|
||
<div class="section-header">
|
||
<i class="fas fa-info-circle me-2"></i>Basic Information
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="row">
|
||
<div class="col-md-8">
|
||
<div class="form-floating mb-3">
|
||
{{ form.name }}
|
||
<label for="{{ form.name.id_for_label }}" class="required-field">Template Name</label>
|
||
{% if form.name.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.name.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Enter a descriptive name for this template</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="mb-3">
|
||
<label for="{{ form.category.id_for_label }}" class="form-label required-field">Category</label>
|
||
{{ form.category }}
|
||
{% if form.category.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.category.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Select the surgical category</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="{{ form.description.id_for_label }}" class="form-label required-field">Description</label>
|
||
{{ form.description }}
|
||
{% if form.description.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.description.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Provide a clear description of when to use this template</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label for="{{ form.status.id_for_label }}" class="form-label required-field">Status</label>
|
||
{{ form.status }}
|
||
{% if form.status.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.status.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Set the template availability status</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label for="{{ form.version.id_for_label }}" class="form-label">Version</label>
|
||
{{ form.version }}
|
||
{% if form.version.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.version.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Version number (auto-incremented if left blank)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Tags</label>
|
||
<div class="tag-input" id="tag-input">
|
||
{% for tag in template.tags.all %}
|
||
<span class="tag-item">
|
||
{{ tag.name }}
|
||
<span class="tag-remove" onclick="removeTag(this)">×</span>
|
||
</span>
|
||
{% endfor %}
|
||
<input type="text" placeholder="Add tags..." onkeypress="handleTagInput(event)">
|
||
</div>
|
||
<input type="hidden" name="tags" id="tags-hidden">
|
||
<div class="field-help">Add tags to help categorize and search for this template</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="{{ form.keywords.id_for_label }}" class="form-label">Keywords</label>
|
||
{{ form.keywords }}
|
||
{% if form.keywords.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.keywords.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Comma-separated keywords for search optimization</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Template Content -->
|
||
<div class="form-section">
|
||
<div class="section-header">
|
||
<div>
|
||
<i class="fas fa-file-alt me-2"></i>Template Content
|
||
</div>
|
||
<div class="btn-group btn-group-sm">
|
||
<button type="button" class="btn btn-outline-secondary" onclick="toggleEditor('wysiwyg')" id="btn-wysiwyg">
|
||
<i class="fas fa-eye me-1"></i>Visual
|
||
</button>
|
||
<button type="button" class="btn btn-outline-secondary" onclick="toggleEditor('code')" id="btn-code">
|
||
<i class="fas fa-code me-1"></i>Code
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="section-content">
|
||
<!-- Editor Toolbar -->
|
||
<div class="editor-toolbar">
|
||
<div class="toolbar-group">
|
||
<button type="button" class="toolbar-btn" onclick="insertVariable('patient_name')" title="Insert Patient Name">
|
||
<i class="fas fa-user"></i>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="insertVariable('surgery_date')" title="Insert Surgery Date">
|
||
<i class="fas fa-calendar"></i>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="insertVariable('surgeon_name')" title="Insert Surgeon Name">
|
||
<i class="fas fa-user-md"></i>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="insertVariable('procedure_name')" title="Insert Procedure Name">
|
||
<i class="fas fa-procedures"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="toolbar-group">
|
||
<button type="button" class="toolbar-btn" onclick="insertSection('preop')" title="Pre-operative Section">
|
||
Pre-op
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="insertSection('procedure')" title="Procedure Section">
|
||
Procedure
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="insertSection('postop')" title="Post-operative Section">
|
||
Post-op
|
||
</button>
|
||
</div>
|
||
|
||
<div class="toolbar-group">
|
||
<button type="button" class="toolbar-btn" onclick="formatText('bold')" title="Bold">
|
||
<i class="fas fa-bold"></i>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="formatText('italic')" title="Italic">
|
||
<i class="fas fa-italic"></i>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" onclick="formatText('underline')" title="Underline">
|
||
<i class="fas fa-underline"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- WYSIWYG Editor -->
|
||
<div id="wysiwyg-editor">
|
||
{{ form.content }}
|
||
</div>
|
||
|
||
<!-- Code Editor -->
|
||
<div id="code-editor" style="display: none;">
|
||
<textarea class="form-control template-editor" name="content_raw" rows="20"
|
||
placeholder="Enter template content here...">{{ template.content|default:"" }}</textarea>
|
||
</div>
|
||
|
||
{% if form.content.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.content.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Create the template content using variables and formatting</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Template Settings -->
|
||
<div class="form-section">
|
||
<div class="section-header">
|
||
<i class="fas fa-cog me-2"></i>Template Settings
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="form-check mb-3">
|
||
{{ form.is_default }}
|
||
<label class="form-check-label" for="{{ form.is_default.id_for_label }}">
|
||
Default Template
|
||
</label>
|
||
<div class="field-help">Set as default template for this category</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="form-check mb-3">
|
||
{{ form.is_public }}
|
||
<label class="form-check-label" for="{{ form.is_public.id_for_label }}">
|
||
Public Template
|
||
</label>
|
||
<div class="field-help">Allow other users to access this template</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="form-check mb-3">
|
||
{{ form.requires_approval }}
|
||
<label class="form-check-label" for="{{ form.requires_approval.id_for_label }}">
|
||
Requires Approval
|
||
</label>
|
||
<div class="field-help">Notes created from this template need approval</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="form-check mb-3">
|
||
{{ form.auto_populate }}
|
||
<label class="form-check-label" for="{{ form.auto_populate.id_for_label }}">
|
||
Auto-populate Fields
|
||
</label>
|
||
<div class="field-help">Automatically fill fields from patient data</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="{{ form.change_summary.id_for_label }}" class="form-label">Change Summary</label>
|
||
{{ form.change_summary }}
|
||
{% if form.change_summary.errors %}
|
||
<div class="invalid-feedback d-block">{{ form.change_summary.errors.0 }}</div>
|
||
{% endif %}
|
||
<div class="field-help">Describe the changes made in this version</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Form Actions -->
|
||
<div class="form-actions d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<button type="button" class="btn btn-outline-secondary" onclick="validateTemplate()">
|
||
<i class="fas fa-check-circle me-1"></i>Validate
|
||
</button>
|
||
<button type="button" class="btn btn-outline-info" onclick="testTemplate()">
|
||
<i class="fas fa-flask me-1"></i>Test
|
||
</button>
|
||
</div>
|
||
|
||
<div>
|
||
<a href="{% url 'operating_theatre:surgical_note_template_list' %}" class="btn btn-outline-secondary me-2">
|
||
<i class="fas fa-times me-1"></i>Cancel
|
||
</a>
|
||
<button type="submit" name="action" value="save_draft" class="btn btn-outline-primary me-2">
|
||
<i class="fas fa-save me-1"></i>Save Draft
|
||
</button>
|
||
<button type="submit" name="action" value="save_active" class="btn btn-success">
|
||
<i class="fas fa-check me-1"></i>
|
||
{% if template.pk %}Update Template{% else %}Create Template{% endif %}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="col-lg-4">
|
||
<!-- Template Variables -->
|
||
<div class="variables-panel">
|
||
<h6 class="mb-3">
|
||
<i class="fas fa-code me-2"></i>Available Variables
|
||
</h6>
|
||
<div class="variable-item" onclick="insertVariable('patient_name')">
|
||
<div>
|
||
<div class="variable-code">{{patient_name}}</div>
|
||
<div class="variable-description">Patient's full name</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('patient_id')">
|
||
<div>
|
||
<div class="variable-code">{{patient_id}}</div>
|
||
<div class="variable-description">Patient ID number</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('surgery_date')">
|
||
<div>
|
||
<div class="variable-code">{{surgery_date}}</div>
|
||
<div class="variable-description">Date of surgery</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('surgeon_name')">
|
||
<div>
|
||
<div class="variable-code">{{surgeon_name}}</div>
|
||
<div class="variable-description">Primary surgeon name</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('procedure_name')">
|
||
<div>
|
||
<div class="variable-code">{{procedure_name}}</div>
|
||
<div class="variable-description">Name of procedure</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('anesthesia_type')">
|
||
<div>
|
||
<div class="variable-code">{{anesthesia_type}}</div>
|
||
<div class="variable-description">Type of anesthesia</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('start_time')">
|
||
<div>
|
||
<div class="variable-code">{{start_time}}</div>
|
||
<div class="variable-description">Surgery start time</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('end_time')">
|
||
<div>
|
||
<div class="variable-code">{{end_time}}</div>
|
||
<div class="variable-description">Surgery end time</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('current_date')">
|
||
<div>
|
||
<div class="variable-code">{{current_date}}</div>
|
||
<div class="variable-description">Current date</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
|
||
<div class="variable-item" onclick="insertVariable('current_time')">
|
||
<div>
|
||
<div class="variable-code">{{current_time}}</div>
|
||
<div class="variable-description">Current time</div>
|
||
</div>
|
||
<i class="fas fa-plus text-primary"></i>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Template Sections -->
|
||
<div class="form-section">
|
||
<div class="section-header">
|
||
<i class="fas fa-list me-2"></i>Quick Sections
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="d-grid gap-2">
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="insertSection('preop')">
|
||
<i class="fas fa-clipboard-check me-1"></i>Pre-operative Section
|
||
</button>
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="insertSection('procedure')">
|
||
<i class="fas fa-procedures me-1"></i>Procedure Section
|
||
</button>
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="insertSection('postop')">
|
||
<i class="fas fa-heartbeat me-1"></i>Post-operative Section
|
||
</button>
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="insertSection('complications')">
|
||
<i class="fas fa-exclamation-triangle me-1"></i>Complications Section
|
||
</button>
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="insertSection('signature')">
|
||
<i class="fas fa-signature me-1"></i>Signature Section
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Preview -->
|
||
<div class="form-section">
|
||
<div class="section-header">
|
||
<i class="fas fa-eye me-2"></i>Live Preview
|
||
</div>
|
||
<div class="section-content">
|
||
<div class="preview-section">
|
||
<div class="preview-content" id="live-preview">
|
||
<p class="text-muted text-center">
|
||
<i class="fas fa-eye fa-2x mb-2"></i><br>
|
||
Preview will appear here as you type
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="text-center">
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="refreshPreview()">
|
||
<i class="fas fa-sync me-1"></i>Refresh Preview
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Preview Modal -->
|
||
<div class="modal fade" id="previewModal" tabindex="-1">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">
|
||
<i class="fas fa-eye me-2"></i>Template Preview
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body" id="preview-modal-content">
|
||
<!-- Preview content will be loaded here -->
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||
<i class="fas fa-times me-1"></i>Close
|
||
</button>
|
||
<button type="button" class="btn btn-primary" onclick="printPreview()">
|
||
<i class="fas fa-print me-1"></i>Print Preview
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Validation Modal -->
|
||
<div class="modal fade" id="validationModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">
|
||
<i class="fas fa-check-circle me-2"></i>Template Validation
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body" id="validation-content">
|
||
<!-- Validation results will be loaded here -->
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
|
||
<i class="fas fa-check me-1"></i>OK
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block js %}
|
||
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
|
||
<script src="{% static 'plugins/summernote/summernote-bs5.min.js' %}"></script>
|
||
|
||
<script>
|
||
$(document).ready(function() {
|
||
// Initialize Select2
|
||
$('.form-select').select2({
|
||
theme: 'bootstrap-5',
|
||
width: '100%'
|
||
});
|
||
|
||
// Initialize Summernote
|
||
$('#id_content').summernote({
|
||
height: 400,
|
||
toolbar: [
|
||
['style', ['style']],
|
||
['font', ['bold', 'italic', 'underline', 'clear']],
|
||
['fontname', ['fontname']],
|
||
['color', ['color']],
|
||
['para', ['ul', 'ol', 'paragraph']],
|
||
['table', ['table']],
|
||
['insert', ['link', 'picture', 'video']],
|
||
['view', ['fullscreen', 'codeview', 'help']]
|
||
],
|
||
callbacks: {
|
||
onChange: function(contents) {
|
||
updateLivePreview(contents);
|
||
scheduleAutoSave();
|
||
}
|
||
}
|
||
});
|
||
|
||
// Initialize tags
|
||
updateTagsHidden();
|
||
|
||
// Auto-save functionality
|
||
let autoSaveTimer;
|
||
$('form input, form textarea, form select').on('input change', function() {
|
||
scheduleAutoSave();
|
||
});
|
||
|
||
// Form validation
|
||
$('#template-form').on('submit', function(e) {
|
||
if (!validateRequiredFields()) {
|
||
e.preventDefault();
|
||
showValidationErrors();
|
||
}
|
||
});
|
||
});
|
||
|
||
function scheduleAutoSave() {
|
||
clearTimeout(autoSaveTimer);
|
||
autoSaveTimer = setTimeout(autoSave, 30000); // Auto-save after 30 seconds
|
||
}
|
||
|
||
function autoSave() {
|
||
const formData = new FormData(document.getElementById('template-form'));
|
||
formData.append('action', 'auto_save');
|
||
|
||
fetch(window.location.href, {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showAutoSaveIndicator();
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Auto-save failed:', error);
|
||
});
|
||
}
|
||
|
||
function showAutoSaveIndicator() {
|
||
const indicator = document.getElementById('auto-save-indicator');
|
||
indicator.style.display = 'block';
|
||
setTimeout(() => {
|
||
indicator.style.display = 'none';
|
||
}, 2000);
|
||
}
|
||
|
||
function saveDraft() {
|
||
const form = document.getElementById('template-form');
|
||
const actionInput = document.createElement('input');
|
||
actionInput.type = 'hidden';
|
||
actionInput.name = 'action';
|
||
actionInput.value = 'save_draft';
|
||
form.appendChild(actionInput);
|
||
form.submit();
|
||
}
|
||
|
||
function toggleEditor(mode) {
|
||
const wysiwygEditor = document.getElementById('wysiwyg-editor');
|
||
const codeEditor = document.getElementById('code-editor');
|
||
const btnWysiwyg = document.getElementById('btn-wysiwyg');
|
||
const btnCode = document.getElementById('btn-code');
|
||
|
||
if (mode === 'wysiwyg') {
|
||
wysiwygEditor.style.display = 'block';
|
||
codeEditor.style.display = 'none';
|
||
btnWysiwyg.classList.add('active');
|
||
btnCode.classList.remove('active');
|
||
|
||
// Sync content from code to WYSIWYG
|
||
const codeContent = codeEditor.querySelector('textarea').value;
|
||
$('#id_content').summernote('code', codeContent);
|
||
} else {
|
||
wysiwygEditor.style.display = 'none';
|
||
codeEditor.style.display = 'block';
|
||
btnWysiwyg.classList.remove('active');
|
||
btnCode.classList.add('active');
|
||
|
||
// Sync content from WYSIWYG to code
|
||
const wysiwygContent = $('#id_content').summernote('code');
|
||
codeEditor.querySelector('textarea').value = wysiwygContent;
|
||
}
|
||
}
|
||
|
||
function insertVariable(variable) {
|
||
const variableText = `{${variable}}`;
|
||
|
||
if (document.getElementById('wysiwyg-editor').style.display !== 'none') {
|
||
$('#id_content').summernote('insertText', variableText);
|
||
} else {
|
||
const textarea = document.querySelector('#code-editor textarea');
|
||
const start = textarea.selectionStart;
|
||
const end = textarea.selectionEnd;
|
||
const text = textarea.value;
|
||
|
||
textarea.value = text.substring(0, start) + variableText + text.substring(end);
|
||
textarea.selectionStart = textarea.selectionEnd = start + variableText.length;
|
||
textarea.focus();
|
||
}
|
||
}
|
||
|
||
function insertSection(sectionType) {
|
||
const sections = {
|
||
preop: `
|
||
PRE-OPERATIVE INFORMATION
|
||
========================
|
||
Pre-operative Diagnosis: {{preoperative_diagnosis}}
|
||
Indication for Surgery: {{indication}}
|
||
Anesthesia Type: {{anesthesia_type}}
|
||
Patient Position: {{patient_position}}
|
||
|
||
`,
|
||
procedure: `
|
||
OPERATIVE PROCEDURE
|
||
==================
|
||
Procedure: {{procedure_name}}
|
||
Technique: {{technique}}
|
||
Findings: {{findings}}
|
||
Specimens: {{specimens}}
|
||
|
||
`,
|
||
postop: `
|
||
POST-OPERATIVE INFORMATION
|
||
=========================
|
||
Post-operative Diagnosis: {{postoperative_diagnosis}}
|
||
Complications: {{complications}}
|
||
Estimated Blood Loss: {{estimated_blood_loss}} mL
|
||
Post-operative Instructions: {{postoperative_instructions}}
|
||
|
||
`,
|
||
complications: `
|
||
COMPLICATIONS
|
||
============
|
||
Intraoperative Complications: None
|
||
Post-operative Complications: None
|
||
Management: N/A
|
||
|
||
`,
|
||
signature: `
|
||
ELECTRONIC SIGNATURE
|
||
===================
|
||
Surgeon: {{surgeon_name}}
|
||
Date: {{current_date}}
|
||
Time: {{current_time}}
|
||
Signature: [Electronic Signature]
|
||
|
||
`
|
||
};
|
||
|
||
const sectionContent = sections[sectionType] || '';
|
||
|
||
if (document.getElementById('wysiwyg-editor').style.display !== 'none') {
|
||
$('#id_content').summernote('insertText', sectionContent);
|
||
} else {
|
||
const textarea = document.querySelector('#code-editor textarea');
|
||
const start = textarea.selectionStart;
|
||
const end = textarea.selectionEnd;
|
||
const text = textarea.value;
|
||
|
||
textarea.value = text.substring(0, start) + sectionContent + text.substring(end);
|
||
textarea.selectionStart = textarea.selectionEnd = start + sectionContent.length;
|
||
textarea.focus();
|
||
}
|
||
}
|
||
|
||
function formatText(format) {
|
||
if (document.getElementById('wysiwyg-editor').style.display !== 'none') {
|
||
$('#id_content').summernote('execCommand', format);
|
||
}
|
||
}
|
||
|
||
function updateLivePreview(content) {
|
||
// Replace variables with sample data for preview
|
||
let previewContent = content;
|
||
const sampleData = {
|
||
'patient_name': 'John Doe',
|
||
'patient_id': 'P123456',
|
||
'surgery_date': new Date().toLocaleDateString(),
|
||
'surgeon_name': 'Dr. Smith',
|
||
'procedure_name': 'Sample Procedure',
|
||
'anesthesia_type': 'General',
|
||
'current_date': new Date().toLocaleDateString(),
|
||
'current_time': new Date().toLocaleTimeString()
|
||
};
|
||
|
||
Object.keys(sampleData).forEach(key => {
|
||
const regex = new RegExp(`{${key}}`, 'g');
|
||
previewContent = previewContent.replace(regex, sampleData[key]);
|
||
});
|
||
|
||
document.getElementById('live-preview').innerHTML = previewContent;
|
||
}
|
||
|
||
function refreshPreview() {
|
||
const content = $('#id_content').summernote('code');
|
||
updateLivePreview(content);
|
||
}
|
||
|
||
function previewTemplate() {
|
||
const content = $('#id_content').summernote('code');
|
||
updateLivePreview(content);
|
||
|
||
document.getElementById('preview-modal-content').innerHTML =
|
||
document.getElementById('live-preview').innerHTML;
|
||
|
||
new bootstrap.Modal(document.getElementById('previewModal')).show();
|
||
}
|
||
|
||
function validateTemplate() {
|
||
const errors = [];
|
||
const warnings = [];
|
||
|
||
// Check required fields
|
||
const requiredFields = document.querySelectorAll('[required]');
|
||
requiredFields.forEach(field => {
|
||
if (!field.value.trim()) {
|
||
errors.push(`${field.labels[0].textContent} is required`);
|
||
}
|
||
});
|
||
|
||
// Check template content
|
||
const content = $('#id_content').summernote('code');
|
||
if (!content.trim()) {
|
||
errors.push('Template content cannot be empty');
|
||
}
|
||
|
||
// Check for common issues
|
||
if (content.includes('{{') && !content.includes('}}')) {
|
||
warnings.push('Unclosed variable syntax detected');
|
||
}
|
||
|
||
if (content.length < 50) {
|
||
warnings.push('Template content seems very short');
|
||
}
|
||
|
||
// Display validation results
|
||
let validationContent = '';
|
||
|
||
if (errors.length === 0 && warnings.length === 0) {
|
||
validationContent = `
|
||
<div class="alert alert-success">
|
||
<i class="fas fa-check-circle me-2"></i>
|
||
Template validation passed! No issues found.
|
||
</div>
|
||
`;
|
||
} else {
|
||
if (errors.length > 0) {
|
||
validationContent += `
|
||
<div class="alert alert-danger">
|
||
<h6><i class="fas fa-exclamation-triangle me-2"></i>Errors:</h6>
|
||
<ul class="mb-0">
|
||
${errors.map(error => `<li>${error}</li>`).join('')}
|
||
</ul>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
if (warnings.length > 0) {
|
||
validationContent += `
|
||
<div class="alert alert-warning">
|
||
<h6><i class="fas fa-exclamation-circle me-2"></i>Warnings:</h6>
|
||
<ul class="mb-0">
|
||
${warnings.map(warning => `<li>${warning}</li>`).join('')}
|
||
</ul>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
document.getElementById('validation-content').innerHTML = validationContent;
|
||
new bootstrap.Modal(document.getElementById('validationModal')).show();
|
||
}
|
||
|
||
{#function testTemplate() {#}
|
||
{# const content = $('#id_content').summernote('code');#}
|
||
{# #}
|
||
{# fetch('{% url "operating_theatre:" %}', {#}
|
||
{# method: 'POST',#}
|
||
{# headers: {#}
|
||
{# 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,#}
|
||
{# 'Content-Type': 'application/json'#}
|
||
{# },#}
|
||
{# body: JSON.stringify({ content: content })#}
|
||
{# })#}
|
||
{# .then(response => response.json())#}
|
||
{# .then(data => {#}
|
||
{# if (data.success) {#}
|
||
{# document.getElementById('preview-modal-content').innerHTML = data.rendered_content;#}
|
||
{# new bootstrap.Modal(document.getElementById('previewModal')).show();#}
|
||
{# } else {#}
|
||
{# showAlert('Error testing template', 'danger');#}
|
||
{# }#}
|
||
{# })#}
|
||
{# .catch(error => {#}
|
||
{# showAlert('Error testing template', 'danger');#}
|
||
{# });#}
|
||
{# }#}
|
||
|
||
function validateRequiredFields() {
|
||
const requiredFields = document.querySelectorAll('[required]');
|
||
let isValid = true;
|
||
|
||
requiredFields.forEach(field => {
|
||
if (!field.value.trim()) {
|
||
field.classList.add('is-invalid');
|
||
isValid = false;
|
||
} else {
|
||
field.classList.remove('is-invalid');
|
||
}
|
||
});
|
||
|
||
return isValid;
|
||
}
|
||
|
||
function showValidationErrors() {
|
||
showAlert('Please fill in all required fields', 'danger');
|
||
}
|
||
|
||
function printPreview() {
|
||
const printContent = document.getElementById('preview-modal-content').innerHTML;
|
||
const printWindow = window.open('', '_blank');
|
||
printWindow.document.write(`
|
||
<html>
|
||
<head>
|
||
<title>Template Preview</title>
|
||
<style>
|
||
body { font-family: 'Times New Roman', serif; line-height: 1.6; }
|
||
@media print { .no-print { display: none !important; } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${printContent}
|
||
</body>
|
||
</html>
|
||
`);
|
||
printWindow.document.close();
|
||
printWindow.print();
|
||
}
|
||
|
||
// Tag management
|
||
function handleTagInput(event) {
|
||
if (event.key === 'Enter' || event.key === ',') {
|
||
event.preventDefault();
|
||
const input = event.target;
|
||
const tagText = input.value.trim();
|
||
|
||
if (tagText && !isTagExists(tagText)) {
|
||
addTag(tagText);
|
||
input.value = '';
|
||
updateTagsHidden();
|
||
}
|
||
}
|
||
}
|
||
|
||
function addTag(tagText) {
|
||
const tagInput = document.getElementById('tag-input');
|
||
const input = tagInput.querySelector('input');
|
||
|
||
const tagElement = document.createElement('span');
|
||
tagElement.className = 'tag-item';
|
||
tagElement.innerHTML = `
|
||
${tagText}
|
||
<span class="tag-remove" onclick="removeTag(this)">×</span>
|
||
`;
|
||
|
||
tagInput.insertBefore(tagElement, input);
|
||
}
|
||
|
||
function removeTag(element) {
|
||
element.parentElement.remove();
|
||
updateTagsHidden();
|
||
}
|
||
|
||
function isTagExists(tagText) {
|
||
const existingTags = document.querySelectorAll('.tag-item');
|
||
return Array.from(existingTags).some(tag =>
|
||
tag.textContent.replace('×', '').trim().toLowerCase() === tagText.toLowerCase()
|
||
);
|
||
}
|
||
|
||
function updateTagsHidden() {
|
||
const tags = Array.from(document.querySelectorAll('.tag-item'))
|
||
.map(tag => tag.textContent.replace('×', '').trim());
|
||
document.getElementById('tags-hidden').value = tags.join(',');
|
||
}
|
||
|
||
function showAlert(message, type) {
|
||
const alertDiv = document.createElement('div');
|
||
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
||
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 300px;';
|
||
alertDiv.innerHTML = `
|
||
${message}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
`;
|
||
|
||
document.body.appendChild(alertDiv);
|
||
|
||
setTimeout(() => {
|
||
if (alertDiv.parentNode) {
|
||
alertDiv.remove();
|
||
}
|
||
}, 5000);
|
||
}
|
||
</script>
|
||
{% endblock %}
|
||
|