1069 lines
42 KiB
HTML
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 %}
|
|
|