diff --git a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx index f96e76b51..04728e0a2 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx @@ -1,15 +1,14 @@ -import { useEffect, useMemo, useState } from "react"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useMemo, useState } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import { CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react"; +import { Check, CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react"; import { Button } from "@edr/ui-common"; import Breadcrumbs from "@/components/Breadcrumbs"; import { api } from "@/services/api"; import type { CreateBookingPayload } from "@/services/bookings.service"; import { - MOCK_VALID_CONTRACTS, STEPS, bookingFormSchema, calcWagons, @@ -22,11 +21,8 @@ import { StepIndicator } from "./new-booking-form/StepIndicator"; import { Step1ContractType, Step2ServiceType, - Step3FirstLastMile, Step4Route, Step5CargoDetails, - Step6WagonAllocation, - Step7Documents, Step8Review, } from "./new-booking-form/steps"; import useAuth from "@/hooks/useAuth"; @@ -35,8 +31,6 @@ export default function NewBookingPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const [step, setStep] = useState(1); - const [renewalValidating, setRenewalValidating] = useState(false); - const [renewalValid, setRenewalValid] = useState(null); const { customer } = useAuth(); const createMutation = useMutation({ mutationFn: (payload: CreateBookingPayload) => @@ -56,6 +50,7 @@ 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( @@ -68,63 +63,20 @@ export default function NewBookingPage() { return calcWagons(containers); }, [containers]); - useEffect(() => { - setRenewalValid(null); - }, [previousContractRef]); - - function validateRenewal() { - const previousContractRef = form.getValues("previousContractRef").trim(); - - if (!previousContractRef) { - form.setError("previousContractRef", { - type: "manual", - message: "Enter a previous contract reference.", - }); - return; - } - - setRenewalValidating(true); - setRenewalValid(null); - setTimeout(() => { - const valid = MOCK_VALID_CONTRACTS.includes( - previousContractRef.toUpperCase(), - ); - setRenewalValidating(false); - setRenewalValid(valid); - if (!valid) { - form.setError("previousContractRef", { - type: "manual", - message: "Contract Reference Number not found or unauthorized.", - }); - } else { - form.clearErrors("previousContractRef"); - } - }, 1200); - } + const renewalValid = contractType === "renewal" && previousContractRef !== ""; async function handleContinue() { const valid = await form.trigger(stepFields[step], { shouldFocus: true }); if (!valid) return; - if (step === 1 && form.getValues("contractType") === "renewal") { - if (renewalValid !== true) { - form.setError("previousContractRef", { - type: "manual", - message: - "Validate the previous contract reference before continuing.", - }); - return; - } - } - setStep((currentStep) => Math.min(STEPS.length, currentStep + 1)); } const handleSubmit = form.handleSubmit((data) => { - if (data.contractType === "renewal" && renewalValid !== true) { + if (data.contractType === "renewal" && !data.previousContractRef) { form.setError("previousContractRef", { type: "manual", - message: "Validate the previous contract reference before submitting.", + message: "Select a previous contract reference.", }); setStep(1); return; @@ -150,18 +102,15 @@ export default function NewBookingPage() { previousContractId: data.previousContractRef || undefined, serviceType: data.serviceType === "rail" ? "RAIL_ONLY" : "RAIL_AND_FORWARDING", - firstMileEnabled: data.firstMileEnabled, - firstMilePickupAddress: data.firstMileEnabled - ? data.pickUpAddress - : undefined, - lastMileEnabled: data.lastMileEnabled, - lastMileDeliveryAddress: data.lastMileEnabled - ? data.deliveryAddress - : undefined, + 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, @@ -220,37 +169,21 @@ export default function NewBookingPage() { className="flex flex-col" onSubmit={handleSubmit} > -
-
- +
+
- {step === 1 && ( - - )} + {step === 1 && } {step === 2 && } - {step === 3 && } - {step === 4 && } - {step === 5 && ( + {step === 3 && } + {step === 4 && ( )} - {step === 6 && } - {step === 7 && } - {step === 8 && ( + {step === 5 && ( Continue - + ) : ( )} diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts index 5ea5d4ded..dbc979595 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts @@ -42,123 +42,79 @@ export const MOCK_VALID_CONTRACTS = [ "EDR-2022-55442", ]; -export const REQUIRED_DOC_KEYS = [ - "tin_certificate", - "business_license", - "business_registration", - // "national_id", +export const CONTAINER_TYPES = [ + "Dry Container", + "High Cubic", + "Reefer Container", + "Open Top", + "Flat Rack", + "Tank Container", + "Open Side", +] as const; + +export const SHIPPING_LINES = [ + "MSC", + "CMA CGM", + "Evergreen", + "COSCO", + "Hapag-Lloyd", + "ONE", + "Yang Ming", + "ZIM", + "Messina Line", + "Safmarine", + "Wan Hai", + "Ethiopian Shipping Lines (ESLSE)", ] as const; export const STEPS = [ { id: 1, label: "Contract Type", short: "Contract" }, - { id: 2, label: "Service Type", short: "Service" }, - { id: 3, label: "First & Last Mile", short: "Mile" }, - { id: 4, label: "Route", short: "Route" }, - { id: 5, label: "Cargo Details", short: "Cargo" }, - { id: 6, label: "Wagon Allocation", short: "Wagons" }, - { id: 7, label: "Documents", short: "Docs" }, - { id: 8, label: "Review & Submit", short: "Submit" }, + { id: 2, label: "Service Type & Mile", short: "Service" }, + { id: 3, label: "Route", short: "Route" }, + { id: 4, label: "Cargo Details", short: "Cargo" }, + { id: 5, label: "Review & Submit", short: "Submit" }, ] as const; -export const BOOKING_DOCS_SETTING = { - id: "booking-compliance", - createdAt: "", - updatedAt: "", - deletedAt: null, - code: "booking_compliance_docs", - label: "Compliance Documents", - description: - "Upload your company's legal credentials. All mandatory documents must be submitted before the contract request can be reviewed by EDR Line Staff.", - entity: "booking" as const, - fields: [ - { - id: "f1", - createdAt: "", - updatedAt: "", - deletedAt: null, - settingId: "booking-compliance", - fileKey: "tin_certificate", - fileLabel: "TIN Certificate", - helpText: - "Tax Identification Number certificate issued by ERCA (10-digit TIN).", - isRequired: true, - isMultiple: false, - maxFiles: 1, - allowedExtensions: ["pdf", "jpg", "jpeg", "png"], - maxSizeMb: 5, - order: 1, - }, - { - id: "f2", - createdAt: "", - updatedAt: "", - deletedAt: null, - settingId: "booking-compliance", - fileKey: "business_license", - fileLabel: "Business / Investment License", - helpText: - "Current business or investment license issued by the relevant government authority.", - isRequired: true, - isMultiple: false, - maxFiles: 1, - allowedExtensions: ["pdf", "jpg", "jpeg", "png"], - maxSizeMb: 5, - order: 2, - }, - { - id: "f3", - createdAt: "", - updatedAt: "", - deletedAt: null, - settingId: "booking-compliance", - fileKey: "business_registration", - fileLabel: "Business Registration Certificate", - helpText: "Certificate of registration from the relevant authority.", - isRequired: true, - isMultiple: false, - maxFiles: 1, - allowedExtensions: ["pdf", "jpg", "jpeg", "png"], - maxSizeMb: 5, - order: 3, - }, - { - id: "f5", - createdAt: "", - updatedAt: "", - deletedAt: null, - settingId: "booking-compliance", - fileKey: "power_of_attorney", - fileLabel: "Power of Attorney (PoA)", - helpText: - "Required only if a representative is signing on behalf of the company.", - isRequired: false, - isMultiple: false, - maxFiles: 1, - allowedExtensions: ["pdf", "jpg", "jpeg", "png"], - maxSizeMb: 5, - order: 5, - }, - ], -}; - -const fileValueSchema = z.union([ - z.custom(), - z.array(z.custom()), - z.null(), -]); - export const bookingFormSchema = z .object({ contractType: z.enum(["new", "renewal"], "Select a contract type."), previousContractRef: z.string(), serviceType: z.enum(["rail", "rail_forwarding"], "Select a service type."), - firstMileEnabled: z.boolean(), - pickUpAddress: z.string(), - lastMileEnabled: z.boolean(), - deliveryAddress: z.string(), + firstMile: z + .object({ + enabled: z.boolean(), + pickUpAddress: z.string(), + }) + .refine( + (data) => { + console.log(data); + return !(data.enabled && !data.pickUpAddress.trim()); + }, + { + message: "Enter the pick-up address.", + path: ["pickUpAddress"], + }, + ), + lastMile: z + .object({ + enabled: z.boolean(), + 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"]), - originYard: z.string(), - destinationYard: z.string(), + customsClearingEnabled: z.boolean(), + originYard: z.string().min(1, "Select an origin yard."), + destinationYard: z.string().min(1, "Select a destination yard."), + shippingLine: z.string(), cargoType: z.enum(["container", "bulk"], "Select a cargo type."), cargoWeight: z.string(), freightType: z.enum(["bulk", "break_bulk"]).optional(), @@ -171,142 +127,116 @@ export const bookingFormSchema = z containers: z.array( z.object({ type: z.enum(["20ft", "40ft"]), + containerType: z.string().min(1, "Select a container type."), qty: z .string() + .refine((q) => q.length !== 0, "Quantity is required.") .refine((q) => !isNaN(+q), "Enter a valid Number") .refine((qty) => Number(qty) >= 1, "Must be greater than 0"), vgm: z .string() + .refine((vgm) => vgm.length !== 0, "VGM is required.") .refine((vgm) => !isNaN(+vgm), "Enter a valid Number") .refine((vgm) => Number(vgm) >= 0, "Must be greater than 0"), }), ), consolidationEnabled: z.boolean(), - documents: z.record(z.string(), fileValueSchema), notes: z.string(), termsAccepted: z.boolean(), }) - .superRefine((data, ctx) => { - if (data.contractType === "renewal" && !data.previousContractRef.trim()) { - ctx.addIssue({ - code: "custom", - path: ["previousContractRef"], - message: "Enter a previous contract reference.", - }); - } - - if (data.firstMileEnabled && !data.pickUpAddress.trim()) { - ctx.addIssue({ - code: "custom", - path: ["pickUpAddress"], - message: "Enter the pick-up address.", - }); - } - - if (data.lastMileEnabled && !data.deliveryAddress.trim()) { - ctx.addIssue({ - code: "custom", - path: ["deliveryAddress"], - message: "Enter the delivery address.", - }); - } - - if (!data.originYard) { - ctx.addIssue({ - code: "custom", - path: ["originYard"], - message: "Select an origin yard.", - }); - } - - if (!data.destinationYard) { - ctx.addIssue({ - code: "custom", - path: ["destinationYard"], - message: "Select a destination yard.", - }); - } - - if ( - data.originYard && - data.destinationYard && - data.originYard === data.destinationYard - ) { - ctx.addIssue({ - code: "custom", - path: ["destinationYard"], - message: "Destination must be different from origin.", - }); - } - - if (data.cargoType === "bulk") { - if (!data.freightType) { - ctx.addIssue({ - code: "custom", - path: ["freightType"], - message: "Select a freight type.", - }); - } - - if (data.freightType === "bulk") { - if (!data.bulkCommodity) { - ctx.addIssue({ - code: "custom", - path: ["bulkCommodity"], - message: "Select a commodity.", - }); - } - if ( - data.bulkCommodity === "Others" && - !data.bulkCommodityOther.trim() - ) { - ctx.addIssue({ - code: "custom", - path: ["bulkCommodityOther"], - message: "Specify the commodity.", - }); - } - } - - if (data.freightType === "break_bulk") { - if (!data.breakBulkType) { - ctx.addIssue({ - code: "custom", - path: ["breakBulkType"], - message: "Select a break-bulk type.", - }); - } - if ( - data.breakBulkType === "Others" && - !data.breakBulkTypeOther.trim() - ) { - ctx.addIssue({ - code: "custom", - path: ["breakBulkTypeOther"], - message: "Specify the break-bulk type.", - }); - } - } - + .refine( + (data) => + !(data.contractType === "renewal" && !data.previousContractRef.trim()), + { + message: "Enter a previous contract reference.", + path: ["previousContractRef"], + }, + ) + .refine((data) => data.originYard !== "", { + message: "Select an origin yard.", + path: ["originYard"], + }) + .refine((data) => data.destinationYard !== "", { + message: "Select a destination yard.", + path: ["destinationYard"], + }) + .refine( + (data) => + !( + data.originYard && + data.destinationYard && + data.originYard === data.destinationYard + ), + { + message: "Destination must be different from origin.", + path: ["destinationYard"], + }, + ) + .refine((data) => !(data.cargoType === "bulk" && !data.freightType), { + message: "Select a freight type.", + path: ["freightType"], + }) + .refine( + (data) => + !( + data.cargoType === "bulk" && + data.freightType === "bulk" && + !data.bulkCommodity + ), + { message: "Select a commodity.", path: ["bulkCommodity"] }, + ) + .refine( + (data) => + !( + data.cargoType === "bulk" && + data.freightType === "bulk" && + data.bulkCommodity === "Others" && + !data.bulkCommodityOther.trim() + ), + { message: "Specify the commodity.", path: ["bulkCommodityOther"] }, + ) + .refine( + (data) => + !( + data.cargoType === "bulk" && + data.freightType === "break_bulk" && + !data.breakBulkType + ), + { message: "Select a break-bulk type.", path: ["breakBulkType"] }, + ) + .refine( + (data) => + !( + data.cargoType === "bulk" && + data.freightType === "break_bulk" && + data.breakBulkType === "Others" && + !data.breakBulkTypeOther.trim() + ), + { + message: "Specify the break-bulk type.", + path: ["breakBulkTypeOther"], + }, + ) + .refine( + (data) => { + if (data.cargoType !== "bulk") return true; const cargoWeight = Number(data.cargoWeight); - if (!data.cargoWeight || Number.isNaN(cargoWeight) || cargoWeight <= 0) { - ctx.addIssue({ - code: "custom", - path: ["cargoWeight"], - message: "Enter a cargo weight greater than 0.", - }); - } - } - + return ( + !!data.cargoWeight && !Number.isNaN(cargoWeight) && cargoWeight > 0 + ); + }, + { message: "Enter a cargo weight greater than 0.", path: ["cargoWeight"] }, + ) + .refine( + (data) => !(data.cargoType === "container" && data.containers.length === 0), + { message: "Add at least one container.", path: ["containers"] }, + ) + .refine((data) => data.termsAccepted, { + message: "Accept the freight contract terms to submit.", + path: ["termsAccepted"], + }) + .superRefine((data, ctx) => { if (data.cargoType === "container") { - if (data.containers.length === 0) { - ctx.addIssue({ - code: "custom", - path: ["containers"], - message: "Add at least one container.", - }); - } - data.containers.forEach((c, i) => { if (!c.qty || +c.qty < 1) { ctx.addIssue({ @@ -325,39 +255,25 @@ export const bookingFormSchema = z } }); } - - for (const key of REQUIRED_DOC_KEYS) { - const value = data.documents[key]; - const hasFile = Array.isArray(value) ? value.length > 0 : Boolean(value); - if (!hasFile) { - ctx.addIssue({ - code: "custom", - path: ["documents", key], - message: "Upload this required document.", - }); - } - } - - if (!data.termsAccepted) { - ctx.addIssue({ - code: "custom", - path: ["termsAccepted"], - message: "Accept the freight contract terms to submit.", - }); - } }); export type BookingFormValues = z.infer; export const initialBookingFormValues: Partial = { previousContractRef: "", - firstMileEnabled: false, - pickUpAddress: "", - lastMileEnabled: false, - deliveryAddress: "", + firstMile: { + enabled: false, + pickUpAddress: "", + }, + lastMile: { + enabled: false, + deliveryAddress: "", + }, equipmentReturn: "with_return", + customsClearingEnabled: false, originYard: "", destinationYard: "", + shippingLine: "", cargoWeight: "", bulkCommodity: "", bulkCommodityOther: "", @@ -365,25 +281,29 @@ export const initialBookingFormValues: Partial = { breakBulkTypeOther: "", isHazardous: false, isRefrigerated: false, - containers: [{ type: "20ft", qty: "1", vgm: "" }], + containers: [{ type: "20ft", containerType: "", qty: "1", vgm: "" }], consolidationEnabled: false, - documents: {}, notes: "", termsAccepted: false, }; export const stepFields: Record> = { 1: ["contractType", "previousContractRef"], - 2: ["serviceType"], - 3: [ - "firstMileEnabled", - "pickUpAddress", - "lastMileEnabled", - "deliveryAddress", + 2: [ + "serviceType", + "firstMile", + "lastMile", "equipmentReturn", + "customsClearingEnabled", ], - 4: ["originYard", "destinationYard", "isHazardous", "isRefrigerated"], - 5: [ + 3: [ + "originYard", + "destinationYard", + "isHazardous", + "isRefrigerated", + "shippingLine", + ], + 4: [ "cargoType", "cargoWeight", "freightType", @@ -392,16 +312,16 @@ export const stepFields: Record> = { "breakBulkType", "breakBulkTypeOther", "containers", + "consolidationEnabled", ], - 6: ["consolidationEnabled"], - 7: ["documents"], - 8: ["notes", "termsAccepted"], + 5: ["notes", "termsAccepted"], }; export type RouteDirection = "import" | "export" | "domestic" | null; export interface ContainerConfig { type: "20ft" | "40ft"; + containerType: string; qty: string; vgm: string; } diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx index 6cfff801a..120aa48a3 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/shared.tsx @@ -22,18 +22,8 @@ import { SelectValue, } from "@edr/ui-common"; import type { BookingFormValues } from "./schema"; -import { REQUIRED_DOC_KEYS } from "./schema"; import { cn } from "@/lib/utils"; -export function getUploadedRequiredCount( - documents: BookingFormValues["documents"], -) { - return REQUIRED_DOC_KEYS.filter((key) => { - const file = documents[key]; - return Array.isArray(file) ? file.length > 0 : Boolean(file); - }).length; -} - export function OptionFieldError({ error }: { error?: { message?: string } }) { return ; } @@ -160,6 +150,8 @@ export function SelectField({ ); } +export { SelectItem }; + export function SelectOptions({ options }: { options: readonly string[] }) { return ( <> diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step1-contract-type.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step1-contract-type.tsx index e8e045e5c..62a66d0e1 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step1-contract-type.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step1-contract-type.tsx @@ -1,22 +1,19 @@ import { Controller, type UseFormReturn } from "react-hook-form"; -import { FileText, Loader2, RefreshCw } from "lucide-react"; -import { Button, Field, FieldLabel, Input } from "@edr/ui-common"; -import { type BookingFormValues } from "./schema"; -import { AlertBox, OptionCard, OptionFieldError, StepHeader } from "./shared"; +import { FileText, RefreshCw } from "lucide-react"; +import { Field } from "@edr/ui-common"; +import { MOCK_VALID_CONTRACTS, type BookingFormValues } from "./schema"; +import { + AlertBox, + OptionCard, + OptionFieldError, + SelectField, + SelectItem, + StepHeader, +} from "./shared"; type BookingForm = UseFormReturn; -export function Step1ContractType({ - form, - renewalValid, - renewalValidating, - onValidate, -}: { - form: BookingForm; - renewalValid: boolean | null; - renewalValidating: boolean; - onValidate: () => void; -}) { +export function Step1ContractType({ form }: { form: BookingForm }) { const contractType = form.watch("contractType"); const previousContractRef = form.watch("previousContractRef"); @@ -38,6 +35,7 @@ export function Step1ContractType({ onClick={() => { field.onChange("new"); form.clearErrors(["contractType", "previousContractRef"]); + form.setValue("previousContractRef", ""); }} >
@@ -45,7 +43,7 @@ export function Step1ContractType({

New Contract

- Blank contract form. A draft ID is auto-generated. + Create a new contract.

@@ -61,7 +59,7 @@ export function Step1ContractType({

Contract Renewal

- Enter a previous reference to auto-populate historical + Select a previous reference to auto-populate historical parameters.

@@ -77,46 +75,26 @@ export function Step1ContractType({ name="previousContractRef" control={form.control} render={({ field, fieldState }) => ( - - - Previous Contract Reference Number - -
- - -
-
+ + {MOCK_VALID_CONTRACTS.map((ref) => ( + + {ref} + + ))} + )} /> - {renewalValid === true && ( + {previousContractRef && ( Contract found. Company details, route, and wagon preferences will be pre-filled. )} - {renewalValid === false && ( - - Contract Reference Number not found or unauthorized. Try{" "} - EDR-2024-10001. - - )}
)}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx index 1e4350e1b..07e1638f3 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step2-service-type.tsx @@ -1,6 +1,6 @@ import { Controller, type UseFormReturn } from "react-hook-form"; -import { Package, Train } from "lucide-react"; -import { Badge, Field, FieldError } from "@edr/ui-common"; +import { FileText, Package, Train, Truck } from "lucide-react"; +import { Badge, Field, FieldError, Input, Switch } from "@edr/ui-common"; import { type BookingFormValues } from "./schema"; import { OptionCard, OptionFieldError, StepHeader } from "./shared"; @@ -8,12 +8,14 @@ type BookingForm = UseFormReturn; export function Step2ServiceType({ form }: { form: BookingForm }) { const serviceType = form.watch("serviceType"); + const firstMileEnabled = form.watch("firstMile.enabled"); + const lastMileEnabled = form.watch("lastMile.enabled"); return (
-

- Customs and Clearance Service cannot be selected independently. It must - be bundled with a Rail Transport service. -

+
+
+ ( +
+
+ +
+

First Mile - Pick-up

+

+ Truck pick-up from your premises (Door to Port) to the + origin rail yard. +

+
+
+ { + field.onChange(value); + if (!value) { + form.setValue("firstMile.pickUpAddress", "", { + shouldDirty: true, + shouldValidate: true, + }); + } + }} + /> +
+ )} + /> + {firstMileEnabled && ( + ( + + + + + )} + /> + )} +
+ +
+ ( +
+
+ +
+

Last Mile - Delivery

+

+ Truck delivery from the destination rail yard to the final + address (Port to Door). +

+
+
+ { + field.onChange(value); + if (!value) { + form.setValue("lastMile.deliveryAddress", "", { + shouldDirty: true, + shouldValidate: true, + }); + form.setValue("equipmentReturn", "with_return", { + shouldDirty: true, + }); + } + }} + /> +
+ )} + /> + {lastMileEnabled && ( + ( + + + + + )} + /> + )} +
+ + {lastMileEnabled && ( +
+ ( +
+
+
+

Equipment Return

+

+ {field.value === "with_return" + ? "Container returned to EDR after unloading." + : "Container retained by the customer after delivery."} +

+
+
+ { + field.onChange(value ? "with_return" : "without_return"); + }} + /> +
+ )} + /> +
+ )} + +
+ ( +
+
+ +
+

+ Customs Clearing Service +

+

+ EDR handles customs documentation and clearance on your + behalf. +

+
+
+ +
+ )} + /> +
+
); } diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step3-first-last-mile.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step3-first-last-mile.tsx index 369eb03f1..3bc5629ca 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step3-first-last-mile.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step3-first-last-mile.tsx @@ -1,7 +1,7 @@ import { Controller, type UseFormReturn } from "react-hook-form"; import { Field, FieldError, Input, Switch } from "@edr/ui-common"; import { type BookingFormValues } from "./schema"; -import { OptionCard, StepHeader } from "./shared"; +import { StepHeader } from "./shared"; type BookingForm = UseFormReturn; @@ -27,7 +27,8 @@ export function Step3FirstLastMile({ form }: { form: BookingForm }) {

First Mile - Pick-up

- Truck pick-up from your premises to the origin rail yard. + Truck pick-up from your premises (Door to Port) to the + origin rail yard.

Last Mile - Delivery

Truck delivery from the destination rail yard to the final - address. + address (Port to Door).

)} - -
-

Equipment Return

-

- Declare whether the container asset will be returned after - unloading. -

- ( -
- field.onChange("with_return")} - > -

With Return

-

- Container returned to EDR after unloading. -

-
- field.onChange("without_return")} - > -

Without Return

-

- Container retained by the customer after delivery. -

-
-
- )} - /> + {lastMileEnabled && ( +
+ ( +
+
+

Equipment Return

+

+ {field.value === "with_return" + ? "Container returned to EDR after unloading." + : "Container retained by the customer after delivery."} +

+
+ { + field.onChange( + value ? "with_return" : "without_return", + ); + }} + /> +
+ )} + /> +
+ )}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx index 18b4c581b..3d98fc854 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step4-route.tsx @@ -1,7 +1,12 @@ import { Controller, type UseFormReturn } from "react-hook-form"; import { Flame, MapPin, Snowflake } from "lucide-react"; import { Field, SelectItem, Separator, Switch } from "@edr/ui-common"; -import { type BookingFormValues, getRouteDirection, STATIONS } from "./schema"; +import { + SHIPPING_LINES, + type BookingFormValues, + getRouteDirection, + STATIONS, +} from "./schema"; import { AlertBox, SelectField, @@ -11,6 +16,7 @@ import { } from "./shared"; import { useDropdownSettingByCode } from "@/hooks/useDropdownSettings"; import { DropdownOption } from "@/types/dropdownSettings"; +import { useEffect } from "react"; type BookingForm = UseFormReturn; @@ -39,6 +45,12 @@ export function Step4Route({ form }: { form: BookingForm }) { }; const stationSelectDisabled = stationsLoading || stationOptions.length === 0; + useEffect(() => { + if (direction === "domestic") { + form.setValue("shippingLine", "", { shouldDirty: true }); + } + }, [direction]); + return (
+ {direction && direction != "domestic" && ( + ( + + + + )} + /> + )}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx index a26bf1876..7e017efbb 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx @@ -11,6 +11,8 @@ import { import { BREAK_BULK_TYPES, BULK_COMMODITIES, + CONTAINER_TYPES, + calcWagons, type BookingFormValues, type RouteDirection, } from "./schema"; @@ -98,7 +100,14 @@ export function Step5CargoDetails({ field.onChange("bulk"); form.setValue( "containers", - [{ type: "20ft", qty: "1", vgm: "0" }], + [ + { + type: "20ft", + containerType: "", + qty: "1", + vgm: "", + }, + ], { shouldDirty: true }, ); }} @@ -259,32 +268,27 @@ export function Step5CargoDetails({ {cargoType === "container" && ( <> -
- Container Configuration + Containers
- {direction && ( -

- - Route detected as{" "} - - {direction} - {" "} - workflow -

- )} - {fields.map((field, index) => { const containerType = containers[index]?.type; const vgm = containers[index]?.vgm ?? 0; @@ -293,12 +297,9 @@ export function Step5CargoDetails({ return (
-

- Container #{index + 1} -

{fields.length > 1 && (