'use client'; import { useMemo } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { ticketService } from '@/services/api'; import type { AssignTicketPayload, CloseTicketPayload, RequestTicketExtensionPayload, ReviewTicketExtensionPayload, ReviewRepairPayload, SubmitRepairPayload, SubmitRepairUploadPayload, TicketDetail, TicketListParams, } from '@/types'; import { ticketKeys } from '../queries/ticketKeys'; const TICKET_TABLE_REFRESH_MS = 5 * 60 * 1000; interface UseTicketsQueryParams { skip: number; limit: number; chainageId?: string; } function buildTicketListParams({ skip, limit, chainageId, }: UseTicketsQueryParams): TicketListParams { return { skip, limit, chainage_id: chainageId || undefined, }; } export function useTicketsQuery(params: UseTicketsQueryParams) { const { skip, limit, chainageId } = params; const listParams = useMemo( () => buildTicketListParams({ skip, limit, chainageId }), [chainageId, limit, skip], ); return useQuery({ queryKey: ticketKeys.list(listParams), queryFn: () => ticketService.getTickets(listParams), staleTime: TICKET_TABLE_REFRESH_MS, refetchInterval: TICKET_TABLE_REFRESH_MS, }); } export function useTicketOverviewQuery(ticketId: string | undefined) { return useQuery({ queryKey: ticketKeys.overview(ticketId ?? ''), queryFn: () => ticketService.getTicketOverview(ticketId as string), enabled: Boolean(ticketId), }); } export function useTicketClassDetailQuery( ticketId: string | undefined, defectClass: string | undefined, ) { return useQuery({ queryKey: ticketKeys.classDetail(ticketId ?? '', defectClass ?? ''), queryFn: () => ticketService.getTicketClassDetail( ticketId as string, defectClass as string, ), enabled: Boolean(ticketId && defectClass), }); } export function useTicketDefectClassesQuery(ticketId: string | undefined) { return useQuery({ queryKey: ticketKeys.defectClasses(ticketId ?? ''), queryFn: () => ticketService.getTicketDefectClasses(ticketId as string), enabled: Boolean(ticketId), }); } export function useTicketExtensionRequestQuery( ticketId: string | undefined, assignmentId: number | undefined, ) { return useQuery({ queryKey: ticketKeys.extensionRequest(ticketId ?? '', assignmentId), queryFn: () => ticketService.getTicketExtensionRequest( ticketId as string, assignmentId as number, ), enabled: Boolean(ticketId && assignmentId), }); } export function useAssignableTicketUsersQuery(enabled: boolean) { return useQuery({ queryKey: ticketKeys.assignableUsers(), queryFn: () => ticketService.getAssignableUsers(), enabled, }); } function mergeTicketDetailResponse( current: TicketDetail | undefined, next: TicketDetail, ) { if (!current) return next; return { ...current, ...next, history: next.history ?? current.history, }; } export function useAssignTicketMutation(ticketId: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: AssignTicketPayload) => ticketService.assignTicket(ticketId, payload), onSuccess: (ticket, payload) => { toast.success('Ticket assigned'); queryClient.setQueryData( ticketKeys.classDetail(ticketId, payload.defect_class), (current) => mergeTicketDetailResponse(current, ticket), ); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to assign ticket'), }); } export function useSubmitRepairMutation(ticketId: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: SubmitRepairPayload) => ticketService.submitRepair(ticketId, payload), onSuccess: (ticket) => { toast.success('Repair submitted'); queryClient.setQueryData( ticketKeys.classDetail(ticketId, ticket.defect_class), (current) => mergeTicketDetailResponse(current, ticket), ); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to submit repair'), }); } export function useSubmitRepairUploadMutation(ticketId: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: SubmitRepairUploadPayload) => ticketService.submitRepairUpload(ticketId, payload), onSuccess: (ticket, payload) => { toast.success('Repair evidence uploaded'); queryClient.setQueryData( ticketKeys.classDetail(ticketId, payload.defect_class), (current) => mergeTicketDetailResponse(current, ticket), ); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to upload repair evidence'), }); } export function useRequestTicketExtensionMutation( ticketId: string, assignmentId: number | undefined, defectClass: string, ) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: RequestTicketExtensionPayload) => { if (assignmentId === undefined) { throw new Error('No active assignment is available for this ticket.'); } return ticketService.requestTicketExtension( ticketId, assignmentId, payload, ); }, onSuccess: () => { toast.success('Extension request submitted'); queryClient.invalidateQueries({ queryKey: ticketKeys.extensionRequest(ticketId, assignmentId), }); queryClient.invalidateQueries({ queryKey: ticketKeys.classDetail(ticketId, defectClass), }); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to request an extension'), }); } export function useReviewTicketExtensionMutation( ticketId: string, assignmentId: number, defectClass: string, ) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: ReviewTicketExtensionPayload) => ticketService.reviewTicketExtension(ticketId, assignmentId, payload), onSuccess: (_response, payload) => { toast.success( payload.action === 'approve' ? 'Extension request approved' : 'Extension request rejected', ); queryClient.invalidateQueries({ queryKey: ticketKeys.extensionRequest(ticketId, assignmentId), }); queryClient.invalidateQueries({ queryKey: ticketKeys.classDetail(ticketId, defectClass), }); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to review extension request'), }); } export function useReviewRepairMutation(ticketId: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: ReviewRepairPayload) => ticketService.reviewRepair(ticketId, payload), onSuccess: (ticket, payload) => { toast.success( payload.action === 'approve' ? 'Repair approved' : 'Repair rejected', ); queryClient.setQueryData( ticketKeys.classDetail(ticketId, payload.defect_class), (current) => mergeTicketDetailResponse(current, ticket), ); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to review repair'), }); } export function useCloseTicketMutation(ticketId: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (payload: CloseTicketPayload) => ticketService.closeTicket(ticketId, payload), onSuccess: (ticket, payload) => { toast.success('Ticket closed'); queryClient.setQueryData( ticketKeys.classDetail(ticketId, payload.defect_class), (current) => mergeTicketDetailResponse(current, ticket), ); queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, onError: () => toast.error('Failed to close ticket'), }); }