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 => {