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 283315351..85453ccb5 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 @@ -19,7 +19,7 @@ import { Snowflake, Trash2, } from "lucide-react"; -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { Controller, useFieldArray, @@ -80,29 +80,39 @@ export function Step4Route({ remove: removeRoute, } = useFieldArray({ control: form.control, name: "extraRoutes" }); + // useFieldArray's `fields` don't re-render on value change, so watch the live + // route values to filter each row's yard options by what it has selected. + const watchedExtraRoutes = form.watch("extraRoutes") ?? []; + const yardOptions = useMemo(() => { if (!referenceData?.yard) return []; return referenceData.yard.map((y) => ({ value: y.id, label: y.name })); }, [referenceData]); - const originData = useMemo(() => { - return yardOptions - .filter((o) => o.value !== destinationYard) - .filter((o) => { - if (!originCountry) return true; - const yard = referenceData?.yard.find((y) => y.id === o.value); - return yard?.country === originCountry; - }); - }, [yardOptions, destinationYard, originCountry, referenceData]); - const destData = useMemo(() => { - return yardOptions - .filter((o) => o.value !== originYard) - .filter((o) => { - if (!destinationCountry) return true; - const yard = referenceData?.yard.find((y) => y.id === o.value); - return yard?.country === destinationCountry; - }); - }, [yardOptions, originYard, destinationCountry, referenceData]); + // Filter the yard list to one side of a route: the yards in `country` (the + // operation type fixes which country each end must be in — see originCountry / + // destinationCountry above), excluding the yard already chosen on the other + // end of the same route so origin and destination can never match. + const yardsForSide = useCallback( + (country: string | null, excludeYardId: string) => + yardOptions + .filter((o) => o.value !== excludeYardId) + .filter((o) => { + if (!country) return true; + const yard = referenceData?.yard.find((y) => y.id === o.value); + return yard?.country === country; + }), + [yardOptions, referenceData], + ); + + const originData = useMemo( + () => yardsForSide(originCountry, destinationYard), + [yardsForSide, originCountry, destinationYard], + ); + const destData = useMemo( + () => yardsForSide(destinationCountry, originYard), + [yardsForSide, destinationCountry, originYard], + ); const origin = referenceData?.yard.find((y) => y.id === originYard); const dest = referenceData?.yard.find((y) => y.id === destinationYard); @@ -123,6 +133,25 @@ export function Step4Route({ } }, [destinationCountry, dest, form]); + // Same cleanup for the extra contract routes: when the operation type changes, + // clear any extra-route yard whose country no longer matches the required side + // so an added route can't contradict the operation either. + useEffect(() => { + watchedExtraRoutes.forEach((route, i) => { + const ro = referenceData?.yard.find((y) => y.id === route?.originYard); + if (originCountry && ro && ro.country !== originCountry) { + form.setValue(`extraRoutes.${i}.originYard`, ""); + } + const rd = referenceData?.yard.find( + (y) => y.id === route?.destinationYard, + ); + if (destinationCountry && rd && rd.country !== destinationCountry) { + form.setValue(`extraRoutes.${i}.destinationYard`, ""); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [originCountry, destinationCountry, referenceData, form]); + const directionStyle: Record = { EXPORT: "bg-sky-50 text-sky-800 border-sky-200", IMPORT: "bg-amber-50 text-amber-800 border-amber-200", @@ -263,57 +292,70 @@ export function Step4Route({ cover. - {extraRoutes.map((rf, i) => ( - - - ( - - )} - /> - - - ( - - )} - /> - - - - ))} + + ( + + )} + /> + + + ( + + )} + /> + + + + ); + })} )}