Merge branch 'freight_feature/profile' of github.com:Tria-plc/edr-platform into freight_feature/profile

This commit is contained in:
marshal
2026-06-25 09:29:32 +03:00

View File

@@ -19,7 +19,7 @@ import {
Snowflake, Snowflake,
Trash2, Trash2,
} from "lucide-react"; } from "lucide-react";
import { useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { import {
Controller, Controller,
useFieldArray, useFieldArray,
@@ -80,29 +80,39 @@ export function Step4Route({
remove: removeRoute, remove: removeRoute,
} = useFieldArray({ control: form.control, name: "extraRoutes" }); } = 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(() => { const yardOptions = useMemo(() => {
if (!referenceData?.yard) return []; if (!referenceData?.yard) return [];
return referenceData.yard.map((y) => ({ value: y.id, label: y.name })); return referenceData.yard.map((y) => ({ value: y.id, label: y.name }));
}, [referenceData]); }, [referenceData]);
const originData = useMemo(() => { // Filter the yard list to one side of a route: the yards in `country` (the
return yardOptions // operation type fixes which country each end must be in — see originCountry /
.filter((o) => o.value !== destinationYard) // destinationCountry above), excluding the yard already chosen on the other
.filter((o) => { // end of the same route so origin and destination can never match.
if (!originCountry) return true; const yardsForSide = useCallback(
const yard = referenceData?.yard.find((y) => y.id === o.value); (country: string | null, excludeYardId: string) =>
return yard?.country === originCountry; yardOptions
}); .filter((o) => o.value !== excludeYardId)
}, [yardOptions, destinationYard, originCountry, referenceData]); .filter((o) => {
const destData = useMemo(() => { if (!country) return true;
return yardOptions const yard = referenceData?.yard.find((y) => y.id === o.value);
.filter((o) => o.value !== originYard) return yard?.country === country;
.filter((o) => { }),
if (!destinationCountry) return true; [yardOptions, referenceData],
const yard = referenceData?.yard.find((y) => y.id === o.value); );
return yard?.country === destinationCountry;
}); const originData = useMemo(
}, [yardOptions, originYard, destinationCountry, referenceData]); () => 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 origin = referenceData?.yard.find((y) => y.id === originYard);
const dest = referenceData?.yard.find((y) => y.id === destinationYard); const dest = referenceData?.yard.find((y) => y.id === destinationYard);
@@ -123,6 +133,25 @@ export function Step4Route({
} }
}, [destinationCountry, dest, form]); }, [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<string, string> = { const directionStyle: Record<string, string> = {
EXPORT: "bg-sky-50 text-sky-800 border-sky-200", EXPORT: "bg-sky-50 text-sky-800 border-sky-200",
IMPORT: "bg-amber-50 text-amber-800 border-amber-200", IMPORT: "bg-amber-50 text-amber-800 border-amber-200",
@@ -263,57 +292,70 @@ export function Step4Route({
cover. cover.
</Text> </Text>
<Stack gap={12}> <Stack gap={12}>
{extraRoutes.map((rf, i) => ( {extraRoutes.map((rf, i) => {
<Group // Each extra route is constrained by the SAME operation type as the
key={rf.id} // primary route: its origin must sit in originCountry and its
gap={10} // destination in destinationCountry. Watch this row's current values
align="flex-start" // so each side also excludes the yard picked on the other side.
wrap="nowrap" const rowOrigin = watchedExtraRoutes[i]?.originYard ?? "";
className="rounded-xl" const rowDestination =
style={{ border: "1px solid #E6ECF2", padding: 12 }} watchedExtraRoutes[i]?.destinationYard ?? "";
> const rowOriginData = yardsForSide(originCountry, rowDestination);
<Box style={{ flex: 1 }}> const rowDestData = yardsForSide(destinationCountry, rowOrigin);
<Controller return (
name={`extraRoutes.${i}.originYard`} <Group
control={form.control} key={rf.id}
render={({ field, fieldState }) => ( gap={10}
<SelectField align="flex-start"
field={field} wrap="nowrap"
error={fieldState.error} className="rounded-xl"
label="Origin" style={{ border: "1px solid #E6ECF2", padding: 12 }}
placeholder="Origin..."
data={yardOptions}
/>
)}
/>
</Box>
<Box style={{ flex: 1 }}>
<Controller
name={`extraRoutes.${i}.destinationYard`}
control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Destination"
placeholder="Destination..."
data={yardOptions}
/>
)}
/>
</Box>
<Button
variant="subtle"
color="red"
size="xs"
mt={24}
px={6}
onClick={() => removeRoute(i)}
> >
<Trash2 size={16} /> <Box style={{ flex: 1 }}>
</Button> <Controller
</Group> name={`extraRoutes.${i}.originYard`}
))} control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Origin"
placeholder="Origin..."
disabled={stationSelectDisabled}
data={rowOriginData}
/>
)}
/>
</Box>
<Box style={{ flex: 1 }}>
<Controller
name={`extraRoutes.${i}.destinationYard`}
control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Destination"
placeholder="Destination..."
disabled={stationSelectDisabled}
data={rowDestData}
/>
)}
/>
</Box>
<Button
variant="subtle"
color="red"
size="xs"
mt={24}
px={6}
onClick={() => removeRoute(i)}
>
<Trash2 size={16} />
</Button>
</Group>
);
})}
</Stack> </Stack>
</Box> </Box>
)} )}