/** * useTicketEvents.ts * * Hook para escutar os Server-Sent Events (SSE) disparados pelo backend. * Conecta via EventSource e despacha atualizações e invalidações de cache. */ import { useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useAuth } from '@/context/AuthContext'; import { useToast } from '@/context/ToastContext'; export function useTicketEvents() { const { token, isAuthenticated, user } = useAuth(); const queryClient = useQueryClient(); const { addToast } = useToast(); useEffect(() => { // Pede permissão para notificações nativas, se suportado if ('Notification' in window && Notification.permission === 'default') { Notification.requestPermission(); } }, []); useEffect(() => { if (!isAuthenticated || !token) return; // Conecta ao endpoint SSE passando o token na query string const url = `${import.meta.env['VITE_API_URL'] ?? '/api/v1'}/events?token=${token}`; const eventSource = new EventSource(url); const notify = (title: string, body?: string) => { // Toast notification addToast(title, 'info'); // Native notification if ('Notification' in window && Notification.permission === 'granted') { new Notification(title, { body, icon: '/favicon.ico' }); } }; // Quando um novo chamado é criado eventSource.addEventListener('ticket.created', (e) => { const data = JSON.parse(e.data); if (user?.role === 'admin' || user?.role === 'agent') { if (data.creatorId !== user.id) { notify(`Novo chamado: #${data.id}`, data.title); } } // Invalida a lista para forçar o refetch e atualizar o grid queryClient.invalidateQueries({ queryKey: ['tickets'] }); queryClient.invalidateQueries({ queryKey: ['stats'] }); }); // Quando o status ou responsável do chamado é atualizado eventSource.addEventListener('ticket.updated', (e) => { const data = JSON.parse(e.data); if (user && (user.id === data.creatorId || user.id === data.assigneeId) && user.id !== data.authorId) { notify(`Chamado #${data.id} atualizado`, `O status ou responsável foi alterado.`); } // Atualiza as listas, os stats e o detalhe do ticket queryClient.invalidateQueries({ queryKey: ['tickets'] }); queryClient.invalidateQueries({ queryKey: ['stats'] }); queryClient.invalidateQueries({ queryKey: ['ticket', data.id] }); }); // Quando um comentário é adicionado eventSource.addEventListener('comment.added', (e) => { const data = JSON.parse(e.data); if (user && (user.id === data.creatorId || user.id === data.assigneeId) && user.id !== data.authorId) { if (!data.isInternal || (data.isInternal && user.role !== 'requester')) { notify(`Nova mensagem no chamado #${data.ticketId}`, 'Você tem uma nova interação no chamado.'); } } // Atualiza os comentários do ticket afetado queryClient.invalidateQueries({ queryKey: ['tickets', data.ticketId, 'comments'] }); }); // Tratamento de erro na conexão eventSource.onerror = (err) => { console.error('Erro na conexão SSE:', err); // O navegador tentará reconectar automaticamente }; return () => { eventSource.close(); }; }, [token, isAuthenticated, queryClient, addToast]); }