/** * 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: '', /** * Initialize component */ async init() { // 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('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('Notification marked as read', 'success'); } catch (error) { notificationsLog.error('Failed to mark as read:', error); Utils.showToast('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('All notifications marked as read', 'success'); } catch (error) { notificationsLog.error('Failed to mark all as read:', error); Utils.showToast('Failed to mark all as read', 'error'); } }, /** * Delete notification */ async deleteNotification(notificationId) { if (!confirm('Are you sure you want to delete this notification?')) { return; } 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('Notification deleted', 'success'); } catch (error) { notificationsLog.error('Failed to delete notification:', error); Utils.showToast('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') || '🔄', 'vendor_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('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('Alert resolved successfully', 'success'); } catch (error) { notificationsLog.error('Failed to resolve alert:', error); Utils.showToast('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;