refactor(bookings): Refine booking form schema with default values and types

This commit is contained in:
ghost2023
2026-05-29 15:58:17 +03:00
parent b58e2ee2be
commit 70f5f9aff8
6 changed files with 382 additions and 385 deletions

View File

@@ -20,9 +20,11 @@ import {
const userSchema = z.object({
email: z.string().email("Invalid email address"),
username: z.string().min(3, "Username must be at least 3 characters"),
countryCode: z.string().min(1, "Country code is required"),
phone: z.string().min(9, "Phone number is too short").max(9, "Phone number is too long"),
phone: z
.string()
.min(9, "Phone number is too short")
.max(9, "Phone number is too long"),
userType: z.string(),
name: z.object({
en: z.string().min(2, "Name is required"),
@@ -46,7 +48,6 @@ export default function SignupPage() {
resolver: zodResolver(userSchema),
defaultValues: {
email: "",
username: "",
countryCode: "+251",
phone: "",
userType: userType.individual,
@@ -58,10 +59,12 @@ export default function SignupPage() {
setError(null);
setLoading(true);
try {
const normalizedPhone = data.phone.startsWith("0") ? data.phone.slice(1) : data.phone;
const normalizedPhone = data.phone.startsWith("0")
? data.phone.slice(1)
: data.phone;
const payload: SignupPayload = {
email: data.email,
username: data.username,
username: data.email,
phoneNumber: `${data.countryCode}${normalizedPhone}`,
userType: data.userType,
name: { en: data.name.en, am: data.name.am ?? "" },
@@ -92,7 +95,12 @@ export default function SignupPage() {
"Enterprise-grade operations",
"Multi-corridor freight monitoring",
],
stats: { label: "Active Corridors", value: "24+", footer: "Operational", progress: "w-[95%]" },
stats: {
label: "Active Corridors",
value: "24+",
footer: "Operational",
progress: "w-[95%]",
},
}}
>
<div className="mb-6">
@@ -125,31 +133,17 @@ export default function SignupPage() {
<FieldError errors={[errors.name?.en]} />
</Field>
<div className="grid grid-cols-2 gap-4">
<Field data-invalid={Boolean(errors.username)}>
<FieldLabel>Username</FieldLabel>
<Input
type="text"
placeholder="john_doe"
disabled={loading}
aria-invalid={Boolean(errors.username)}
{...register("username")}
/>
<FieldError errors={[errors.username]} />
</Field>
<Field data-invalid={Boolean(errors.email)}>
<FieldLabel>Email Address</FieldLabel>
<Input
type="email"
placeholder="john@example.com"
disabled={loading}
aria-invalid={Boolean(errors.email)}
{...register("email")}
/>
<FieldError errors={[errors.email]} />
</Field>
</div>
<Field data-invalid={Boolean(errors.email)}>
<FieldLabel>Email Address</FieldLabel>
<Input
type="email"
placeholder="john@example.com"
disabled={loading}
aria-invalid={Boolean(errors.email)}
{...register("email")}
/>
<FieldError errors={[errors.email]} />
</Field>
<PhoneInput
disabled={loading}
@@ -160,12 +154,7 @@ export default function SignupPage() {
/>
</FieldGroup>
<Button
type="submit"
disabled={loading}
size="lg"
className="w-full"
>
<Button type="submit" disabled={loading} size="lg" className="w-full">
{loading ? (
<>
<Loader2 className="animate-spin" data-icon="inline-start" />

View File

@@ -50,8 +50,6 @@ export default function NewBookingPage() {
const originYard = form.watch("originYard");
const destinationYard = form.watch("destinationYard");
const containers = form.watch("containers");
const contractType = form.watch("contractType");
const previousContractRef = form.watch("previousContractRef");
const direction = useMemo(
() => getRouteDirection(originYard, destinationYard),
@@ -63,8 +61,6 @@ export default function NewBookingPage() {
return calcWagons(containers);
}, [containers]);
const renewalValid = contractType === "renewal" && previousContractRef !== "";
async function handleContinue() {
const valid = await form.trigger(stepFields[step], { shouldFocus: true });
if (!valid) return;
@@ -101,16 +97,22 @@ export default function NewBookingPage() {
data.contractType.toUpperCase() as CreateBookingPayload["contractType"],
previousContractId: data.previousContractRef || undefined,
serviceType:
data.serviceType === "rail" ? "RAIL_ONLY" : "RAIL_AND_FORWARDING",
firstMileEnabled: data.firstMile.enabled,
firstMilePickupAddress: data.firstMile.pickUpAddress ?? undefined,
lastMileEnabled: data.lastMile.enabled,
lastMileDeliveryAddress: data.lastMile.deliveryAddress ?? undefined,
equipmentReturn:
data.equipmentReturn === "with_return"
? ("WITH_RETURN" as const)
: ("WITHOUT_RETURN" as const),
customsClearingEnabled: data.customsClearingEnabled,
data.service.serviceType === "rail"
? "RAIL_ONLY"
: "RAIL_AND_FORWARDING",
...(data.service.serviceType === "rail"
? {}
: {
firstMileEnabled: data.firstMile.enabled,
firstMilePickupAddress: data.firstMile.pickUpAddress ?? undefined,
lastMileEnabled: data.lastMile.enabled,
lastMileDeliveryAddress: data.lastMile.deliveryAddress ?? undefined,
equipmentReturn:
data.equipmentReturn === "with_return"
? ("WITH_RETURN" as const)
: ("WITHOUT_RETURN" as const),
customsClearingEnabled: data.customsClearingEnabled,
}),
originStation: data.originYard,
destinationStation: data.destinationYard,
cargoTotalWeightVgm: totalWeight,
@@ -184,12 +186,7 @@ export default function NewBookingPage() {
<Step5CargoDetails form={form} direction={direction} />
)}
{step === 5 && (
<Step8Review
form={form}
setStep={setStep}
wagons={wagons}
direction={direction}
/>
<Step8Review form={form} setStep={setStep} direction={direction} />
)}
</div>
</div>

View File

@@ -1,3 +1,4 @@
import { DeepPartial, Path } from "react-hook-form";
import * as z from "zod";
export const STATIONS = [
@@ -82,36 +83,26 @@ export const bookingFormSchema = z
serviceType: z.enum(["rail", "rail_forwarding"], "Select a service type."),
firstMile: z
.object({
enabled: z.boolean(),
enabled: z.boolean().default(false),
pickUpAddress: z.string(),
})
.refine(
(data) => {
console.log(data);
return !(data.enabled && !data.pickUpAddress.trim());
},
{
message: "Enter the pick-up address.",
path: ["pickUpAddress"],
},
),
.refine((data) => !(data.enabled && !data.pickUpAddress.trim()), {
message: "Enter the pick-up address.",
path: ["pickUpAddress"],
}),
lastMile: z
.object({
enabled: z.boolean(),
enabled: z.boolean().default(false),
deliveryAddress: z.string(),
})
.refine(
(data) => {
console.log(data);
return !(data.enabled && !data.deliveryAddress.trim());
},
{
message: "Enter the delivery address.",
path: ["deliveryAddress"],
},
),
equipmentReturn: z.enum(["with_return", "without_return"]),
customsClearingEnabled: z.boolean(),
.refine((data) => !(data.enabled && !data.deliveryAddress.trim()), {
message: "Enter the delivery address.",
path: ["deliveryAddress"],
}),
equipmentReturn: z
.enum(["with_return", "without_return"])
.default("with_return"),
customsClearingEnabled: z.boolean().default(false),
originYard: z.string().min(1, "Select an origin yard."),
destinationYard: z.string().min(1, "Select a destination yard."),
shippingLine: z.string(),
@@ -259,8 +250,9 @@ export const bookingFormSchema = z
export type BookingFormValues = z.infer<typeof bookingFormSchema>;
export const initialBookingFormValues: Partial<BookingFormValues> = {
export const initialBookingFormValues: DeepPartial<BookingFormValues> = {
previousContractRef: "",
firstMile: {
enabled: false,
pickUpAddress: "",
@@ -287,7 +279,7 @@ export const initialBookingFormValues: Partial<BookingFormValues> = {
termsAccepted: false,
};
export const stepFields: Record<number, Array<keyof BookingFormValues>> = {
export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
1: ["contractType", "previousContractRef"],
2: [
"serviceType",

View File

@@ -1,3 +1,4 @@
import { useEffect, useRef } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { FileText, Package, Train, Truck } from "lucide-react";
import { Badge, Field, FieldError, Input, Switch } from "@edr/ui-common";
@@ -11,6 +12,59 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
const firstMileEnabled = form.watch("firstMile.enabled");
const lastMileEnabled = form.watch("lastMile.enabled");
const prevServiceType = useRef(serviceType);
useEffect(() => {
const prev = prevServiceType.current;
prevServiceType.current = serviceType;
if (!prev || prev === serviceType) return;
if (serviceType === "rail") {
form.setValue(
"firstMile",
{ enabled: false, pickUpAddress: "" },
{ shouldDirty: true, shouldValidate: true },
);
form.setValue(
"lastMile",
{ enabled: false, deliveryAddress: "" },
{ shouldDirty: true, shouldValidate: true },
);
form.setValue("equipmentReturn", "with_return", {
shouldDirty: true,
});
form.setValue("customsClearingEnabled", false, {
shouldDirty: true,
});
} else if (serviceType === "rail_forwarding") {
form.setValue(
"firstMile",
{
enabled: false,
pickUpAddress: "",
},
{
shouldDirty: false,
shouldValidate: false,
},
);
form.setValue(
"lastMile",
{
enabled: false,
deliveryAddress: "",
},
{
shouldDirty: false,
shouldValidate: false,
},
);
}
}, [serviceType, form]);
const showServiceSections = serviceType === "rail_forwarding";
return (
<div className="space-y-6">
<StepHeader
@@ -48,9 +102,7 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
<div className="mb-2 flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10">
<Package className="h-4 w-4 text-primary" />
</div>
<p className="font-semibold">
Rail Transport & Freight Forwarding
</p>
<p className="font-semibold">Logistics</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Rail transport plus documentation, customs liaison, and a
dedicated coordinator.
@@ -65,164 +117,172 @@ export function Step2ServiceType({ form }: { form: BookingForm }) {
)}
/>
<div className="divide-y divide-border rounded-xl border border-border">
<div className="p-4">
<Controller
name="firstMile.enabled"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
<p className="text-sm font-medium">First Mile - Pick-up</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Truck pick-up from your premises (Door to Port) to the
origin rail yard.
</p>
</div>
</div>
<Switch
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
if (!value) {
form.setValue("firstMile.pickUpAddress", "", {
shouldDirty: true,
shouldValidate: true,
});
}
}}
/>
</div>
)}
/>
{firstMileEnabled && (
<Controller
name="firstMile.pickUpAddress"
control={form.control}
render={({ field, fieldState }) => (
<Field className="mt-3" data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Pick-up address *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
)}
</div>
<div className="p-4">
<Controller
name="lastMile.enabled"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
<p className="text-sm font-medium">Last Mile - Delivery</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Truck delivery from the destination rail yard to the final
address (Port to Door).
</p>
</div>
</div>
<Switch
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
if (!value) {
form.setValue("lastMile.deliveryAddress", "", {
shouldDirty: true,
shouldValidate: true,
});
form.setValue("equipmentReturn", "with_return", {
shouldDirty: true,
});
}
}}
/>
</div>
)}
/>
{lastMileEnabled && (
<Controller
name="lastMile.deliveryAddress"
control={form.control}
render={({ field, fieldState }) => (
<Field className="mt-3" data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Delivery address *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
)}
</div>
{lastMileEnabled && (
{showServiceSections && (
<div className="divide-y divide-border rounded-xl border border-border">
<div className="p-4">
<Controller
name="equipmentReturn"
name="firstMile.enabled"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
<p className="text-sm font-medium">Equipment Return</p>
<p className="text-sm font-medium">
First Mile - Pick-up
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{field.value === "with_return"
? "Container returned to EDR after unloading."
: "Container retained by the customer after delivery."}
Truck pick-up from your premises (Door to Port) to the
origin rail yard.
</p>
</div>
</div>
<Switch
checked={field.value === "with_return"}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value ? "with_return" : "without_return");
field.onChange(value);
if (!value) {
form.setValue("firstMile.pickUpAddress", "", {
shouldDirty: true,
shouldValidate: true,
});
}
}}
/>
</div>
)}
/>
</div>
)}
<div className="p-4">
<Controller
name="customsClearingEnabled"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<FileText className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
<p className="text-sm font-medium">
Customs Clearing Service
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
EDR handles customs documentation and clearance on your
behalf.
</p>
</div>
</div>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</div>
{firstMileEnabled && (
<Controller
name="firstMile.pickUpAddress"
control={form.control}
render={({ field, fieldState }) => (
<Field className="mt-3" data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Pick-up address *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
)}
/>
</div>
<div className="p-4">
<Controller
name="lastMile.enabled"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<Truck className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
<p className="text-sm font-medium">
Last Mile - Delivery
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Truck delivery from the destination rail yard to the
final address (Port to Door).
</p>
</div>
</div>
<Switch
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
if (!value) {
form.setValue("lastMile.deliveryAddress", "", {
shouldDirty: true,
shouldValidate: true,
});
form.setValue("equipmentReturn", "with_return", {
shouldDirty: true,
});
}
}}
/>
</div>
)}
/>
{lastMileEnabled && (
<Controller
name="lastMile.deliveryAddress"
control={form.control}
render={({ field, fieldState }) => (
<Field className="mt-3" data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Delivery address *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
)}
</div>
{lastMileEnabled && (
<div className="p-4">
<Controller
name="equipmentReturn"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<div>
<p className="text-sm font-medium">Equipment Return</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{field.value === "with_return"
? "Container returned to EDR after unloading."
: "Container retained by the customer after delivery."}
</p>
</div>
</div>
<Switch
checked={field.value === "with_return"}
onCheckedChange={(value) => {
field.onChange(
value ? "with_return" : "without_return",
);
}}
/>
</div>
)}
/>
</div>
)}
<div className="p-4">
<Controller
name="customsClearingEnabled"
control={form.control}
render={({ field }) => (
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<FileText className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
<div>
<p className="text-sm font-medium">
Customs Clearing Service
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
EDR handles customs documentation and clearance on your
behalf.
</p>
</div>
</div>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</div>
)}
/>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,13 +1,6 @@
import { Controller, useFieldArray, type UseFormReturn } from "react-hook-form";
import { MapPin, Package, Plus, Trash2, Weight } from "lucide-react";
import {
Button,
Field,
FieldError,
FieldLabel,
Input,
Separator,
} from "@edr/ui-common";
import { Button, Field, FieldError, FieldLabel, Input } from "@edr/ui-common";
import {
BREAK_BULK_TYPES,
BULK_COMMODITIES,
@@ -83,7 +76,6 @@ export function Step5CargoDetails({
form.setValue("freightType", undefined, {
shouldDirty: true,
});
form.setValue("cargoWeight", "", { shouldDirty: true });
}}
>
<div className="mb-2 flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10">
@@ -98,18 +90,7 @@ export function Step5CargoDetails({
selected={cargoType === "bulk"}
onClick={() => {
field.onChange("bulk");
form.setValue(
"containers",
[
{
type: "20ft",
containerType: "",
qty: "1",
vgm: "",
},
],
{ shouldDirty: true },
);
form.setValue("containers", [], { shouldDirty: true });
}}
>
<div className="mb-2 flex h-9 w-9 items-center justify-center rounded-lg bg-amber-100">
@@ -127,143 +108,137 @@ export function Step5CargoDetails({
/>
</div>
<div className="space-y-3">
<StepLabel>Weight</StepLabel>
<Controller
name="cargoWeight"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor="cargoWeight">
Total Cargo Weight(Tons)*
</FieldLabel>
<div className="relative">
<Weight className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
{...field}
id="cargoWeight"
type="number"
aria-invalid={fieldState.invalid}
placeholder="0.00"
className="pl-9"
min="0"
step="0.01"
/>
</div>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
</div>
{cargoType === "bulk" && (
<>
<Separator />
<div className="space-y-3">
<StepLabel>Freight Type *</StepLabel>
<Controller
name="freightType"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<div className="grid gap-3 sm:grid-cols-2">
<OptionCard
selected={freightType === "bulk"}
onClick={() => field.onChange("bulk")}
>
<p className="font-semibold">Bulk</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Coffee, fertilizer, grain, ore, etc.
</p>
</OptionCard>
<OptionCard
selected={freightType === "break_bulk"}
onClick={() => field.onChange("break_bulk")}
>
<p className="font-semibold">Break-Bulk</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Machinery, vehicles, project cargo, etc.
</p>
</OptionCard>
</div>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
<div className="space-y-3">
<StepLabel>Freight Type *</StepLabel>
<Controller
name="freightType"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<div className="grid gap-3 sm:grid-cols-2">
<OptionCard
selected={freightType === "bulk"}
onClick={() => field.onChange("bulk")}
>
<p className="font-semibold">Bulk</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Coffee, fertilizer, grain, ore, etc.
</p>
</OptionCard>
<OptionCard
selected={freightType === "break_bulk"}
onClick={() => field.onChange("break_bulk")}
>
<p className="font-semibold">Break-Bulk</p>
<p className="mt-0.5 text-xs text-muted-foreground">
Machinery, vehicles, project cargo, etc.
</p>
</OptionCard>
</div>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
{freightType === "bulk" && (
<div className="space-y-2">
{freightType === "bulk" && (
<div className="space-y-2">
<Controller
name="bulkCommodity"
control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Commodity *"
placeholder="Select commodity *"
>
<SelectOptions options={BULK_COMMODITIES} />
</SelectField>
)}
/>
{bulkCommodity === "Others" && (
<Controller
name="bulkCommodity"
name="bulkCommodityOther"
control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Commodity *"
placeholder="Select commodity *"
>
<SelectOptions options={BULK_COMMODITIES} />
</SelectField>
<Field data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Specify commodity *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
{bulkCommodity === "Others" && (
<Controller
name="bulkCommodityOther"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Specify commodity *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
)}
</div>
)}
)}
</div>
)}
{freightType === "break_bulk" && (
<div className="space-y-2">
{freightType === "break_bulk" && (
<div className="space-y-2">
<Controller
name="breakBulkType"
control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Break-bulk type *"
placeholder="Select type *"
>
<SelectOptions options={BREAK_BULK_TYPES} />
</SelectField>
)}
/>
{breakBulkType === "Others" && (
<Controller
name="breakBulkType"
name="breakBulkTypeOther"
control={form.control}
render={({ field, fieldState }) => (
<SelectField
field={field}
error={fieldState.error}
label="Break-bulk type *"
placeholder="Select type *"
>
<SelectOptions options={BREAK_BULK_TYPES} />
</SelectField>
<Field data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Specify break-bulk type *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
{breakBulkType === "Others" && (
<Controller
name="breakBulkTypeOther"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Specify break-bulk type *"
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
)}
</div>
)}
</div>
<Separator />
<div className="space-y-3">
<StepLabel>Weight</StepLabel>
<Controller
name="cargoWeight"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor="cargoWeight">
Total Cargo Weight - VGM (Tons) *
</FieldLabel>
<div className="relative">
<Weight className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
{...field}
id="cargoWeight"
type="number"
aria-invalid={fieldState.invalid}
placeholder="0.00"
className="pl-9"
min="0"
step="0.01"
/>
</div>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
</div>
</>
</div>
)}
</div>
)}
{cargoType === "container" && (
@@ -407,7 +382,7 @@ export function Step5CargoDetails({
control={form.control}
render={({ field: vgmField, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel>VGM (Tons) *</FieldLabel>
<FieldLabel>Tons*</FieldLabel>
<Input
value={vgmField.value ?? 0}
onChange={(e) => vgmField.onChange(e.target.value)}

View File

@@ -22,12 +22,10 @@ type BookingForm = UseFormReturn<BookingFormValues>;
export function Step8Review({
form,
setStep,
wagons,
direction,
}: {
form: BookingForm;
setStep: (step: number) => void;
wagons: WagonCalcResult | null;
direction: RouteDirection;
}) {
const values = form.watch();
@@ -102,11 +100,6 @@ export function Step8Review({
value={values.contractType === "new" ? "New Contract" : "Renewal"}
target={1}
/>
<Row
label="Contract ID"
value={values.previousContractRef}
target={1}
/>
<Row
label="Service"
value={
@@ -224,15 +217,6 @@ export function Step8Review({
value={totalVgm > 0 ? `${totalVgm.toFixed(1)} tons` : ""}
target={4}
/>
<Row
label="Wagons"
value={
wagons
? `${wagons.totalWagons} wagon${wagons.totalWagons > 1 ? "s" : ""}`
: ""
}
target={4}
/>
</CardContent>
</Card>
</div>