499 lines
24 KiB
HTML
499 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if object %}Edit Message{% else %}Compose Message{% 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 Message{% else %}Compose Message{% 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:message_list' %}">Messages</a></li>
|
|
<li class="breadcrumb-item active">{% if object %}Edit{% else %}Compose{% endif %}</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="messageForm">
|
|
{% 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-envelope me-2"></i>
|
|
Message Details
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Subject -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.subject.id_for_label }}" class="form-label">Subject *</label>
|
|
{{ form.subject }}
|
|
{% if form.subject.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.subject.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Enter a clear, descriptive subject line</div>
|
|
</div>
|
|
|
|
<!-- Message Type and Priority -->
|
|
<div class="row">
|
|
<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 class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.priority.id_for_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>
|
|
</div>
|
|
|
|
<!-- Template Selection -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.template.id_for_label }}" class="form-label">Template</label>
|
|
<div class="input-group">
|
|
{{ form.template }}
|
|
<button class="btn btn-outline-secondary" type="button" onclick="loadTemplate()">
|
|
<i class="fas fa-download me-1"></i>Load
|
|
</button>
|
|
<button class="btn btn-outline-info" type="button" onclick="previewTemplate()">
|
|
<i class="fas fa-eye me-1"></i>Preview
|
|
</button>
|
|
</div>
|
|
{% if form.template.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.template.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Optional: Select a template to pre-fill the message content</div>
|
|
</div>
|
|
|
|
<!-- Message Body -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.body.id_for_label }}" class="form-label">Message Content *</label>
|
|
{{ form.body }}
|
|
{% if form.body.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.body.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Enter your message content. You can use variables like {patient_name}, {appointment_date}</span>
|
|
<span id="charCount" class="text-muted">0 characters</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scheduled Time -->
|
|
<div class="mb-3">
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" id="scheduleMessage">
|
|
<label class="form-check-label" for="scheduleMessage">
|
|
Schedule for later
|
|
</label>
|
|
</div>
|
|
<div id="scheduledTimeGroup" style="display: none;">
|
|
<label for="{{ form.scheduled_time.id_for_label }}" class="form-label">Scheduled Time</label>
|
|
{{ form.scheduled_time }}
|
|
{% if form.scheduled_time.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.scheduled_time.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Leave empty to send immediately</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipients -->
|
|
<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-users me-2"></i>
|
|
Recipients
|
|
</h5>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="selectAllRecipients()">
|
|
Select All
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="clearAllRecipients()">
|
|
Clear All
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Recipient Search -->
|
|
<div class="mb-3">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" placeholder="Search recipients..." id="recipientSearch">
|
|
<button class="btn btn-outline-secondary" type="button">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Recipient Groups -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Quick Select:</label>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-primary" onclick="selectGroup('doctors')">
|
|
Doctors
|
|
</button>
|
|
<button type="button" class="btn btn-outline-success" onclick="selectGroup('nurses')">
|
|
Nurses
|
|
</button>
|
|
<button type="button" class="btn btn-outline-info" onclick="selectGroup('admin')">
|
|
Admin Staff
|
|
</button>
|
|
<button type="button" class="btn btn-outline-warning" onclick="selectGroup('patients')">
|
|
Patients
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipients List -->
|
|
<div class="recipients-container" style="max-height: 300px; overflow-y: auto;">
|
|
{{ form.recipients }}
|
|
</div>
|
|
{% if form.recipients.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.recipients.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Select one or more recipients for this message</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_draft" class="btn btn-secondary">
|
|
<i class="fas fa-save me-1"></i>
|
|
Save as Draft
|
|
</button>
|
|
<button type="submit" name="action" value="send_now" class="btn btn-success">
|
|
<i class="fas fa-paper-plane me-1"></i>
|
|
Send Now
|
|
</button>
|
|
<button type="submit" name="action" value="schedule" class="btn btn-primary">
|
|
<i class="fas fa-clock me-1"></i>
|
|
Schedule Message
|
|
</button>
|
|
|
|
<hr>
|
|
|
|
<button type="button" class="btn btn-outline-info" onclick="previewMessage()">
|
|
<i class="fas fa-eye me-1"></i>
|
|
Preview Message
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="testMessage()">
|
|
<i class="fas fa-flask me-1"></i>
|
|
Send Test
|
|
</button>
|
|
|
|
<hr>
|
|
|
|
<a href="{% url 'communications:message_list' %}" class="btn btn-outline-danger">
|
|
<i class="fas fa-times me-1"></i>
|
|
Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Help -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-question-circle me-2"></i>
|
|
Help & Tips
|
|
</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="#variables">
|
|
Message Variables
|
|
</button>
|
|
</h2>
|
|
<div id="variables" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<small>
|
|
<strong>Patient Variables:</strong><br>
|
|
{patient_name}, {patient_id}, {patient_phone}<br><br>
|
|
<strong>Appointment Variables:</strong><br>
|
|
{appointment_date}, {appointment_time}, {provider_name}<br><br>
|
|
<strong>System Variables:</strong><br>
|
|
{hospital_name}, {current_date}, {current_time}
|
|
</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="#messageTypes">
|
|
Message Types
|
|
</button>
|
|
</h2>
|
|
<div id="messageTypes" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<small>
|
|
<strong>Internal:</strong> Staff-to-staff communication<br>
|
|
<strong>Email:</strong> External email messages<br>
|
|
<strong>SMS:</strong> Text messages to mobile phones<br>
|
|
<strong>Push:</strong> Mobile app notifications<br>
|
|
<strong>System:</strong> Automated system messages
|
|
</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>
|
|
• Keep subjects clear and concise<br>
|
|
• Use appropriate priority levels<br>
|
|
• Test messages before sending<br>
|
|
• Schedule non-urgent messages appropriately<br>
|
|
• Use templates for consistency
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Statistics -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-chart-bar me-2"></i>
|
|
Estimated Reach
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless table-sm">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Selected Recipients:</td>
|
|
<td><span id="selectedCount">0</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Estimated Cost:</td>
|
|
<td><span id="estimatedCost">$0.00</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Delivery Time:</td>
|
|
<td><span id="deliveryTime">Immediate</span></td>
|
|
</tr>
|
|
</table>
|
|
</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">Message Preview</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="previewContent"></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="sendFromPreview()">Send Message</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
// Character counter
|
|
document.getElementById('{{ form.body.id_for_label }}').addEventListener('input', function() {
|
|
document.getElementById('charCount').textContent = this.value.length + ' characters';
|
|
});
|
|
|
|
// Schedule message toggle
|
|
document.getElementById('scheduleMessage').addEventListener('change', function() {
|
|
const scheduledTimeGroup = document.getElementById('scheduledTimeGroup');
|
|
scheduledTimeGroup.style.display = this.checked ? 'block' : 'none';
|
|
});
|
|
|
|
// Recipient selection functions
|
|
function selectAllRecipients() {
|
|
const checkboxes = document.querySelectorAll('input[name="{{ form.recipients.name }}"]');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = true;
|
|
});
|
|
updateSelectedCount();
|
|
}
|
|
|
|
function clearAllRecipients() {
|
|
const checkboxes = document.querySelectorAll('input[name="{{ form.recipients.name }}"]');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = false;
|
|
});
|
|
updateSelectedCount();
|
|
}
|
|
|
|
function selectGroup(group) {
|
|
// Implementation for selecting specific groups
|
|
alert(`Selecting ${group} group - functionality to be implemented`);
|
|
}
|
|
|
|
function updateSelectedCount() {
|
|
const selected = document.querySelectorAll('input[name="{{ form.recipients.name }}"]:checked').length;
|
|
document.getElementById('selectedCount').textContent = selected;
|
|
|
|
// Update estimated cost (example calculation)
|
|
const messageType = document.getElementById('{{ form.message_type.id_for_label }}').value;
|
|
let costPerMessage = 0;
|
|
if (messageType === 'SMS') costPerMessage = 0.05;
|
|
else if (messageType === 'EMAIL') costPerMessage = 0.01;
|
|
|
|
const estimatedCost = (selected * costPerMessage).toFixed(2);
|
|
document.getElementById('estimatedCost').textContent = '$' + estimatedCost;
|
|
}
|
|
|
|
// Template functions
|
|
function loadTemplate() {
|
|
const templateSelect = document.getElementById('{{ form.template.id_for_label }}');
|
|
const templateId = templateSelect.value;
|
|
|
|
if (templateId) {
|
|
fetch(`/communications/templates/${templateId}/content/`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('{{ form.subject.id_for_label }}').value = data.subject;
|
|
document.getElementById('{{ form.body.id_for_label }}').value = data.body;
|
|
updateSelectedCount();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function previewTemplate() {
|
|
const templateSelect = document.getElementById('{{ form.template.id_for_label }}');
|
|
const templateId = templateSelect.value;
|
|
|
|
if (templateId) {
|
|
window.open(`/communications/templates/${templateId}/preview/`, '_blank');
|
|
} else {
|
|
alert('Please select a template first');
|
|
}
|
|
}
|
|
|
|
// Message functions
|
|
function previewMessage() {
|
|
const subject = document.getElementById('{{ form.subject.id_for_label }}').value;
|
|
const body = document.getElementById('{{ form.body.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('previewContent').innerHTML = previewContent;
|
|
new bootstrap.Modal(document.getElementById('previewModal')).show();
|
|
}
|
|
|
|
function testMessage() {
|
|
if (confirm('Send a test message to yourself?')) {
|
|
// Implementation for test message
|
|
alert('Test message functionality will be implemented');
|
|
}
|
|
}
|
|
|
|
function sendFromPreview() {
|
|
document.querySelector('button[name="action"][value="send_now"]').click();
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
updateSelectedCount();
|
|
|
|
// Add event listeners to recipient checkboxes
|
|
const checkboxes = document.querySelectorAll('input[name="{{ form.recipients.name }}"]');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.addEventListener('change', updateSelectedCount);
|
|
});
|
|
|
|
// Recipient search functionality
|
|
document.getElementById('recipientSearch').addEventListener('input', function() {
|
|
const searchTerm = this.value.toLowerCase();
|
|
const labels = document.querySelectorAll('.recipients-container label');
|
|
|
|
labels.forEach(label => {
|
|
const text = label.textContent.toLowerCase();
|
|
label.style.display = text.includes(searchTerm) ? 'block' : 'none';
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|