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

1069 lines
42 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Communication History - {{ 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">Communication History</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 active">History</li>
</ol>
</nav>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-primary" onclick="exportHistory()">
<i class="fas fa-download me-2"></i>Export History
</button>
<button type="button" class="btn btn-outline-success" onclick="generateReport()">
<i class="fas fa-chart-line me-2"></i>Generate Report
</button>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-2"></i>Options
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showTrendAnalysis()">
<i class="fas fa-trending-up me-2"></i>Trend Analysis
</a></li>
<li><a class="dropdown-item" href="#" onclick="showPatternAnalysis()">
<i class="fas fa-search me-2"></i>Pattern Analysis
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="scheduleReport()">
<i class="fas fa-calendar me-2"></i>Schedule Report
</a></li>
</ul>
</div>
</div>
</div>
<!-- Summary Cards -->
<div class="row mb-4" hx-get="{% url 'communications:history_stats' %}" hx-trigger="load, every 300s">
<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">Total Communications</div>
<div class="h4 mb-0" id="total-communications">{{ stats.total|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-comments fa-2x"></i>
</div>
</div>
<div class="mt-2">
<small class="text-white-75">
<i class="fas fa-calendar me-1"></i>
All time
</small>
</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">This Month</div>
<div class="h4 mb-0" id="month-communications">{{ stats.this_month|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-calendar-alt fa-2x"></i>
</div>
</div>
<div class="mt-2">
<small class="text-white-75">
<i class="fas fa-arrow-up me-1"></i>
{{ stats.month_change|default:0 }}% from last month
</small>
</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">Active Conversations</div>
<div class="h4 mb-0" id="active-conversations">{{ stats.active_conversations|default:0 }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-comment-dots fa-2x"></i>
</div>
</div>
<div class="mt-2">
<small class="text-white-75">
Last 7 days
</small>
</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">Average Response Time</div>
<div class="h4 mb-0" id="avg-response-time">{{ stats.avg_response_time|default:"0h" }}</div>
</div>
<div class="text-white-50">
<i class="fas fa-clock fa-2x"></i>
</div>
</div>
<div class="mt-2">
<small class="text-white-75">
Last 30 days
</small>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Filters and Timeline -->
<div class="col-lg-3 mb-4">
<!-- Filters -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filters
</h5>
</div>
<div class="card-body">
<!-- Date Range -->
<div class="mb-3">
<label class="form-label fw-bold">Date Range</label>
<select class="form-select" id="dateRangeFilter" onchange="applyFilters()">
<option value="all">All Time</option>
<option value="today">Today</option>
<option value="week" selected>This Week</option>
<option value="month">This Month</option>
<option value="quarter">This Quarter</option>
<option value="year">This Year</option>
<option value="custom">Custom Range</option>
</select>
</div>
<!-- Custom Date Range -->
<div id="customDateRange" style="display: none;">
<div class="mb-2">
<label class="form-label small">From</label>
<input type="date" class="form-control form-control-sm" id="dateFrom" onchange="applyFilters()">
</div>
<div class="mb-3">
<label class="form-label small">To</label>
<input type="date" class="form-control form-control-sm" id="dateTo" onchange="applyFilters()">
</div>
</div>
<!-- Communication Type -->
<div class="mb-3">
<label class="form-label fw-bold">Type</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="typeEmail" value="EMAIL" checked onchange="applyFilters()">
<label class="form-check-label" for="typeEmail">
<i class="fas fa-envelope me-2"></i>Email
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="typeSMS" value="SMS" checked onchange="applyFilters()">
<label class="form-check-label" for="typeSMS">
<i class="fas fa-sms me-2"></i>SMS
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="typeMessage" value="MESSAGE" checked onchange="applyFilters()">
<label class="form-check-label" for="typeMessage">
<i class="fas fa-comment me-2"></i>Message
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="typeNotification" value="NOTIFICATION" checked onchange="applyFilters()">
<label class="form-check-label" for="typeNotification">
<i class="fas fa-bell me-2"></i>Notification
</label>
</div>
</div>
<!-- Participants -->
<div class="mb-3">
<label class="form-label fw-bold">Participants</label>
<select class="form-select" id="participantFilter" onchange="applyFilters()">
<option value="">All Participants</option>
<!-- Options will be populated dynamically -->
</select>
</div>
<!-- Department -->
<div class="mb-3">
<label class="form-label fw-bold">Department</label>
<select class="form-select" id="departmentFilter" onchange="applyFilters()">
<option value="">All Departments</option>
<option value="EMERGENCY">Emergency</option>
<option value="CARDIOLOGY">Cardiology</option>
<option value="NEUROLOGY">Neurology</option>
<option value="PEDIATRICS">Pediatrics</option>
<option value="SURGERY">Surgery</option>
<option value="RADIOLOGY">Radiology</option>
<option value="LABORATORY">Laboratory</option>
<option value="PHARMACY">Pharmacy</option>
<option value="ADMINISTRATION">Administration</option>
</select>
</div>
<!-- Status -->
<div class="mb-3">
<label class="form-label fw-bold">Status</label>
<select class="form-select" id="statusFilter" onchange="applyFilters()">
<option value="">All Statuses</option>
<option value="ACTIVE">Active</option>
<option value="COMPLETED">Completed</option>
<option value="ARCHIVED">Archived</option>
</select>
</div>
<!-- Clear Filters -->
<button type="button" class="btn btn-outline-secondary btn-sm w-100" onclick="clearFilters()">
<i class="fas fa-times me-2"></i>Clear All Filters
</button>
</div>
</div>
<!-- Timeline View Toggle -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-eye me-2"></i>View Options
</h5>
</div>
<div class="card-body">
<div class="btn-group w-100 mb-3" role="group">
<input type="radio" class="btn-check" name="viewType" id="listView" value="list" checked onchange="changeView('list')">
<label class="btn btn-outline-primary" for="listView">
<i class="fas fa-list me-1"></i>List
</label>
<input type="radio" class="btn-check" name="viewType" id="timelineView" value="timeline" onchange="changeView('timeline')">
<label class="btn btn-outline-primary" for="timelineView">
<i class="fas fa-stream me-1"></i>Timeline
</label>
<input type="radio" class="btn-check" name="viewType" id="conversationView" value="conversation" onchange="changeView('conversation')">
<label class="btn btn-outline-primary" for="conversationView">
<i class="fas fa-comments me-1"></i>Chat
</label>
</div>
<div class="mb-3">
<label class="form-label small">Group By</label>
<select class="form-select form-select-sm" id="groupByFilter" onchange="applyFilters()">
<option value="date">Date</option>
<option value="participant">Participant</option>
<option value="department">Department</option>
<option value="type">Type</option>
</select>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="showArchived" onchange="applyFilters()">
<label class="form-check-label" for="showArchived">
Show Archived
</label>
</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="col-lg-9">
<!-- Search Bar -->
<div class="card mb-3">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search communications..." id="searchInput" onkeyup="searchHistory()">
<button class="btn btn-outline-secondary" type="button" onclick="searchHistory()">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="col-md-4 text-end">
<div class="btn-group">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="refreshHistory()">
<i class="fas fa-sync-alt me-1"></i>Refresh
</button>
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-sort me-1"></i>Sort
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="sortHistory('newest')">Newest First</a></li>
<li><a class="dropdown-item" href="#" onclick="sortHistory('oldest')">Oldest First</a></li>
<li><a class="dropdown-item" href="#" onclick="sortHistory('participant')">By Participant</a></li>
<li><a class="dropdown-item" href="#" onclick="sortHistory('type')">By Type</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Content Views -->
<div id="historyContent">
<!-- List View -->
<div id="listViewContent" class="view-content">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Communication History</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Date/Time</th>
<th>Type</th>
<th>Participants</th>
<th>Subject/Content</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="history-entries" hx-get="{% url 'communications:history_entries' %}" hx-trigger="load">
<tr>
<td colspan="6" class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading history...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted small">
Showing <span id="showing-count">0</span> of <span id="total-count">0</span> entries
</div>
<nav aria-label="History pagination">
<ul class="pagination pagination-sm mb-0" id="pagination">
<!-- Pagination will be loaded dynamically -->
</ul>
</nav>
</div>
</div>
</div>
</div>
<!-- Timeline View -->
<div id="timelineViewContent" class="view-content" style="display: none;">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Communication Timeline</h5>
</div>
<div class="card-body">
<div id="timeline-container" hx-get="{% url 'communications:history_timeline' %}" hx-trigger="load">
<div class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading timeline...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Conversation View -->
<div id="conversationViewContent" class="view-content" style="display: none;">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">Conversations</h6>
</div>
<div class="card-body p-0">
<div id="conversation-list" hx-get="{% url 'communications:conversation_list' %}" hx-trigger="load">
<div class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading conversations...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h6 class="mb-0" id="conversation-title">Select a conversation</h6>
</div>
<div class="card-body">
<div id="conversation-messages" class="conversation-messages">
<div class="text-center text-muted p-4">
<i class="fas fa-comments fa-3x mb-3"></i>
<p>Select a conversation to view messages</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Communication Detail Modal -->
<div class="modal fade" id="communicationDetailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Communication Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="communicationDetailContent">
<!-- Content will be loaded dynamically -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-primary" onclick="replyToCommunication()">
<i class="fas fa-reply me-2"></i>Reply
</button>
<button type="button" class="btn btn-outline-info" onclick="forwardCommunication()">
<i class="fas fa-share me-2"></i>Forward
</button>
</div>
</div>
</div>
</div>
<!-- Trend Analysis Modal -->
<div class="modal fade" id="trendAnalysisModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Communication Trends</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<canvas id="volumeTrendChart" width="400" height="200"></canvas>
</div>
<div class="col-md-6">
<canvas id="typeTrendChart" width="400" height="200"></canvas>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6">
<canvas id="responseTimeTrendChart" width="400" height="200"></canvas>
</div>
<div class="col-md-6">
<canvas id="departmentTrendChart" width="400" height="200"></canvas>
</div>
</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-outline-primary" onclick="exportTrendAnalysis()">
<i class="fas fa-download me-2"></i>Export Analysis
</button>
</div>
</div>
</div>
</div>
<!-- Pattern Analysis Modal -->
<div class="modal fade" id="patternAnalysisModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Communication Patterns</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="patternAnalysisContent">
<!-- Content will be loaded dynamically -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-primary" onclick="exportPatternAnalysis()">
<i class="fas fa-download me-2"></i>Export Patterns
</button>
</div>
</div>
</div>
</div>
<script>
let currentView = 'list';
let currentCommunicationId = null;
let currentConversationId = null;
// Initialize communication history page
document.addEventListener('DOMContentLoaded', function() {
loadHistoryEntries();
loadParticipantOptions();
setupDateRangeHandler();
});
function loadHistoryEntries(page = 1, filters = {}) {
const params = new URLSearchParams({
page: page,
view: currentView,
...filters
});
const targetSelector = currentView === 'list' ? '#history-entries' :
currentView === 'timeline' ? '#timeline-container' :
'#conversation-list';
htmx.ajax('GET', `{% url 'communications:history_entries' %}?${params}`, {
target: targetSelector,
swap: 'innerHTML'
});
}
function loadParticipantOptions() {
fetch('{% url "communications:participant_options" %}')
.then(response => response.json())
.then(data => {
const participantSelect = document.getElementById('participantFilter');
data.participants.forEach(participant => {
const option = document.createElement('option');
option.value = participant.id;
option.textContent = participant.name;
participantSelect.appendChild(option);
});
})
.catch(error => console.error('Error loading participant options:', error));
}
function setupDateRangeHandler() {
document.getElementById('dateRangeFilter').addEventListener('change', function() {
const customRange = document.getElementById('customDateRange');
if (this.value === 'custom') {
customRange.style.display = 'block';
} else {
customRange.style.display = 'none';
}
});
}
function changeView(viewType) {
currentView = viewType;
// Hide all view contents
document.querySelectorAll('.view-content').forEach(content => {
content.style.display = 'none';
});
// Show selected view
document.getElementById(viewType + 'ViewContent').style.display = 'block';
// Load content for the selected view
applyFilters();
}
function applyFilters() {
const filters = {
date_range: document.getElementById('dateRangeFilter').value,
participant: document.getElementById('participantFilter').value,
department: document.getElementById('departmentFilter').value,
status: document.getElementById('statusFilter').value,
group_by: document.getElementById('groupByFilter').value,
search: document.getElementById('searchInput').value,
show_archived: document.getElementById('showArchived').checked
};
// Add communication types
const types = [];
['typeEmail', 'typeSMS', 'typeMessage', 'typeNotification'].forEach(id => {
const checkbox = document.getElementById(id);
if (checkbox.checked) {
types.push(checkbox.value);
}
});
filters.types = types.join(',');
// Add custom date range if selected
if (filters.date_range === 'custom') {
filters.date_from = document.getElementById('dateFrom').value;
filters.date_to = document.getElementById('dateTo').value;
}
// Remove empty filters
Object.keys(filters).forEach(key => {
if (!filters[key] || filters[key] === 'false') delete filters[key];
});
loadHistoryEntries(1, filters);
}
function clearFilters() {
document.getElementById('dateRangeFilter').value = 'week';
document.getElementById('participantFilter').value = '';
document.getElementById('departmentFilter').value = '';
document.getElementById('statusFilter').value = '';
document.getElementById('groupByFilter').value = 'date';
document.getElementById('searchInput').value = '';
document.getElementById('showArchived').checked = false;
document.getElementById('customDateRange').style.display = 'none';
// Reset communication type checkboxes
['typeEmail', 'typeSMS', 'typeMessage', 'typeNotification'].forEach(id => {
document.getElementById(id).checked = true;
});
loadHistoryEntries();
}
function searchHistory() {
applyFilters();
}
function sortHistory(sortBy) {
const currentFilters = getCurrentFilters();
currentFilters.sort = sortBy;
loadHistoryEntries(1, currentFilters);
}
function getCurrentFilters() {
return {
date_range: document.getElementById('dateRangeFilter').value,
participant: document.getElementById('participantFilter').value,
department: document.getElementById('departmentFilter').value,
status: document.getElementById('statusFilter').value,
group_by: document.getElementById('groupByFilter').value,
search: document.getElementById('searchInput').value,
show_archived: document.getElementById('showArchived').checked
};
}
function refreshHistory() {
applyFilters();
updateStats();
}
function updateStats() {
htmx.trigger('[hx-get*="history_stats"]', 'refresh');
}
function viewCommunicationDetail(communicationId) {
currentCommunicationId = communicationId;
fetch(`{% url 'communications:communication_detail' 'COMM_ID' %}`.replace('COMM_ID', communicationId))
.then(response => response.text())
.then(html => {
document.getElementById('communicationDetailContent').innerHTML = html;
const modal = new bootstrap.Modal(document.getElementById('communicationDetailModal'));
modal.show();
})
.catch(error => {
console.error('Error loading communication detail:', error);
showToast('Error', 'Failed to load communication details', 'error');
});
}
function loadConversation(conversationId) {
currentConversationId = conversationId;
fetch(`{% url 'communications:conversation_messages' 'CONV_ID' %}`.replace('CONV_ID', conversationId))
.then(response => response.text())
.then(html => {
document.getElementById('conversation-messages').innerHTML = html;
// Update conversation title
fetch(`{% url 'communications:conversation_info' 'CONV_ID' %}`.replace('CONV_ID', conversationId))
.then(response => response.json())
.then(data => {
document.getElementById('conversation-title').textContent = data.title;
})
.catch(error => console.error('Error loading conversation info:', error));
})
.catch(error => {
console.error('Error loading conversation messages:', error);
showToast('Error', 'Failed to load conversation', 'error');
});
}
function replyToCommunication() {
if (!currentCommunicationId) return;
// Redirect to compose with reply context
window.location.href = `{% url 'communications:compose' %}?reply_to=${currentCommunicationId}`;
}
function forwardCommunication() {
if (!currentCommunicationId) return;
// Redirect to compose with forward context
window.location.href = `{% url 'communications:compose' %}?forward=${currentCommunicationId}`;
}
function exportHistory() {
const filters = getCurrentFilters();
const params = new URLSearchParams(filters);
window.open(`{% url 'communications:export_history' %}?${params}`, '_blank');
}
function generateReport() {
const filters = getCurrentFilters();
fetch('{% url "communications:generate_history_report" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
filters: filters,
view_type: currentView
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('Success', 'Report generated successfully', 'success');
if (data.download_url) {
window.open(data.download_url, '_blank');
}
} else {
showToast('Error', data.error || 'Failed to generate report', 'error');
}
})
.catch(error => {
console.error('Error generating report:', error);
showToast('Error', 'Failed to generate report', 'error');
});
}
function showTrendAnalysis() {
const modal = new bootstrap.Modal(document.getElementById('trendAnalysisModal'));
modal.show();
// Load trend charts
setTimeout(loadTrendCharts, 500); // Wait for modal to be fully shown
}
function loadTrendCharts() {
const filters = getCurrentFilters();
fetch('{% url "communications:trend_analysis" %}?' + new URLSearchParams(filters))
.then(response => response.json())
.then(data => {
createVolumeTrendChart(data.volume_trend);
createTypeTrendChart(data.type_trend);
createResponseTimeTrendChart(data.response_time_trend);
createDepartmentTrendChart(data.department_trend);
})
.catch(error => console.error('Error loading trend analysis:', error));
}
function createVolumeTrendChart(data) {
const ctx = document.getElementById('volumeTrendChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: data.labels,
datasets: [{
label: 'Communication Volume',
data: data.values,
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Communication Volume Trend'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function createTypeTrendChart(data) {
const ctx = document.getElementById('typeTrendChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels,
datasets: data.datasets
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Communication Types Over Time'
}
},
scales: {
x: {
stacked: true
},
y: {
stacked: true,
beginAtZero: true
}
}
}
});
}
function createResponseTimeTrendChart(data) {
const ctx = document.getElementById('responseTimeTrendChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: data.labels,
datasets: [{
label: 'Average Response Time (hours)',
data: data.values,
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.1
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Response Time Trend'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function createDepartmentTrendChart(data) {
const ctx = document.getElementById('departmentTrendChart').getContext('2d');
new Chart(ctx, {
type: 'doughnut',
data: {
labels: data.labels,
datasets: [{
data: data.values,
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40',
'#FF6384',
'#C9CBCF',
'#4BC0C0'
]
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Communications by Department'
}
}
}
});
}
function exportTrendAnalysis() {
const filters = getCurrentFilters();
const params = new URLSearchParams(filters);
window.open(`{% url 'communications:export_trend_analysis' %}?${params}`, '_blank');
}
function showPatternAnalysis() {
const modal = new bootstrap.Modal(document.getElementById('patternAnalysisModal'));
modal.show();
const filters = getCurrentFilters();
fetch('{% url "communications:pattern_analysis" %}?' + new URLSearchParams(filters))
.then(response => response.text())
.then(html => {
document.getElementById('patternAnalysisContent').innerHTML = html;
})
.catch(error => {
console.error('Error loading pattern analysis:', error);
showToast('Error', 'Failed to load pattern analysis', 'error');
});
}
function exportPatternAnalysis() {
const filters = getCurrentFilters();
const params = new URLSearchParams(filters);
window.open(`{% url 'communications:export_pattern_analysis' %}?${params}`, '_blank');
}
function scheduleReport() {
// Implementation would depend on your scheduling system
showToast('Info', 'Report scheduling feature coming soon', 'info');
}
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}`);
}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
switch (e.key) {
case 'r':
refreshHistory();
break;
case 'f':
document.getElementById('searchInput').focus();
break;
case 'c':
clearFilters();
break;
case '1':
document.getElementById('listView').click();
break;
case '2':
document.getElementById('timelineView').click();
break;
case '3':
document.getElementById('conversationView').click();
break;
}
});
// Auto-refresh for real-time updates
setInterval(function() {
if (currentView === 'conversation' && currentConversationId) {
loadConversation(currentConversationId);
}
}, 30000); // Refresh every 30 seconds
</script>
<style>
.conversation-messages {
height: 400px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
}
.timeline-item {
position: relative;
padding-left: 3rem;
margin-bottom: 2rem;
}
.timeline-item::before {
content: '';
position: absolute;
left: 1rem;
top: 0;
bottom: -2rem;
width: 2px;
background: #dee2e6;
}
.timeline-item::after {
content: '';
position: absolute;
left: 0.5rem;
top: 0.5rem;
width: 1rem;
height: 1rem;
border-radius: 50%;
background: #007bff;
border: 2px solid #fff;
box-shadow: 0 0 0 2px #dee2e6;
}
.timeline-item:last-child::before {
display: none;
}
.conversation-item {
padding: 0.75rem;
border-bottom: 1px solid #dee2e6;
cursor: pointer;
transition: background-color 0.2s;
}
.conversation-item:hover {
background-color: #f8f9fa;
}
.conversation-item.active {
background-color: #e3f2fd;
border-left: 3px solid #007bff;
}
.message-bubble {
max-width: 70%;
margin-bottom: 1rem;
padding: 0.75rem 1rem;
border-radius: 1rem;
position: relative;
}
.message-bubble.sent {
background-color: #007bff;
color: white;
margin-left: auto;
border-bottom-right-radius: 0.25rem;
}
.message-bubble.received {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-bottom-left-radius: 0.25rem;
}
.message-time {
font-size: 0.75rem;
opacity: 0.7;
margin-top: 0.25rem;
}
</style>
{% endblock %}