import { useState, useEffect, useRef, useCallback } from 'react'; import { Bell, AlertCircle, Info, Trash2 } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import type { Notification as PortalNotification } from '../types'; export default function Notifications() { const { token } = useAuth(); const [notifications, setNotifications] = useState([]); const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const fetchNotifications = useCallback(async () => { if (!token) return; try { // Fetch core notifications const res = await fetch('/api/portal/notificacoes', { headers: { Authorization: `Bearer ${token}` }, }); const data = await res.json(); let allNotifs = data.notifications || []; // Fetch financial status for dynamic alert const finRes = await fetch('/api/portal/financeiro', { headers: { Authorization: `Bearer ${token}` } }); if (finRes.ok) { const finData = await finRes.json(); const atrasadas = (finData.payments || []).filter((p: any) => p.status === 'atrasada'); if (atrasadas.length > 0) { const overdueNotif: PortalNotification = { id: 'finance-overdue', title: 'Pagamento Pendente', message: `Identificamos ${atrasadas.length} ${atrasadas.length === 1 ? 'parcela atrasada' : 'parcelas atrasadas'}. Regularize agora para evitar suspensões.`, read: false, createdAt: new Date().toISOString(), type: 'alert', studentId: '' }; allNotifs = [overdueNotif, ...allNotifs]; } } setNotifications(allNotifs); } catch (err) { console.error('Erro ao buscar notificações', err); } }, [token]); useEffect(() => { fetchNotifications(); // Polling a cada 30s para simular realtime via SchoolData const interval = setInterval(fetchNotifications, 30000); return () => clearInterval(interval); }, [fetchNotifications]); const navigate = useNavigate(); const unreadCount = notifications.filter(n => !n.read).length; const markAsRead = async (id: string) => { try { await fetch(`/api/portal/notificacoes/ler/${id}`, { method: 'PUT', headers: { Authorization: `Bearer ${token}` }, }); setNotifications(prev => prev.map(n => n.id === id ? { ...n, read: true } : n)); } catch (err) { console.error(err); } }; const deleteAllRead = async () => { const readNotifs = notifications.filter(n => n.read); // Remove locally first for instant UI feedback setNotifications(prev => prev.filter(n => !n.read)); // Then fire API calls for (const notif of readNotifs) { try { await fetch(`/api/portal/notificacoes/${notif.id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); } catch (err) { console.error(err); } } }; const formatDate = (dateStr?: string) => { if (!dateStr) return 'Agora'; try { return new Date(dateStr).toLocaleString('pt-BR'); } catch { return dateStr; } }; return (
{isOpen && (

Notificações

{unreadCount} {unreadCount === 1 ? 'não lida' : 'não lidas'} {notifications.some(n => n.read) && ( )}
{notifications.length === 0 ? (

Nenhuma notificação

) : (
{notifications.map(notif => { const msgLower = (notif.title + ' ' + notif.message).toLowerCase(); const isCancelamento = msgLower.includes('cancel') || msgLower.includes('exclu') || msgLower.includes('remov'); const isReposicao = msgLower.includes('reposi'); const isExtra = msgLower.includes('extra'); const isReagendamento = msgLower.includes('reagend') || msgLower.includes('altera'); return (
{ if (!notif.read) markAsRead(notif.id); if (notif.id === 'finance-overdue') { navigate('/financeiro?filter=overdue'); setIsOpen(false); } else if (isCancelamento || isReagendamento || isExtra || isReposicao) { navigate('/minhas-aulas'); setIsOpen(false); } }} style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)', background: notif.read ? 'transparent' : 'var(--bg-primary-alpha)', display: 'flex', gap: '0.75rem', alignItems: 'flex-start', transition: 'all 0.3s ease', opacity: notif.read ? 0.5 : 1, cursor: 'pointer', }} >
{isCancelamento || isReagendamento ? : }

{notif.title}

{notif.message}

{formatDate(notif.createdAt)}
{!notif.read && (
)}
); })}
)}
)}
); }