import { useState, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { getNotifications, markNotificationRead } from '../api/client'; import PageHeader from '../components/PageHeader'; import StatusBadge from '../components/StatusBadge'; import ErrorState from '../components/ErrorState'; import { formatDateTime, timeAgo } from '../api/utils'; import type { Notification } from '../api/types'; type ViewMode = 'list' | 'grouped'; export default function NotificationsPage() { const [viewMode, setViewMode] = useState('grouped'); const [typeFilter, setTypeFilter] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const queryClient = useQueryClient(); const { data, isLoading, error, refetch } = useQuery({ queryKey: ['notifications'], queryFn: () => getNotifications({ per_page: '100' }), refetchInterval: 30000, }); const markRead = useMutation({ mutationFn: markNotificationRead, onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notifications'] }), }); const notifications = data?.data || []; const filtered = useMemo(() => { return notifications.filter((n) => { if (typeFilter && n.type !== typeFilter) return false; if (statusFilter && n.status !== statusFilter) return false; return true; }); }, [notifications, typeFilter, statusFilter]); const types = useMemo(() => [...new Set(notifications.map(n => n.type))], [notifications]); const statuses = useMemo(() => [...new Set(notifications.map(n => n.status))], [notifications]); // Group by certificate_id const grouped = useMemo(() => { const groups: Record = {}; for (const n of filtered) { const key = n.certificate_id || 'general'; if (!groups[key]) groups[key] = []; groups[key].push(n); } return Object.entries(groups).sort(([, a], [, b]) => { const aTime = new Date(a[0].created_at).getTime(); const bTime = new Date(b[0].created_at).getTime(); return bTime - aTime; }); }, [filtered]); const unreadCount = filtered.filter(n => n.status === 'Pending' || n.status === 'pending').length; if (isLoading) { return ( <>
Loading...
); } if (error) { return ( <> refetch()} /> ); } return ( <>
{(typeFilter || statusFilter) && ( )}
{viewMode === 'grouped' ? ( grouped.length === 0 ? (
No notifications
) : ( grouped.map(([certId, items]) => (
{certId === 'general' ? 'General' : certId} {items.length} notification{items.length !== 1 ? 's' : ''}
{items.map((n) => ( markRead.mutate(n.id)} /> ))}
)) ) ) : ( filtered.length === 0 ? (
No notifications
) : (
{filtered.map((n) => ( markRead.mutate(n.id)} /> ))}
) )}
); } function NotificationRow({ notification: n, onMarkRead }: { notification: Notification; onMarkRead: () => void }) { const isUnread = n.status === 'Pending' || n.status === 'pending'; return (
{n.type.replace(/([A-Z])/g, ' $1').trim()} {n.channel}

{n.message || n.subject}

{n.recipient} {timeAgo(n.created_at)}
{isUnread && ( )}
); }