diff --git a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
index 7792c712a..2cf59dd40 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
@@ -1,10 +1,11 @@
import { useMemo, useState } from "react";
-import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { Check, CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@edr/ui-common";
+import type { Freight } from "@edr/types";
import Breadcrumbs from "@/components/Breadcrumbs";
import { api } from "@/services/api";
import type { CreateBookingPayload } from "@/services/bookings.service";
@@ -32,6 +33,10 @@ export default function NewBookingPage() {
const queryClient = useQueryClient();
const [step, setStep] = useState(1);
const { customer } = useAuth();
+ const { data: referenceData } = useQuery(
+ api.bookings.referenceData.queryOptions(),
+ );
+
const createMutation = useMutation({
mutationFn: (payload: CreateBookingPayload) =>
api.bookings.create.call(payload),
@@ -49,18 +54,12 @@ export default function NewBookingPage() {
const originYard = form.watch("originYard");
const destinationYard = form.watch("destinationYard");
- const containers = form.watch("containers");
const direction = useMemo(
() => getRouteDirection(originYard, destinationYard),
[originYard, destinationYard],
);
- const wagons = useMemo(() => {
- if (!containers || containers.length === 0) return null;
- return calcWagons(containers);
- }, [containers]);
-
async function handleContinue() {
const valid = await form.trigger(stepFields[step], { shouldFocus: true });
if (!valid) return;
@@ -181,9 +180,15 @@ export default function NewBookingPage() {
{step === 1 &&
}
{step === 2 &&
}
- {step === 3 &&
}
+ {step === 3 && (
+
+ )}
{step === 4 && (
-
+
)}
{step === 5 && (
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx
index 3d98fc854..89c2a648a 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx
@@ -1,37 +1,48 @@
import { Controller, type UseFormReturn } from "react-hook-form";
import { Flame, MapPin, Snowflake } from "lucide-react";
import { Field, SelectItem, Separator, Switch } from "@edr/ui-common";
+import type { Freight } from "@edr/types";
import {
- SHIPPING_LINES,
type BookingFormValues,
getRouteDirection,
- STATIONS,
} from "./schema";
import {
AlertBox,
SelectField,
- SelectOptions,
StepHeader,
StepLabel,
} from "./shared";
-import { useDropdownSettingByCode } from "@/hooks/useDropdownSettings";
-import { DropdownOption } from "@/types/dropdownSettings";
-import { useEffect } from "react";
+import { useEffect, useMemo } from "react";
type BookingForm = UseFormReturn
;
-const STATION_DROPDOWN_CODE = "stations_ter";
-
-export function Step4Route({ form }: { form: BookingForm }) {
+export function Step4Route({
+ form,
+ referenceData,
+}: {
+ form: BookingForm;
+ referenceData?: Freight.BookingReferenceData;
+}) {
const originYard = form.watch("originYard");
const destinationYard = form.watch("destinationYard");
- const {
- data: stationSetting,
- isLoading: stationsLoading,
- isError: stationsError,
- error: stationsFetchError,
- } = useDropdownSettingByCode(STATION_DROPDOWN_CODE);
- const stationOptions = getStationOptions(stationSetting?.children);
+
+ const yardOptions = useMemo(() => {
+ if (!referenceData?.yard) return [];
+ return referenceData.yard.map((y) => ({
+ value: y.name,
+ label: y.name,
+ country: y.country,
+ }));
+ }, [referenceData]);
+
+ const shippingLineOptions = useMemo(() => {
+ if (!referenceData?.shipping_line) return [];
+ return referenceData.shipping_line.map((sl) => ({
+ value: sl.name,
+ label: sl.name,
+ }));
+ }, [referenceData]);
+
const direction = getRouteDirection(originYard, destinationYard);
const directionStyle: Record = {
export: "bg-sky-50 text-sky-800 border-sky-200",
@@ -43,7 +54,6 @@ export function Step4Route({ form }: { form: BookingForm }) {
import: "Import workflow (outside country to inside country)",
domestic: "Domestic corridor",
};
- const stationSelectDisabled = stationsLoading || stationOptions.length === 0;
useEffect(() => {
if (direction === "domestic") {
@@ -51,6 +61,8 @@ export function Step4Route({ form }: { form: BookingForm }) {
}
}, [direction]);
+ const stationSelectDisabled = yardOptions.length === 0;
+
return (
-
)}
@@ -91,21 +102,17 @@ export function Step4Route({ form }: { form: BookingForm }) {
placeholder="Select destination..."
disabled={stationSelectDisabled}
>
-
)}
/>
- {stationsError && (
-
- Failed to load stations from the API.{" "}
- {stationsFetchError instanceof Error
- ? stationsFetchError.message
- : "Try again later."}
+ {!referenceData && (
+
+ Loading reference data...
)}
{direction && (
@@ -129,7 +136,11 @@ export function Step4Route({ form }: { form: BookingForm }) {
label="Shipping Line"
placeholder="Select shipping line..."
>
-
+ {shippingLineOptions.map((sl) => (
+
+ {sl.label}
+
+ ))}
)}
/>
@@ -179,23 +190,17 @@ export function Step4Route({ form }: { form: BookingForm }) {
);
}
-function getStationOptions(options?: DropdownOption[]): DropdownOption[] {
- return [...(options ?? [])].sort((a, b) => a.order - b.order);
-}
-
-function StationSelectOptions({
+function YardSelectOptions({
options,
excludeValue,
- isLoading,
}: {
- options: DropdownOption[];
+ options: Array<{ value: string; label: string; country: string }>;
excludeValue: string;
- isLoading: boolean;
}) {
- if (isLoading) {
+ if (options.length === 0) {
return (
-
- Loading stations...
+
+ No yards available
);
}
@@ -204,22 +209,10 @@ function StationSelectOptions({
(option) => option.value !== excludeValue,
);
- if (availableOptions.length === 0) {
- return (
-
- No stations available
-
- );
- }
-
return (
<>
{availableOptions.map((option) => (
-
+
{option.label}
))}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
index 1898feb73..58af0897b 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
@@ -1,10 +1,9 @@
+import { useMemo } from "react";
import { Controller, useFieldArray, type UseFormReturn } from "react-hook-form";
import { MapPin, Package, Plus, Trash2, Weight } from "lucide-react";
import { Button, Field, FieldError, FieldLabel, Input } from "@edr/ui-common";
+import type { Freight } from "@edr/types";
import {
- BREAK_BULK_TYPES,
- BULK_COMMODITIES,
- CONTAINER_TYPES,
calcWagons,
type BookingFormValues,
type RouteDirection,
@@ -13,7 +12,7 @@ import {
AlertBox,
OptionCard,
SelectField,
- SelectOptions,
+ SelectItem,
StepHeader,
StepLabel,
} from "./shared";
@@ -23,9 +22,11 @@ type BookingForm = UseFormReturn;
export function Step5CargoDetails({
form,
direction,
+ referenceData,
}: {
form: BookingForm;
direction: RouteDirection;
+ referenceData?: Freight.BookingReferenceData;
}) {
const cargoType = form.watch("cargoType");
const freightType = form.watch("freightType");
@@ -38,6 +39,20 @@ export function Step5CargoDetails({
name: "containers",
});
+ const containerTypeOptions = useMemo(() => {
+ if (!referenceData?.containers) return [];
+ return referenceData.containers.flatMap((group) =>
+ group.types.map((t) => t.name),
+ );
+ }, [referenceData]);
+
+ const bulkCommodityOptions = useMemo(() => {
+ if (!referenceData?.cargo_type) return [];
+ return referenceData.cargo_type.flatMap((group) =>
+ group.children?.map((c) => c.name) ?? [],
+ );
+ }, [referenceData]);
+
function getOverweightAlert(
type: "20ft" | "40ft",
vgm: number,
@@ -181,7 +196,11 @@ export function Step5CargoDetails({
label="Commodity *"
placeholder="Select commodity *"
>
-
+ {bulkCommodityOptions.map((option) => (
+
+ {option}
+
+ ))}
)}
/>
@@ -216,7 +235,11 @@ export function Step5CargoDetails({
label="Break-bulk type *"
placeholder="Select type *"
>
-
+ {bulkCommodityOptions.map((option) => (
+
+ {option}
+
+ ))}
)}
/>
@@ -408,7 +431,11 @@ export function Step5CargoDetails({
label="Container Type *"
placeholder="Select type..."
>
-
+ {containerTypeOptions.map((option) => (
+
+ {option}
+
+ ))}
)}
/>
diff --git a/apps/edr-freight-web/portal/src/services/bookings.service.ts b/apps/edr-freight-web/portal/src/services/bookings.service.ts
index d4342eba6..dd4c9f0dd 100644
--- a/apps/edr-freight-web/portal/src/services/bookings.service.ts
+++ b/apps/edr-freight-web/portal/src/services/bookings.service.ts
@@ -18,7 +18,7 @@ export const bookingsService = {
return data.data;
},
getReferenceData: async (): Promise => {
- const { data } = await client.get("/bookings/reference-data");
+ const { data } = await client.get("/api/bookings/reference-data");
return data.data;
},
remove: async (id: string): Promise => {