194 lines
5.5 KiB
TypeScript
194 lines
5.5 KiB
TypeScript
'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,
|
|
ReviewRepairPayload,
|
|
SubmitRepairPayload,
|
|
SubmitRepairUploadPayload,
|
|
TicketDetail,
|
|
TicketListParams,
|
|
TicketStatus,
|
|
} from '@/types';
|
|
|
|
import { ticketKeys } from '../queries/ticketKeys';
|
|
|
|
const TICKET_TABLE_REFRESH_MS = 5 * 60 * 1000;
|
|
|
|
interface UseTicketsQueryParams {
|
|
skip: number;
|
|
limit: number;
|
|
status?: TicketStatus | 'all';
|
|
chainageId?: string;
|
|
}
|
|
|
|
function buildTicketListParams({
|
|
skip,
|
|
limit,
|
|
status,
|
|
chainageId,
|
|
}: UseTicketsQueryParams): TicketListParams {
|
|
return {
|
|
skip,
|
|
limit,
|
|
status: status === 'all' ? undefined : status,
|
|
chainage_id: chainageId || undefined,
|
|
};
|
|
}
|
|
|
|
export function useTicketsQuery(params: UseTicketsQueryParams) {
|
|
const { skip, limit, status, chainageId } = params;
|
|
const listParams = useMemo(
|
|
() => buildTicketListParams({ skip, limit, status, chainageId }),
|
|
[chainageId, limit, skip, status],
|
|
);
|
|
|
|
return useQuery({
|
|
queryKey: ticketKeys.list(listParams),
|
|
queryFn: () => ticketService.getTickets(listParams),
|
|
staleTime: TICKET_TABLE_REFRESH_MS,
|
|
refetchInterval: TICKET_TABLE_REFRESH_MS,
|
|
});
|
|
}
|
|
|
|
export function useTicketDetailQuery(ticketId: string | undefined) {
|
|
return useQuery({
|
|
queryKey: ticketKeys.detail(ticketId ?? ''),
|
|
queryFn: () => ticketService.getTicketDetail(ticketId as string),
|
|
enabled: Boolean(ticketId),
|
|
});
|
|
}
|
|
|
|
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) => {
|
|
toast.success('Ticket assigned');
|
|
queryClient.setQueryData<TicketDetail>(
|
|
ticketKeys.detail(ticketId),
|
|
(current) => mergeTicketDetailResponse(current, ticket),
|
|
);
|
|
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
|
|
},
|
|
onError: () => toast.error('Failed to assign ticket'),
|
|
});
|
|
}
|
|
|
|
export function useStartTicketMutation(ticketId: string) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: () => ticketService.startTicket(ticketId),
|
|
onSuccess: (ticket) => {
|
|
toast.success('Ticket started');
|
|
queryClient.setQueryData<TicketDetail>(
|
|
ticketKeys.detail(ticketId),
|
|
(current) => mergeTicketDetailResponse(current, ticket),
|
|
);
|
|
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
|
|
},
|
|
onError: () => toast.error('Failed to start 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<TicketDetail>(
|
|
ticketKeys.detail(ticketId),
|
|
(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) => {
|
|
toast.success('Repair evidence uploaded');
|
|
queryClient.setQueryData<TicketDetail>(
|
|
ticketKeys.detail(ticketId),
|
|
(current) => mergeTicketDetailResponse(current, ticket),
|
|
);
|
|
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
|
|
},
|
|
onError: () => toast.error('Failed to upload repair evidence'),
|
|
});
|
|
}
|
|
|
|
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<TicketDetail>(
|
|
ticketKeys.detail(ticketId),
|
|
(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: () => ticketService.closeTicket(ticketId),
|
|
onSuccess: (ticket) => {
|
|
toast.success('Ticket closed');
|
|
queryClient.setQueryData<TicketDetail>(
|
|
ticketKeys.detail(ticketId),
|
|
(current) => mergeTicketDetailResponse(current, ticket),
|
|
);
|
|
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
|
|
},
|
|
onError: () => toast.error('Failed to close ticket'),
|
|
});
|
|
}
|