diff --git a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx index 43f1abb21..bfb175895 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx @@ -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%]", + }, }} >
@@ -125,31 +133,17 @@ export default function SignupPage() { -
- - Username - - - - - - Email Address - - - -
+ + Email Address + + + - ) : ( )} 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..4da8ed6ef 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 @@ -1,3 +1,4 @@ +import { DeepPartial, Path } from "react-hook-form"; import * as z from "zod"; export const STATIONS = [ @@ -42,123 +43,69 @@ 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(), - equipmentReturn: z.enum(["with_return", "without_return"]), - originYard: z.string(), - destinationYard: z.string(), + firstMile: z + .object({ + enabled: z.boolean().default(false), + pickUpAddress: z.string(), + }) + .refine((data) => !(data.enabled && !data.pickUpAddress.trim()), { + message: "Enter the pick-up address.", + path: ["pickUpAddress"], + }), + lastMile: z + .object({ + enabled: z.boolean().default(false), + deliveryAddress: z.string(), + }) + .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(), cargoType: z.enum(["container", "bulk"], "Select a cargo type."), cargoWeight: z.string(), freightType: z.enum(["bulk", "break_bulk"]).optional(), @@ -171,142 +118,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 +246,26 @@ 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 = { +export const initialBookingFormValues: DeepPartial = { 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 +273,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> = { +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 +304,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..e9bd1d331 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,7 @@ +import { useEffect, useRef } from "react"; 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 +9,67 @@ 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"); + + 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 (
-

- Rail Transport & Freight Forwarding -

+

Logistics

Rail transport plus documentation, customs liaison, and a dedicated coordinator. @@ -63,10 +117,172 @@ export function Step2ServiceType({ form }: { form: BookingForm }) { )} /> -

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

+ {showServiceSections && ( +
+
+ ( +
+
+ +
+

+ 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..1898feb73 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 @@ -1,16 +1,11 @@ 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, + CONTAINER_TYPES, + calcWagons, type BookingFormValues, type RouteDirection, } from "./schema"; @@ -81,7 +76,6 @@ export function Step5CargoDetails({ form.setValue("freightType", undefined, { shouldDirty: true, }); - form.setValue("cargoWeight", "", { shouldDirty: true }); }} >
@@ -96,11 +90,7 @@ export function Step5CargoDetails({ selected={cargoType === "bulk"} onClick={() => { field.onChange("bulk"); - form.setValue( - "containers", - [{ type: "20ft", qty: "1", vgm: "0" }], - { shouldDirty: true }, - ); + form.setValue("containers", [], { shouldDirty: true }); }} >
@@ -118,173 +108,162 @@ export function Step5CargoDetails({ />
+
+ Weight + ( + + + Total Cargo Weight(Tons)* + +
+ + +
+ +
+ )} + /> +
{cargoType === "bulk" && ( - <> - -
- Freight Type * - ( - -
- field.onChange("bulk")} - > -

Bulk

-

- Coffee, fertilizer, grain, ore, etc. -

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

Break-Bulk

-

- Machinery, vehicles, project cargo, etc. -

-
-
- -
- )} - /> +
+ Freight Type * + ( + +
+ field.onChange("bulk")} + > +

Bulk

+

+ Coffee, fertilizer, grain, ore, etc. +

+
+ field.onChange("break_bulk")} + > +

Break-Bulk

+

+ Machinery, vehicles, project cargo, etc. +

+
+
+ +
+ )} + /> - {freightType === "bulk" && ( -
+ {freightType === "bulk" && ( +
+ ( + + + + )} + /> + {bulkCommodity === "Others" && ( ( - - - + + + + )} /> - {bulkCommodity === "Others" && ( - ( - - - - - )} - /> - )} -
- )} + )} +
+ )} - {freightType === "break_bulk" && ( -
+ {freightType === "break_bulk" && ( +
+ ( + + + + )} + /> + {breakBulkType === "Others" && ( ( - - - + + + + )} /> - {breakBulkType === "Others" && ( - ( - - - - - )} - /> - )} -
- )} -
- - - -
- Weight - ( - - - Total Cargo Weight - VGM (Tons) * - -
- - -
- -
)} - /> -
- +
+ )} +
)} {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 +272,9 @@ export function Step5CargoDetails({ return (
-

- Container #{index + 1} -

{fields.length > 1 && (