Files
orion/app/modules/messaging/static/admin/js/notifications.js
Samir Boulahtit 167bb50f4f
Some checks failed
CI / ruff (push) Successful in 9s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
fix: replace all native confirm() dialogs with styled modal macros
Migrated ~68 native browser confirm() calls across 74 files to use the
project's confirm_modal/confirm_modal_dynamic Jinja2 macros, providing
consistent styled confirmation dialogs instead of plain browser popups.

Modules updated: core, tenancy, cms, marketplace, messaging, billing,
customers, orders, cart. Uses danger/warning/info variants and
double-confirm pattern for destructive delete operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:56:25 +01:00

312 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Admin Notifications Page
*
* Handles the notifications management interface including:
* - Notifications list with filtering and pagination
* - Platform alerts management
* - Mark as read, delete, and bulk operations
*/
const notificationsLog = window.LogConfig?.createLogger('NOTIFICATIONS') || console;
/**
* Admin Notifications Component
*/
function adminNotifications() {
return {
...data(),
currentPage: 'notifications',
// Loading states
loading: true,
error: null,
loadingNotifications: false,
loadingAlerts: false,
// Tab state
activeTab: 'notifications',
// Notifications state
notifications: [],
page: 1,
skip: 0,
limit: 10,
stats: {
total: 0,
unread_count: 0
},
// Notifications filters
filters: {
priority: '',
is_read: ''
},
// Alerts state
alerts: [],
alertPage: 1,
alertSkip: 0,
alertLimit: 10,
alertStats: {
total: 0,
active_alerts: 0,
critical_alerts: 0,
resolved_today: 0,
by_type: {},
by_severity: {}
},
// Alerts filters
alertFilters: {
severity: '',
is_resolved: ''
},
// Resolve modal state
showResolveModal: false,
resolvingAlert: null,
resolutionNotes: '',
// Delete notification confirm state
showDeleteNotificationConfirm: false,
pendingDeleteNotificationId: null,
/**
* Initialize component
*/
async init() {
// Load i18n translations
await I18n.loadModule('messaging');
// Guard against multiple initialization
if (window._adminNotificationsInitialized) return;
window._adminNotificationsInitialized = true;
try {
notificationsLog.debug('Initializing notifications page');
await Promise.all([
this.loadNotifications(),
this.loadAlertStats()
]);
} catch (error) {
notificationsLog.error('Failed to initialize notifications page:', error);
} finally {
this.loading = false;
}
},
// ============================================================================
// NOTIFICATIONS
// ============================================================================
/**
* Load notifications with current filters
*/
async loadNotifications() {
this.loadingNotifications = true;
try {
this.skip = (this.page - 1) * this.limit;
const params = new URLSearchParams();
params.append('skip', this.skip);
params.append('limit', this.limit);
if (this.filters.priority) params.append('priority', this.filters.priority);
if (this.filters.is_read !== '') params.append('is_read', this.filters.is_read);
const response = await apiClient.get(`/admin/notifications?${params}`);
this.notifications = response.notifications || [];
this.stats.total = response.total || 0;
this.stats.unread_count = response.unread_count || 0;
notificationsLog.debug(`Loaded ${this.notifications.length} notifications`);
} catch (error) {
notificationsLog.error('Failed to load notifications:', error);
Utils.showToast(I18n.t('messaging.messages.failed_to_load_notifications'), 'error');
} finally {
this.loadingNotifications = false;
}
},
/**
* Mark notification as read
*/
async markAsRead(notification) {
try {
await apiClient.put(`/admin/notifications/${notification.id}/read`);
// Update local state
notification.is_read = true;
this.stats.unread_count = Math.max(0, this.stats.unread_count - 1);
Utils.showToast(I18n.t('messaging.messages.notification_marked_as_read'), 'success');
} catch (error) {
notificationsLog.error('Failed to mark as read:', error);
Utils.showToast(I18n.t('messaging.messages.failed_to_mark_notification_as_read'), 'error');
}
},
/**
* Mark all notifications as read
*/
async markAllAsRead() {
try {
await apiClient.put('/admin/notifications/mark-all-read');
// Update local state
this.notifications.forEach(n => n.is_read = true);
this.stats.unread_count = 0;
Utils.showToast(I18n.t('messaging.messages.all_notifications_marked_as_read'), 'success');
} catch (error) {
notificationsLog.error('Failed to mark all as read:', error);
Utils.showToast(I18n.t('messaging.messages.failed_to_mark_all_as_read'), 'error');
}
},
/**
* Delete notification
*/
async deleteNotification(notificationId) {
try {
await apiClient.delete(`/admin/notifications/${notificationId}`);
// Remove from local state
const wasUnread = this.notifications.find(n => n.id === notificationId && !n.is_read);
this.notifications = this.notifications.filter(n => n.id !== notificationId);
this.stats.total = Math.max(0, this.stats.total - 1);
if (wasUnread) {
this.stats.unread_count = Math.max(0, this.stats.unread_count - 1);
}
Utils.showToast(I18n.t('messaging.messages.notification_deleted'), 'success');
} catch (error) {
notificationsLog.error('Failed to delete notification:', error);
Utils.showToast(I18n.t('messaging.messages.failed_to_delete_notification'), 'error');
}
},
/**
* Get notification icon based on type
*/
getNotificationIcon(type) {
const icons = {
'import_failure': window.$icon?.('x-circle', 'w-5 h-5') || '❌',
'sync_issue': window.$icon?.('refresh', 'w-5 h-5') || '🔄',
'store_alert': window.$icon?.('exclamation-triangle', 'w-5 h-5') || '⚠️',
'system_health': window.$icon?.('heart', 'w-5 h-5') || '💓',
'security': window.$icon?.('shield-exclamation', 'w-5 h-5') || '🛡️',
'performance': window.$icon?.('chart-bar', 'w-5 h-5') || '📊',
'info': window.$icon?.('information-circle', 'w-5 h-5') || ''
};
return icons[type] || window.$icon?.('bell', 'w-5 h-5') || '🔔';
},
// ============================================================================
// PLATFORM ALERTS
// ============================================================================
/**
* Load platform alerts
*/
async loadAlerts() {
this.loadingAlerts = true;
try {
this.alertSkip = (this.alertPage - 1) * this.alertLimit;
const params = new URLSearchParams();
params.append('skip', this.alertSkip);
params.append('limit', this.alertLimit);
if (this.alertFilters.severity) params.append('severity', this.alertFilters.severity);
if (this.alertFilters.is_resolved !== '') params.append('is_resolved', this.alertFilters.is_resolved);
const response = await apiClient.get(`/admin/notifications/alerts?${params}`);
this.alerts = response.alerts || [];
this.alertStats.total = response.total || 0;
this.alertStats.active_alerts = response.active_count || 0;
this.alertStats.critical_alerts = response.critical_count || 0;
notificationsLog.debug(`Loaded ${this.alerts.length} alerts`);
} catch (error) {
notificationsLog.error('Failed to load alerts:', error);
Utils.showToast(I18n.t('messaging.messages.failed_to_load_alerts'), 'error');
} finally {
this.loadingAlerts = false;
}
},
/**
* Load alert statistics
*/
async loadAlertStats() {
try {
const response = await apiClient.get('/admin/notifications/alerts/stats');
this.alertStats = {
...this.alertStats,
total: response.total || 0,
active_alerts: response.active || 0,
critical_alerts: response.critical || 0,
resolved_today: response.resolved_today || 0,
by_type: response.by_type || {},
by_severity: response.by_severity || {}
};
} catch (error) {
notificationsLog.error('Failed to load alert stats:', error);
}
},
/**
* Resolve alert
*/
async resolveAlert(alert) {
const notes = prompt('Resolution notes (optional):');
if (notes === null) return; // User cancelled
try {
await apiClient.put(`/admin/notifications/alerts/${alert.id}/resolve`, {
resolution_notes: notes
});
// Update local state
alert.is_resolved = true;
alert.resolution_notes = notes;
this.alertStats.active_alerts = Math.max(0, this.alertStats.active_alerts - 1);
if (alert.severity === 'critical') {
this.alertStats.critical_alerts = Math.max(0, this.alertStats.critical_alerts - 1);
}
this.alertStats.resolved_today++;
Utils.showToast(I18n.t('messaging.messages.alert_resolved_successfully'), 'success');
} catch (error) {
notificationsLog.error('Failed to resolve alert:', error);
Utils.showToast(I18n.t('messaging.messages.failed_to_resolve_alert'), 'error');
}
},
// ============================================================================
// HELPERS
// ============================================================================
/**
* Format date for display
*/
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
const now = new Date();
const diff = Math.floor((now - date) / 1000);
// Show relative time for recent dates
if (diff < 60) return 'Just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
if (diff < 172800) return 'Yesterday';
// Show full date for older dates
return date.toLocaleString();
}
};
}
// Make available globally
window.adminNotifications = adminNotifications;