diff --git a/apps/edr-freight-web/backoffice/src/components/fleet/FleetFormDialog.tsx b/apps/edr-freight-web/backoffice/src/components/fleet/FleetFormDialog.tsx index 7262e7927..dfb27ed36 100644 --- a/apps/edr-freight-web/backoffice/src/components/fleet/FleetFormDialog.tsx +++ b/apps/edr-freight-web/backoffice/src/components/fleet/FleetFormDialog.tsx @@ -271,8 +271,14 @@ const FleetFormDialog = ({ return; } if (!validate()) return; + // Derived fields are never edited, so form state for them can be stale (or + // seeded from the record) — recompute before building the payload. + const submitted: Record = { ...values }; + fields.forEach((field) => { + if (field.derivedValue) submitted[field.name] = field.derivedValue(values); + }); const payload = Object.fromEntries( - Object.entries(values) + Object.entries(submitted) .map(([key, value]) => { if (value === FLEET_SELECT_NONE || value === "") return [key, undefined]; @@ -294,6 +300,23 @@ const FleetFormDialog = ({ // only by verification and never hand-edited. const isDisabled = Boolean(field.disabled || field.faydaLocked); + // Computed from other fields (e.g. the import run implied by the export + // run) — read-only, and recomputed here rather than read from form state. + if (field.derivedValue) { + return ( + + ); + } + if (field.type === "radio") { return ( ) => string; /** * Field is owned by the Fayda identity — populated only by verification and * never hand-edited. Rendered disabled in the form. @@ -116,6 +123,32 @@ const WAGON_STATUS_OPTIONS = [ { label: "Detained", value: Freight.WagonStatus.Detained }, ]; +/** + * EDR run-number pairs, keyed by the odd EXPORT run (Ethiopia → Djibouti). The + * even IMPORT run (Djibouti → Ethiopia) is fixed by the export run, so choosing + * an export number fully determines the import one. Listed explicitly rather + * than computed as export+1, so a pair that ever breaks that convention stays + * correct here. + */ +const TRAIN_RUN_PAIRS: Record = { + "8001": "8002", + "81001": "81002", + "82001": "82002", + "83001": "83002", + "84001": "84002", + "85001": "85002", + "86001": "86002", + "87001": "87002", + "88001": "88002", + "8901": "8902", + "9001": "9002", +}; + +const EXPORT_TRAIN_OPTIONS = Object.keys(TRAIN_RUN_PAIRS).map((run) => ({ + label: run, + value: run, +})); + export const FLEET_RESOURCES: FleetResourceConfig[] = [ @@ -272,13 +305,15 @@ export const FLEET_RESOURCES: FleetResourceConfig[] = [ ], formFields: [ // Run numbers are optional — a wagon sits in the fleet unassigned to any - // run until an operator fills these in. + // run until an operator picks an export run. The import run is fixed by + // that choice, so it is derived rather than typed. { name: "exportTrainNumber", label: "Export train number", - type: "text", + type: "select", description: "Odd — Ethiopia → Djibouti runs", placeholder: "e.g. 8001", + options: EXPORT_TRAIN_OPTIONS, }, { name: "importTrainNumber", @@ -286,6 +321,8 @@ export const FLEET_RESOURCES: FleetResourceConfig[] = [ type: "text", description: "Even — Djibouti → Ethiopia runs", placeholder: "e.g. 8002", + derivedValue: (values) => + TRAIN_RUN_PAIRS[String(values.exportTrainNumber ?? "")] ?? "", }, { name: "wagonNumber", label: "Wagon number", type: "text", required: true }, { name: "wagonTypeId", label: "Wagon type", type: "select", required: true, dynamicOptions: "wagonTypes" },