mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
features: dynamic terminal components
This commit is contained in:
@@ -485,6 +485,13 @@ export function getRouteDirection(
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStationLocation(value: string): "inside" | "outside" | null {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized.startsWith("inside")) return "inside";
|
||||
if (normalized.startsWith("outside")) return "outside";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function calcWagons(containers: ContainerConfig[]): WagonCalcResult {
|
||||
const Ft40Wagons = containers
|
||||
.filter((c) => c.type === "40ft")
|
||||
@@ -495,20 +502,6 @@ export function calcWagons(containers: ContainerConfig[]): WagonCalcResult {
|
||||
const wagonLayout: WagonConfig[] = [];
|
||||
let hasOddUnit = Ft20Wagons % 2 === 1;
|
||||
let sharedWagons = Math.floor(Ft20Wagons / 2);
|
||||
function getStationLocation(value: string): "inside" | "outside" | null {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized.startsWith("inside")) return "inside";
|
||||
if (normalized.startsWith("outside")) return "outside";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function calcWagons(
|
||||
type: BookingFormValues["containerType"],
|
||||
qty: number,
|
||||
): WagonCalcResult {
|
||||
if (type === "40ft") {
|
||||
return { totalWagons: qty, hasOddUnit: false, sharedWagons: qty };
|
||||
}
|
||||
|
||||
return {
|
||||
totalWagons: sharedWagons + Ft40Wagons,
|
||||
@@ -518,4 +511,4 @@ export function calcWagons(
|
||||
ft20Wagons: Ft20Wagons,
|
||||
wagonLayout,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,25 @@
|
||||
import { Controller, type UseFormReturn } from "react-hook-form";
|
||||
import { Flame, MapPin, Snowflake } from "lucide-react";
|
||||
import { Field, Separator, Switch } from "@edr/ui-common";
|
||||
import { Field, SelectItem, Separator, Switch } from "@edr/ui-common";
|
||||
import { type BookingFormValues, getRouteDirection, STATIONS } from "./schema";
|
||||
import { SelectField, SelectOptions, StepHeader, StepLabel } from "./shared";
|
||||
import { useDropdownSettingByCode } from "@/hooks/useDropdownSettings";
|
||||
import { DropdownOption } from "@/types/dropdownSettings";
|
||||
|
||||
type BookingForm = UseFormReturn<BookingFormValues>;
|
||||
|
||||
const STATION_DROPDOWN_CODE = "stations_ter";
|
||||
|
||||
export function Step4Route({ form }: { form: BookingForm }) {
|
||||
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 direction = getRouteDirection(originYard, destinationYard);
|
||||
const directionStyle: Record<string, string> = {
|
||||
export: "bg-sky-50 text-sky-800 border-sky-200",
|
||||
@@ -16,10 +27,11 @@ export function Step4Route({ form }: { form: BookingForm }) {
|
||||
domestic: "bg-muted text-muted-foreground border-border",
|
||||
};
|
||||
const directionLabel: Record<string, string> = {
|
||||
export: "Export workflow (Ethiopia to Djibouti)",
|
||||
import: "Import workflow (Djibouti to Ethiopia)",
|
||||
export: "Export workflow (inside country to outside country)",
|
||||
import: "Import workflow (outside country to inside country)",
|
||||
domestic: "Domestic corridor",
|
||||
};
|
||||
const stationSelectDisabled = stationsLoading || stationOptions.length === 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -29,6 +41,7 @@ export function Step4Route({ form }: { form: BookingForm }) {
|
||||
/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<StepLabel>Route</StepLabel>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<Controller
|
||||
name="originYard"
|
||||
@@ -39,9 +52,12 @@ export function Step4Route({ form }: { form: BookingForm }) {
|
||||
error={fieldState.error}
|
||||
label="Origin Yard*"
|
||||
placeholder="Select origin..."
|
||||
disabled={stationSelectDisabled}
|
||||
>
|
||||
<SelectOptions
|
||||
options={STATIONS.filter((s) => s !== destinationYard)}
|
||||
<StationSelectOptions
|
||||
options={stationOptions}
|
||||
excludeValue={destinationYard}
|
||||
isLoading={stationsLoading}
|
||||
/>
|
||||
</SelectField>
|
||||
)}
|
||||
@@ -55,14 +71,25 @@ export function Step4Route({ form }: { form: BookingForm }) {
|
||||
error={fieldState.error}
|
||||
label="Destination Yard *"
|
||||
placeholder="Select destination..."
|
||||
disabled={stationSelectDisabled}
|
||||
>
|
||||
<SelectOptions
|
||||
options={STATIONS.filter((s) => s !== originYard)}
|
||||
<StationSelectOptions
|
||||
options={stationOptions}
|
||||
excludeValue={originYard}
|
||||
isLoading={stationsLoading}
|
||||
/>
|
||||
</SelectField>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{stationsError && (
|
||||
<AlertBox tone="error">
|
||||
Failed to load stations from the API.{" "}
|
||||
{stationsFetchError instanceof Error
|
||||
? stationsFetchError.message
|
||||
: "Try again later."}
|
||||
</AlertBox>
|
||||
)}
|
||||
{direction && (
|
||||
<div
|
||||
className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium ${directionStyle[direction]}`}
|
||||
@@ -117,3 +144,53 @@ export function Step4Route({ form }: { form: BookingForm }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function getStationOptions(options?: DropdownOption[]): DropdownOption[] {
|
||||
return [...(options ?? [])].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
|
||||
function StationSelectOptions({
|
||||
options,
|
||||
excludeValue,
|
||||
isLoading,
|
||||
}: {
|
||||
options: DropdownOption[];
|
||||
excludeValue: string;
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SelectItem value="__stations_loading" disabled>
|
||||
Loading stations...
|
||||
</SelectItem>
|
||||
);
|
||||
}
|
||||
|
||||
const availableOptions = options.filter(
|
||||
(option) => option.value !== excludeValue,
|
||||
);
|
||||
|
||||
if (availableOptions.length === 0) {
|
||||
return (
|
||||
<SelectItem value="__stations_empty" disabled>
|
||||
No stations available
|
||||
</SelectItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{availableOptions.map((option) => (
|
||||
<SelectItem
|
||||
key={option.id}
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// vite.config.ts
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig } from "file:///home/meng/projects/edr-platform/node_modules/.pnpm/vite@5.4.21_@types+node@24.12.4_lightningcss@1.32.0_terser@5.48.0/node_modules/vite/dist/node/index.js";
|
||||
import react from "file:///home/meng/projects/edr-platform/node_modules/.pnpm/@vitejs+plugin-react@4.7.0_vite@5.4.21_@types+node@24.12.4_lightningcss@1.32.0_terser@5.48.0_/node_modules/@vitejs/plugin-react/dist/index.js";
|
||||
import tailwindcss from "file:///home/meng/projects/edr-platform/node_modules/.pnpm/@tailwindcss+vite@4.3.0_vite@5.4.21_@types+node@24.12.4_lightningcss@1.32.0_terser@5.48.0_/node_modules/@tailwindcss/vite/dist/index.mjs";
|
||||
var __vite_injected_original_import_meta_url = "file:///home/meng/projects/edr-platform/apps/edr-freight-web/portal/vite.config.ts";
|
||||
var __dirname = path.dirname(fileURLToPath(__vite_injected_original_import_meta_url));
|
||||
var vite_config_default = defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src")
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
host: "0.0.0.0"
|
||||
}
|
||||
});
|
||||
export {
|
||||
vite_config_default as default
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9tZW5nL3Byb2plY3RzL2Vkci1wbGF0Zm9ybS9hcHBzL2Vkci1mcmVpZ2h0LXdlYi9wb3J0YWxcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL21lbmcvcHJvamVjdHMvZWRyLXBsYXRmb3JtL2FwcHMvZWRyLWZyZWlnaHQtd2ViL3BvcnRhbC92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9tZW5nL3Byb2plY3RzL2Vkci1wbGF0Zm9ybS9hcHBzL2Vkci1mcmVpZ2h0LXdlYi9wb3J0YWwvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgcGF0aCBmcm9tIFwibm9kZTpwYXRoXCI7XG5pbXBvcnQgeyBmaWxlVVJMVG9QYXRoIH0gZnJvbSBcIm5vZGU6dXJsXCI7XG5cbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gXCJ2aXRlXCI7XG5pbXBvcnQgcmVhY3QgZnJvbSBcIkB2aXRlanMvcGx1Z2luLXJlYWN0XCI7XG5pbXBvcnQgdGFpbHdpbmRjc3MgZnJvbSBcIkB0YWlsd2luZGNzcy92aXRlXCI7XG5cbmNvbnN0IF9fZGlybmFtZSA9IHBhdGguZGlybmFtZShmaWxlVVJMVG9QYXRoKGltcG9ydC5tZXRhLnVybCkpO1xuXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBwbHVnaW5zOiBbcmVhY3QoKSwgdGFpbHdpbmRjc3MoKV0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgXCJAXCI6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsIFwiLi9zcmNcIiksXG4gICAgfSxcbiAgfSxcbiAgc2VydmVyOiB7XG4gICAgcG9ydDogNTE3MyxcbiAgICBob3N0OiBcIjAuMC4wLjBcIixcbiAgfSxcbn0pO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFzVyxPQUFPLFVBQVU7QUFDdlgsU0FBUyxxQkFBcUI7QUFFOUIsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxXQUFXO0FBQ2xCLE9BQU8saUJBQWlCO0FBTHdNLElBQU0sMkNBQTJDO0FBT2pSLElBQU0sWUFBWSxLQUFLLFFBQVEsY0FBYyx3Q0FBZSxDQUFDO0FBRTdELElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxNQUFNLEdBQUcsWUFBWSxDQUFDO0FBQUEsRUFDaEMsU0FBUztBQUFBLElBQ1AsT0FBTztBQUFBLE1BQ0wsS0FBSyxLQUFLLFFBQVEsV0FBVyxPQUFPO0FBQUEsSUFDdEM7QUFBQSxFQUNGO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsRUFDUjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==
|
||||
Reference in New Issue
Block a user