add goverment booking

This commit is contained in:
Marshal
2026-07-31 00:41:51 +00:00
parent 34e4a7d13f
commit 65f18b4796
35 changed files with 913 additions and 88 deletions

View File

@@ -56,6 +56,7 @@ import { RescheduleTrainDialog } from "@/components/trainScheduling/RescheduleTr
import BookingWindowSettingsModal from "@/components/trainScheduling/BookingWindowSettingsModal";
// import { ScheduleBatchPanel } from "@/components/trainScheduling/ScheduleBatchPanel";
import { ScheduleBookingsStep } from "@/components/trainScheduling/ScheduleBookingsStep";
import { SwitchGovernmentBookingModal } from "@/components/trainScheduling/SwitchGovernmentBookingModal";
import { ScheduleWorkspacePanel } from "@/components/trainScheduling/ScheduleWorkspacePanel";
import { FreightTypeBadge } from "@/components/trainScheduling/ScheduleStatusBadge";
import {
@@ -79,6 +80,7 @@ import { trainSchedulingService } from "@/services/trainScheduling.service";
import { useToast } from "@/hooks/use-toast";
import type {
ContainerPlacement,
EligibleContainerBooking,
FreightType,
TrainSchedulePreviewResponse,
} from "@/types/trainScheduling";
@@ -110,6 +112,7 @@ export default function TrainScheduleV2DetailPage() {
const [gatepassFileUrl, setGatepassFileUrl] = useState("");
const [gatepassNotes, setGatepassNotes] = useState("");
const [dispatchConfirmOpen, setDispatchConfirmOpen] = useState(false);
const [switchTarget, setSwitchTarget] = useState<EligibleContainerBooking | null>(null);
const autoPreviewedRef = useRef(false);
const detailQuery = useQuery(
@@ -192,6 +195,7 @@ export default function TrainScheduleV2DetailPage() {
const preview = useMutation(api.trainScheduling.preview.mutationOptions());
const assign = useMutation(api.trainScheduling.assignBookings.mutationOptions());
const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
const switchGov = useMutation(api.trainScheduling.switchGovernmentBooking.mutationOptions());
const finalize = useMutation(api.trainScheduling.finalizeSchedule.mutationOptions());
const dispatch = useMutation(api.trainScheduling.dispatchSchedule.mutationOptions());
const downloadMarshalling = useMutation({
@@ -511,6 +515,17 @@ export default function TrainScheduleV2DetailPage() {
};
const handleUnassign = async (bookingId: string) => {
// Gov bookings never leave a train by removal — only by switching. The API
// enforces this too; the guard here just gives the warning without a call.
if (schedule?.bookings?.some((b) => b.id === bookingId && b.isGovernment)) {
toast({
title: "Government booking cannot be removed",
description:
"Government bookings cannot be removed from the train. They can only be switched onto another allocation.",
variant: "destructive",
});
return;
}
try {
await unassign.mutateAsync({ id: scheduleId, bookingId });
toast({ title: "Booking unassigned" });
@@ -631,6 +646,7 @@ export default function TrainScheduleV2DetailPage() {
id: b.id,
reference: b.reference ?? b.id.slice(0, 8),
weightTons: b.weightTons,
isGovernment: b.isGovernment,
}))}
eligibleItems={eligibleQuery.data?.items ?? []}
eligibleLoading={eligibleQuery.isLoading}
@@ -643,6 +659,7 @@ export default function TrainScheduleV2DetailPage() {
freightType={freightType}
canRemove={canModifyBookings}
onRemove={handleUnassign}
onSwitch={canModifyBookings ? setSwitchTarget : undefined}
/>
{canEditBookings ? (
@@ -1274,6 +1291,36 @@ export default function TrainScheduleV2DetailPage() {
onSaved={() => void detailQuery.refetch()}
/>
<SwitchGovernmentBookingModal
key={switchTarget?.id ?? "none"}
opened={Boolean(switchTarget)}
onClose={() => setSwitchTarget(null)}
govBooking={switchTarget}
assignedBookings={schedule.bookings ?? []}
loading={switchGov.isPending}
onConfirm={async (removeBookingIds) => {
if (!scheduleId || !switchTarget) return;
try {
await switchGov.mutateAsync({
id: scheduleId,
governmentBookingId: switchTarget.id,
removeBookingIds,
});
toast({ title: `Government booking ${switchTarget.reference} switched onto the train` });
setSwitchTarget(null);
setSelectedBookingIds([]);
setPreviewResult(null);
autoPreviewedRef.current = false;
} catch (err) {
toast({
title: "Switch failed",
description: parseError(err, "Could not switch the government booking"),
variant: "destructive",
});
}
}}
/>
<Modal
opened={dispatchConfirmOpen}
onClose={() => setDispatchConfirmOpen(false)}