939 lines
43 KiB
HTML
939 lines
43 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Compose Message - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">Compose Message</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:dashboard' %}">Communications</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:message_inbox' %}">Messages</a></li>
|
|
<li class="breadcrumb-item active">Compose</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% url 'communications:message_inbox' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-arrow-left me-2"></i>Back to Inbox
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="composeForm" enctype="multipart/form-data" hx-post="{% url 'communications:message_compose' %}" hx-target="#form-container">
|
|
{% csrf_token %}
|
|
|
|
<div class="row">
|
|
<!-- Main Compose Area -->
|
|
<div class="col-lg-9">
|
|
<div id="form-container">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-edit me-2"></i>New Message
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="saveDraft()">
|
|
<i class="fas fa-save me-1"></i>Save Draft
|
|
</button>
|
|
<button type="button" class="btn btn-outline-info" onclick="previewMessage()">
|
|
<i class="fas fa-eye me-1"></i>Preview
|
|
</button>
|
|
<button type="button" class="btn btn-outline-warning" onclick="scheduleMessage()">
|
|
<i class="fas fa-clock me-1"></i>Schedule
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Message Type Selection -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="form-floating">
|
|
{{ form.message_type }}
|
|
<label for="{{ form.message_type.id_for_label }}">Message Type *</label>
|
|
{% 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="form-floating">
|
|
{{ form.priority }}
|
|
<label for="{{ form.priority.id_for_label }}">Priority *</label>
|
|
{% if form.priority.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.priority.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipients Section -->
|
|
<div class="mb-3">
|
|
<label class="form-label fw-bold">Recipients *</label>
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<!-- To Field -->
|
|
<div class="mb-3">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<label class="form-label mb-0 me-3" style="min-width: 60px;">To:</label>
|
|
<div class="flex-grow-1">
|
|
<input type="text" class="form-control" id="recipientInput" placeholder="Enter names, emails, or select from contacts..." autocomplete="off">
|
|
<div id="recipientSuggestions" class="dropdown-menu w-100" style="display: none;"></div>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-primary btn-sm ms-2" onclick="showContactPicker()">
|
|
<i class="fas fa-address-book"></i>
|
|
</button>
|
|
</div>
|
|
<div id="selectedRecipients" class="d-flex flex-wrap gap-2">
|
|
<!-- Selected recipients will appear here as badges -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CC Field -->
|
|
<div class="mb-3" id="ccField" style="display: none;">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<label class="form-label mb-0 me-3" style="min-width: 60px;">CC:</label>
|
|
<div class="flex-grow-1">
|
|
<input type="text" class="form-control" id="ccInput" placeholder="Carbon copy recipients...">
|
|
</div>
|
|
</div>
|
|
<div id="selectedCcRecipients" class="d-flex flex-wrap gap-2">
|
|
<!-- CC recipients will appear here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BCC Field -->
|
|
<div class="mb-3" id="bccField" style="display: none;">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<label class="form-label mb-0 me-3" style="min-width: 60px;">BCC:</label>
|
|
<div class="flex-grow-1">
|
|
<input type="text" class="form-control" id="bccInput" placeholder="Blind carbon copy recipients...">
|
|
</div>
|
|
</div>
|
|
<div id="selectedBccRecipients" class="d-flex flex-wrap gap-2">
|
|
<!-- BCC recipients will appear here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CC/BCC Toggle -->
|
|
<div class="text-end">
|
|
<button type="button" class="btn btn-link btn-sm p-0" onclick="toggleCcBcc()">
|
|
<i class="fas fa-plus me-1"></i>Add CC/BCC
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Subject -->
|
|
<div class="mb-3">
|
|
<div class="form-floating">
|
|
{{ form.subject }}
|
|
<label for="{{ form.subject.id_for_label }}">Subject *</label>
|
|
{% if form.subject.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.subject.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Content -->
|
|
<div class="mb-3">
|
|
<label for="{{ form.content.id_for_label }}" class="form-label fw-bold">Message Content *</label>
|
|
<div class="card">
|
|
<div class="card-header p-2">
|
|
<!-- Rich Text Editor Toolbar -->
|
|
<div class="btn-toolbar" role="toolbar">
|
|
<div class="btn-group btn-group-sm me-2" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="formatText('bold')" title="Bold">
|
|
<i class="fas fa-bold"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="formatText('italic')" title="Italic">
|
|
<i class="fas fa-italic"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="formatText('underline')" title="Underline">
|
|
<i class="fas fa-underline"></i>
|
|
</button>
|
|
</div>
|
|
<div class="btn-group btn-group-sm me-2" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="formatText('insertUnorderedList')" title="Bullet List">
|
|
<i class="fas fa-list-ul"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="formatText('insertOrderedList')" title="Numbered List">
|
|
<i class="fas fa-list-ol"></i>
|
|
</button>
|
|
</div>
|
|
<div class="btn-group btn-group-sm me-2" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="insertLink()" title="Insert Link">
|
|
<i class="fas fa-link"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="insertTable()" title="Insert Table">
|
|
<i class="fas fa-table"></i>
|
|
</button>
|
|
</div>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="insertTemplate()" title="Insert Template">
|
|
<i class="fas fa-file-alt"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="insertSignature()" title="Insert Signature">
|
|
<i class="fas fa-signature"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div id="messageEditor" contenteditable="true" style="min-height: 300px; padding: 15px; border: none; outline: none;">
|
|
<!-- Rich text content will be entered here -->
|
|
</div>
|
|
<textarea name="content" id="{{ form.content.id_for_label }}" style="display: none;">{{ form.content.value|default:'' }}</textarea>
|
|
</div>
|
|
</div>
|
|
{% if form.content.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.content.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Attachments -->
|
|
<div class="mb-3">
|
|
<label class="form-label fw-bold">Attachments</label>
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<input type="file" class="form-control" id="attachmentInput" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.jpg,.jpeg,.png,.gif">
|
|
<button type="button" class="btn btn-outline-primary ms-2" onclick="addAttachment()">
|
|
<i class="fas fa-plus"></i>
|
|
</button>
|
|
</div>
|
|
<div id="attachmentList" class="mt-3">
|
|
<!-- Attached files will appear here -->
|
|
</div>
|
|
<div class="form-text">
|
|
Maximum file size: 10MB per file. Allowed types: PDF, DOC, XLS, PPT, TXT, Images
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Options -->
|
|
<div class="mb-3">
|
|
<label class="form-label fw-bold">Message Options</label>
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-check">
|
|
{{ form.is_urgent }}
|
|
<label class="form-check-label" for="{{ form.is_urgent.id_for_label }}">
|
|
<i class="fas fa-exclamation-triangle text-warning me-1"></i>Mark as Urgent
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
{{ form.requires_acknowledgment }}
|
|
<label class="form-check-label" for="{{ form.requires_acknowledgment.id_for_label }}">
|
|
<i class="fas fa-check-circle text-info me-1"></i>Require Acknowledgment
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
{{ form.is_confidential }}
|
|
<label class="form-check-label" for="{{ form.is_confidential.id_for_label }}">
|
|
<i class="fas fa-lock text-danger me-1"></i>Mark as Confidential
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-floating">
|
|
{{ form.expires_at }}
|
|
<label for="{{ form.expires_at.id_for_label }}">Expiration Date</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<a href="{% url 'communications:message_inbox' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-times me-2"></i>Cancel
|
|
</a>
|
|
</div>
|
|
<div class="btn-group">
|
|
<button type="submit" name="action" value="draft" class="btn btn-outline-primary">
|
|
<i class="fas fa-save me-2"></i>Save Draft
|
|
</button>
|
|
<button type="submit" name="action" value="send" class="btn btn-success">
|
|
<i class="fas fa-paper-plane me-2"></i>Send Message
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-3">
|
|
<!-- Quick Templates -->
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-file-alt me-2"></i>Quick Templates
|
|
</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="list-group list-group-flush" id="templateList">
|
|
<button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('appointment_reminder')">
|
|
<i class="fas fa-calendar me-2"></i>Appointment Reminder
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('lab_results')">
|
|
<i class="fas fa-flask me-2"></i>Lab Results Available
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('medication_reminder')">
|
|
<i class="fas fa-pills me-2"></i>Medication Reminder
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('billing_notice')">
|
|
<i class="fas fa-file-invoice me-2"></i>Billing Notice
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('general_notice')">
|
|
<i class="fas fa-info-circle me-2"></i>General Notice
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<a href="{% url 'communications:template_list' %}" class="btn btn-outline-primary btn-sm w-100">
|
|
<i class="fas fa-external-link-alt me-1"></i>Manage Templates
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Contacts -->
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-users me-2"></i>Recent Contacts
|
|
</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="list-group list-group-flush" id="recentContactsList" hx-get="{% url 'communications:recent_contacts' %}" hx-trigger="load">
|
|
<div class="text-center p-3">
|
|
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Statistics -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-chart-bar me-2"></i>Your Statistics
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-6">
|
|
<div class="border-end">
|
|
<div class="h4 text-primary mb-0">{{ user_stats.sent_today|default:0 }}</div>
|
|
<div class="small text-muted">Sent Today</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="h4 text-success mb-0">{{ user_stats.sent_week|default:0 }}</div>
|
|
<div class="small text-muted">This Week</div>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<div class="row text-center">
|
|
<div class="col-6">
|
|
<div class="border-end">
|
|
<div class="h4 text-info mb-0">{{ user_stats.drafts|default:0 }}</div>
|
|
<div class="small text-muted">Drafts</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="h4 text-warning mb-0">{{ user_stats.scheduled|default:0 }}</div>
|
|
<div class="small text-muted">Scheduled</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Contact Picker Modal -->
|
|
<div class="modal fade" id="contactPickerModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Select Contacts</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<input type="text" class="form-control" id="contactSearch" placeholder="Search contacts...">
|
|
</div>
|
|
<div id="contactList" style="max-height: 400px; overflow-y: auto;">
|
|
<!-- Contact list will be loaded here -->
|
|
</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="addSelectedContacts()">Add Selected</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Schedule Message Modal -->
|
|
<div class="modal fade" id="scheduleModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Schedule Message</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="scheduledDateTime" class="form-label">Send Date & Time</label>
|
|
<input type="datetime-local" class="form-control" id="scheduledDateTime" name="scheduled_at">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="timezone" class="form-label">Timezone</label>
|
|
<select class="form-select" id="timezone">
|
|
<option value="UTC">UTC</option>
|
|
<option value="America/New_York">Eastern Time</option>
|
|
<option value="America/Chicago">Central Time</option>
|
|
<option value="America/Denver">Mountain Time</option>
|
|
<option value="America/Los_Angeles">Pacific Time</option>
|
|
</select>
|
|
</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="confirmSchedule()">Schedule Message</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let selectedRecipients = [];
|
|
let selectedCcRecipients = [];
|
|
let selectedBccRecipients = [];
|
|
let attachments = [];
|
|
|
|
// Initialize compose form
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeEditor();
|
|
setupRecipientInput();
|
|
setupAutoSave();
|
|
|
|
// Load draft if editing
|
|
const draftId = new URLSearchParams(window.location.search).get('draft');
|
|
if (draftId) {
|
|
loadDraft(draftId);
|
|
}
|
|
});
|
|
|
|
function initializeEditor() {
|
|
const editor = document.getElementById('messageEditor');
|
|
const hiddenTextarea = document.getElementById('{{ form.content.id_for_label }}');
|
|
|
|
// Sync editor content with hidden textarea
|
|
editor.addEventListener('input', function() {
|
|
hiddenTextarea.value = editor.innerHTML;
|
|
});
|
|
|
|
// Load initial content
|
|
if (hiddenTextarea.value) {
|
|
editor.innerHTML = hiddenTextarea.value;
|
|
}
|
|
}
|
|
|
|
function formatText(command, value = null) {
|
|
document.execCommand(command, false, value);
|
|
document.getElementById('messageEditor').focus();
|
|
}
|
|
|
|
function insertLink() {
|
|
const url = prompt('Enter URL:');
|
|
if (url) {
|
|
formatText('createLink', url);
|
|
}
|
|
}
|
|
|
|
function insertTable() {
|
|
const rows = prompt('Number of rows:', '3');
|
|
const cols = prompt('Number of columns:', '3');
|
|
|
|
if (rows && cols) {
|
|
let tableHtml = '<table class="table table-bordered"><tbody>';
|
|
for (let i = 0; i < parseInt(rows); i++) {
|
|
tableHtml += '<tr>';
|
|
for (let j = 0; j < parseInt(cols); j++) {
|
|
tableHtml += '<td> </td>';
|
|
}
|
|
tableHtml += '</tr>';
|
|
}
|
|
tableHtml += '</tbody></table>';
|
|
|
|
formatText('insertHTML', tableHtml);
|
|
}
|
|
}
|
|
|
|
function setupRecipientInput() {
|
|
const input = document.getElementById('recipientInput');
|
|
const suggestions = document.getElementById('recipientSuggestions');
|
|
|
|
input.addEventListener('input', function() {
|
|
const query = this.value;
|
|
if (query.length >= 2) {
|
|
searchContacts(query);
|
|
} else {
|
|
suggestions.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
input.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter' || e.key === ',') {
|
|
e.preventDefault();
|
|
addRecipient(this.value.trim());
|
|
this.value = '';
|
|
suggestions.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
function searchContacts(query) {
|
|
fetch(`{% url 'communications:search_contacts' %}?q=${encodeURIComponent(query)}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const suggestions = document.getElementById('recipientSuggestions');
|
|
suggestions.innerHTML = '';
|
|
|
|
data.contacts.forEach(contact => {
|
|
const item = document.createElement('a');
|
|
item.className = 'dropdown-item';
|
|
item.href = '#';
|
|
item.innerHTML = `
|
|
<div class="d-flex align-items-center">
|
|
<div class="me-3">
|
|
<i class="fas fa-user-circle fa-lg text-muted"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold">${contact.name}</div>
|
|
<div class="small text-muted">${contact.email}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
item.onclick = function(e) {
|
|
e.preventDefault();
|
|
addRecipient(contact);
|
|
document.getElementById('recipientInput').value = '';
|
|
suggestions.style.display = 'none';
|
|
};
|
|
suggestions.appendChild(item);
|
|
});
|
|
|
|
suggestions.style.display = data.contacts.length > 0 ? 'block' : 'none';
|
|
})
|
|
.catch(error => console.error('Error searching contacts:', error));
|
|
}
|
|
|
|
function addRecipient(recipient, type = 'to') {
|
|
let recipientObj;
|
|
|
|
if (typeof recipient === 'string') {
|
|
// Parse email string
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (emailRegex.test(recipient)) {
|
|
recipientObj = { email: recipient, name: recipient };
|
|
} else {
|
|
return; // Invalid email
|
|
}
|
|
} else {
|
|
recipientObj = recipient;
|
|
}
|
|
|
|
// Add to appropriate list
|
|
let targetList, targetContainer;
|
|
if (type === 'cc') {
|
|
targetList = selectedCcRecipients;
|
|
targetContainer = 'selectedCcRecipients';
|
|
} else if (type === 'bcc') {
|
|
targetList = selectedBccRecipients;
|
|
targetContainer = 'selectedBccRecipients';
|
|
} else {
|
|
targetList = selectedRecipients;
|
|
targetContainer = 'selectedRecipients';
|
|
}
|
|
|
|
// Check for duplicates
|
|
if (targetList.some(r => r.email === recipientObj.email)) {
|
|
return;
|
|
}
|
|
|
|
targetList.push(recipientObj);
|
|
updateRecipientDisplay(targetContainer, targetList);
|
|
}
|
|
|
|
function updateRecipientDisplay(containerId, recipients) {
|
|
const container = document.getElementById(containerId);
|
|
container.innerHTML = '';
|
|
|
|
recipients.forEach((recipient, index) => {
|
|
const badge = document.createElement('span');
|
|
badge.className = 'badge bg-primary d-flex align-items-center';
|
|
badge.innerHTML = `
|
|
${recipient.name || recipient.email}
|
|
<button type="button" class="btn-close btn-close-white ms-2" onclick="removeRecipient('${containerId}', ${index})"></button>
|
|
`;
|
|
container.appendChild(badge);
|
|
});
|
|
}
|
|
|
|
function removeRecipient(containerId, index) {
|
|
let targetList;
|
|
if (containerId === 'selectedCcRecipients') {
|
|
targetList = selectedCcRecipients;
|
|
} else if (containerId === 'selectedBccRecipients') {
|
|
targetList = selectedBccRecipients;
|
|
} else {
|
|
targetList = selectedRecipients;
|
|
}
|
|
|
|
targetList.splice(index, 1);
|
|
updateRecipientDisplay(containerId, targetList);
|
|
}
|
|
|
|
function toggleCcBcc() {
|
|
const ccField = document.getElementById('ccField');
|
|
const bccField = document.getElementById('bccField');
|
|
|
|
if (ccField.style.display === 'none') {
|
|
ccField.style.display = 'block';
|
|
bccField.style.display = 'block';
|
|
} else {
|
|
ccField.style.display = 'none';
|
|
bccField.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function showContactPicker() {
|
|
const modal = new bootstrap.Modal(document.getElementById('contactPickerModal'));
|
|
modal.show();
|
|
|
|
// Load contacts
|
|
loadContactList();
|
|
}
|
|
|
|
function loadContactList() {
|
|
fetch('{% url "communications:contact_list_api" %}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const container = document.getElementById('contactList');
|
|
container.innerHTML = '';
|
|
|
|
data.contacts.forEach(contact => {
|
|
const item = document.createElement('div');
|
|
item.className = 'form-check';
|
|
item.innerHTML = `
|
|
<input class="form-check-input" type="checkbox" value="${contact.id}" id="contact_${contact.id}">
|
|
<label class="form-check-label d-flex align-items-center" for="contact_${contact.id}">
|
|
<div class="me-3">
|
|
<i class="fas fa-user-circle fa-lg text-muted"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold">${contact.name}</div>
|
|
<div class="small text-muted">${contact.email}</div>
|
|
<div class="small text-muted">${contact.role || ''}</div>
|
|
</div>
|
|
</label>
|
|
`;
|
|
container.appendChild(item);
|
|
});
|
|
})
|
|
.catch(error => console.error('Error loading contacts:', error));
|
|
}
|
|
|
|
function addSelectedContacts() {
|
|
const checkboxes = document.querySelectorAll('#contactList input[type="checkbox"]:checked');
|
|
checkboxes.forEach(checkbox => {
|
|
const label = checkbox.nextElementSibling;
|
|
const name = label.querySelector('.fw-bold').textContent;
|
|
const email = label.querySelector('.small').textContent;
|
|
|
|
addRecipient({ name, email });
|
|
});
|
|
|
|
bootstrap.Modal.getInstance(document.getElementById('contactPickerModal')).hide();
|
|
}
|
|
|
|
function addAttachment() {
|
|
const input = document.getElementById('attachmentInput');
|
|
const files = input.files;
|
|
|
|
for (let file of files) {
|
|
if (file.size > 10 * 1024 * 1024) { // 10MB limit
|
|
alert(`File "${file.name}" is too large. Maximum size is 10MB.`);
|
|
continue;
|
|
}
|
|
|
|
attachments.push(file);
|
|
displayAttachment(file);
|
|
}
|
|
|
|
input.value = ''; // Clear input
|
|
}
|
|
|
|
function displayAttachment(file) {
|
|
const container = document.getElementById('attachmentList');
|
|
const item = document.createElement('div');
|
|
item.className = 'attachment-item d-flex align-items-center justify-content-between p-2 border rounded mb-2';
|
|
item.innerHTML = `
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-file me-2"></i>
|
|
<div>
|
|
<div class="fw-bold">${file.name}</div>
|
|
<div class="small text-muted">${formatFileSize(file.size)}</div>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeAttachment('${file.name}')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
container.appendChild(item);
|
|
}
|
|
|
|
function removeAttachment(fileName) {
|
|
attachments = attachments.filter(file => file.name !== fileName);
|
|
|
|
// Remove from display
|
|
const items = document.querySelectorAll('.attachment-item');
|
|
items.forEach(item => {
|
|
if (item.querySelector('.fw-bold').textContent === fileName) {
|
|
item.remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function loadTemplate(templateId) {
|
|
fetch(`{% url 'communications:template_content' 'TEMPLATE_ID' %}`.replace('TEMPLATE_ID', templateId))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('{{ form.subject.id_for_label }}').value = data.subject;
|
|
document.getElementById('messageEditor').innerHTML = data.content;
|
|
document.getElementById('{{ form.content.id_for_label }}').value = data.content;
|
|
}
|
|
})
|
|
.catch(error => console.error('Error loading template:', error));
|
|
}
|
|
|
|
function insertTemplate() {
|
|
// Show template picker modal
|
|
// Implementation depends on your template system
|
|
}
|
|
|
|
function insertSignature() {
|
|
fetch('{% url "communications:user_signature" %}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.signature) {
|
|
const editor = document.getElementById('messageEditor');
|
|
editor.innerHTML += '<br><br>' + data.signature;
|
|
document.getElementById('{{ form.content.id_for_label }}').value = editor.innerHTML;
|
|
}
|
|
})
|
|
.catch(error => console.error('Error loading signature:', error));
|
|
}
|
|
|
|
function saveDraft() {
|
|
const formData = new FormData(document.getElementById('composeForm'));
|
|
formData.set('action', 'draft');
|
|
|
|
// Add recipients
|
|
formData.set('recipients', JSON.stringify(selectedRecipients));
|
|
formData.set('cc_recipients', JSON.stringify(selectedCcRecipients));
|
|
formData.set('bcc_recipients', JSON.stringify(selectedBccRecipients));
|
|
|
|
// Add attachments
|
|
attachments.forEach((file, index) => {
|
|
formData.append(`attachment_${index}`, file);
|
|
});
|
|
|
|
fetch('{% url "communications:message_compose" %}', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast('Success', 'Draft saved successfully', 'success');
|
|
} else {
|
|
showToast('Error', data.error || 'Failed to save draft', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error saving draft:', error);
|
|
showToast('Error', 'Failed to save draft', 'error');
|
|
});
|
|
}
|
|
|
|
function previewMessage() {
|
|
const subject = document.getElementById('{{ form.subject.id_for_label }}').value;
|
|
const content = document.getElementById('messageEditor').innerHTML;
|
|
|
|
const previewWindow = window.open('', '_blank', 'width=800,height=600');
|
|
previewWindow.document.write(`
|
|
<html>
|
|
<head>
|
|
<title>Message Preview</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
</head>
|
|
<body>
|
|
<div class="container mt-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>Subject: ${subject}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
${content}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`);
|
|
}
|
|
|
|
function scheduleMessage() {
|
|
const modal = new bootstrap.Modal(document.getElementById('scheduleModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function confirmSchedule() {
|
|
const scheduledDateTime = document.getElementById('scheduledDateTime').value;
|
|
if (!scheduledDateTime) {
|
|
alert('Please select a date and time.');
|
|
return;
|
|
}
|
|
|
|
document.getElementById('{{ form.scheduled_at.id_for_label }}').value = scheduledDateTime;
|
|
bootstrap.Modal.getInstance(document.getElementById('scheduleModal')).hide();
|
|
|
|
showToast('Success', 'Message scheduled for ' + new Date(scheduledDateTime).toLocaleString(), 'info');
|
|
}
|
|
|
|
function setupAutoSave() {
|
|
// Auto-save draft every 2 minutes
|
|
setInterval(function() {
|
|
if (document.getElementById('{{ form.subject.id_for_label }}').value.trim() ||
|
|
document.getElementById('messageEditor').innerHTML.trim()) {
|
|
saveDraft();
|
|
}
|
|
}, 120000);
|
|
}
|
|
|
|
function loadDraft(draftId) {
|
|
fetch(`{% url 'communications:draft_detail' 'DRAFT_ID' %}`.replace('DRAFT_ID', draftId))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Populate form with draft data
|
|
document.getElementById('{{ form.subject.id_for_label }}').value = data.subject;
|
|
document.getElementById('messageEditor').innerHTML = data.content;
|
|
document.getElementById('{{ form.content.id_for_label }}').value = data.content;
|
|
|
|
// Load recipients
|
|
selectedRecipients = data.recipients || [];
|
|
selectedCcRecipients = data.cc_recipients || [];
|
|
selectedBccRecipients = data.bcc_recipients || [];
|
|
|
|
updateRecipientDisplay('selectedRecipients', selectedRecipients);
|
|
updateRecipientDisplay('selectedCcRecipients', selectedCcRecipients);
|
|
updateRecipientDisplay('selectedBccRecipients', selectedBccRecipients);
|
|
}
|
|
})
|
|
.catch(error => console.error('Error loading draft:', error));
|
|
}
|
|
|
|
// Form submission handler
|
|
document.getElementById('composeForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
// Validate recipients
|
|
if (selectedRecipients.length === 0) {
|
|
alert('Please add at least one recipient.');
|
|
return;
|
|
}
|
|
|
|
// Prepare form data
|
|
const formData = new FormData(this);
|
|
formData.set('recipients', JSON.stringify(selectedRecipients));
|
|
formData.set('cc_recipients', JSON.stringify(selectedCcRecipients));
|
|
formData.set('bcc_recipients', JSON.stringify(selectedBccRecipients));
|
|
|
|
// Add attachments
|
|
attachments.forEach((file, index) => {
|
|
formData.append(`attachment_${index}`, file);
|
|
});
|
|
|
|
// Submit form
|
|
fetch('{% url "communications:message_compose" %}', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.action === 'send') {
|
|
showToast('Success', 'Message sent successfully', 'success');
|
|
setTimeout(() => {
|
|
window.location.href = '{% url "communications:message_sent" %}';
|
|
}, 1500);
|
|
} else {
|
|
showToast('Success', 'Draft saved successfully', 'success');
|
|
}
|
|
} else {
|
|
showToast('Error', data.error || 'Failed to process message', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error submitting form:', error);
|
|
showToast('Error', 'Failed to process message', 'error');
|
|
});
|
|
});
|
|
|
|
function showToast(title, message, type) {
|
|
// Implementation depends on your toast system
|
|
console.log(`${type.toUpperCase()}: ${title} - ${message}`);
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|