572 lines
29 KiB
HTML
572 lines
29 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if object %}Edit Template{% else %}Create Template{% endif %} - Communications{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Breadcrumb -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
|
|
<h4 class="mb-sm-0">{% if object %}Edit Template{% else %}Create Template{% endif %}</h4>
|
|
<div class="page-title-right">
|
|
<ol class="breadcrumb m-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:dashboard' %}">Communications</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:notification_template_list' %}">Templates</a></li>
|
|
<li class="breadcrumb-item active">{% if object %}Edit{% else %}Create{% endif %}</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="templateForm">
|
|
{% csrf_token %}
|
|
<div class="row">
|
|
<!-- Main Form -->
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-file-alt me-2"></i>
|
|
Template Details
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Template Name -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.template_name.id_for_label }}" class="form-label">Template Name *</label>
|
|
{{ form.template_name }}
|
|
{% if form.template_name.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.template_name.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Enter a unique, descriptive name for this template</div>
|
|
</div>
|
|
|
|
<!-- Category and Message Type -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.category.id_for_label }}" class="form-label">Category *</label>
|
|
{{ form.category }}
|
|
{% if form.category.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.category.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.message_type.id_for_label }}" class="form-label">Message Type *</label>
|
|
{{ form.message_type }}
|
|
{% if form.message_type.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.message_type.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.description.id_for_label }}" class="form-label">Description</label>
|
|
{{ form.description }}
|
|
{% if form.description.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.description.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Describe the purpose and usage of this template</div>
|
|
</div>
|
|
|
|
<!-- Language -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.language.id_for_label }}" class="form-label">Language</label>
|
|
{{ form.language }}
|
|
{% if form.language.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.language.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Select the primary language for this template</div>
|
|
</div>
|
|
|
|
<!-- Subject Template -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.subject_template.id_for_label }}" class="form-label">Subject Template *</label>
|
|
{{ form.subject_template }}
|
|
{% if form.subject_template.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.subject_template.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Use variables like {patient_name}, {appointment_date}</span>
|
|
<span id="subjectCharCount" class="text-muted">0 characters</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body Template -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.body_template.id_for_label }}" class="form-label">Body Template *</label>
|
|
{{ form.body_template }}
|
|
{% if form.body_template.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.body_template.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Enter the main content template with variables</span>
|
|
<span id="bodyCharCount" class="text-muted">0 characters</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status -->
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
{{ form.is_active }}
|
|
<label class="form-check-label" for="{{ form.is_active.id_for_label }}">
|
|
Active Template
|
|
</label>
|
|
</div>
|
|
{% if form.is_active.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.is_active.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Only active templates can be used for sending messages</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Template Variables -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-code me-2"></i>
|
|
Template Variables
|
|
</h5>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="addVariable()">
|
|
<i class="fas fa-plus me-1"></i>Add Variable
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="variablesContainer">
|
|
<!-- Variables will be added here dynamically -->
|
|
<div class="variable-item mb-3" style="display: none;">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<input type="text" class="form-control" placeholder="Variable name" name="variable_names[]">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input type="text" class="form-control" placeholder="Description" name="variable_descriptions[]">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeVariable(this)">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-muted">
|
|
<small>Define variables that can be used in your template. Variables should be referenced as {variable_name} in the template content.</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Template Preview -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-eye me-2"></i>
|
|
Live Preview
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="text-muted mb-2">Template Preview:</h6>
|
|
<div class="border rounded p-3" id="templatePreview">
|
|
<div class="mb-2">
|
|
<strong>Subject:</strong>
|
|
<div class="mt-1" id="previewSubject">[Subject will appear here]</div>
|
|
</div>
|
|
<div>
|
|
<strong>Body:</strong>
|
|
<div class="mt-1" id="previewBody">[Body will appear here]</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6 class="text-muted mb-2">Sample Data:</h6>
|
|
<div class="bg-light p-3 rounded">
|
|
<div class="mb-2">
|
|
<label class="form-label form-label-sm">Patient Name:</label>
|
|
<input type="text" class="form-control form-control-sm" id="samplePatientName" value="John Doe" onchange="updatePreview()">
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label form-label-sm">Appointment Date:</label>
|
|
<input type="text" class="form-control form-control-sm" id="sampleAppointmentDate" value="March 15, 2024" onchange="updatePreview()">
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label form-label-sm">Provider Name:</label>
|
|
<input type="text" class="form-control form-control-sm" id="sampleProviderName" value="Dr. Smith" onchange="updatePreview()">
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label form-label-sm">Hospital Name:</label>
|
|
<input type="text" class="form-control form-control-sm" id="sampleHospitalName" value="General Hospital" onchange="updatePreview()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Form Actions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-cog me-2"></i>
|
|
Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" name="action" value="save" class="btn btn-primary">
|
|
<i class="fas fa-save me-1"></i>
|
|
Save Template
|
|
</button>
|
|
<button type="submit" name="action" value="save_and_test" class="btn btn-success">
|
|
<i class="fas fa-flask me-1"></i>
|
|
Save & Test
|
|
</button>
|
|
|
|
<hr>
|
|
|
|
<button type="button" class="btn btn-outline-info" onclick="previewTemplate()">
|
|
<i class="fas fa-eye me-1"></i>
|
|
Full Preview
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="validateTemplate()">
|
|
<i class="fas fa-check me-1"></i>
|
|
Validate Template
|
|
</button>
|
|
|
|
<hr>
|
|
|
|
<a href="{% url 'communications:notification_template_list' %}" class="btn btn-outline-danger">
|
|
<i class="fas fa-times me-1"></i>
|
|
Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Template Help -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-question-circle me-2"></i>
|
|
Help & Variables
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="accordion" id="helpAccordion">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#patientVariables">
|
|
Patient Variables
|
|
</button>
|
|
</h2>
|
|
<div id="patientVariables" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<small>
|
|
<code>{patient_name}</code> - Full patient name<br>
|
|
<code>{patient_first_name}</code> - First name only<br>
|
|
<code>{patient_id}</code> - Patient ID number<br>
|
|
<code>{patient_phone}</code> - Phone number<br>
|
|
<code>{patient_email}</code> - Email address<br>
|
|
<code>{patient_dob}</code> - Date of birth
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#appointmentVariables">
|
|
Appointment Variables
|
|
</button>
|
|
</h2>
|
|
<div id="appointmentVariables" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<small>
|
|
<code>{appointment_date}</code> - Appointment date<br>
|
|
<code>{appointment_time}</code> - Appointment time<br>
|
|
<code>{provider_name}</code> - Healthcare provider<br>
|
|
<code>{department}</code> - Department name<br>
|
|
<code>{location}</code> - Appointment location<br>
|
|
<code>{appointment_type}</code> - Type of appointment
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#systemVariables">
|
|
System Variables
|
|
</button>
|
|
</h2>
|
|
<div id="systemVariables" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<small>
|
|
<code>{hospital_name}</code> - Hospital name<br>
|
|
<code>{hospital_phone}</code> - Hospital phone<br>
|
|
<code>{hospital_address}</code> - Hospital address<br>
|
|
<code>{current_date}</code> - Current date<br>
|
|
<code>{current_time}</code> - Current time<br>
|
|
<code>{system_url}</code> - System URL
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bestPractices">
|
|
Best Practices
|
|
</button>
|
|
</h2>
|
|
<div id="bestPractices" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<small>
|
|
• Use clear, descriptive template names<br>
|
|
• Keep subject lines concise and informative<br>
|
|
• Test templates before activating<br>
|
|
• Use appropriate variables for personalization<br>
|
|
• Consider different message types (SMS vs Email)<br>
|
|
• Include fallback text for missing variables
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Variable Inserter -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-plus-circle me-2"></i>
|
|
Quick Insert
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-1">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertVariable('patient_name')">
|
|
Insert {patient_name}
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertVariable('appointment_date')">
|
|
Insert {appointment_date}
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertVariable('provider_name')">
|
|
Insert {provider_name}
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertVariable('hospital_name')">
|
|
Insert {hospital_name}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Preview Modal -->
|
|
<div class="modal fade" id="previewModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Template Preview</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="fullPreviewContent"></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="saveTemplate()">Save Template</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
// Character counters
|
|
document.getElementById('{{ form.subject_template.id_for_label }}').addEventListener('input', function() {
|
|
document.getElementById('subjectCharCount').textContent = this.value.length + ' characters';
|
|
updatePreview();
|
|
});
|
|
|
|
document.getElementById('{{ form.body_template.id_for_label }}').addEventListener('input', function() {
|
|
document.getElementById('bodyCharCount').textContent = this.value.length + ' characters';
|
|
updatePreview();
|
|
});
|
|
|
|
// Variable management
|
|
function addVariable() {
|
|
const container = document.getElementById('variablesContainer');
|
|
const template = document.querySelector('.variable-item');
|
|
const newItem = template.cloneNode(true);
|
|
newItem.style.display = 'block';
|
|
container.appendChild(newItem);
|
|
}
|
|
|
|
function removeVariable(button) {
|
|
button.closest('.variable-item').remove();
|
|
}
|
|
|
|
// Preview functionality
|
|
function updatePreview() {
|
|
const subject = document.getElementById('{{ form.subject_template.id_for_label }}').value;
|
|
const body = document.getElementById('{{ form.body_template.id_for_label }}').value;
|
|
|
|
// Sample data
|
|
const sampleData = {
|
|
'patient_name': document.getElementById('samplePatientName').value,
|
|
'appointment_date': document.getElementById('sampleAppointmentDate').value,
|
|
'provider_name': document.getElementById('sampleProviderName').value,
|
|
'hospital_name': document.getElementById('sampleHospitalName').value
|
|
};
|
|
|
|
// Replace variables in subject and body
|
|
let previewSubject = subject;
|
|
let previewBody = body;
|
|
|
|
Object.keys(sampleData).forEach(key => {
|
|
const regex = new RegExp(`{${key}}`, 'g');
|
|
previewSubject = previewSubject.replace(regex, sampleData[key]);
|
|
previewBody = previewBody.replace(regex, sampleData[key]);
|
|
});
|
|
|
|
document.getElementById('previewSubject').textContent = previewSubject || '[Subject will appear here]';
|
|
document.getElementById('previewBody').innerHTML = previewBody.replace(/\n/g, '<br>') || '[Body will appear here]';
|
|
}
|
|
|
|
// Variable insertion
|
|
function insertVariable(variableName) {
|
|
const activeElement = document.activeElement;
|
|
const variable = `{${variableName}}`;
|
|
|
|
if (activeElement && (activeElement.id === '{{ form.subject_template.id_for_label }}' || activeElement.id === '{{ form.body_template.id_for_label }}')) {
|
|
const start = activeElement.selectionStart;
|
|
const end = activeElement.selectionEnd;
|
|
const text = activeElement.value;
|
|
|
|
activeElement.value = text.substring(0, start) + variable + text.substring(end);
|
|
activeElement.selectionStart = activeElement.selectionEnd = start + variable.length;
|
|
activeElement.focus();
|
|
|
|
updatePreview();
|
|
} else {
|
|
alert('Please click in the subject or body field first, then click the variable to insert.');
|
|
}
|
|
}
|
|
|
|
// Template actions
|
|
function previewTemplate() {
|
|
const subject = document.getElementById('{{ form.subject_template.id_for_label }}').value;
|
|
const body = document.getElementById('{{ form.body_template.id_for_label }}').value;
|
|
|
|
const previewContent = `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">${subject}</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
${body.replace(/\n/g, '<br>')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('fullPreviewContent').innerHTML = previewContent;
|
|
new bootstrap.Modal(document.getElementById('previewModal')).show();
|
|
}
|
|
|
|
function validateTemplate() {
|
|
const subject = document.getElementById('{{ form.subject_template.id_for_label }}').value;
|
|
const body = document.getElementById('{{ form.body_template.id_for_label }}').value;
|
|
|
|
// Basic validation
|
|
const errors = [];
|
|
|
|
if (!subject.trim()) {
|
|
errors.push('Subject template is required');
|
|
}
|
|
|
|
if (!body.trim()) {
|
|
errors.push('Body template is required');
|
|
}
|
|
|
|
// Check for unmatched braces
|
|
const subjectBraces = (subject.match(/{/g) || []).length - (subject.match(/}/g) || []).length;
|
|
const bodyBraces = (body.match(/{/g) || []).length - (body.match(/}/g) || []).length;
|
|
|
|
if (subjectBraces !== 0) {
|
|
errors.push('Unmatched braces in subject template');
|
|
}
|
|
|
|
if (bodyBraces !== 0) {
|
|
errors.push('Unmatched braces in body template');
|
|
}
|
|
|
|
if (errors.length === 0) {
|
|
alert('Template validation passed! ✓');
|
|
} else {
|
|
alert('Template validation failed:\n\n' + errors.join('\n'));
|
|
}
|
|
}
|
|
|
|
function saveTemplate() {
|
|
document.querySelector('button[name="action"][value="save"]').click();
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
updatePreview();
|
|
|
|
// Add existing variables if editing
|
|
{% if object and object.variables %}
|
|
{% for variable in object.variables %}
|
|
addVariable();
|
|
const lastItem = document.querySelector('#variablesContainer .variable-item:last-child');
|
|
lastItem.querySelector('input[name="variable_names[]"]').value = '{{ variable.name }}';
|
|
lastItem.querySelector('input[name="variable_descriptions[]"]').value = '{{ variable.description }}';
|
|
{% endfor %}
|
|
{% endif %}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|