shoc-frontend-new/src/domain/uplifts/use-cases/use-uplift-actions.ts
Arthur Bassi 3c5941b895 chore(work-orders): resolve SH-388 uplift conflicts onto current main
Keep the slide-over uplift tab and the evidence upload while taking the
current board, dashboard, and vendor changes from main.
2026-09-24 15:17:34 -03:00

145 lines
4.8 KiB
TypeScript

import {
useMutation,
useQuery,
useQueryClient,
type UseMutationResult,
type UseQueryResult,
} from "@tanstack/react-query";
import { toast } from "react-toastify";
import { upliftsApi } from "@/domain/uplifts/api/uplifts-api";
import type { UpliftRequest } from "@/domain/uplifts/types/uplift";
import { queryKeys } from "@/infra/query-key/query-key";
import { requireQueryParam } from "@/lib/query/require-query-param";
export function useUpliftsForDispatch(
dispatchId: string | number | undefined,
): UseQueryResult<UpliftRequest[], Error> {
return useQuery({
queryKey: queryKeys.uplifts.forDispatch(dispatchId ?? ""),
queryFn: () => upliftsApi.listForDispatch(requireQueryParam(dispatchId, "Dispatch id")),
enabled: dispatchId !== undefined && dispatchId !== "",
});
}
function invalidateUpliftQueries(
queryClient: ReturnType<typeof useQueryClient>,
dispatchId?: string | number,
) {
void queryClient.invalidateQueries({ queryKey: queryKeys.uplifts.all });
// why: Queue decisions update the same uplift records rendered by Work Order views. Invalidate that
// shared cache even when the queue has no dispatch context.
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
if (dispatchId !== undefined && dispatchId !== "") {
void queryClient.invalidateQueries({ queryKey: queryKeys.uplifts.forDispatch(dispatchId) });
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.dispatch(dispatchId) });
void queryClient.invalidateQueries({ queryKey: queryKeys.vendorPos.detail(dispatchId) });
}
}
function invalidateWorkOrderUpliftQueries(queryClient: ReturnType<typeof useQueryClient>) {
void queryClient.invalidateQueries({ queryKey: queryKeys.uplifts.all });
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
}
export interface ApproveUpliftInput {
id: string | number;
note?: string;
amount?: number;
woNumber?: string;
}
export interface DecisionUpliftInput {
id: string | number;
note: string;
amount?: number;
woNumber?: string;
}
function decisionAmount(amount: number | undefined): string {
if (amount == null || !Number.isFinite(amount)) return "the requested amount";
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(amount);
}
function decisionWorkOrder(woNumber: string | undefined): string {
return woNumber?.trim() || "the work order";
}
export function useApproveUplift(
dispatchId?: string | number,
): UseMutationResult<void, Error, ApproveUpliftInput> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, note }: ApproveUpliftInput) => upliftsApi.approve(id, note),
onSuccess: (_, variables) => {
invalidateUpliftQueries(queryClient, dispatchId);
toast.success(
`Uplift approved — ${decisionAmount(variables.amount)} on ${decisionWorkOrder(variables.woNumber)}`,
);
},
onError: (error: Error) => {
toast.error(error.message || "Failed to approve uplift");
},
});
}
export function useRejectUplift(
dispatchId?: string | number,
): UseMutationResult<void, Error, DecisionUpliftInput> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, note }: DecisionUpliftInput) => upliftsApi.reject(id, note),
onSuccess: (_, variables) => {
invalidateUpliftQueries(queryClient, dispatchId);
toast.success(
`Uplift rejected — ${decisionAmount(variables.amount)} on ${decisionWorkOrder(variables.woNumber)}`,
);
},
onError: (error: Error) => {
toast.error(error.message || "Failed to reject uplift");
},
});
}
export function useRequestChangesUplift(
dispatchId?: string | number,
): UseMutationResult<void, Error, DecisionUpliftInput> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, note }: { id: string | number; note: string }) =>
upliftsApi.requestChanges(id, note),
onSuccess: () => {
invalidateUpliftQueries(queryClient, dispatchId);
toast.success("Changes requested");
},
onError: (error: Error) => {
toast.error(error.message || "Failed to request changes");
},
});
}
export interface RevokeUpliftInput {
upliftId: string | number;
reason: string;
amount?: number;
woNumber?: string;
}
export function useRevokeUplift(): UseMutationResult<void, Error, RevokeUpliftInput> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ upliftId, reason }: RevokeUpliftInput) => upliftsApi.revoke(upliftId, reason),
onSuccess: (_, variables) => {
invalidateWorkOrderUpliftQueries(queryClient);
toast.success(
`Uplift revoked — ${decisionAmount(variables.amount)} on ${decisionWorkOrder(variables.woNumber)}`,
);
},
onError: (error: Error) => {
toast.error(error.message || "Failed to revoke uplift");
},
});
}