mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
add goverment booking
This commit is contained in:
@@ -5,6 +5,7 @@ import toast from "react-hot-toast";
|
||||
|
||||
import { api } from "@/services/api";
|
||||
import { ContractSignaturePad } from "@/components/bookings/ContractSignaturePad";
|
||||
import { StampUpload } from "@/components/contracts/StampUpload";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -40,6 +41,7 @@ export function MySignatureCard() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [signerName, setSignerName] = useState("");
|
||||
const [signatureData, setSignatureData] = useState<string | null>(null);
|
||||
const [stampData, setStampData] = useState<string | null>(null);
|
||||
|
||||
const defaultName =
|
||||
user?.name?.en || user?.username || user?.email || "";
|
||||
@@ -47,6 +49,7 @@ export function MySignatureCard() {
|
||||
const openDialog = () => {
|
||||
setSignerName(saved?.signerDisplayName ?? defaultName);
|
||||
setSignatureData(null);
|
||||
setStampData(saved?.stampImageUrl ?? null);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
@@ -56,6 +59,10 @@ export function MySignatureCard() {
|
||||
{
|
||||
signerDisplayName: signerName.trim(),
|
||||
signatureImageBase64: signatureData,
|
||||
// Only send the stamp when it changed — omitted keeps the saved one.
|
||||
...(stampData && stampData !== saved?.stampImageUrl
|
||||
? { stampImageBase64: stampData }
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
@@ -101,6 +108,18 @@ export function MySignatureCard() {
|
||||
You have not saved a signature yet.
|
||||
</p>
|
||||
)}
|
||||
{saved?.stampImageUrl && (
|
||||
<div className="space-y-2">
|
||||
<div className="overflow-hidden rounded-lg border-2 border-dashed border-border bg-white">
|
||||
<img
|
||||
src={saved.stampImageUrl}
|
||||
alt="My saved company stamp"
|
||||
className="mx-auto h-24 w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">Company stamp</p>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={openDialog}>
|
||||
{saved?.signatureImageUrl ? "Update signature" : "Add signature"}
|
||||
</Button>
|
||||
@@ -126,6 +145,11 @@ export function MySignatureCard() {
|
||||
/>
|
||||
</div>
|
||||
<ContractSignaturePad onChange={setSignatureData} />
|
||||
<StampUpload
|
||||
value={stampData}
|
||||
onChange={setStampData}
|
||||
description="Stored on your profile and prefilled when you sign contracts."
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import { ArrowRight, Package } from "lucide-react";
|
||||
import { ArrowRight, Landmark, Package } from "lucide-react";
|
||||
import {
|
||||
Accordion,
|
||||
Badge,
|
||||
@@ -21,11 +21,13 @@ function EligibleBookingRow({
|
||||
freightType,
|
||||
selected,
|
||||
onToggle,
|
||||
onSwitch,
|
||||
}: {
|
||||
booking: EligibleContainerBooking;
|
||||
freightType?: FreightType;
|
||||
selected: boolean;
|
||||
onToggle: () => void;
|
||||
onSwitch?: (booking: EligibleContainerBooking) => void;
|
||||
}) {
|
||||
const resolvedFreightType = booking.freightType ?? freightType;
|
||||
const isBulk = resolvedFreightType === "BULK";
|
||||
@@ -61,6 +63,26 @@ function EligibleBookingRow({
|
||||
{booking.schedulingStatus}
|
||||
</Badge>
|
||||
) : null}
|
||||
{booking.isGovernment ? (
|
||||
<Badge
|
||||
variant="light"
|
||||
size="xs"
|
||||
color="yellow"
|
||||
leftSection={<Landmark size={10} />}
|
||||
>
|
||||
Government
|
||||
</Badge>
|
||||
) : null}
|
||||
{booking.isGovernment && onSwitch ? (
|
||||
<Button
|
||||
variant="light"
|
||||
color="yellow"
|
||||
size="compact-xs"
|
||||
onClick={() => onSwitch(booking)}
|
||||
>
|
||||
Switch onto train
|
||||
</Button>
|
||||
) : null}
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed">
|
||||
{booking.customer}
|
||||
@@ -102,6 +124,7 @@ export function EligibleBookingsPanel({
|
||||
onSelectionChange,
|
||||
assignedIds = [],
|
||||
freightType,
|
||||
onSwitch,
|
||||
}: {
|
||||
items: EligibleContainerBooking[];
|
||||
isLoading?: boolean;
|
||||
@@ -109,6 +132,7 @@ export function EligibleBookingsPanel({
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
assignedIds?: string[];
|
||||
freightType?: FreightType;
|
||||
onSwitch?: (booking: EligibleContainerBooking) => void;
|
||||
}) {
|
||||
const assignedSet = useMemo(() => new Set(assignedIds), [assignedIds]);
|
||||
|
||||
@@ -230,6 +254,7 @@ export function EligibleBookingsPanel({
|
||||
freightType={freightType}
|
||||
selected={selectedIds.includes(booking.id)}
|
||||
onToggle={() => toggle(booking.id)}
|
||||
onSwitch={onSwitch}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Tabs,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { ArrowRight, Package, Train } from "lucide-react";
|
||||
import { ArrowRight, Landmark, Package, Train } from "lucide-react";
|
||||
|
||||
import type { EligibleContainerBooking, FreightType } from "@/types/trainScheduling";
|
||||
|
||||
@@ -17,6 +17,7 @@ export type AssignedBookingRow = {
|
||||
id: string;
|
||||
reference: string;
|
||||
weightTons?: number;
|
||||
isGovernment?: boolean;
|
||||
};
|
||||
|
||||
export function ScheduleBookingsStep({
|
||||
@@ -29,6 +30,7 @@ export function ScheduleBookingsStep({
|
||||
freightType,
|
||||
canRemove,
|
||||
onRemove,
|
||||
onSwitch,
|
||||
}: {
|
||||
assignedBookings: AssignedBookingRow[];
|
||||
eligibleItems: EligibleContainerBooking[];
|
||||
@@ -39,6 +41,7 @@ export function ScheduleBookingsStep({
|
||||
freightType?: FreightType;
|
||||
canRemove?: boolean;
|
||||
onRemove?: (bookingId: string) => void;
|
||||
onSwitch?: (booking: EligibleContainerBooking) => void;
|
||||
}) {
|
||||
return (
|
||||
<Paper p="md" radius="lg" withBorder style={{ borderColor: "var(--mantine-color-gray-2)" }}>
|
||||
@@ -91,6 +94,16 @@ export function ScheduleBookingsStep({
|
||||
{booking.weightTons}T
|
||||
</Badge>
|
||||
) : null}
|
||||
{booking.isGovernment ? (
|
||||
<Badge
|
||||
variant="light"
|
||||
size="xs"
|
||||
color="yellow"
|
||||
leftSection={<Landmark size={10} />}
|
||||
>
|
||||
Government
|
||||
</Badge>
|
||||
) : null}
|
||||
</Group>
|
||||
<Group gap={6}>
|
||||
<Text size="xs" c="dimmed">
|
||||
@@ -130,6 +143,7 @@ export function ScheduleBookingsStep({
|
||||
onSelectionChange={onSelectionChange}
|
||||
assignedIds={assignedIds}
|
||||
freightType={freightType}
|
||||
onSwitch={onSwitch}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Landmark } from "lucide-react";
|
||||
|
||||
import type {
|
||||
EligibleContainerBooking,
|
||||
TrainScheduleDetail,
|
||||
} from "@/types/trainScheduling";
|
||||
|
||||
/**
|
||||
* Government-priority switch confirmation: pick the assigned commercial
|
||||
* bookings to take off the train so the government booking can have their
|
||||
* wagons. Mount with key={govBooking.id} so selection resets per booking.
|
||||
*/
|
||||
export function SwitchGovernmentBookingModal({
|
||||
opened,
|
||||
onClose,
|
||||
govBooking,
|
||||
assignedBookings,
|
||||
loading,
|
||||
onConfirm,
|
||||
}: {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
govBooking: EligibleContainerBooking | null;
|
||||
assignedBookings: TrainScheduleDetail["bookings"];
|
||||
loading?: boolean;
|
||||
onConfirm: (removeBookingIds: string[]) => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
const candidates = useMemo(
|
||||
() => assignedBookings.filter((b) => !b.isGovernment && b.wagonAssigned),
|
||||
[assignedBookings],
|
||||
);
|
||||
const freedWagons = candidates
|
||||
.filter((b) => selected.includes(b.id))
|
||||
.reduce((sum, b) => sum + (b.wagonsRequired ?? 0), 0);
|
||||
|
||||
const toggle = (id: string) =>
|
||||
setSelected((ids) =>
|
||||
ids.includes(id) ? ids.filter((x) => x !== id) : [...ids, id],
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
title={
|
||||
<Group gap="xs">
|
||||
<Landmark size={18} />
|
||||
<Text fw={600}>Switch in government booking</Text>
|
||||
</Group>
|
||||
}
|
||||
radius="lg"
|
||||
size="lg"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Alert color="yellow" radius="md" variant="light">
|
||||
Government bookings have priority. Select the booking(s) to switch out —
|
||||
together they must free at least as many wagons as{" "}
|
||||
<Text span fw={600}>
|
||||
{govBooking?.reference}
|
||||
</Text>{" "}
|
||||
needs. Switched-out customers are notified to rebook.
|
||||
</Alert>
|
||||
|
||||
{candidates.length ? (
|
||||
<Stack gap="xs">
|
||||
{candidates.map((b) => (
|
||||
<Group
|
||||
key={b.id}
|
||||
justify="space-between"
|
||||
p="sm"
|
||||
style={{
|
||||
border: "1px solid var(--mantine-color-gray-2)",
|
||||
borderRadius: 12,
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
color="edr-green"
|
||||
checked={selected.includes(b.id)}
|
||||
onChange={() => toggle(b.id)}
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<Text fw={600} size="sm">
|
||||
{b.reference}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{b.customer}
|
||||
</Text>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Badge variant="outline" size="xs" color="edr-green">
|
||||
{b.weightTons}T
|
||||
</Badge>
|
||||
<Badge variant="light" size="xs">
|
||||
{b.wagonsRequired ?? "?"} wagon{b.wagonsRequired === 1 ? "" : "s"}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Text size="sm" c="dimmed" ta="center" py="md">
|
||||
No commercial bookings with wagons on this train to switch out.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Group justify="space-between">
|
||||
<Badge variant="light" color={selected.length ? "edr-green" : "gray"}>
|
||||
Frees {freedWagons} wagon{freedWagons === 1 ? "" : "s"}
|
||||
</Badge>
|
||||
<Group gap="sm">
|
||||
<Button variant="default" radius="md" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
disabled={!selected.length}
|
||||
loading={loading}
|
||||
onClick={() => onConfirm(selected)}
|
||||
>
|
||||
Confirm switch
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -366,6 +366,8 @@ export const URL_CONSTANTS = {
|
||||
},
|
||||
UNASSIGN_BOOKING: (scheduleId: string, bookingId: string) =>
|
||||
`/train-scheduling/schedules/${scheduleId}/bookings/${bookingId}`,
|
||||
SWITCH_GOVERNMENT_BOOKING: (id: string) =>
|
||||
`/train-scheduling/schedules/${id}/switch-government-booking`,
|
||||
PIN_WAGONS: (id: string) => `/train-scheduling/schedules/${id}/pin-wagons`,
|
||||
FINALIZE: (id: string) => `/train-scheduling/schedules/${id}/finalize`,
|
||||
DISPATCH: (id: string) => `/train-scheduling/schedules/${id}/dispatch`,
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -659,6 +659,22 @@ export const api = {
|
||||
() => TRAIN_SCHEDULING_INVALIDATIONS,
|
||||
),
|
||||
|
||||
switchGovernmentBooking: endpoint<
|
||||
{ id: string; governmentBookingId: string; removeBookingIds: string[] },
|
||||
TrainScheduleDetail
|
||||
>(
|
||||
"train-scheduling",
|
||||
"switch-government-booking",
|
||||
({ id, governmentBookingId, removeBookingIds }) =>
|
||||
trainSchedulingService.switchGovernmentBooking(
|
||||
id,
|
||||
governmentBookingId,
|
||||
removeBookingIds,
|
||||
),
|
||||
undefined,
|
||||
() => TRAIN_SCHEDULING_INVALIDATIONS,
|
||||
),
|
||||
|
||||
setLoadingStatus: endpoint<
|
||||
{ id: string; bookingIds: string[]; loadingStatus: LoadingStatus },
|
||||
TrainScheduleDetail
|
||||
|
||||
@@ -114,6 +114,7 @@ export interface ContractView {
|
||||
savedSignature?: {
|
||||
signerDisplayName: string;
|
||||
signatureImageUrl?: string | null;
|
||||
stampImageUrl?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@ const SIGNATURE_URL = "/me/signature";
|
||||
export interface SavedSignature {
|
||||
signerDisplayName: string;
|
||||
signatureImageUrl?: string | null;
|
||||
stampImageUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface SaveSignaturePayload {
|
||||
signerDisplayName: string;
|
||||
signatureImageBase64: string;
|
||||
/** Omit to keep the existing saved stamp. */
|
||||
stampImageBase64?: string;
|
||||
}
|
||||
|
||||
export const signaturesService = {
|
||||
|
||||
@@ -367,6 +367,18 @@ export const trainSchedulingService = {
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
switchGovernmentBooking: async (
|
||||
scheduleId: string,
|
||||
governmentBookingId: string,
|
||||
removeBookingIds: string[],
|
||||
): Promise<TrainScheduleDetail> => {
|
||||
const response = await client.post<TrainScheduleDetail>(
|
||||
URL_CONSTANTS.TRAIN_SCHEDULING.SWITCH_GOVERNMENT_BOOKING(scheduleId),
|
||||
{ governmentBookingId, removeBookingIds },
|
||||
);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
pinWagons: async (
|
||||
scheduleId: string,
|
||||
payload: PinWagonsPayload,
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface EligibleContainerBooking {
|
||||
status: string;
|
||||
schedulingStatus?: SchedulingStatus;
|
||||
priorityScore?: number;
|
||||
isGovernment?: boolean;
|
||||
}
|
||||
|
||||
export interface EligibleContainerBookingsResponse {
|
||||
@@ -686,6 +687,7 @@ export interface TrainScheduleDetail {
|
||||
arrivedAt?: string | null;
|
||||
loadingStatus?: "LOADED" | "UNLOADED";
|
||||
wagonAssigned?: boolean;
|
||||
isGovernment?: boolean;
|
||||
}>;
|
||||
/** Ordered corridor stops (route milestones) — for per-segment occupancy. */
|
||||
stops?: Array<{ yardId: string; label: string }>;
|
||||
|
||||
Reference in New Issue
Block a user