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

719 lines
32 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}{{ message.subject }} - {{ 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">Message Details</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'communications:dashboard' %}">Communications</a></li>
<li class="breadcrumb-item"><a href="{% url 'communications:message_inbox' %}">Messages</a></li>
<li class="breadcrumb-item active">{{ message.subject|truncatechars:30 }}</li>
</ol>
</nav>
</div>
<div class="btn-group">
<a href="{% url 'communications:message_inbox' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>Back to Inbox
</a>
<div class="dropdown">
<button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-2"></i>Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'communications:message_reply' message.message_id %}">
<i class="fas fa-reply me-2"></i>Reply
</a></li>
<li><a class="dropdown-item" href="{% url 'communications:message_forward' message.message_id %}">
<i class="fas fa-share me-2"></i>Forward
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="toggleStar()">
<i class="fas fa-star me-2"></i>{% if message.is_starred %}Unstar{% else %}Star{% endif %}
</a></li>
<li><a class="dropdown-item" href="#" onclick="markAsUnread()">
<i class="fas fa-envelope me-2"></i>Mark as Unread
</a></li>
<li><a class="dropdown-item" href="#" onclick="archiveMessage()">
<i class="fas fa-archive me-2"></i>Archive
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteMessage()">
<i class="fas fa-trash me-2"></i>Delete
</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
<!-- Main Message Content -->
<div class="col-lg-9">
<!-- Message Header -->
<div class="card mb-4">
<div class="card-header">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h4 class="mb-2">
{{ message.subject }}
{% if message.is_urgent %}
<span class="badge bg-danger ms-2">
<i class="fas fa-exclamation-triangle me-1"></i>Urgent
</span>
{% endif %}
{% if message.is_confidential %}
<span class="badge bg-warning ms-2">
<i class="fas fa-lock me-1"></i>Confidential
</span>
{% endif %}
{% if message.requires_acknowledgment %}
<span class="badge bg-info ms-2">
<i class="fas fa-check-circle me-1"></i>Requires Acknowledgment
</span>
{% endif %}
</h4>
<div class="row">
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<strong class="me-2">From:</strong>
<div class="d-flex align-items-center">
{% if message.sender.profile.avatar %}
<img src="{{ message.sender.profile.avatar.url }}" alt="{{ message.sender.get_full_name }}" class="rounded-circle me-2" width="24" height="24">
{% else %}
<i class="fas fa-user-circle fa-lg text-muted me-2"></i>
{% endif %}
<span>{{ message.sender.get_full_name }}</span>
<small class="text-muted ms-2">&lt;{{ message.sender.email }}&gt;</small>
</div>
</div>
<div class="d-flex align-items-center mb-2">
<strong class="me-2">To:</strong>
<div>
{% for recipient in message.recipients.all %}
{% if recipient.user %}
<span class="badge bg-light text-dark me-1">{{ recipient.user.get_full_name }}</span>
{% elif recipient.email_address %}
<span class="badge bg-light text-dark me-1">{{ recipient.email_address }}</span>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center mb-2">
<strong class="me-2">Date:</strong>
<span>{{ message.created_at|date:"M d, Y g:i A" }}</span>
</div>
<div class="d-flex align-items-center mb-2">
<strong class="me-2">Type:</strong>
<span class="badge bg-primary">{{ message.get_message_type_display }}</span>
</div>
<div class="d-flex align-items-center mb-2">
<strong class="me-2">Priority:</strong>
{% if message.priority == 'CRITICAL' %}
<span class="badge bg-danger">{{ message.get_priority_display }}</span>
{% elif message.priority == 'URGENT' %}
<span class="badge bg-warning">{{ message.get_priority_display }}</span>
{% elif message.priority == 'HIGH' %}
<span class="badge bg-info">{{ message.get_priority_display }}</span>
{% else %}
<span class="badge bg-secondary">{{ message.get_priority_display }}</span>
{% endif %}
</div>
</div>
</div>
</div>
<div class="text-end">
{% if message.is_starred %}
<i class="fas fa-star text-warning fa-lg" title="Starred"></i>
{% endif %}
{% if message.status == 'READ' %}
<i class="fas fa-envelope-open text-success fa-lg ms-2" title="Read"></i>
{% else %}
<i class="fas fa-envelope text-primary fa-lg ms-2" title="Unread"></i>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Message Content -->
<div class="card mb-4">
<div class="card-body">
<div class="message-content">
{{ message.content|safe }}
</div>
</div>
</div>
<!-- Attachments -->
{% if message.has_attachments %}
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-paperclip me-2"></i>Attachments
</h5>
</div>
<div class="card-body">
<div class="row" id="attachmentsList">
{% for attachment in message.attachments.all %}
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border">
<div class="card-body text-center">
<div class="mb-2">
{% if attachment.file_type == 'image' %}
<i class="fas fa-image fa-2x text-primary"></i>
{% elif attachment.file_type == 'pdf' %}
<i class="fas fa-file-pdf fa-2x text-danger"></i>
{% elif attachment.file_type == 'document' %}
<i class="fas fa-file-word fa-2x text-info"></i>
{% elif attachment.file_type == 'spreadsheet' %}
<i class="fas fa-file-excel fa-2x text-success"></i>
{% else %}
<i class="fas fa-file fa-2x text-muted"></i>
{% endif %}
</div>
<h6 class="card-title">{{ attachment.filename }}</h6>
<p class="card-text small text-muted">{{ attachment.file_size|filesizeformat }}</p>
<div class="btn-group btn-group-sm">
<a href="{{ attachment.file.url }}" class="btn btn-outline-primary" target="_blank">
<i class="fas fa-eye me-1"></i>View
</a>
<a href="{{ attachment.file.url }}" class="btn btn-outline-secondary" download>
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Acknowledgment Section -->
{% if message.requires_acknowledgment %}
<div class="card mb-4">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
<i class="fas fa-check-circle me-2"></i>Acknowledgment Required
</h5>
</div>
<div class="card-body">
{% if not user_acknowledgment %}
<div class="alert alert-warning">
<h6 class="alert-heading">Action Required</h6>
<p class="mb-3">This message requires your acknowledgment. Please confirm that you have read and understood the content.</p>
<button type="button" class="btn btn-success" onclick="acknowledgeMessage()">
<i class="fas fa-check me-2"></i>Acknowledge Message
</button>
</div>
{% else %}
<div class="alert alert-success">
<h6 class="alert-heading">Acknowledged</h6>
<p class="mb-0">You acknowledged this message on {{ user_acknowledgment.acknowledged_at|date:"M d, Y g:i A" }}.</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Thread/Conversation -->
{% if message.message_thread_id %}
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-comments me-2"></i>Conversation Thread
</h5>
</div>
<div class="card-body p-0">
<div id="conversationThread" hx-get="{% url 'communications:message_thread' message.message_thread_id %}" hx-trigger="load">
<div class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading conversation...</span>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Quick Reply -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-reply me-2"></i>Quick Reply
</h5>
</div>
<div class="card-body">
<form id="quickReplyForm" hx-post="{% url 'communications:message_quick_reply' message.message_id %}" hx-target="#quickReplyResult">
{% csrf_token %}
<div class="mb-3">
<textarea class="form-control" name="reply_content" rows="4" placeholder="Type your reply here..." required></textarea>
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="reply_to_all" id="replyToAll">
<label class="form-check-label" for="replyToAll">
Reply to all recipients
</label>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">
<i class="fas fa-reply me-2"></i>Send Reply
</button>
<a href="{% url 'communications:message_reply' message.message_id %}" class="btn btn-outline-primary">
<i class="fas fa-edit me-2"></i>Full Reply
</a>
</div>
</div>
</form>
<div id="quickReplyResult" class="mt-3"></div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-3">
<!-- Message Status -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-info-circle me-2"></i>Message Status
</h5>
</div>
<div class="card-body">
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Status:</span>
{% if message.status == 'SENT' %}
<span class="badge bg-success">{{ message.get_status_display }}</span>
{% elif message.status == 'DELIVERED' %}
<span class="badge bg-info">{{ message.get_status_display }}</span>
{% elif message.status == 'READ' %}
<span class="badge bg-primary">{{ message.get_status_display }}</span>
{% elif message.status == 'FAILED' %}
<span class="badge bg-danger">{{ message.get_status_display }}</span>
{% else %}
<span class="badge bg-secondary">{{ message.get_status_display }}</span>
{% endif %}
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Sent:</span>
<span>{{ message.sent_at|date:"M d, Y g:i A"|default:"Not sent" }}</span>
</div>
</div>
{% if message.scheduled_at %}
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Scheduled:</span>
<span>{{ message.scheduled_at|date:"M d, Y g:i A" }}</span>
</div>
</div>
{% endif %}
{% if message.expires_at %}
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Expires:</span>
<span class="{% if message.is_expired %}text-danger{% endif %}">
{{ message.expires_at|date:"M d, Y g:i A" }}
</span>
</div>
</div>
{% endif %}
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Message ID:</span>
<span class="font-monospace small">{{ message.message_id|truncatechars:8 }}</span>
</div>
</div>
</div>
</div>
<!-- Delivery Status -->
{% if message.recipients.count > 1 %}
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-users me-2"></i>Delivery Status
</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
{% for recipient in message.recipients.all %}
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<div class="fw-bold">
{% if recipient.user %}
{{ recipient.user.get_full_name }}
{% else %}
{{ recipient.email_address }}
{% endif %}
</div>
<div class="small text-muted">
{% if recipient.read_at %}
Read: {{ recipient.read_at|date:"M d, g:i A" }}
{% elif recipient.delivered_at %}
Delivered: {{ recipient.delivered_at|date:"M d, g:i A" }}
{% elif recipient.sent_at %}
Sent: {{ recipient.sent_at|date:"M d, g:i A" }}
{% else %}
Pending
{% endif %}
</div>
</div>
<div>
{% if recipient.status == 'READ' %}
<i class="fas fa-envelope-open text-success"></i>
{% elif recipient.status == 'delivered' %}
<i class="fas fa-check text-info"></i>
{% elif recipient.status == 'sent' %}
<i class="fas fa-paper-plane text-primary"></i>
{% elif recipient.status == 'failed' %}
<i class="fas fa-exclamation-triangle text-danger"></i>
{% else %}
<i class="fas fa-clock text-muted"></i>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Related Messages -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-link me-2"></i>Related Messages
</h5>
</div>
<div class="card-body p-0">
<div id="relatedMessages" hx-get="{% url 'communications:related_messages' message.message_id %}" hx-trigger="load">
<div class="text-center p-3">
<div class="spinner-border spinner-border-sm text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="card">
<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_reply' message.message_id %}" class="btn btn-outline-primary">
<i class="fas fa-reply me-2"></i>Reply
</a>
<a href="{% url 'communications:message_forward' message.message_id %}" class="btn btn-outline-success">
<i class="fas fa-share me-2"></i>Forward
</a>
<button type="button" class="btn btn-outline-info" onclick="printMessage()">
<i class="fas fa-print me-2"></i>Print
</button>
<button type="button" class="btn btn-outline-warning" onclick="exportMessage()">
<i class="fas fa-download me-2"></i>Export
</button>
<button type="button" class="btn btn-outline-secondary" onclick="reportMessage()">
<i class="fas fa-flag me-2"></i>Report
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Acknowledgment Modal -->
<div class="modal fade" id="acknowledgmentModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Acknowledge Message</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>By acknowledging this message, you confirm that you have:</p>
<ul>
<li>Read and understood the content</li>
<li>Noted any required actions</li>
<li>Understood the implications</li>
</ul>
<div class="form-floating">
<textarea class="form-control" id="acknowledgmentNotes" placeholder="Optional notes..." style="height: 100px"></textarea>
<label for="acknowledgmentNotes">Optional Notes</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" onclick="confirmAcknowledgment()">
<i class="fas fa-check me-2"></i>Acknowledge
</button>
</div>
</div>
</div>
</div>
<script>
// Mark message as read when page loads
document.addEventListener('DOMContentLoaded', function() {
markAsRead();
});
function markAsRead() {
fetch('{% url "communications:mark_message_read" message.message_id %}', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken()
}
})
.catch(error => console.error('Error marking message as read:', error));
}
function markAsUnread() {
fetch('{% url "communications:mark_message_unread" message.message_id %}', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken()
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', 'Message marked as unread', 'success');
// Update UI
location.reload();
}
})
.catch(error => console.error('Error marking message as unread:', error));
}
function toggleStar() {
fetch('{% url "communications:toggle_message_star" message.message_id %}', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken()
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', data.starred ? 'Message starred' : 'Message unstarred', 'success');
// Update UI
location.reload();
}
})
.catch(error => console.error('Error toggling star:', error));
}
function archiveMessage() {
if (confirm('Are you sure you want to archive this message?')) {
fetch('{% url "communications:archive_message" message.message_id %}', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken()
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', 'Message archived', 'success');
setTimeout(() => {
window.location.href = '{% url "communications:message_inbox" %}';
}, 1500);
}
})
.catch(error => console.error('Error archiving message:', error));
}
}
function deleteMessage() {
if (confirm('Are you sure you want to delete this message? This action cannot be undone.')) {
fetch('{% url "communications:delete_message" message.message_id %}', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken()
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', 'Message deleted', 'success');
setTimeout(() => {
window.location.href = '{% url "communications:message_inbox" %}';
}, 1500);
}
})
.catch(error => console.error('Error deleting message:', error));
}
}
function acknowledgeMessage() {
const modal = new bootstrap.Modal(document.getElementById('acknowledgmentModal'));
modal.show();
}
function confirmAcknowledgment() {
const notes = document.getElementById('acknowledgmentNotes').value;
fetch('{% url "communications:acknowledge_message" message.message_id %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
notes: notes
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', 'Message acknowledged', 'success');
bootstrap.Modal.getInstance(document.getElementById('acknowledgmentModal')).hide();
// Reload page to update acknowledgment status
location.reload();
} else {
showToast('Error', data.error || 'Failed to acknowledge message', 'error');
}
})
.catch(error => {
console.error('Error acknowledging message:', error);
showToast('Error', 'Failed to acknowledge message', 'error');
});
}
function printMessage() {
const printWindow = window.open('', '_blank');
const messageContent = document.querySelector('.message-content').innerHTML;
const subject = '{{ message.subject|escapejs }}';
const sender = '{{ message.sender.get_full_name|escapejs }}';
const date = '{{ message.created_at|date:"M d, Y g:i A"|escapejs }}';
printWindow.document.write(`
<html>
<head>
<title>Print Message</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { border-bottom: 2px solid #ccc; padding-bottom: 10px; margin-bottom: 20px; }
.content { line-height: 1.6; }
</style>
</head>
<body>
<div class="header">
<h2>${subject}</h2>
<p><strong>From:</strong> ${sender}</p>
<p><strong>Date:</strong> ${date}</p>
</div>
<div class="content">
${messageContent}
</div>
</body>
</html>
`);
printWindow.document.close();
printWindow.print();
}
function exportMessage() {
const messageData = {
subject: '{{ message.subject|escapejs }}',
sender: '{{ message.sender.get_full_name|escapejs }}',
date: '{{ message.created_at|date:"M d, Y g:i A"|escapejs }}',
content: document.querySelector('.message-content').innerText
};
const dataStr = JSON.stringify(messageData, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});
const link = document.createElement('a');
link.href = URL.createObjectURL(dataBlob);
link.download = `message_${Date.now()}.json`;
link.click();
}
function reportMessage() {
if (confirm('Report this message as inappropriate or spam?')) {
fetch('{% url "communications:report_message" message.message_id %}', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken()
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', 'Message reported', 'success');
} else {
showToast('Error', data.error || 'Failed to report message', 'error');
}
})
.catch(error => {
console.error('Error reporting message:', error);
showToast('Error', 'Failed to report message', 'error');
});
}
}
function getCsrfToken() {
return document.querySelector('[name=csrfmiddlewaretoken]').value;
}
function showToast(title, message, type) {
// Implementation depends on your toast system
console.log(`${type.toUpperCase()}: ${title} - ${message}`);
}
// Auto-refresh delivery status every 30 seconds
setInterval(function() {
htmx.trigger('#relatedMessages', 'refresh');
}, 30000);
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
switch (e.key) {
case 'r':
window.location.href = '{% url "communications:message_reply" message.message_id %}';
break;
case 'f':
window.location.href = '{% url "communications:message_forward" message.message_id %}';
break;
case 's':
toggleStar();
break;
case 'a':
archiveMessage();
break;
case 'Delete':
deleteMessage();
break;
case 'Escape':
window.location.href = '{% url "communications:message_inbox" %}';
break;
}
});
</script>
{% endblock %}