384 lines
20 KiB
HTML
384 lines
20 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Notification Templates - 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">Notification Templates</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 active">Templates</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Templates List -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="row align-items-center">
|
|
<div class="col">
|
|
<h4 class="card-title">Notification Templates</h4>
|
|
</div>
|
|
<div class="col-auto">
|
|
<a href="{% url 'communications:notification_template_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>
|
|
Create Template
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Search and Filters -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<div class="search-box">
|
|
<div class="position-relative">
|
|
<input type="text" class="form-control search"
|
|
placeholder="Search templates..."
|
|
id="templateSearch">
|
|
<i class="bx bx-search-alt search-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select class="form-select" id="categoryFilter">
|
|
<option value="">All Categories</option>
|
|
<option value="APPOINTMENT">Appointment</option>
|
|
<option value="REMINDER">Reminder</option>
|
|
<option value="ALERT">Alert</option>
|
|
<option value="NOTIFICATION">Notification</option>
|
|
<option value="SYSTEM">System</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select class="form-select" id="statusFilter">
|
|
<option value="">All Status</option>
|
|
<option value="true">Active</option>
|
|
<option value="false">Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" id="refreshTemplates">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="exportTemplates('json')">JSON</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportTemplates('csv')">CSV</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Templates Grid -->
|
|
<div class="row" id="templatesGrid">
|
|
{% for template in object_list %}
|
|
<div class="col-lg-4 col-md-6 mb-4">
|
|
<div class="card template-card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-sm me-2">
|
|
<div class="avatar-title bg-{% if template.category == 'APPOINTMENT' %}primary{% elif template.category == 'REMINDER' %}warning{% elif template.category == 'ALERT' %}danger{% else %}info{% endif %} text-white rounded">
|
|
<i class="fas fa-{% if template.category == 'APPOINTMENT' %}calendar{% elif template.category == 'REMINDER' %}bell{% elif template.category == 'ALERT' %}exclamation-triangle{% else %}envelope{% endif %}"></i>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-0">{{ template.template_name|truncatechars:20 }}</h6>
|
|
<small class="text-muted">{{ template.get_category_display }}</small>
|
|
</div>
|
|
</div>
|
|
<div class="dropdown">
|
|
<a href="#" class="dropdown-toggle btn btn-light btn-sm" data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</a>
|
|
<div class="dropdown-menu dropdown-menu-end">
|
|
<a class="dropdown-item" href="{% url 'communications:notification_template_detail' template.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View
|
|
</a>
|
|
<a class="dropdown-item" href="{% url 'communications:notification_template_update' template.pk %}">
|
|
<i class="fas fa-edit me-2"></i>Edit
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="duplicateTemplate({{ template.id }})">
|
|
<i class="fas fa-copy me-2"></i>Duplicate
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="previewTemplate({{ template.id }})">
|
|
<i class="fas fa-eye me-2"></i>Preview
|
|
</a>
|
|
<div class="dropdown-divider"></div>
|
|
{% if template.is_active %}
|
|
<a class="dropdown-item text-warning" href="#" onclick="toggleTemplate({{ template.id }}, false)">
|
|
<i class="fas fa-pause me-2"></i>Deactivate
|
|
</a>
|
|
{% else %}
|
|
<a class="dropdown-item text-success" href="#" onclick="toggleTemplate({{ template.id }}, true)">
|
|
<i class="fas fa-play me-2"></i>Activate
|
|
</a>
|
|
{% endif %}
|
|
<a class="dropdown-item text-danger" href="{% url 'communications:notification_template_delete' template.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Template Status -->
|
|
<div class="mb-2">
|
|
<span class="badge bg-{% if template.is_active %}success{% else %}secondary{% endif %} me-2">
|
|
{% if template.is_active %}Active{% else %}Inactive{% endif %}
|
|
</span>
|
|
<span class="badge bg-info">
|
|
{{ template.get_message_type_display }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Template Description -->
|
|
<p class="text-muted mb-3">
|
|
{{ template.description|truncatechars:100|default:"No description provided" }}
|
|
</p>
|
|
|
|
<!-- Template Subject -->
|
|
<div class="mb-2">
|
|
<small class="text-muted">Subject:</small>
|
|
<p class="mb-1 fw-medium">{{ template.subject_template|truncatechars:50 }}</p>
|
|
</div>
|
|
|
|
<!-- Template Body Preview -->
|
|
<div class="mb-3">
|
|
<small class="text-muted">Content Preview:</small>
|
|
<div class="bg-light p-2 rounded small">
|
|
{{ template.body_template|truncatechars:120|linebreaks }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Usage Statistics -->
|
|
<div class="row text-center">
|
|
<div class="col-4">
|
|
<div class="border-end">
|
|
<h6 class="mb-0 text-primary">{{ template.usage_count|default:0 }}</h6>
|
|
<small class="text-muted">Used</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<div class="border-end">
|
|
<h6 class="mb-0 text-success">{{ template.variables.count|default:0 }}</h6>
|
|
<small class="text-muted">Variables</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<h6 class="mb-0 text-info">{{ template.languages.count|default:1 }}</h6>
|
|
<small class="text-muted">Languages</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer bg-transparent">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<small class="text-muted">
|
|
Updated {{ template.updated_at|timesince }} ago
|
|
</small>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a href="{% url 'communications:notification_template_detail' template.pk %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'communications:notification_template_update' template.pk %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<button class="btn btn-outline-info" onclick="useTemplate({{ template.id }})">
|
|
<i class="fas fa-paper-plane"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<div class="d-flex flex-column align-items-center">
|
|
<i class="fas fa-file-alt fa-4x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No templates found</h5>
|
|
<p class="text-muted">Create your first notification template to get started</p>
|
|
<a href="{% url 'communications:notification_template_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create Template
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="row">
|
|
<div class="col-lg-12">
|
|
<ul class="pagination pagination-rounded justify-content-center mt-3 mb-4 pb-1">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">
|
|
<i class="mdi mdi-chevron-left"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a href="?page={{ num }}" class="page-link">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a href="?page={{ page_obj.next_page_number }}" class="page-link">
|
|
<i class="mdi mdi-chevron-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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="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" id="useTemplateBtn">Use Template</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
// Search functionality
|
|
document.getElementById('templateSearch').addEventListener('input', function() {
|
|
const searchTerm = this.value.toLowerCase();
|
|
const cards = document.querySelectorAll('.template-card');
|
|
|
|
cards.forEach(card => {
|
|
const text = card.textContent.toLowerCase();
|
|
const cardContainer = card.closest('.col-lg-4');
|
|
cardContainer.style.display = text.includes(searchTerm) ? 'block' : 'none';
|
|
});
|
|
});
|
|
|
|
// Filter functionality
|
|
document.getElementById('categoryFilter').addEventListener('change', filterTemplates);
|
|
document.getElementById('statusFilter').addEventListener('change', filterTemplates);
|
|
|
|
function filterTemplates() {
|
|
const category = document.getElementById('categoryFilter').value;
|
|
const status = document.getElementById('statusFilter').value;
|
|
const cards = document.querySelectorAll('.template-card');
|
|
|
|
cards.forEach(card => {
|
|
let show = true;
|
|
|
|
if (category) {
|
|
const cardCategory = card.querySelector('.text-muted').textContent.trim();
|
|
show = show && cardCategory.toLowerCase().includes(category.toLowerCase());
|
|
}
|
|
|
|
if (status !== '') {
|
|
const isActive = card.querySelector('.badge').textContent.includes('Active');
|
|
show = show && ((status === 'true' && isActive) || (status === 'false' && !isActive));
|
|
}
|
|
|
|
const cardContainer = card.closest('.col-lg-4');
|
|
cardContainer.style.display = show ? 'block' : 'none';
|
|
});
|
|
}
|
|
|
|
// Template actions
|
|
function duplicateTemplate(templateId) {
|
|
if (confirm('Create a copy of this template?')) {
|
|
window.location.href = `/communications/templates/create/?duplicate=${templateId}`;
|
|
}
|
|
}
|
|
|
|
function previewTemplate(templateId) {
|
|
fetch(`/communications/templates/${templateId}/preview/`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('previewContent').innerHTML = data.html;
|
|
document.getElementById('useTemplateBtn').onclick = () => useTemplate(templateId);
|
|
new bootstrap.Modal(document.getElementById('previewModal')).show();
|
|
} else {
|
|
alert('Error loading preview: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function toggleTemplate(templateId, activate) {
|
|
const action = activate ? 'activate' : 'deactivate';
|
|
if (confirm(`${action.charAt(0).toUpperCase() + action.slice(1)} this template?`)) {
|
|
fetch(`/communications/templates/${templateId}/toggle/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ active: activate })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating template: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function useTemplate(templateId) {
|
|
window.location.href = `/communications/messages/create/?template=${templateId}`;
|
|
}
|
|
|
|
function exportTemplates(format) {
|
|
window.open(`/communications/export/templates/?format=${format}`, '_blank');
|
|
}
|
|
|
|
// Refresh templates
|
|
document.getElementById('refreshTemplates').addEventListener('click', function() {
|
|
location.reload();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|