mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 20:05:41 +00:00
add goverment booking
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
ArrowLeft,
|
||||
FileSignature,
|
||||
FolderOpen,
|
||||
Layers,
|
||||
LayoutGrid,
|
||||
@@ -254,6 +255,20 @@ export default function BookingRequestDetailPage() {
|
||||
booking={booking}
|
||||
mutations={mutations}
|
||||
/>
|
||||
{booking.isGovernment && booking.contractSummary && (
|
||||
<Button
|
||||
fullWidth
|
||||
variant="default"
|
||||
leftSection={<FileSignature size={16} />}
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/dashboard/booking-requests/${booking.id}/contract`,
|
||||
)
|
||||
}
|
||||
>
|
||||
View / sign contract
|
||||
</Button>
|
||||
)}
|
||||
{booking.customsClearingEnabled && (
|
||||
<Button
|
||||
fullWidth
|
||||
|
||||
@@ -400,8 +400,13 @@ export default function NewBookingPage() {
|
||||
}),
|
||||
onSuccess: async (booking) => {
|
||||
if (isGovernment) {
|
||||
// The server already expedited + generated the contract at creation;
|
||||
// this call is an idempotent no-op that doubles as a retry if that
|
||||
// best-effort step failed.
|
||||
await bookingsService.governmentExpedite(booking.id);
|
||||
toast.success("Government booking created and expedited to scheduling");
|
||||
toast.success(
|
||||
"Government booking created — contract generated, priority scheduling queued",
|
||||
);
|
||||
} else {
|
||||
toast.success("Booking created as draft");
|
||||
}
|
||||
@@ -891,7 +896,7 @@ export default function NewBookingPage() {
|
||||
<Info size={14} color="var(--mantine-color-gray-5)" style={{ marginTop: 2, flexShrink: 0 }} />
|
||||
<Text size="xs" c="dimmed">
|
||||
{isGovernment
|
||||
? "Government bookings skip the commercial 3-hour hold and enter the priority lane."
|
||||
? "Government bookings skip every customer step: paid & eligible immediately, contract generated automatically (signable any time), priority seat on any open train of the route."
|
||||
: "Overweight container lines are allowed here and flagged later at scheduling."}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -98,7 +98,8 @@ export default function ContractViewPage() {
|
||||
const openSign = () => {
|
||||
setSignerName(data?.savedSignature?.signerDisplayName ?? "");
|
||||
setSignatureData(null);
|
||||
setStampData(null);
|
||||
// Prefill with the reusable stamp saved on the profile; still replaceable.
|
||||
setStampData(data?.savedSignature?.stampImageUrl ?? null);
|
||||
setDrawNew(false);
|
||||
setSignOpen(true);
|
||||
};
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user