Files
docupulse/static/js/notifications.js
2025-05-31 23:15:46 +02:00

417 lines
17 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function() {
// Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Initialize variables
let currentPage = 1;
let totalPages = parseInt(document.getElementById('totalPages').textContent) || 1;
let isFetching = false;
// Get filter elements
const notifTypeFilter = document.getElementById('notifTypeFilter');
const dateRangeFilter = document.getElementById('dateRangeFilter');
const clearFiltersBtn = document.getElementById('clearFilters');
const markAllReadBtn = document.getElementById('markAllRead');
// Get pagination elements
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
const currentPageSpan = document.getElementById('currentPage');
const totalPagesSpan = document.getElementById('totalPages');
const notifsTableBody = document.getElementById('notifsTableBody');
// Notification details modal
const notifDetailsModal = document.getElementById('notifDetailsModal');
const notifDetailsContent = document.getElementById('notifDetailsContent');
// Get CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Function to update URL with current filters
function updateURL() {
const params = new URLSearchParams(window.location.search);
params.set('notif_type', notifTypeFilter.value);
params.set('date_range', dateRangeFilter.value);
params.set('page', currentPage);
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
}
// Function to fetch notifications
async function fetchNotifications() {
if (isFetching) return;
isFetching = true;
// Show loading state
notifsTableBody.innerHTML = '<tr><td colspan="6" class="text-center">Loading...</td></tr>';
const params = new URLSearchParams({
notif_type: notifTypeFilter.value,
date_range: dateRangeFilter.value,
page: currentPage,
ajax: 'true'
});
try {
const response = await fetch(`${window.location.pathname}?${params.toString()}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
// Update the table with the notifications
if (data.notifications && data.notifications.length > 0) {
notifsTableBody.innerHTML = data.notifications.map(notif => `
<tr class="${notif.read ? '' : 'table-warning'}" data-notif-id="${notif.id}">
<td>${notif.timestamp}</td>
<td>${getNotifTypeBadge(notif.notif_type)}</td>
<td>${notif.sender ? `${notif.sender.username} ${notif.sender.last_name}` : 'System'}</td>
<td>
${notif.read ?
'<span class="badge bg-success">Read</span>' :
'<span class="badge bg-warning">Unread</span>'}
</td>
<td>
<div class="btn-group">
${getActionButtons(notif)}
<button class="btn btn-sm btn-secondary"
data-bs-toggle="modal"
data-bs-target="#notifDetailsModal"
data-notif-id="${notif.id}"
data-bs-toggle="tooltip"
title="View Details">
<i class="fas fa-info-circle"></i>
</button>
${!notif.read ? `
<button class="btn btn-sm btn-success mark-read"
data-notif-id="${notif.id}"
data-bs-toggle="tooltip"
title="Mark as Read">
<i class="fas fa-check"></i>
</button>
` : ''}
<button class="btn btn-sm btn-danger delete-notif"
data-notif-id="${notif.id}"
data-bs-toggle="tooltip"
title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
`).join('');
} else {
notifsTableBody.innerHTML = '<tr><td colspan="5" class="text-center">No notifications found</td></tr>';
}
// Update pagination
totalPages = data.total_pages;
currentPage = data.current_page;
currentPageSpan.textContent = currentPage;
totalPagesSpan.textContent = totalPages;
prevPageBtn.disabled = currentPage <= 1;
nextPageBtn.disabled = currentPage >= totalPages;
// Reinitialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Reattach event listeners
attachEventListeners();
} catch (error) {
console.error('Error fetching notifications:', error);
notifsTableBody.innerHTML = '<tr><td colspan="6" class="text-center">Error loading notifications</td></tr>';
} finally {
isFetching = false;
}
}
// Function to get notification type badge
function getNotifTypeBadge(type) {
const badges = {
'account_created': '<span class="badge bg-success">Account Created</span>',
'password_reset': '<span class="badge bg-warning">Password Reset</span>',
'account_deleted': '<span class="badge bg-danger">Account Deleted</span>',
'account_updated': '<span class="badge bg-info">Account Updated</span>',
'room_invite': '<span class="badge bg-primary">Room Invite</span>',
'room_invite_removed': '<span class="badge bg-secondary">Room Invite Removed</span>',
'conversation_invite': '<span class="badge bg-primary">Conversation Invite</span>',
'conversation_invite_removed': '<span class="badge bg-secondary">Conversation Invite Removed</span>',
'conversation_message': '<span class="badge bg-info">Conversation Message</span>'
};
return badges[type] || `<span class="badge bg-secondary">${type}</span>`;
}
// Function to load notification details
function loadNotifDetails(notifId) {
fetch(`/api/notifications/${notifId}`)
.then(response => response.json())
.then(data => {
const detailsContent = document.getElementById('notifDetailsContent');
if (data.details) {
detailsContent.textContent = JSON.stringify(data.details, null, 2);
} else {
detailsContent.textContent = 'No additional details available';
}
})
.catch(error => {
console.error('Error loading notification details:', error);
document.getElementById('notifDetailsContent').textContent = 'Error loading notification details';
});
}
// Function to update notification counter
function updateNotificationCounter() {
counter.textContent = document.querySelector('.nav-link[href*="notifications"] .badge');
}
// Function to mark notification as read
async function markAsRead(notifId) {
try {
const response = await fetch(`/api/notifications/${notifId}/read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.success) {
// Update the UI to show the notification as read
const notifRow = document.querySelector(`tr[data-notif-id="${notifId}"]`);
if (notifRow) {
notifRow.classList.remove('table-warning');
const statusCell = notifRow.querySelector('td:nth-last-child(2)');
if (statusCell) {
statusCell.innerHTML = '<span class="badge bg-success">Read</span>';
}
const actionsCell = notifRow.querySelector('td:last-child');
if (actionsCell) {
const markReadBtn = actionsCell.querySelector('.mark-read');
if (markReadBtn) {
markReadBtn.remove();
}
}
}
// Update the notification counter
updateNotificationCounter();
}
} catch (error) {
console.error('Error marking notification as read:', error);
}
}
// Function to delete notification
async function deleteNotification(notifId) {
try {
const response = await fetch(`/api/notifications/${notifId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.success) {
// Remove the notification row from the table
const notifRow = document.querySelector(`tr[data-notif-id="${notifId}"]`);
if (notifRow) {
// Only update counter if the notification was unread
if (notifRow.classList.contains('table-warning')) {
updateNotificationCounter();
}
notifRow.remove();
}
}
} catch (error) {
console.error('Error deleting notification:', error);
}
}
// Function to mark all notifications as read
async function markAllAsRead() {
try {
const response = await fetch('/api/notifications/mark-all-read', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.success) {
// Update all notifications to show as read
document.querySelectorAll('tr[data-notif-id]').forEach(row => {
row.classList.remove('table-warning');
const statusCell = row.querySelector('td:nth-last-child(2)');
if (statusCell) {
statusCell.innerHTML = '<span class="badge bg-success">Read</span>';
}
const actionsCell = row.querySelector('td:last-child');
if (actionsCell) {
const markReadBtn = actionsCell.querySelector('.mark-read');
if (markReadBtn) {
markReadBtn.remove();
}
}
});
// Remove the notification counter
const counter = document.querySelector('.nav-link[href*="notifications"] .badge');
if (counter) {
counter.remove();
}
}
} catch (error) {
console.error('Error marking all notifications as read:', error);
}
}
// Function to handle notification action clicks
function handleNotificationAction(notifId, actionType) {
// Mark the notification as read when an action is taken
markAsRead(notifId);
// Additional handling for specific action types can be added here
if (actionType === 'view_room' || actionType === 'view_conversation') {
// The link will handle the navigation automatically
return true;
}
}
// Function to attach event listeners
function attachEventListeners() {
// Mark as read buttons
document.querySelectorAll('.mark-read').forEach(btn => {
btn.addEventListener('click', (e) => {
const notifId = e.target.closest('.mark-read').dataset.notifId;
markAsRead(notifId);
});
});
// Delete buttons
document.querySelectorAll('.delete-notif').forEach(btn => {
btn.addEventListener('click', (e) => {
const notifId = e.target.closest('.delete-notif').dataset.notifId;
deleteNotification(notifId);
});
});
// View details buttons
document.querySelectorAll('[data-bs-target="#notifDetailsModal"]').forEach(btn => {
btn.addEventListener('click', (e) => {
const notifId = e.target.closest('[data-notif-id]').dataset.notifId;
loadNotifDetails(notifId);
});
});
// Action buttons (View Room, View Conversation)
document.querySelectorAll('.btn-group a').forEach(btn => {
btn.addEventListener('click', (e) => {
const notifId = e.target.closest('tr').dataset.notifId;
const actionType = btn.classList.contains('fa-door-open') ? 'view_room' : 'view_conversation';
handleNotificationAction(notifId, actionType);
});
});
}
// Initialize event listeners
attachEventListeners();
// Add event listeners for filters
notifTypeFilter.addEventListener('change', () => {
currentPage = 1;
fetchNotifications();
updateURL();
});
dateRangeFilter.addEventListener('change', () => {
currentPage = 1;
fetchNotifications();
updateURL();
});
clearFiltersBtn.addEventListener('click', () => {
notifTypeFilter.value = '';
dateRangeFilter.value = '7d';
currentPage = 1;
fetchNotifications();
updateURL();
});
markAllReadBtn.addEventListener('click', markAllAsRead);
// Add event listeners for pagination
prevPageBtn.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
fetchNotifications();
updateURL();
}
});
nextPageBtn.addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
fetchNotifications();
updateURL();
}
});
});
// Function to get action buttons based on notification type
function getActionButtons(notif) {
if (notif.notif_type === 'room_invite' || notif.notif_type === 'room_invite_removed') {
if (notif.details && notif.details.room_id) {
return `
<a href="/rooms/${notif.details.room_id}"
class="btn btn-sm btn-primary"
data-bs-toggle="tooltip"
title="View Room">
<i class="fas fa-door-open"></i>
</a>
`;
}
} else if (notif.notif_type === 'conversation_invite' ||
notif.notif_type === 'conversation_invite_removed' ||
notif.notif_type === 'conversation_message') {
if (notif.details && notif.details.conversation_id) {
return `
<a href="/conversations/${notif.details.conversation_id}"
class="btn btn-sm btn-primary"
data-bs-toggle="tooltip"
title="View Conversation">
<i class="fas fa-comments"></i>
</a>
`;
}
}
return '';
}