'use client'; import { useCallback, useMemo, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { API_ROUTES } from '@/constants/apiRoutes'; import { useSseWithToken } from '@/hooks/sse/useSseWithToken'; import { ticketService } from '@/services/api'; import type { TicketListItem, TicketListParams, TicketListResponse, TicketTableStatusEvent, } from '@/types'; import { ticketKeys } from '../queries/ticketKeys'; function patchTicketLists( current: TicketListResponse | undefined, event: TicketTableStatusEvent, params?: TicketListParams, ) { if (!current || !event.id) return current; const nextRow = buildTicketListItem(event); const existingIndex = current.items.findIndex( (ticket) => ticket.id === event.id, ); if (existingIndex >= 0) { return { ...current, items: current.items.map((ticket) => ticket.id === event.id ? mergeTicketListItem(ticket, event) : ticket, ), }; } if (event.kind !== 'created') return current; if (!shouldCountCreatedTicket(event, params)) return current; if (!nextRow || !isFirstPage(params)) { return { ...current, total: current.total + 1, }; } const pageLimit = params?.limit ?? current.items.length + 1; return { ...current, items: [nextRow, ...current.items].slice(0, pageLimit), total: current.total + 1, }; } function shouldCountCreatedTicket( event: TicketTableStatusEvent, params?: TicketListParams, ) { if (params?.chainage_id && event.chainage_id !== params.chainage_id) { return false; } return true; } function isFirstPage(params?: TicketListParams) { return (params?.skip ?? 0) === 0; } function getTicketListParams(queryKey: readonly unknown[]) { const params = queryKey[2]; if (!params || typeof params !== 'object' || Array.isArray(params)) { return undefined; } return params as TicketListParams; } function buildTicketListItem( event: TicketTableStatusEvent, ): TicketListItem | null { if ( !event.id || !event.ticket_name || event.detection_count === undefined || !event.updated_at ) { return null; } return { id: event.id, ticket_name: event.ticket_name, chainage_id: event.chainage_id ?? null, chainage_name: event.chainage_name ?? null, default_defect_class: event.default_defect_class ?? null, detection_count: event.detection_count, created_by_name: event.created_by_name ?? null, created_at: event.created_at ?? event.updated_at, updated_at: event.updated_at, }; } function mergeTicketListItem( current: TicketListItem, event: TicketTableStatusEvent, ): TicketListItem { return { ...current, ticket_name: event.ticket_name ?? current.ticket_name, chainage_id: event.chainage_id ?? current.chainage_id, chainage_name: event.chainage_name ?? current.chainage_name, default_defect_class: event.default_defect_class ?? current.default_defect_class, detection_count: event.detection_count ?? current.detection_count, created_by_name: event.created_by_name ?? current.created_by_name, created_at: event.created_at ?? current.created_at, updated_at: event.updated_at ?? current.updated_at, }; } export function useTenantTicketTableEvents(enabled = true) { const queryClient = useQueryClient(); const hasInvalidatedAfterConnectionErrorRef = useRef(false); const events = useMemo( () => ({ ticket_status: (event: TicketTableStatusEvent) => { queryClient .getQueryCache() .findAll({ queryKey: ticketKeys.lists() }) .forEach((query) => { queryClient.setQueryData( query.queryKey, (current) => patchTicketLists( current, event, getTicketListParams(query.queryKey), ), ); }); }, }), [queryClient], ); const getPath = useCallback( (token: string) => API_ROUTES.TICKET_EVENTS.TENANT(token), [], ); const onConnectionError = useCallback(() => { if (hasInvalidatedAfterConnectionErrorRef.current) return; hasInvalidatedAfterConnectionErrorRef.current = true; queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, [queryClient]); const onConnectionOpen = useCallback(() => { hasInvalidatedAfterConnectionErrorRef.current = false; }, []); useSseWithToken({ enabled, getToken: ticketService.createTenantTicketEventsToken, getPath, events, onConnectionError, onConnectionOpen, }); }