'use client'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { useTranslations, useLocale } from 'next-intl'; 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 t = useTranslations('dashboard.notificationsPage'); const locale = useLocale(); const dateLocale = locale === 'fr' ? 'fr-FR' : 'en-US'; const [selectedFilter, setSelectedFilter] = useState<'all' | 'unread' | 'read'>('all'); const [currentPage, setCurrentPage] = useState(1); const queryClient = useQueryClient(); const router = useRouter(); 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; const markAsReadMutation = useMutation({ mutationFn: markNotificationAsRead, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); const markAllAsReadMutation = useMutation({ mutationFn: markAllNotificationsAsRead, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); const deleteNotificationMutation = useMutation({ mutationFn: deleteNotification, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); const handleNotificationClick = (notification: NotificationResponse) => { if (!notification.read) { markAsReadMutation.mutate(notification.id); } if (notification.actionUrl) { router.push(notification.actionUrl); } }; const handleDelete = (e: React.MouseEvent, notificationId: string) => { e.stopPropagation(); if (confirm(t('deleteConfirm'))) { 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 getPriorityLabel = (priority: string) => { const map: Record = { urgent: t('priority.urgent'), high: t('priority.high'), medium: t('priority.medium'), low: t('priority.low'), }; return map[priority] || priority.toUpperCase(); }; 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 t('time.now'); if (diffMins < 60) return t('time.minutes', { count: diffMins }); if (diffHours < 24) return t('time.hours', { count: diffHours }); if (diffDays < 7) return t('time.days', { count: diffDays }); return date.toLocaleDateString(dateLocale, { month: 'long', day: 'numeric', year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined, hour: '2-digit', minute: '2-digit', }); }; return (

{t('title')}

{t('totalLabel', { count: total })} {unreadCount > 0 && t('unreadSuffix', { count: unreadCount })}

{unreadCount > 0 && ( )}
{t('filter.label')}
{(['all', 'unread', 'read'] as const).map((filter) => ( ))}
{isLoading ? (

{t('loading')}

) : notifications.length === 0 ? (

{t('empty.title')}

{selectedFilter === 'unread' ? t('empty.upToDate') : t('empty.none')}

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

{notification.title}

{!notification.read && ( {t('new')} )}

{notification.message}

{formatTime(notification.createdAt)} {notification.type.replace(/_/g, ' ').toUpperCase()} {notification.priority && ( {getPriorityLabel(notification.priority)} )}
{notification.actionUrl && ( {t('viewDetails')} )}
))}
)}
{totalPages > 1 && (
{t.rich('pagination.info', { current: currentPage, total: totalPages, items: total, b: (chunks) => {chunks}, })}
{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 ( ); })}
)}
); }