hospital-management/templates/communications/delivery_log_detail.html
2025-08-12 13:33:25 +03:00

576 lines
28 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Delivery Log - {{ object.subject|default:object.content|truncatechars:50 }}{% 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">Delivery Log Details</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"><a href="{% url 'communications:delivery_log_list' %}">Delivery Logs</a></li>
<li class="breadcrumb-item active">{{ object.id }}</li>
</ol>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Main Content -->
<div class="col-lg-8">
<!-- Message Overview -->
<div class="card">
<div class="card-header">
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<div class="avatar-sm me-3">
<div class="avatar-title bg-soft-{% if object.channel.channel_type == 'EMAIL' %}primary{% elif object.channel.channel_type == 'SMS' %}success{% elif object.channel.channel_type == 'PUSH' %}warning{% elif object.channel.channel_type == 'WEBHOOK' %}info{% elif object.channel.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} text-{% if object.channel.channel_type == 'EMAIL' %}primary{% elif object.channel.channel_type == 'SMS' %}success{% elif object.channel.channel_type == 'PUSH' %}warning{% elif object.channel.channel_type == 'WEBHOOK' %}info{% elif object.channel.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} rounded">
<i class="fas fa-{% if object.channel.channel_type == 'EMAIL' %}envelope{% elif object.channel.channel_type == 'SMS' %}sms{% elif object.channel.channel_type == 'PUSH' %}bell{% elif object.channel.channel_type == 'WEBHOOK' %}link{% elif object.channel.channel_type == 'SLACK' %}slack{% elif object.channel.channel_type == 'TEAMS' %}microsoft{% else %}broadcast-tower{% endif %}"></i>
</div>
</div>
<div>
<h5 class="card-title mb-0">{{ object.subject|default:"Message Content"|truncatechars:60 }}</h5>
<small class="text-muted">{{ object.get_message_type_display|default:"Communication" }}</small>
</div>
</div>
<div>
<span class="badge bg-{% if object.status == 'DELIVERED' %}success{% elif object.status == 'FAILED' %}danger{% elif object.status == 'PENDING' %}warning{% elif object.status == 'RETRYING' %}info{% else %}secondary{% endif %} fs-6">
{{ object.get_status_display }}
</span>
</div>
</div>
</div>
<div class="card-body">
<!-- Message Details -->
<div class="row mb-4">
<div class="col-md-6">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<i class="fas fa-user text-muted me-2"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">Recipient</h6>
<p class="text-muted mb-0">{{ object.recipient }}</p>
{% if object.recipient_name %}
<small class="text-muted">{{ object.recipient_name }}</small>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<i class="fas fa-broadcast-tower text-muted me-2"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">Channel</h6>
<p class="text-muted mb-0">
<a href="{% url 'communications:communication_channel_detail' object.channel.pk %}" class="text-primary">
{{ object.channel.name }}
</a>
</p>
<small class="text-muted">{{ object.channel.get_channel_type_display }}</small>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<i class="fas fa-clock text-muted me-2"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">Sent At</h6>
<p class="text-muted mb-0">{{ object.sent_at|date:"M d, Y g:i:s A" }}</p>
<small class="text-muted">{{ object.sent_at|timesince }} ago</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<i class="fas fa-tachometer-alt text-muted me-2"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">Delivery Time</h6>
{% if object.delivery_time %}
<p class="text-{% if object.delivery_time < 1000 %}success{% elif object.delivery_time < 5000 %}warning{% else %}danger{% endif %} mb-0">
{{ object.delivery_time }}ms
</p>
<small class="text-muted">
{% if object.delivery_time < 1000 %}Excellent{% elif object.delivery_time < 5000 %}Good{% else %}Slow{% endif %}
</small>
{% else %}
<p class="text-muted mb-0">-</p>
<small class="text-muted">Not available</small>
{% endif %}
</div>
</div>
</div>
</div>
{% if object.retry_count > 0 %}
<div class="row mb-4">
<div class="col-md-6">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<i class="fas fa-redo text-muted me-2"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">Retry Count</h6>
<p class="text-warning mb-0">{{ object.retry_count }} attempt(s)</p>
{% if object.next_retry_at %}
<small class="text-muted">Next retry: {{ object.next_retry_at|date:"M d, g:i A" }}</small>
{% endif %}
</div>
</div>
</div>
{% if object.last_retry_at %}
<div class="col-md-6">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<i class="fas fa-history text-muted me-2"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">Last Retry</h6>
<p class="text-muted mb-0">{{ object.last_retry_at|date:"M d, Y g:i:s A" }}</p>
<small class="text-muted">{{ object.last_retry_at|timesince }} ago</small>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
<!-- Message Content -->
{% if object.subject %}
<div class="mb-4">
<h6 class="text-muted mb-2">Subject</h6>
<div class="p-3 bg-light rounded">
{{ object.subject }}
</div>
</div>
{% endif %}
<div class="mb-4">
<h6 class="text-muted mb-2">Message Content</h6>
<div class="p-3 bg-light rounded" style="max-height: 300px; overflow-y: auto;">
{% if object.content_type == 'HTML' %}
<div class="border rounded p-2 mb-2">
<small class="text-muted">HTML Preview:</small>
<div class="mt-2">{{ object.content|safe }}</div>
</div>
<div class="border rounded p-2">
<small class="text-muted">Raw HTML:</small>
<pre class="mt-2"><code>{{ object.content|escape }}</code></pre>
</div>
{% else %}
<pre class="mb-0">{{ object.content }}</pre>
{% endif %}
</div>
</div>
<!-- Error Information -->
{% if object.error_message %}
<div class="mb-4">
<h6 class="text-danger mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>
Error Details
</h6>
<div class="alert alert-danger" role="alert">
<div class="d-flex">
<div class="flex-shrink-0">
<i class="fas fa-times-circle fa-lg"></i>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="alert-heading">Delivery Failed</h6>
<p class="mb-2">{{ object.error_message }}</p>
{% if object.error_code %}
<hr>
<div class="mb-0">
<small><strong>Error Code:</strong> {{ object.error_code }}</small>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
<!-- Metadata -->
{% if object.metadata %}
<div class="mb-4">
<h6 class="text-muted mb-2">Additional Metadata</h6>
<div class="p-3 bg-light rounded">
<pre class="mb-0"><code>{{ object.metadata|pprint }}</code></pre>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Delivery Timeline -->
{% if delivery_timeline %}
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-history me-2"></i>
Delivery Timeline
</h5>
</div>
<div class="card-body">
<div class="timeline">
{% for event in delivery_timeline %}
<div class="timeline-item">
<div class="timeline-marker bg-{% if event.status == 'DELIVERED' %}success{% elif event.status == 'FAILED' %}danger{% elif event.status == 'PENDING' %}warning{% elif event.status == 'RETRYING' %}info{% else %}secondary{% endif %}">
<i class="fas fa-{% if event.status == 'DELIVERED' %}check{% elif event.status == 'FAILED' %}times{% elif event.status == 'PENDING' %}clock{% elif event.status == 'RETRYING' %}redo{% else %}circle{% endif %}"></i>
</div>
<div class="timeline-content">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="mb-1">{{ event.get_status_display }}</h6>
<p class="text-muted mb-1">{{ event.description }}</p>
{% if event.error_message %}
<small class="text-danger">{{ event.error_message }}</small>
{% endif %}
</div>
<small class="text-muted">{{ event.timestamp|date:"M d, g:i:s A" }}</small>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-bolt me-2"></i>
Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if object.status == 'FAILED' %}
<button type="button" class="btn btn-warning" onclick="retryDelivery()">
<i class="fas fa-redo me-2"></i>
Retry Delivery
</button>
{% endif %}
{% if object.channel.channel_type == 'EMAIL' and object.status == 'DELIVERED' %}
<button type="button" class="btn btn-outline-primary" onclick="viewEmailContent()">
<i class="fas fa-envelope-open me-2"></i>
View Email Content
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary" onclick="exportLog()">
<i class="fas fa-download me-2"></i>
Export Log
</button>
<button type="button" class="btn btn-outline-info" onclick="copyMessageId()">
<i class="fas fa-copy me-2"></i>
Copy Message ID
</button>
{% if object.status == 'DELIVERED' %}
<button type="button" class="btn btn-outline-success" onclick="testSimilarMessage()">
<i class="fas fa-flask me-2"></i>
Test Similar Message
</button>
{% endif %}
</div>
</div>
</div>
<!-- Message Statistics -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>
Message Statistics
</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6">
<div class="p-2">
<h4 class="mb-1 text-primary">{{ message_stats.total_size|filesizeformat }}</h4>
<p class="text-muted mb-0">Message Size</p>
</div>
</div>
<div class="col-6">
<div class="p-2">
<h4 class="mb-1 text-info">{{ message_stats.attachments_count|default:0 }}</h4>
<p class="text-muted mb-0">Attachments</p>
</div>
</div>
</div>
{% if message_stats.delivery_metrics %}
<hr>
<div class="row text-center">
<div class="col-6">
<div class="p-2">
<h4 class="mb-1 text-success">{{ message_stats.delivery_metrics.queue_time }}ms</h4>
<p class="text-muted mb-0">Queue Time</p>
</div>
</div>
<div class="col-6">
<div class="p-2">
<h4 class="mb-1 text-warning">{{ message_stats.delivery_metrics.processing_time }}ms</h4>
<p class="text-muted mb-0">Processing Time</p>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Channel Information -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-broadcast-tower me-2"></i>
Channel Information
</h5>
</div>
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="avatar-sm me-3">
<div class="avatar-title bg-soft-{% if object.channel.channel_type == 'EMAIL' %}primary{% elif object.channel.channel_type == 'SMS' %}success{% elif object.channel.channel_type == 'PUSH' %}warning{% elif object.channel.channel_type == 'WEBHOOK' %}info{% elif object.channel.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} text-{% if object.channel.channel_type == 'EMAIL' %}primary{% elif object.channel.channel_type == 'SMS' %}success{% elif object.channel.channel_type == 'PUSH' %}warning{% elif object.channel.channel_type == 'WEBHOOK' %}info{% elif object.channel.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} rounded">
<i class="fas fa-{% if object.channel.channel_type == 'EMAIL' %}envelope{% elif object.channel.channel_type == 'SMS' %}sms{% elif object.channel.channel_type == 'PUSH' %}bell{% elif object.channel.channel_type == 'WEBHOOK' %}link{% elif object.channel.channel_type == 'SLACK' %}slack{% elif object.channel.channel_type == 'TEAMS' %}microsoft{% else %}broadcast-tower{% endif %}"></i>
</div>
</div>
<div>
<h6 class="mb-0">
<a href="{% url 'communications:communication_channel_detail' object.channel.pk %}" class="text-primary">
{{ object.channel.name }}
</a>
</h6>
<small class="text-muted">{{ object.channel.get_channel_type_display }}</small>
</div>
</div>
<div class="row text-center">
<div class="col-6">
<div class="p-2">
<h5 class="mb-1 text-success">{{ channel_stats.success_rate }}%</h5>
<p class="text-muted mb-0 small">Success Rate</p>
</div>
</div>
<div class="col-6">
<div class="p-2">
<h5 class="mb-1 text-info">{{ channel_stats.avg_delivery_time }}ms</h5>
<p class="text-muted mb-0 small">Avg Delivery</p>
</div>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-between align-items-center mb-1">
<small class="text-muted">Channel Health</small>
<small class="text-{% if object.channel.health_status == 'HEALTHY' %}success{% elif object.channel.health_status == 'WARNING' %}warning{% else %}danger{% endif %}">
{{ object.channel.get_health_status_display }}
</small>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-{% if object.channel.health_status == 'HEALTHY' %}success{% elif object.channel.health_status == 'WARNING' %}warning{% else %}danger{% endif %}"
style="width: {% if object.channel.health_status == 'HEALTHY' %}100{% elif object.channel.health_status == 'WARNING' %}60{% else %}20{% endif %}%"></div>
</div>
</div>
</div>
</div>
<!-- Related Messages -->
{% if related_messages %}
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-link me-2"></i>
Related Messages
</h5>
</div>
<div class="card-body">
{% for message in related_messages %}
<div class="d-flex align-items-center mb-2">
<div class="flex-shrink-0 me-2">
<span class="badge bg-{% if message.status == 'DELIVERED' %}success{% elif message.status == 'FAILED' %}danger{% elif message.status == 'PENDING' %}warning{% else %}secondary{% endif %} badge-sm">
{{ message.get_status_display }}
</span>
</div>
<div class="flex-grow-1">
<a href="{% url 'communications:delivery_log_detail' message.pk %}" class="text-dark text-decoration-none">
<small>{{ message.subject|default:message.content|truncatechars:30 }}</small>
</a>
<br><small class="text-muted">{{ message.sent_at|timesince }} ago</small>
</div>
</div>
{% endfor %}
{% if related_messages|length > 5 %}
<div class="text-center mt-2">
<a href="{% url 'communications:delivery_log_list' %}?recipient={{ object.recipient }}" class="btn btn-sm btn-outline-primary">
View All Messages to {{ object.recipient|truncatechars:20 }}
</a>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block css %}
<style>
.timeline {
position: relative;
padding-left: 30px;
}
.timeline::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background: #dee2e6;
}
.timeline-item {
position: relative;
margin-bottom: 20px;
}
.timeline-item:last-child::before {
display: none;
}
.timeline-marker {
position: absolute;
left: -22px;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
.timeline-content {
margin-left: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 3px solid #dee2e6;
}
.badge-sm {
font-size: 0.65rem;
padding: 0.2em 0.4em;
}
</style>
{% endblock %}
{% block js %}
<script>
function retryDelivery() {
if (confirm('Retry delivery for this message?')) {
fetch(`{% url 'communications:delivery_log_detail' object.pk %}retry/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Message queued for retry');
location.reload();
} else {
alert('Error retrying message: ' + data.error);
}
});
}
}
function viewEmailContent() {
window.open(`{% url 'communications:delivery_log_detail' object.pk %}email-content/`, '_blank');
}
function exportLog() {
window.open(`{% url 'communications:delivery_log_detail' object.pk %}export/`, '_blank');
}
function copyMessageId() {
const messageId = '{{ object.id }}';
navigator.clipboard.writeText(messageId).then(() => {
alert('Message ID copied to clipboard: ' + messageId);
}).catch(() => {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = messageId;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
alert('Message ID copied to clipboard: ' + messageId);
});
}
function testSimilarMessage() {
if (confirm('Create a test message with similar content and settings?')) {
fetch(`{% url 'communications:delivery_log_detail' object.pk %}test-similar/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Test message created and queued for delivery');
if (data.test_message_id) {
window.open(`/communications/delivery-logs/${data.test_message_id}/`, '_blank');
}
} else {
alert('Error creating test message: ' + data.error);
}
});
}
}
// Auto-refresh for pending/retrying messages
{% if object.status == 'PENDING' or object.status == 'RETRYING' %}
setTimeout(() => {
location.reload();
}, 10000); // Refresh every 10 seconds for pending messages
{% endif %}
</script>
{% endblock %}