2025-08-12 13:33:25 +03:00

521 lines
22 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Communications Dashboard - {{ 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">Communications Dashboard</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'accounts:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item active">Communications</li>
</ol>
</nav>
</div>
<div class="btn-group">
<a href="{% url 'communications:message_compose' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Compose Message
</a>
<button type="button" class="btn btn-outline-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'communications:broadcast_create' %}">
<i class="fas fa-bullhorn me-2"></i>Create Broadcast
</a></li>
<li><a class="dropdown-item" href="{% url 'communications:template_create' %}">
<i class="fas fa-file-alt me-2"></i>Create Template
</a></li>
<li><a class="dropdown-item" href="{% url 'communications:alert_rule_create' %}">
<i class="fas fa-exclamation-triangle me-2"></i>Create Alert Rule
</a></li>
</ul>
</div>
</div>
<!-- Quick Stats -->
<div class="row mb-4" hx-get="{% url 'communications:dashboard_stats' %}" hx-trigger="load, every 30s">
<div class="col-xl-3 col-md-6 mb-3">
<div class="card bg-gradient-primary text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="text-white-75 small">Unread Messages</div>
<div class="h4 mb-0" id="unread-messages">{{ stats.unread_messages|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-envelope fa-2x"></i>
</div>
</div>
</div>
<div class="card-footer d-flex align-items-center justify-content-between small">
<a class="text-white stretched-link" href="{% url 'communications:message_inbox' %}">View Inbox</a>
<div class="text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card bg-gradient-success text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="text-white-75 small">Messages Sent Today</div>
<div class="h4 mb-0" id="messages-sent-today">{{ stats.messages_sent_today|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-paper-plane fa-2x"></i>
</div>
</div>
</div>
<div class="card-footer d-flex align-items-center justify-content-between small">
<a class="text-white stretched-link" href="{% url 'communications:message_sent' %}">View Sent</a>
<div class="text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card bg-gradient-warning text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="text-white-75 small">Active Alerts</div>
<div class="h4 mb-0" id="active-alerts">{{ stats.active_alerts|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
<div class="card-footer d-flex align-items-center justify-content-between small">
<a class="text-white stretched-link" href="{% url 'communications:alert_list' %}">View Alerts</a>
<div class="text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card bg-gradient-info text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="text-white-75 small">Pending Notifications</div>
<div class="h4 mb-0" id="pending-notifications">{{ stats.pending_notifications|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-bell fa-2x"></i>
</div>
</div>
</div>
<div class="card-footer d-flex align-items-center justify-content-between small">
<a class="text-white stretched-link" href="{% url 'communications:notification_list' %}">View Notifications</a>
<div class="text-white"><i class="fas fa-angle-right"></i></div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Recent Messages -->
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-inbox me-2"></i>Recent Messages
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" hx-get="{% url 'communications:recent_messages' %}" hx-target="#recent-messages-list">
<i class="fas fa-sync-alt"></i>
</button>
<a href="{% url 'communications:message_inbox' %}" class="btn btn-outline-primary">
<i class="fas fa-external-link-alt"></i>
</a>
</div>
</div>
<div class="card-body p-0">
<div id="recent-messages-list" hx-get="{% url 'communications:recent_messages' %}" hx-trigger="load">
<div class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Communication Activity -->
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-chart-line me-2"></i>Communication Activity
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" onclick="refreshActivityChart()">
<i class="fas fa-sync-alt"></i>
</button>
<div class="dropdown">
<button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-calendar"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="updateActivityPeriod('7d')">Last 7 Days</a></li>
<li><a class="dropdown-item" href="#" onclick="updateActivityPeriod('30d')">Last 30 Days</a></li>
<li><a class="dropdown-item" href="#" onclick="updateActivityPeriod('90d')">Last 90 Days</a></li>
</ul>
</div>
</div>
</div>
<div class="card-body">
<canvas id="activityChart" width="100%" height="50"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Active Alerts -->
<div class="col-lg-8 mb-4">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>Active Alerts
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" hx-get="{% url 'communications:active_alerts' %}" hx-target="#active-alerts-list">
<i class="fas fa-sync-alt"></i>
</button>
<a href="{% url 'communications:alert_list' %}" class="btn btn-outline-primary">
<i class="fas fa-external-link-alt"></i>
</a>
</div>
</div>
<div class="card-body p-0">
<div id="active-alerts-list" hx-get="{% url 'communications:active_alerts' %}" hx-trigger="load">
<div class="text-center p-4">
<div class="spinner-border text-warning" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="col-lg-4 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-bolt me-2"></i>Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="{% url 'communications:message_compose' %}" class="btn btn-outline-primary">
<i class="fas fa-edit me-2"></i>Compose Message
</a>
<a href="{% url 'communications:broadcast_create' %}" class="btn btn-outline-success">
<i class="fas fa-bullhorn me-2"></i>Send Broadcast
</a>
<a href="{% url 'communications:template_list' %}" class="btn btn-outline-info">
<i class="fas fa-file-alt me-2"></i>Manage Templates
</a>
<a href="{% url 'communications:notification_settings' %}" class="btn btn-outline-warning">
<i class="fas fa-cog me-2"></i>Notification Settings
</a>
<a href="{% url 'communications:communication_log' %}" class="btn btn-outline-secondary">
<i class="fas fa-history me-2"></i>Communication Log
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Message Types Distribution -->
<div class="row">
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-chart-pie me-2"></i>Message Types Distribution
</h5>
</div>
<div class="card-body">
<canvas id="messageTypesChart" width="100%" height="50"></canvas>
</div>
</div>
</div>
<!-- Delivery Status Overview -->
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-chart-bar me-2"></i>Delivery Status Overview
</h5>
</div>
<div class="card-body">
<canvas id="deliveryStatusChart" width="100%" height="50"></canvas>
</div>
</div>
</div>
</div>
<!-- System Status -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-server me-2"></i>Communication System Status
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" hx-get="{% url 'communications:system_status' %}" hx-target="#system-status-content">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<div class="card-body">
<div id="system-status-content" hx-get="{% url 'communications:system_status' %}" hx-trigger="load, every 60s">
<div class="text-center p-4">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Chart.js configurations
let activityChart, messageTypesChart, deliveryStatusChart;
// Initialize charts when page loads
document.addEventListener('DOMContentLoaded', function() {
initializeCharts();
// Auto-refresh dashboard every 5 minutes
setInterval(function() {
refreshDashboard();
}, 300000);
});
function initializeCharts() {
// Activity Chart
const activityCtx = document.getElementById('activityChart').getContext('2d');
activityChart = new Chart(activityCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Messages Sent',
data: [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.1
}, {
label: 'Messages Received',
data: [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
// Message Types Chart
const messageTypesCtx = document.getElementById('messageTypesChart').getContext('2d');
messageTypesChart = new Chart(messageTypesCtx, {
type: 'doughnut',
data: {
labels: ['Email', 'SMS', 'Internal', 'Push', 'Slack'],
datasets: [{
data: [0, 0, 0, 0, 0],
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
// Delivery Status Chart
const deliveryStatusCtx = document.getElementById('deliveryStatusChart').getContext('2d');
deliveryStatusChart = new Chart(deliveryStatusCtx, {
type: 'bar',
data: {
labels: ['Sent', 'Delivered', 'Read', 'Failed'],
datasets: [{
label: 'Count',
data: [0, 0, 0, 0],
backgroundColor: [
'#28a745',
'#17a2b8',
'#6f42c1',
'#dc3545'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
// Load chart data
loadChartData();
}
function loadChartData() {
// Load activity chart data
fetch('{% url "communications:activity_chart_data" %}')
.then(response => response.json())
.then(data => {
activityChart.data.labels = data.labels;
activityChart.data.datasets[0].data = data.sent;
activityChart.data.datasets[1].data = data.received;
activityChart.update();
})
.catch(error => console.error('Error loading activity chart data:', error));
// Load message types chart data
fetch('{% url "communications:message_types_chart_data" %}')
.then(response => response.json())
.then(data => {
messageTypesChart.data.labels = data.labels;
messageTypesChart.data.datasets[0].data = data.data;
messageTypesChart.update();
})
.catch(error => console.error('Error loading message types chart data:', error));
// Load delivery status chart data
fetch('{% url "communications:delivery_status_chart_data" %}')
.then(response => response.json())
.then(data => {
deliveryStatusChart.data.labels = data.labels;
deliveryStatusChart.data.datasets[0].data = data.data;
deliveryStatusChart.update();
})
.catch(error => console.error('Error loading delivery status chart data:', error));
}
function refreshActivityChart() {
loadChartData();
}
function updateActivityPeriod(period) {
fetch(`{% url "communications:activity_chart_data" %}?period=${period}`)
.then(response => response.json())
.then(data => {
activityChart.data.labels = data.labels;
activityChart.data.datasets[0].data = data.sent;
activityChart.data.datasets[1].data = data.received;
activityChart.update();
})
.catch(error => console.error('Error updating activity chart:', error));
}
function refreshDashboard() {
// Trigger HTMX refresh for dynamic content
htmx.trigger('#recent-messages-list', 'refresh');
htmx.trigger('#active-alerts-list', 'refresh');
htmx.trigger('#system-status-content', 'refresh');
// Refresh charts
loadChartData();
}
// Real-time notifications
if (typeof EventSource !== "undefined") {
const eventSource = new EventSource('{% url "communications:sse_notifications" %}');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'new_message') {
// Update unread count
const unreadElement = document.getElementById('unread-messages');
if (unreadElement) {
unreadElement.textContent = parseInt(unreadElement.textContent) + 1;
}
// Refresh recent messages
htmx.trigger('#recent-messages-list', 'refresh');
// Show toast notification
showToast('New Message', data.message, 'info');
} else if (data.type === 'new_alert') {
// Update alert count
const alertElement = document.getElementById('active-alerts');
if (alertElement) {
alertElement.textContent = parseInt(alertElement.textContent) + 1;
}
// Refresh alerts list
htmx.trigger('#active-alerts-list', 'refresh');
// Show toast notification
showToast('New Alert', data.message, 'warning');
}
};
eventSource.onerror = function(event) {
console.error('SSE connection error:', event);
};
}
function showToast(title, message, type) {
// Create toast notification
const toastHtml = `
<div class="toast align-items-center text-white bg-${type === 'info' ? 'primary' : 'warning'} border-0" role="alert">
<div class="d-flex">
<div class="toast-body">
<strong>${title}</strong><br>
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`;
// Add to toast container (assuming it exists in base template)
const toastContainer = document.getElementById('toast-container');
if (toastContainer) {
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toast = new bootstrap.Toast(toastContainer.lastElementChild);
toast.show();
}
}
</script>
{% endblock %}