diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/GlCreateBookingForm.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/GlCreateBookingForm.tsx
index 4a1cb402f..52f7ee40d 100644
--- a/apps/edr-freight-web/backoffice/src/components/contracts/GlCreateBookingForm.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/contracts/GlCreateBookingForm.tsx
@@ -20,7 +20,6 @@ import {
Loader,
Modal,
Paper,
- SegmentedControl,
Select,
Stack,
Switch,
@@ -49,7 +48,11 @@ import {
X,
} from "lucide-react";
import type { Freight } from "@edr/types";
-import { ExportTrainPicker, OperationDatePicker } from "@edr/ui-common";
+import {
+ CurrencySelector,
+ ExportTrainPicker,
+ OperationDatePicker,
+} from "@edr/ui-common";
import { api } from "@/services/api";
import { PageContainer } from "@/components/page";
@@ -1713,47 +1716,42 @@ export default function GlCreateBookingForm() {
title="Schedule"
description="Pick the binding shipment day. Only days with an open train that has enough matching wagons for the cargo can be selected."
/>
- {cargoQuery === null ? (
- }
- >
- Enter your cargo details first — available shipment days depend
- on the wagons your cargo needs.
-
- ) : (
-
- Shipment day *
-
- {
- setScheduledDate(d);
- // A new day invalidates the old train pick.
- setTrainScheduleId("");
- }}
- />
-
- {showErrors && dateError && (
-
- {dateError}
-
- )}
- {isExportPick && scheduledDate ? (
-
- ) : null}
+
+ Shipment day *
+
+ {/* Calendar stays visible before cargo is entered — all days
+ disabled with a hint, since availability depends on cargo. */}
+ {
+ setScheduledDate(d);
+ // A new day invalidates the old train pick.
+ setTrainScheduleId("");
+ }}
+ />
- )}
+ {showErrors && dateError && (
+
+ {dateError}
+
+ )}
+ {isExportPick && scheduledDate ? (
+
+ ) : null}
+
)}
@@ -1769,16 +1767,10 @@ export default function GlCreateBookingForm() {
? "Requested by the customer on their shipment request."
: "The contract is quoted in USD — pick the currency this shipment is invoiced in."}
- setPaymentCurrency(v as "USD" | "ETB")}
+ onChange={setPaymentCurrency}
disabled={isIntercity}
- data={[
- { label: "USD", value: "USD" },
- { label: "ETB", value: "ETB" },
- ]}
- color="edr-green"
- radius={10}
/>
diff --git a/apps/edr-freight-web/portal/src/pages/contracts/NewShipmentPage.tsx b/apps/edr-freight-web/portal/src/pages/contracts/NewShipmentPage.tsx
index 33a1343a2..4d423abe8 100644
--- a/apps/edr-freight-web/portal/src/pages/contracts/NewShipmentPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/contracts/NewShipmentPage.tsx
@@ -22,7 +22,6 @@ import {
Loader,
Modal,
Paper,
- SegmentedControl,
Stack,
Switch,
Text,
@@ -50,7 +49,11 @@ import {
} from "lucide-react";
import type { Freight } from "@edr/types";
-import { ExportTrainPicker, OperationDatePicker } from "@edr/ui-common";
+import {
+ CurrencySelector,
+ ExportTrainPicker,
+ OperationDatePicker,
+} from "@edr/ui-common";
import { api } from "@/services/api";
import {
contractsService,
@@ -1144,75 +1147,56 @@ function ScheduleStep({
Your contract is quoted in USD. Pick the currency this shipment is
invoiced in — the total is converted for you.
- {/* Rendered unselected until the customer chooses: SegmentedControl
- highlights whatever value it is given, so passing a fallback
- here would look like a made choice. */}
- field.onChange(v)}
- data={[
- { label: "Select…", value: "", disabled: true },
- { label: "USD", value: "USD" },
- { label: "ETB", value: "ETB" },
- ]}
- color="edr-green"
- radius={10}
+ error={fieldState.error?.message}
/>
- {fieldState.error && (
-
- {fieldState.error.message}
-
- )}
)}
/>
- {cargoQuery === null ? (
- }
- >
- Enter your cargo details first — available shipment days depend on the
- wagons your cargo needs.
-
- ) : (
- (
-
- Shipment day *
-
- {
- field.onChange(d);
- // A new day invalidates the old train pick.
- form.setValue("trainScheduleId", "");
- }}
- />
-
- {fieldState.error?.message && (
-
- {fieldState.error.message}
-
- )}
- {isExportPick && scheduledDate ? (
- form.setValue("trainScheduleId", id)}
- />
- ) : null}
+ (
+
+ Shipment day *
+
+ {/* Calendar stays visible before cargo is entered — all days
+ disabled with a hint, since availability depends on cargo. */}
+ {
+ field.onChange(d);
+ // A new day invalidates the old train pick.
+ form.setValue("trainScheduleId", "");
+ }}
+ />
- )}
- />
- )}
+ {fieldState.error?.message && (
+
+ {fieldState.error.message}
+
+ )}
+ {isExportPick && scheduledDate ? (
+ form.setValue("trainScheduleId", id)}
+ />
+ ) : null}
+
+ )}
+ />
);
}
diff --git a/packages/ui-common/src/components/CurrencySelector/CurrencySelector.tsx b/packages/ui-common/src/components/CurrencySelector/CurrencySelector.tsx
new file mode 100644
index 000000000..4d310efc0
--- /dev/null
+++ b/packages/ui-common/src/components/CurrencySelector/CurrencySelector.tsx
@@ -0,0 +1,131 @@
+import { Box, Text } from "@mantine/core";
+import { Check } from "lucide-react";
+
+export interface CurrencySelectorProps {
+ /** Selected currency code, or "" when none picked yet. */
+ value: string;
+ onChange: (currency: "USD" | "ETB") => void;
+ disabled?: boolean;
+ /** Validation error shown under the cards. */
+ error?: string;
+}
+
+const OPTIONS = [
+ {
+ code: "USD",
+ symbol: "$",
+ name: "US Dollar",
+ hint: "As quoted on the contract",
+ },
+ {
+ code: "ETB",
+ symbol: "Br",
+ name: "Ethiopian Birr",
+ hint: "Converted from the USD total",
+ },
+] as const;
+
+/**
+ * Card-style USD/ETB billing-currency picker. Renders unselected when `value`
+ * is "" so a required choice never looks pre-made.
+ */
+export function CurrencySelector({
+ value,
+ onChange,
+ disabled = false,
+ error,
+}: CurrencySelectorProps) {
+ return (
+
+
+ {OPTIONS.map((o) => {
+ const selected = value === o.code;
+ return (
+
+ );
+ })}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+}
+
+export default CurrencySelector;
diff --git a/packages/ui-common/src/components/CurrencySelector/index.ts b/packages/ui-common/src/components/CurrencySelector/index.ts
new file mode 100644
index 000000000..9e20c6863
--- /dev/null
+++ b/packages/ui-common/src/components/CurrencySelector/index.ts
@@ -0,0 +1,2 @@
+export { CurrencySelector, default } from "./CurrencySelector";
+export type { CurrencySelectorProps } from "./CurrencySelector";
diff --git a/packages/ui-common/src/components/OperationDatePicker/OperationDatePicker.tsx b/packages/ui-common/src/components/OperationDatePicker/OperationDatePicker.tsx
index d8fbcaf85..f8a45e5a3 100644
--- a/packages/ui-common/src/components/OperationDatePicker/OperationDatePicker.tsx
+++ b/packages/ui-common/src/components/OperationDatePicker/OperationDatePicker.tsx
@@ -18,6 +18,8 @@ export interface OperationDatePickerProps {
onChange: (date: string) => void;
/** Stretch to the full width of the parent container. */
fullWidth?: boolean;
+ /** Text shown under the grid when no days are available. */
+ emptyMessage?: string;
}
/** `yyyy-MM-dd` for a local date. */
@@ -58,6 +60,7 @@ export function OperationDatePicker({
value,
onChange,
fullWidth = false,
+ emptyMessage = "No scheduled departures found for this route yet.",
}: OperationDatePickerProps) {
const [month, setMonth] = useState(() => {
const now = new Date();
@@ -270,7 +273,7 @@ export function OperationDatePicker({
)}
{departureDays.size === 0 && (
- No scheduled departures found for this route yet.
+ {emptyMessage}
)}
>
diff --git a/packages/ui-common/src/index.ts b/packages/ui-common/src/index.ts
index 60e5c39f8..c9754ceb4 100644
--- a/packages/ui-common/src/index.ts
+++ b/packages/ui-common/src/index.ts
@@ -26,6 +26,8 @@ export { OperationDatePicker } from "./components/OperationDatePicker";
export type { OperationDatePickerProps } from "./components/OperationDatePicker";
export { ExportTrainPicker } from "./components/ExportTrainPicker";
export type { ExportTrainPickerProps } from "./components/ExportTrainPicker";
+export { CurrencySelector } from "./components/CurrencySelector";
+export type { CurrencySelectorProps } from "./components/CurrencySelector";
export { CountdownTimer } from "./components/CountdownTimer";
export type { CountdownTimerProps } from "./components/CountdownTimer";