add payment currency field to booking forms with ETB/USD selection support

This commit is contained in:
marshal
2026-06-17 23:01:08 +03:00
parent 8396b83f46
commit 1d9e0f2108
6 changed files with 100 additions and 2 deletions

View File

@@ -53,6 +53,7 @@ import {
type BookingFormValues, type BookingFormValues,
} from "./new-booking-form/schema"; } from "./new-booking-form/schema";
import { SelectField } from "./new-booking-form/shared"; import { SelectField } from "./new-booking-form/shared";
import { PaymentCurrencyField } from "./new-booking-form/payment-currency-field";
import { Step5CargoDetails, StepScheduling } from "./new-booking-form/steps"; import { Step5CargoDetails, StepScheduling } from "./new-booking-form/steps";
const EDIT_SECTIONS = [ const EDIT_SECTIONS = [
@@ -137,6 +138,8 @@ function mapBookingToFormValues(
isRefrigerated: booking.isRefrigerated ?? false, isRefrigerated: booking.isRefrigerated ?? false,
shippingLine: (booking as any).shippingLine?.name ?? "", shippingLine: (booking as any).shippingLine?.name ?? "",
consolidationEnabled: booking.allowConsolidation ?? false, consolidationEnabled: booking.allowConsolidation ?? false,
paymentCurrency:
booking.paymentCurrency === "ETB" ? "ETB" : "USD",
scheduledDate: booking.scheduledDate scheduledDate: booking.scheduledDate
? new Date(booking.scheduledDate).toISOString().slice(0, 10) ? new Date(booking.scheduledDate).toISOString().slice(0, 10)
: "", : "",
@@ -426,7 +429,7 @@ export default function EditBookingPage() {
cargoTypeId: data.cargoType === "container" ? undefined : cargoTypeId, cargoTypeId: data.cargoType === "container" ? undefined : cargoTypeId,
cargoTotalWeightVgm: totalWeight, cargoTotalWeightVgm: totalWeight,
isHazardous: data.isHazardous, isHazardous: data.isHazardous,
paymentCurrency: "USD", paymentCurrency: data.paymentCurrency,
allowConsolidation: data.consolidationEnabled, allowConsolidation: data.consolidationEnabled,
freightType: freightType:
data.cargoType === "container" data.cargoType === "container"
@@ -607,6 +610,8 @@ export default function EditBookingPage() {
/> />
</SimpleGrid> </SimpleGrid>
<PaymentCurrencyField control={form.control} />
{(selectedService?.includesFirstMile || {(selectedService?.includesFirstMile ||
selectedService?.includesLastMile || selectedService?.includesLastMile ||
selectedService?.includesCustoms) && ( selectedService?.includesCustoms) && (

View File

@@ -274,7 +274,7 @@ export default function NewBookingPage() {
data.equipmentReturn === "with_return" data.equipmentReturn === "with_return"
? "WITH_RETURN" ? "WITH_RETURN"
: "WITHOUT_RETURN", : "WITHOUT_RETURN",
paymentCurrency: "USD", paymentCurrency: data.paymentCurrency,
originYardId: data.originYard, originYardId: data.originYard,
destinationYardId: data.destinationYard, destinationYardId: data.destinationYard,
tradeDirection: direction!, tradeDirection: direction!,

View File

@@ -0,0 +1,59 @@
import { Box, Text } from "@mantine/core";
import { Banknote, DollarSign } from "lucide-react";
import { Controller, type Control } from "react-hook-form";
import {
PAYMENT_CURRENCY_OPTIONS,
type BookingFormInputValues,
type BookingFormValues,
type PaymentCurrency,
} from "./schema";
import { OptionCard, OptionFieldError, StepLabel } from "./shared";
const CURRENCY_ICONS: Record<
PaymentCurrency,
{ icon: typeof DollarSign; bg: string; color: string }
> = {
USD: { icon: DollarSign, bg: "#EEF0FB", color: "#4F46E5" },
ETB: { icon: Banknote, bg: "#ECF6F1", color: "#0A6F4D" },
};
export function PaymentCurrencyField({
control,
}: {
control: Control<BookingFormInputValues, any, BookingFormValues>;
}) {
return (
<Box mt={24}>
<StepLabel>Payment currency</StepLabel>
<Text fz={12} c="#6B7C8E" mt={4} mb={12}>
Choose the currency for your freight quote and invoices.
</Text>
<Controller
name="paymentCurrency"
control={control}
render={({ field, fieldState }) => (
<div>
<div className="grid gap-4 md:grid-cols-2">
{PAYMENT_CURRENCY_OPTIONS.map((option) => {
const Icon = CURRENCY_ICONS[option.value].icon;
return (
<OptionCard
key={option.value}
selected={field.value === option.value}
onClick={() => field.onChange(option.value)}
icon={<Icon className="h-5 w-5" />}
iconBg={CURRENCY_ICONS[option.value].bg}
iconColor={CURRENCY_ICONS[option.value].color}
title={option.label}
description={option.description}
/>
);
})}
</div>
<OptionFieldError error={fieldState.error} />
</div>
)}
/>
</Box>
);
}

View File

@@ -61,11 +61,32 @@ export const BOOKING_DOCS_SETTING: Freight.IFileUploadSetting = {
export type BookingDocuments = Record<string, File | File[] | null>; export type BookingDocuments = Record<string, File | File[] | null>;
export const PAYMENT_CURRENCIES = ["USD", "ETB"] as const;
export type PaymentCurrency = (typeof PAYMENT_CURRENCIES)[number];
export const PAYMENT_CURRENCY_OPTIONS: Array<{
value: PaymentCurrency;
label: string;
description: string;
}> = [
{
value: "USD",
label: "USD",
description: "US Dollar — international pricing and invoicing.",
},
{
value: "ETB",
label: "ETB",
description: "Ethiopian Birr — local pricing and invoicing.",
},
];
export const bookingFormSchema = z export const bookingFormSchema = z
.object({ .object({
contractType: z.enum(["new", "renewal"], "Select a contract type."), contractType: z.enum(["new", "renewal"], "Select a contract type."),
previousContractRef: z.string(), previousContractRef: z.string(),
serviceTypeId: z.string("Select a service type."), serviceTypeId: z.string("Select a service type."),
paymentCurrency: z.enum(PAYMENT_CURRENCIES, "Select a payment currency."),
firstMile: z firstMile: z
.object({ .object({
@@ -200,6 +221,7 @@ export const initialBookingFormValues: DeepPartial<BookingFormValues> = {
previousContractRef: "", previousContractRef: "",
serviceTypeId: "", serviceTypeId: "",
paymentCurrency: "USD",
firstMile: { firstMile: {
enabled: false, enabled: false,
pickUpAddress: "", pickUpAddress: "",
@@ -230,6 +252,7 @@ export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
1: ["contractType", "previousContractRef"], 1: ["contractType", "previousContractRef"],
2: [ 2: [
"serviceTypeId", "serviceTypeId",
"paymentCurrency",
"firstMile", "firstMile",
"lastMile", "lastMile",
"equipmentReturn", "equipmentReturn",

View File

@@ -12,6 +12,7 @@ import {
StepHeader, StepHeader,
StepLabel, StepLabel,
} from "./shared"; } from "./shared";
import { PaymentCurrencyField } from "./payment-currency-field";
import type { Freight } from "@edr/types"; import type { Freight } from "@edr/types";
@@ -102,6 +103,8 @@ export function Step2ServiceType({
)} )}
/> />
<PaymentCurrencyField control={form.control} />
{showServiceSections && ( {showServiceSections && (
<Stack gap={12} mt={24}> <Stack gap={12} mt={24}>
<StepLabel>Trucking & customs options</StepLabel> <StepLabel>Trucking & customs options</StepLabel>

View File

@@ -248,6 +248,10 @@ export function Step8Review({
<DetailRow label="Previous ref" value={values.previousContractRef} /> <DetailRow label="Previous ref" value={values.previousContractRef} />
)} )}
<DetailRow label="Service" value={serviceType?.name ?? ""} /> <DetailRow label="Service" value={serviceType?.name ?? ""} />
<DetailRow
label="Payment currency"
value={values.paymentCurrency ?? "USD"}
/>
<Button <Button
type="button" type="button"
variant="subtle" variant="subtle"
@@ -431,6 +435,10 @@ export function Step8Review({
</Text> </Text>
<Stack gap="sm"> <Stack gap="sm">
<ReadinessItem done={Boolean(values.serviceTypeId)} label="Service configured" /> <ReadinessItem done={Boolean(values.serviceTypeId)} label="Service configured" />
<ReadinessItem
done={Boolean(values.paymentCurrency)}
label="Payment currency selected"
/>
<ReadinessItem <ReadinessItem
done={Boolean(values.originYard && values.destinationYard)} done={Boolean(values.originYard && values.destinationYard)}
label="Route selected" label="Route selected"