/** * Notifications Page * * Full page view for managing all user notifications */ 'use client'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { listNotifications, markNotificationAsRead, markAllNotificationsAsRead, deleteNotification, } from '@/lib/api'; import type { NotificationResponse } from '@/types/api'; import { Trash2, CheckCheck, Filter, Bell, ChevronLeft, ChevronRight, Package, RefreshCw, XCircle, CheckCircle, Mail, Clock, FileText, Megaphone, User, Building2 } from 'lucide-react'; import type { ReactNode } from 'react'; export default function NotificationsPage() { const [selectedFilter, setSelectedFilter] = useState<'all' | 'unread' | 'read'>('all'); const [currentPage, setCurrentPage] = useState(1); const queryClient = useQueryClient(); const router = useRouter(); // Fetch notifications with pagination const { data, isLoading } = useQuery({ queryKey: ['notifications', 'page', selectedFilter, currentPage], queryFn: () => listNotifications({ page: currentPage, limit: 20, isRead: selectedFilter === 'all' ? undefined : selectedFilter === 'read', }), }); const notifications = data?.notifications || []; const total = data?.total || 0; const totalPages = Math.ceil(total / 20); const unreadCount = notifications.filter((n: NotificationResponse) => !n.read).length; // Mark single notification as read const markAsReadMutation = useMutation({ mutationFn: markNotificationAsRead, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); // Mark all as read const markAllAsReadMutation = useMutation({ mutationFn: markAllNotificationsAsRead, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); // Delete notification const deleteNotificationMutation = useMutation({ mutationFn: deleteNotification, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); const handleNotificationClick = (notification: NotificationResponse) => { if (!notification.read) { markAsReadMutation.mutate(notification.id); } // Navigate to actionUrl if available if (notification.actionUrl) { router.push(notification.actionUrl); } }; const handleDelete = (e: React.MouseEvent, notificationId: string) => { e.stopPropagation(); if (confirm('Êtes-vous sûr de vouloir supprimer cette notification ?')) { deleteNotificationMutation.mutate(notificationId); } }; const getPriorityColor = (priority: string) => { const colors = { urgent: 'border-l-4 border-red-500 bg-red-50 hover:bg-red-100', high: 'border-l-4 border-orange-500 bg-orange-50 hover:bg-orange-100', medium: 'border-l-4 border-yellow-500 bg-yellow-50 hover:bg-yellow-100', low: 'border-l-4 border-blue-500 bg-blue-50 hover:bg-blue-100', }; return colors[priority as keyof typeof colors] || 'border-l-4 border-gray-300 hover:bg-gray-100'; }; const getNotificationIcon = (type: string): ReactNode => { const iconClass = "h-8 w-8"; const icons: Record = { booking_created: , booking_updated: , booking_cancelled: , booking_confirmed: , csv_booking_accepted: , csv_booking_rejected: , csv_booking_request_sent: , rate_quote_expiring: , document_uploaded: , system_announcement: , user_invited: , organization_update: , }; return icons[type.toLowerCase()] || ; }; const formatTime = (dateString: string) => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return 'A l\'instant'; if (diffMins < 60) return `Il y a ${diffMins}min`; if (diffHours < 24) return `Il y a ${diffHours}h`; if (diffDays < 7) return `Il y a ${diffDays}j`; return date.toLocaleDateString('fr-FR', { month: 'long', day: 'numeric', year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined, hour: '2-digit', minute: '2-digit', }); }; return (
{/* Header */}

Notifications

{total} notification{total !== 1 ? 's' : ''} au total {unreadCount > 0 && ` • ${unreadCount} non lue${unreadCount > 1 ? 's' : ''}`}

{unreadCount > 0 && ( )}
{/* Main Content */}
{/* Filter Bar */}
Filtrer :
{(['all', 'unread', 'read'] as const).map((filter) => ( ))}
{/* Notifications List */}
{isLoading ? (

Chargement des notifications...

) : notifications.length === 0 ? (

Aucune notification

{selectedFilter === 'unread' ? 'Vous êtes à jour !' : 'Aucune notification à afficher'}

) : (
{notifications.map((notification: NotificationResponse) => (
handleNotificationClick(notification)} className={`p-6 transition-all cursor-pointer group ${ !notification.read ? 'bg-blue-50/50' : '' } ${getPriorityColor(notification.priority || 'low')}`} >
{/* Icon */}
{getNotificationIcon(notification.type)}
{/* Content */}

{notification.title}

{!notification.read && ( NOUVEAU )}

{notification.message}

{/* Metadata */}
{formatTime(notification.createdAt)} {notification.type.replace(/_/g, ' ').toUpperCase()} {notification.priority && ( {notification.priority.toUpperCase()} )}
{notification.actionUrl && ( Voir les détails )}
))}
)}
{/* Pagination */} {totalPages > 1 && (
Page {currentPage} sur{' '} {totalPages} {' • '} {total} notification{total !== 1 ? 's' : ''} au total
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { let pageNum; if (totalPages <= 5) { pageNum = i + 1; } else if (currentPage <= 3) { pageNum = i + 1; } else if (currentPage >= totalPages - 2) { pageNum = totalPages - 4 + i; } else { pageNum = currentPage - 2 + i; } return ( ); })}
)}
); }