320 lines
13 KiB
HTML
320 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n static %}
|
|
|
|
{% block title %}{% trans "Notifications Dashboard" %} - Tenhal{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.stat-card {
|
|
border-left: 4px solid;
|
|
transition: transform 0.2s;
|
|
}
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
.stat-card.primary { border-left-color: #0d6efd; }
|
|
.stat-card.success { border-left-color: #198754; }
|
|
.stat-card.danger { border-left-color: #dc3545; }
|
|
.stat-card.warning { border-left-color: #ffc107; }
|
|
.stat-card.info { border-left-color: #0dcaf0; }
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 300px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h1 class="page-header mb-0">
|
|
<i class="fas fa-bell me-2"></i>{% trans "Notifications Dashboard" %}
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">{% trans "Dashboard" %}</a></li>
|
|
<li class="breadcrumb-item active">{% trans "Notifications" %}</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div>
|
|
<a href="{% url 'notifications:message_list' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-list me-1"></i>{% trans "All Messages" %}
|
|
</a>
|
|
<a href="{% url 'notifications:template_list' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-file-alt me-1"></i>{% trans "Templates" %}
|
|
</a>
|
|
<a href="{% url 'notifications:bulk_message' %}" class="btn btn-primary">
|
|
<i class="fas fa-paper-plane me-1"></i>{% trans "Send Bulk Message" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Time Range Filter -->
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3 align-items-end">
|
|
<div class="col-auto">
|
|
<label class="form-label">{% trans "Time Range" %}</label>
|
|
<select name="days" class="form-select" onchange="this.form.submit()">
|
|
<option value="7" {% if days == 7 %}selected{% endif %}>{% trans "Last 7 Days" %}</option>
|
|
<option value="30" {% if days == 30 %}selected{% endif %}>{% trans "Last 30 Days" %}</option>
|
|
<option value="90" {% if days == 90 %}selected{% endif %}>{% trans "Last 90 Days" %}</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row g-3 mb-4">
|
|
<!-- Total Messages -->
|
|
<div class="col-md-3">
|
|
<div class="card stat-card primary h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="text-muted mb-2">{% trans "Total Messages" %}</h6>
|
|
<h2 class="mb-0">{{ stats.total|default:0 }}</h2>
|
|
</div>
|
|
<div class="text-primary">
|
|
<i class="fas fa-envelope fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delivered -->
|
|
<div class="col-md-3">
|
|
<div class="card stat-card success h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="text-muted mb-2">{% trans "Delivered" %}</h6>
|
|
<h2 class="mb-0">{{ stats.delivered|default:0 }}</h2>
|
|
<small class="text-success">
|
|
<i class="fas fa-arrow-up"></i> {{ stats.success_rate }}%
|
|
</small>
|
|
</div>
|
|
<div class="text-success">
|
|
<i class="fas fa-check-circle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Failed -->
|
|
<div class="col-md-3">
|
|
<div class="card stat-card danger h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="text-muted mb-2">{% trans "Failed" %}</h6>
|
|
<h2 class="mb-0">{{ stats.failed|default:0 }}</h2>
|
|
{% if failed_messages_count > 0 %}
|
|
<small class="text-danger">
|
|
{{ failed_messages_count }} {% trans "can retry" %}
|
|
</small>
|
|
{% endif %}
|
|
</div>
|
|
<div class="text-danger">
|
|
<i class="fas fa-exclamation-circle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Queued -->
|
|
<div class="col-md-3">
|
|
<div class="card stat-card warning h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="text-muted mb-2">{% trans "Queued" %}</h6>
|
|
<h2 class="mb-0">{{ stats.queued|default:0 }}</h2>
|
|
</div>
|
|
<div class="text-warning">
|
|
<i class="fas fa-clock fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<!-- Channel Breakdown -->
|
|
<div class="col-md-4">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">{% trans "By Channel" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="list-group list-group-flush">
|
|
{% for channel, count in stats.by_channel.items %}
|
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span>
|
|
{% if channel == 'SMS' %}
|
|
<i class="fas fa-sms text-primary"></i>
|
|
{% elif channel == 'WHATSAPP' %}
|
|
<i class="fab fa-whatsapp text-success"></i>
|
|
{% elif channel == 'EMAIL' %}
|
|
<i class="fas fa-envelope text-info"></i>
|
|
{% endif %}
|
|
{{ channel }}
|
|
</span>
|
|
<span class="badge bg-secondary rounded-pill">{{ count }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Daily Trend Chart -->
|
|
<div class="col-md-8">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">{% trans "Daily Trend" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="chart-container">
|
|
<canvas id="dailyTrendChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Messages -->
|
|
<div class="card mt-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">{% trans "Recent Messages" %}</h5>
|
|
<a href="{% url 'notifications:message_list' %}" class="btn btn-sm btn-outline-primary">
|
|
{% trans "View All" %}
|
|
</a>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "Date" %}</th>
|
|
<th>{% trans "Channel" %}</th>
|
|
<th>{% trans "Recipient" %}</th>
|
|
<th>{% trans "Template" %}</th>
|
|
<th>{% trans "Status" %}</th>
|
|
<th>{% trans "Actions" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for message in recent_messages %}
|
|
<tr>
|
|
<td>{{ message.created_at|date:"Y-m-d H:i" }}</td>
|
|
<td>
|
|
{% if message.channel == 'SMS' %}
|
|
<span class="badge bg-primary">SMS</span>
|
|
{% elif message.channel == 'WHATSAPP' %}
|
|
<span class="badge bg-success">WhatsApp</span>
|
|
{% elif message.channel == 'EMAIL' %}
|
|
<span class="badge bg-info">Email</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ message.recipient }}</td>
|
|
<td>{{ message.template.name|default:"-" }}</td>
|
|
<td>
|
|
{% if message.status == 'DELIVERED' or message.status == 'READ' %}
|
|
<span class="badge bg-success">{{ message.get_status_display }}</span>
|
|
{% elif message.status == 'FAILED' %}
|
|
<span class="badge bg-danger">{{ message.get_status_display }}</span>
|
|
{% elif message.status == 'QUEUED' %}
|
|
<span class="badge bg-warning">{{ message.get_status_display }}</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ message.get_status_display }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'notifications:message_detail' message.pk %}"
|
|
class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="6" class="text-center text-muted">
|
|
{% trans "No messages found" %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
// Daily Trend Chart
|
|
const dailyTrendCtx = document.getElementById('dailyTrendChart').getContext('2d');
|
|
const dailyTrendData = {{ stats.daily_stats|safe }};
|
|
|
|
new Chart(dailyTrendCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: dailyTrendData.map(d => d.date),
|
|
datasets: [
|
|
{
|
|
label: '{% trans "Total" %}',
|
|
data: dailyTrendData.map(d => d.total),
|
|
borderColor: '#0d6efd',
|
|
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
|
tension: 0.4
|
|
},
|
|
{
|
|
label: '{% trans "Delivered" %}',
|
|
data: dailyTrendData.map(d => d.delivered),
|
|
borderColor: '#198754',
|
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
|
tension: 0.4
|
|
},
|
|
{
|
|
label: '{% trans "Failed" %}',
|
|
data: dailyTrendData.map(d => d.failed),
|
|
borderColor: '#dc3545',
|
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
|
tension: 0.4
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
stepSize: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|