chore: ticket section implement sse

This commit is contained in:
2026-06-19 18:06:11 +05:30
parent a567a52e26
commit fffd8e3e12
25 changed files with 711 additions and 307 deletions

View File

@@ -0,0 +1,119 @@
'use client';
import { useCallback, useEffect, useMemo, useState } 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 {
TicketDetailStatusEvent,
TicketProgressEvent,
TicketStreamErrorEvent,
} from '@/types';
import { ticketKeys } from '../queries/ticketKeys';
export interface TicketProcessingProgress {
progress: number;
message: string | null;
}
export interface TicketDetailLiveState {
progress: TicketProcessingProgress | null;
errorMessage: string | null;
}
const TICKET_DETAIL_RECONNECT_EVENTS = ['error'] as const;
function isHeartbeatEvent(event: { type?: string } | null | undefined) {
return event?.type === 'heartbeat';
}
export function useTicketDetailEvents(
ticketId: string | undefined,
enabled = true,
): TicketDetailLiveState {
const queryClient = useQueryClient();
const [progress, setProgress] = useState<TicketProcessingProgress | null>(
null,
);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const refetchTicket = useCallback(() => {
if (!ticketId) return;
queryClient.refetchQueries({ queryKey: ticketKeys.detail(ticketId) });
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
}, [queryClient, ticketId]);
const getToken = useCallback(
() => ticketService.createTicketEventsToken(ticketId as string),
[ticketId],
);
const getPath = useCallback(
(token: string) =>
API_ROUTES.TICKETS.DETAIL_EVENTS(ticketId as string, token),
[ticketId],
);
const events = useMemo(
() => ({
ticket_status: (event: TicketDetailStatusEvent) => {
if (isHeartbeatEvent(event)) return;
refetchTicket();
},
progress: (event: TicketProgressEvent) => {
setErrorMessage(null);
setProgress({
progress: event.progress,
message: event.message,
});
},
complete: (event: TicketProgressEvent) => {
setErrorMessage(null);
setProgress({
progress: 100,
message: event.message,
});
refetchTicket();
},
error: (event: TicketStreamErrorEvent) => {
setErrorMessage(
event.message ?? 'Live ticket updates disconnected. Reconnecting...',
);
},
heartbeat: () => undefined,
}),
[refetchTicket],
);
const shouldStop = useCallback(
(eventName: keyof typeof events, event: unknown) =>
eventName === 'ticket_status' &&
(event as TicketDetailStatusEvent).status === 'closed',
[],
);
const onConnectionError = useCallback(() => {
setErrorMessage('Live ticket updates disconnected. Reconnecting...');
}, []);
useEffect(() => {
setProgress(null);
setErrorMessage(null);
}, [ticketId]);
useSseWithToken({
enabled: Boolean(enabled && ticketId),
getToken,
getPath,
events,
reconnectOnEvents: TICKET_DETAIL_RECONNECT_EVENTS,
shouldStop,
onConnectionError,
});
return { progress, errorMessage };
}