import { useMutation, useQueryClient, type UseMutationResult } from "@tanstack/react-query"; import { useNavigate } from "react-router"; import { toast } from "react-toastify"; import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api"; import { BoardPatchConflictError } from "@/domain/work-orders/errors/board-patch-errors"; import type { UploadableMediaCategory } from "@/domain/work-orders/mappers/work-order-board-detail-mapper"; import type { WorkOrderFormSchemaValues } from "@/domain/work-orders/schemas/work-order-schema"; import type { DispatchComment, DispatchSignoff, DispatchToVendorPayload, UpdateDispatchPayload, WorkOrderComment, WorkOrderDetail, WorkOrderFormValues, WorkOrderVendorUpdatePayload, } from "@/domain/work-orders/types/work-order"; import type { WorkOrderBoardCompletion, WorkOrderBoardMediaItem, } from "@/domain/work-orders/types/work-order-board-detail"; import { queryKeys } from "@/infra/query-key/query-key"; function invalidateWorkOrders(queryClient: ReturnType) { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all }); } function invalidateBoardDetail( queryClient: ReturnType, workOrderId: string | number, ) { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.boardDetail(workOrderId), }); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.boardMedia(workOrderId), }); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); } export interface AddWorkOrderCommentInput { text: string; mentions?: string[]; commentType?: "customer" | "internal"; } export interface UpdateWorkOrderCommentInput { commentId: string | number; text: string; } export interface UploadWorkOrderMediaInput { file: File; category: UploadableMediaCategory; } export interface UploadCompletionDocInput { file: File; workOrderVersion: string; signOffName?: string; signOffSignature?: string; } export interface AddDispatchCommentInput { text: string; sendEmail: boolean; } export interface UpdateChecklistItemInput { itemId: string | number; isCompleted: boolean; completedBy: string; } export interface AddDispatchSignoffInput { signoffType: string; name: string; signature: string; signatureMethod: string; } export interface ChangeWorkOrderAssignmentInput { userId: string | number; userName: string; } export interface UpdateWorkOrderVendorInput { vendorId: string | number; payload: WorkOrderVendorUpdatePayload; } export function useCreateWorkOrder(): UseMutationResult< WorkOrderDetail, Error, WorkOrderFormSchemaValues | WorkOrderFormValues > { const queryClient = useQueryClient(); const navigate = useNavigate(); return useMutation({ mutationFn: (values: WorkOrderFormSchemaValues | WorkOrderFormValues) => workOrdersApi.create(values), onSuccess: (data) => { invalidateWorkOrders(queryClient); toast.success("Work Order created successfully!"); navigate(`/workorders/${data.id}`); }, onError: (error: Error) => { toast.error(error.message || "Failed to create work order"); }, }); } export function useUpdateWorkOrder( id: string | number, ): UseMutationResult { const queryClient = useQueryClient(); const navigate = useNavigate(); return useMutation({ mutationFn: (values: WorkOrderFormSchemaValues | WorkOrderFormValues) => workOrdersApi.update(id, values), onSuccess: () => { invalidateWorkOrders(queryClient); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(id) }); toast.success("Work Order updated successfully!"); navigate(`/workorders/${id}`); }, onError: (error: Error) => { toast.error(error.message || "Failed to update work order"); }, }); } export function useUpdateWorkOrderVendor( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ vendorId, payload }) => workOrdersApi.updateVendorFromWorkOrder(vendorId, payload), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all }); void queryClient.invalidateQueries({ queryKey: queryKeys.vendors.all }); toast.success("Vendor contact updated"); }, onError: (error: Error) => { toast.error(error.message || "Failed to update vendor contact"); }, }); } export function useAddWorkOrderComment( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ text, mentions }: AddWorkOrderCommentInput) => workOrdersApi.createComment(workOrderId, text, mentions), onSuccess: () => { invalidateBoardDetail(queryClient, workOrderId); }, onError: (error: Error) => { toast.error(error.message || "Failed to add comment"); }, }); } export function useUpdateWorkOrderComment( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ commentId, text }: UpdateWorkOrderCommentInput) => workOrdersApi.updateComment(workOrderId, commentId, text), onSuccess: () => { invalidateBoardDetail(queryClient, workOrderId); }, onError: (error: Error) => { toast.error(error.message || "Failed to update comment"); }, }); } export function useUploadWorkOrderMedia( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ file, category }: UploadWorkOrderMediaInput) => workOrdersApi.uploadMedia(workOrderId, file, category), onSuccess: () => { invalidateBoardDetail(queryClient, workOrderId); invalidateWorkOrders(queryClient); toast.success("File uploaded"); }, onError: (error: Error) => { toast.error(error.message || "Failed to upload file"); }, }); } export function useDeleteWorkOrderMedia( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: (mediaId: string | number) => workOrdersApi.deleteMedia(workOrderId, mediaId), onSuccess: () => { invalidateBoardDetail(queryClient, workOrderId); invalidateWorkOrders(queryClient); toast.success("File removed"); }, onError: (error: Error) => { toast.error(error.message || "Failed to remove file"); }, }); } export function useUploadCompletionDoc( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: (params: UploadCompletionDocInput) => workOrdersApi.uploadCompletionDoc(workOrderId, params), onSuccess: () => { invalidateBoardDetail(queryClient, workOrderId); invalidateWorkOrders(queryClient); toast.success("Completion document uploaded"); }, onError: (error: Error) => { if (error instanceof BoardPatchConflictError) { invalidateBoardDetail(queryClient, workOrderId); toast.error("Document is out of date. Refresh and try again."); return; } toast.error(error.message || "Failed to upload completion document"); }, }); } export function useChangeWorkOrderStatus( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: (status: string) => workOrdersApi.changeStatus(workOrderId, status), onSuccess: (_data, status) => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); invalidateWorkOrders(queryClient); toast.success(`Status changed to ${status}`); }, onError: (error: Error) => { toast.error(error.message || "Failed to update status"); }, }); } export function useChangeWorkOrderAssignment( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ userId, userName }: ChangeWorkOrderAssignmentInput) => workOrdersApi.changeAssignment(workOrderId, userId).then(() => userName), onSuccess: (userName) => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); invalidateWorkOrders(queryClient); toast.success(`Assigned to ${userName || "Unassigned"}`); }, onError: (error: Error) => { toast.error(error.message || "Failed to update assignment"); }, }); } export function useDispatchToVendor( workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: workOrdersApi.dispatchToVendor, onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); invalidateWorkOrders(queryClient); toast.success("Dispatched successfully"); }, onError: (error: Error) => { toast.error(error.message || "Failed to dispatch"); }, }); } export function useUpdateDispatch( dispatchId: string | number, workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: workOrdersApi.updateDispatch, onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) }); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); toast.success("Dispatch updated"); }, onError: (error: Error) => { toast.error(error.message || "Failed to update dispatch"); }, }); } export function useAddDispatchComment( dispatchId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ text, sendEmail }: AddDispatchCommentInput) => workOrdersApi.addDispatchComment(dispatchId, text, sendEmail), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) }); }, onError: (error: Error) => { toast.error(error.message || "Failed to send message"); }, }); } export function useUpdateChecklistItem( dispatchId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ itemId, isCompleted, completedBy }: UpdateChecklistItemInput) => workOrdersApi.updateChecklistItem(itemId, isCompleted, completedBy), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) }); }, onError: (error: Error) => { toast.error(error.message || "Failed to update checklist item"); }, }); } export function useAddChecklistItem( dispatchId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: (itemText: string) => workOrdersApi.addChecklistItem(dispatchId, itemText), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) }); }, onError: (error: Error) => { toast.error(error.message || "Failed to add checklist item"); }, }); } export function useAddDispatchSignoff( dispatchId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: (input: AddDispatchSignoffInput) => workOrdersApi.addDispatchSignoff(dispatchId, input), onSuccess: (_data, variables) => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) }); toast.success(`${variables.signoffType} sign-off collected`); }, onError: (error: Error) => { toast.error(error.message || "Failed to save sign-off"); }, }); } export function useVerifyDispatch( dispatchId: string | number, workOrderId: string | number, ): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: () => workOrdersApi.verifyDispatch(dispatchId), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) }); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.detail(workOrderId) }); toast.success("Dispatch verified"); }, onError: (error: Error) => { toast.error(error.message || "Failed to verify"); }, }); }