import { useCallback, useState } from "react"; import { getBookingActions, type BookingActionContext, type BookingActionDef, } from "@/features/bookings/booking-actions.config"; import { useAuth } from "@/auth/useAuth"; import { useBookingMutations } from "@/hooks/bookings/useBookings"; /** A contract validity window must be a whole number of days, 1–365. */ function isValidValidityDays(value: string): boolean { const days = Number(value.trim()); return Number.isInteger(days) && days >= 1 && days <= 365; } /** * Decisions that must be applied to BOTH halves of a consolidated pair. The two * bookings share one wagon: accepting one alone would put half a wagon into the * approval chain, and cancelling one alone would strand the other on a wagon it * can no longer fill. */ const PAIRED_DECISIONS = { accept: "accept", cancel: "cancel", operationAccept: "operationAccept", requestChanges: "requestChanges", } as const; export function useBookingActionDialog( bookingId: string, context: BookingActionContext, ) { const [pendingAction, setPendingAction] = useState(null); const [inputValue, setInputValue] = useState(""); const [selectedFile, setSelectedFile] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); // Bookings no longer have an approval chain, so the dialog needs nothing // beyond the list-row context it was handed. const detailLoading = false; const mergedContext: BookingActionContext = { ...context }; const { user } = useAuth(); const mutations = useBookingMutations(bookingId); const actions = getBookingActions(mergedContext, user); const openAction = useCallback((action: BookingActionDef) => { setPendingAction(action); setInputValue(""); setSelectedFile(null); setDialogOpen(true); }, []); const closeDialog = useCallback(() => { setDialogOpen(false); setPendingAction(null); setInputValue(""); setSelectedFile(null); }, []); const runAction = useCallback(() => { if (!pendingAction) return; const onSuccess = () => closeDialog(); // A booking on a shared wagon routes the four pairable decisions through the // paired endpoint, which applies them to both halves all-or-nothing. Every // other action stays per booking. const pairedDecision = PAIRED_DECISIONS[pendingAction.id as keyof typeof PAIRED_DECISIONS]; if (context.consolidationPartnerId && pairedDecision) { if (pairedDecision === "accept") { const days = Number(inputValue.trim()); if (!Number.isInteger(days) || days < 1 || days > 365) return; mutations.pairedDecision.mutate( { decision: "accept", validityDays: days }, { onSuccess }, ); return; } mutations.pairedDecision.mutate( pairedDecision === "cancel" ? { decision: "cancel", reason: inputValue.trim() } : { decision: pairedDecision, note: inputValue.trim() }, { onSuccess }, ); return; } switch (pendingAction.id) { case "accept": { const days = Number(inputValue.trim()); if (!Number.isInteger(days) || days < 1 || days > 365) return; mutations.staffAccept.mutate(days, { onSuccess }); break; } case "requestChanges": mutations.requestChanges.mutate(inputValue.trim(), { onSuccess }); break; case "reject": mutations.staffReject.mutate(inputValue.trim(), { onSuccess }); break; case "operationAccept": mutations.reviewOperation.mutate({ decision: "ACCEPT" }, { onSuccess }); break; case "operationRequestChanges": mutations.reviewOperation.mutate( { decision: "REQUEST_CHANGES", note: inputValue.trim() }, { onSuccess }, ); break; case "viewContract": break; case "startTransit": mutations.startTransit.mutate(undefined, { onSuccess }); break; case "complete": mutations.complete.mutate(undefined, { onSuccess }); break; case "cancel": mutations.cancel.mutate(inputValue.trim(), { onSuccess }); break; default: break; } }, [ pendingAction, inputValue, selectedFile, mutations, closeDialog, ]); const confirmDisabled = mutations.isPending || (pendingAction?.input === "file" && !selectedFile) || (pendingAction?.input === "reason" && !inputValue.trim()) || (pendingAction?.input === "note" && !inputValue.trim()) || (pendingAction?.input === "days" && !isValidValidityDays(inputValue)); return { actions, pendingAction, inputValue, setInputValue, selectedFile, setSelectedFile, dialogOpen, setDialogOpen: (open: boolean) => { if (!open) closeDialog(); else setDialogOpen(true); }, openAction, closeDialog, runAction, mutations, confirmDisabled, detailLoading, mergedContext, }; }