/** * 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 } from 'lucide-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('Are you sure you want to delete this 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) => { 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 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString('en-US', { 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' : ''} total {unreadCount > 0 && ` • ${unreadCount} unread`}

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

Loading notifications...

) : notifications.length === 0 ? (
🔔

No notifications

{selectedFilter === 'unread' ? "You're all caught up! Great job!" : 'No notifications to display'}

) : (
{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 && ( NEW )}

{notification.message}

{/* Metadata */}
{formatTime(notification.createdAt)} {notification.type.replace(/_/g, ' ').toUpperCase()} {notification.priority && ( {notification.priority.toUpperCase()} )}
{notification.actionUrl && ( View details )}
))}
)}
{/* Pagination */} {totalPages > 1 && (
Showing page {currentPage} of{' '} {totalPages} {' • '} {total} total notification {total !== 1 ? 's' : ''}
{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 ( ); })}
)}
); }