aboutsummaryrefslogtreecommitdiff
path: root/web/src/hooks/useTicketEvents.ts
blob: 7994ace33b078784b485f5527c7f9bd269a6f386 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
 * 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]);
}