503 lines
30 KiB
HTML
503 lines
30 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Broadcast Message{% endblock %}
|
|
|
|
{% block css %}
|
|
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'assets/plugins/summernote/dist/summernote-lite.min.css' %}" rel="stylesheet" />
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div id="content" class="app-content">
|
|
<div class="container">
|
|
<div class="row justify-content-center">
|
|
<div class="col-xl-10">
|
|
<ul class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:message_list' %}">Messages</a></li>
|
|
<li class="breadcrumb-item active">Broadcast</li>
|
|
</ul>
|
|
|
|
<div class="row align-items-center mb-3">
|
|
<div class="col">
|
|
<h1 class="page-header">Broadcast Message</h1>
|
|
<p class="text-muted">Send important announcements and alerts to multiple recipients</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-primary" onclick="loadTemplate('emergency')">
|
|
<i class="fa fa-exclamation-triangle me-2"></i>Emergency
|
|
</button>
|
|
<button type="button" class="btn btn-outline-info" onclick="loadTemplate('announcement')">
|
|
<i class="fa fa-bullhorn me-2"></i>Announcement
|
|
</button>
|
|
<button type="button" class="btn btn-outline-success" onclick="loadTemplate('update')">
|
|
<i class="fa fa-info-circle me-2"></i>System Update
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Broadcast Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<div class="fs-20px fw-600 text-primary">{{ total_users }}</div>
|
|
<div class="text-muted small">Total Users</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<div class="fs-20px fw-600 text-success">{{ online_users }}</div>
|
|
<div class="text-muted small">Online Now</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<div class="fs-20px fw-600 text-info">{{ departments_count }}</div>
|
|
<div class="text-muted small">Departments</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center">
|
|
<div class="card-body">
|
|
<div class="fs-20px fw-600 text-warning">{{ recent_broadcasts }}</div>
|
|
<div class="text-muted small">Recent Broadcasts</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body">
|
|
{% if messages %}
|
|
{% for message in messages %}
|
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<form method="post" class="form-horizontal" enctype="multipart/form-data" id="broadcastForm">
|
|
{% csrf_token %}
|
|
|
|
<div class="row mb-4">
|
|
<label class="col-form-label col-md-2">Broadcast Type <span class="text-danger">*</span></label>
|
|
<div class="col-md-10">
|
|
<div class="btn-group w-100" role="group">
|
|
<input type="radio" class="btn-check" name="broadcast_type" id="type_announcement" value="announcement" checked>
|
|
<label class="btn btn-outline-primary" for="type_announcement">
|
|
<i class="fa fa-bullhorn me-2"></i>Announcement
|
|
</label>
|
|
|
|
<input type="radio" class="btn-check" name="broadcast_type" id="type_alert" value="alert">
|
|
<label class="btn btn-outline-warning" for="type_alert">
|
|
<i class="fa fa-exclamation-triangle me-2"></i>Alert
|
|
</label>
|
|
|
|
<input type="radio" class="btn-check" name="broadcast_type" id="type_emergency" value="emergency">
|
|
<label class="btn btn-outline-danger" for="type_emergency">
|
|
<i class="fa fa-exclamation-circle me-2"></i>Emergency
|
|
</label>
|
|
|
|
<input type="radio" class="btn-check" name="broadcast_type" id="type_maintenance" value="maintenance">
|
|
<label class="btn btn-outline-info" for="type_maintenance">
|
|
<i class="fa fa-wrench me-2"></i>Maintenance
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Priority Level <span class="text-danger">*</span></label>
|
|
<div class="col-md-10">
|
|
<select name="priority" class="form-select" required>
|
|
<option value="normal">Normal - Standard notification</option>
|
|
<option value="high">High - Important message</option>
|
|
<option value="urgent">Urgent - Immediate attention required</option>
|
|
<option value="critical">Critical - Emergency response needed</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Target Audience <span class="text-danger">*</span></label>
|
|
<div class="col-md-10">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="card-title mb-0">Departments</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="select_all_departments">
|
|
<label class="form-check-label fw-bold" for="select_all_departments">
|
|
Select All Departments
|
|
</label>
|
|
</div>
|
|
<hr>
|
|
{% for department in departments %}
|
|
<div class="form-check">
|
|
<input class="form-check-input department-check" type="checkbox"
|
|
name="departments" value="{{ department.id }}" id="dept_{{ department.id }}">
|
|
<label class="form-check-label" for="dept_{{ department.id }}">
|
|
{{ department.name }}
|
|
<small class="text-muted">({{ department.user_count }} users)</small>
|
|
</label>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="card-title mb-0">User Roles</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="select_all_roles">
|
|
<label class="form-check-label fw-bold" for="select_all_roles">
|
|
Select All Roles
|
|
</label>
|
|
</div>
|
|
<hr>
|
|
{% for role in user_roles %}
|
|
<div class="form-check">
|
|
<input class="form-check-input role-check" type="checkbox"
|
|
name="roles" value="{{ role.id }}" id="role_{{ role.id }}">
|
|
<label class="form-check-label" for="role_{{ role.id }}">
|
|
{{ role.name }}
|
|
<small class="text-muted">({{ role.user_count }} users)</small>
|
|
</label>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="include_all_staff" id="include_all_staff">
|
|
<label class="form-check-label" for="include_all_staff">
|
|
<strong>Broadcast to ALL Staff</strong>
|
|
<small class="text-muted d-block">Override selections and send to everyone</small>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Subject <span class="text-danger">*</span></label>
|
|
<div class="col-md-10">
|
|
<input type="text" name="subject" class="form-control" required
|
|
placeholder="Enter broadcast subject" maxlength="200">
|
|
<div class="form-text">Keep it concise and descriptive (max 200 characters)</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Message Content <span class="text-danger">*</span></label>
|
|
<div class="col-md-10">
|
|
<textarea name="content" id="broadcastContent" class="form-control" required></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Delivery Channels</label>
|
|
<div class="col-md-10">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="channels" value="in_app" id="channel_app" checked>
|
|
<label class="form-check-label" for="channel_app">
|
|
<i class="fa fa-bell text-primary me-2"></i>In-App Notification
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="channels" value="email" id="channel_email">
|
|
<label class="form-check-label" for="channel_email">
|
|
<i class="fa fa-envelope text-info me-2"></i>Email Notification
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="channels" value="sms" id="channel_sms">
|
|
<label class="form-check-label" for="channel_sms">
|
|
<i class="fa fa-mobile text-success me-2"></i>SMS Alert
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="channels" value="push" id="channel_push">
|
|
<label class="form-check-label" for="channel_push">
|
|
<i class="fa fa-mobile-alt text-warning me-2"></i>Push Notification
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Broadcast Schedule</label>
|
|
<div class="col-md-10">
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="radio" name="schedule_type" id="send_immediately" value="immediate" checked>
|
|
<label class="form-check-label" for="send_immediately">
|
|
Send Immediately
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="radio" name="schedule_type" id="schedule_broadcast" value="scheduled">
|
|
<label class="form-check-label" for="schedule_broadcast">
|
|
Schedule for Later
|
|
</label>
|
|
</div>
|
|
|
|
<div id="schedule_options" style="display: none;">
|
|
<div class="row mt-2">
|
|
<div class="col-md-6">
|
|
<input type="datetime-local" name="scheduled_at" class="form-control">
|
|
<small class="form-text text-muted">Select broadcast date and time</small>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<select name="timezone" class="form-select">
|
|
<option value="local">Local Time</option>
|
|
<option value="utc">UTC</option>
|
|
<option value="est">Eastern Time</option>
|
|
<option value="pst">Pacific Time</option>
|
|
</select>
|
|
<small class="form-text text-muted">Time zone</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Broadcast Options</label>
|
|
<div class="col-md-10">
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" name="require_acknowledgment" id="require_ack">
|
|
<label class="form-check-label" for="require_ack">
|
|
Require acknowledgment from recipients
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" name="track_delivery" id="track_delivery" checked>
|
|
<label class="form-check-label" for="track_delivery">
|
|
Track delivery and read status
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-2">
|
|
<input class="form-check-input" type="checkbox" name="persistent_notification" id="persistent_notif">
|
|
<label class="form-check-label" for="persistent_notif">
|
|
Keep notification visible until acknowledged
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="auto_expire" id="auto_expire">
|
|
<label class="form-check-label" for="auto_expire">
|
|
Auto-expire after 7 days
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<label class="col-form-label col-md-2">Attachments</label>
|
|
<div class="col-md-10">
|
|
<input type="file" name="attachments" class="form-control" multiple
|
|
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif">
|
|
<small class="form-text text-muted">
|
|
Attach relevant documents or images. Max 25MB total.
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipient Summary -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-10 offset-md-2">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Broadcast Summary</h6>
|
|
<div id="recipientSummary">
|
|
<div class="text-muted">Select departments or roles to see recipient count</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-10 offset-md-2">
|
|
<button type="submit" name="action" value="broadcast" class="btn btn-primary btn-lg">
|
|
<i class="fa fa-broadcast-tower me-2"></i>Send Broadcast
|
|
</button>
|
|
<button type="submit" name="action" value="preview" class="btn btn-info ms-2">
|
|
<i class="fa fa-eye me-2"></i>Preview
|
|
</button>
|
|
<button type="submit" name="action" value="draft" class="btn btn-secondary ms-2">
|
|
<i class="fa fa-save me-2"></i>Save Draft
|
|
</button>
|
|
<a href="{% url 'communications:message_list' %}" class="btn btn-outline-secondary ms-2">
|
|
<i class="fa fa-times me-2"></i>Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
|
|
<script src="{% static 'assets/plugins/summernote/dist/summernote-lite.min.js' %}"></script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize Summernote
|
|
$('#broadcastContent').summernote({
|
|
height: 250,
|
|
toolbar: [
|
|
['style', ['style']],
|
|
['font', ['bold', 'italic', 'underline', 'clear']],
|
|
['fontname', ['fontname']],
|
|
['color', ['color']],
|
|
['para', ['ul', 'ol', 'paragraph']],
|
|
['table', ['table']],
|
|
['insert', ['link']],
|
|
['view', ['fullscreen', 'codeview', 'help']]
|
|
]
|
|
});
|
|
|
|
// Handle schedule type changes
|
|
$('input[name="schedule_type"]').change(function() {
|
|
if ($(this).val() === 'scheduled') {
|
|
$('#schedule_options').show();
|
|
} else {
|
|
$('#schedule_options').hide();
|
|
}
|
|
});
|
|
|
|
// Handle select all checkboxes
|
|
$('#select_all_departments').change(function() {
|
|
$('.department-check').prop('checked', $(this).is(':checked'));
|
|
updateRecipientSummary();
|
|
});
|
|
|
|
$('#select_all_roles').change(function() {
|
|
$('.role-check').prop('checked', $(this).is(':checked'));
|
|
updateRecipientSummary();
|
|
});
|
|
|
|
// Handle individual checkbox changes
|
|
$('.department-check, .role-check').change(function() {
|
|
updateRecipientSummary();
|
|
});
|
|
|
|
// Handle include all staff
|
|
$('#include_all_staff').change(function() {
|
|
if ($(this).is(':checked')) {
|
|
$('.department-check, .role-check').prop('disabled', true);
|
|
$('#select_all_departments, #select_all_roles').prop('disabled', true);
|
|
} else {
|
|
$('.department-check, .role-check').prop('disabled', false);
|
|
$('#select_all_departments, #select_all_roles').prop('disabled', false);
|
|
}
|
|
updateRecipientSummary();
|
|
});
|
|
|
|
// Set default scheduled time
|
|
var now = new Date();
|
|
now.setHours(now.getHours() + 1);
|
|
var dateTimeString = now.toISOString().slice(0, 16);
|
|
$('input[name="scheduled_at"]').val(dateTimeString);
|
|
|
|
// Initial summary update
|
|
updateRecipientSummary();
|
|
});
|
|
|
|
function updateRecipientSummary() {
|
|
var summary = '';
|
|
var totalRecipients = 0;
|
|
|
|
if ($('#include_all_staff').is(':checked')) {
|
|
summary = '<div class="alert alert-info mb-0"><i class="fa fa-users me-2"></i><strong>Broadcasting to ALL STAFF</strong> ({{ total_users }} users)</div>';
|
|
totalRecipients = {{ total_users }};
|
|
} else {
|
|
var selectedDepts = $('.department-check:checked').length;
|
|
var selectedRoles = $('.role-check:checked').length;
|
|
|
|
if (selectedDepts === 0 && selectedRoles === 0) {
|
|
summary = '<div class="text-muted">No recipients selected</div>';
|
|
} else {
|
|
var parts = [];
|
|
if (selectedDepts > 0) {
|
|
parts.push(selectedDepts + ' department' + (selectedDepts > 1 ? 's' : ''));
|
|
}
|
|
if (selectedRoles > 0) {
|
|
parts.push(selectedRoles + ' role' + (selectedRoles > 1 ? 's' : ''));
|
|
}
|
|
|
|
// Calculate approximate recipient count (simplified)
|
|
totalRecipients = (selectedDepts * 15) + (selectedRoles * 8); // Rough estimate
|
|
|
|
summary = '<div class="text-success"><i class="fa fa-check me-2"></i>Broadcasting to ' + parts.join(' and ') + ' (~' + totalRecipients + ' users)</div>';
|
|
}
|
|
}
|
|
|
|
$('#recipientSummary').html(summary);
|
|
}
|
|
|
|
function loadTemplate(type) {
|
|
var templates = {
|
|
emergency: {
|
|
subject: 'EMERGENCY ALERT - Immediate Action Required',
|
|
content: '<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 5px;"><h4 style="color: #721c24;">🚨 EMERGENCY ALERT</h4><p>This is an emergency notification requiring immediate attention from all staff.</p><p><strong>Action Required:</strong> [Describe required action]</p><p><strong>Timeline:</strong> [Specify timeline]</p><p><strong>Contact:</strong> [Emergency contact information]</p></div>',
|
|
priority: 'critical',
|
|
type: 'emergency'
|
|
},
|
|
announcement: {
|
|
subject: 'Important Announcement - [Subject]',
|
|
content: '<h4>📢 Announcement</h4><p>We would like to inform all staff about the following:</p><p>[Your announcement content here]</p><p>If you have any questions, please contact [contact information].</p><p>Thank you for your attention.</p>',
|
|
priority: 'normal',
|
|
type: 'announcement'
|
|
},
|
|
update: {
|
|
subject: 'System Update Notification',
|
|
content: '<h4>🔄 System Update</h4><p>A system update has been scheduled with the following details:</p><ul><li><strong>Update Date:</strong> [Date and time]</li><li><strong>Expected Duration:</strong> [Duration]</li><li><strong>Affected Systems:</strong> [List systems]</li><li><strong>Impact:</strong> [Describe impact]</li></ul><p>Please plan accordingly and contact IT support if you have any concerns.</p>',
|
|
priority: 'high',
|
|
type: 'maintenance'
|
|
}
|
|
};
|
|
|
|
var template = templates[type];
|
|
if (template) {
|
|
$('input[name="subject"]').val(template.subject);
|
|
$('#broadcastContent').summernote('code', template.content);
|
|
$('select[name="priority"]').val(template.priority);
|
|
$('input[name="broadcast_type"][value="' + template.type + '"]').prop('checked', true);
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|