import { useState, useEffect, useRef, useCallback } from 'react' import { useNavigate, Link } from 'react-router-dom' import { Bell, AlertTriangle, AlertCircle, FileText, CheckCircle, TrendingUp, } from 'lucide-react' import { notificationsApi } from '@/api/notifications' import type { AppNotification } from '@/types/notification' function timeAgo(dateStr: string): string { const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000) 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` return `${Math.floor(diff / 86400)}d ago` } function EventIcon({ event }: { event: string }) { switch (event) { case 'session.escalated': return case 'session.high_priority': return case 'proposal.pending': return case 'proposal.approved': return case 'knowledge_gap.detected': return default: return } } export function NotificationsPanel() { const [open, setOpen] = useState(false) const [notifications, setNotifications] = useState([]) const [unreadCount, setUnreadCount] = useState(0) const ref = useRef(null) const navigate = useNavigate() // Poll unread count every 30 seconds useEffect(() => { const fetchCount = () => { notificationsApi.unreadCount().then(setUnreadCount).catch(() => {}) } fetchCount() const interval = setInterval(fetchCount, 30000) return () => clearInterval(interval) }, []) // Fetch full list when dropdown opens useEffect(() => { if (open) { notificationsApi.list({ limit: 20 }).then(setNotifications).catch(() => {}) } }, [open]) // Click outside to close useEffect(() => { const handler = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) } if (open) document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [open]) const handleMarkAllRead = useCallback(async () => { try { await notificationsApi.markAllRead() setUnreadCount(0) setNotifications(prev => prev.map(n => ({ ...n, is_read: true }))) } catch { // silently ignore } }, []) const handleNotificationClick = useCallback(async (notification: AppNotification) => { try { if (!notification.is_read) { await notificationsApi.markRead(notification.id) setUnreadCount(prev => Math.max(0, prev - 1)) setNotifications(prev => prev.map(n => (n.id === notification.id ? { ...n, is_read: true } : n)) ) } } catch { // silently ignore } setOpen(false) if (notification.link) { navigate(notification.link) } }, [navigate]) return (
{open && (
{ if (e.key === 'Escape') setOpen(false) }} >

Notifications

{unreadCount > 0 && ( )}
{notifications.length === 0 ? (
No notifications yet
) : (
{notifications.map(notification => ( ))}
)}
setOpen(false)} className="text-xs text-muted-foreground hover:text-foreground transition-colors" > Notification settings
)}
) }