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 49d233167..da777d491 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx @@ -1,11 +1,11 @@ import { useEffect, 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 { Button } from "@edr/ui-common"; import Breadcrumbs from "@/components/Breadcrumbs"; -import { addBooking } from "./bookings.mock"; import { getCurrentCustomer } from "@/lib/currentCustomer"; import { api } from "@/services/api"; import type { CreateBookingPayload } from "@/services/bookings.service"; @@ -33,10 +33,18 @@ import { 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 [submitted, setSubmitted] = useState(false); + const createMutation = useMutation({ + mutationFn: (payload: CreateBookingPayload) => + api.bookings.create.call(payload), + onSuccess: (booking) => { + queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() }); + setTimeout(() => navigate(`/bookings/${booking.id}`), 2500); + }, + }); const form = useForm({ defaultValues: initialBookingFormValues, @@ -48,8 +56,6 @@ export default function NewBookingPage() { const destinationYard = form.watch("destinationYard"); const containers = form.watch("containers"); const previousContractRef = form.watch("previousContractRef"); - const contractId = - form.watch("draftContractId") || form.watch("previousContractRef"); const direction = useMemo( () => getRouteDirection(originYard, destinationYard), @@ -113,7 +119,7 @@ export default function NewBookingPage() { setStep((currentStep) => Math.min(STEPS.length, currentStep + 1)); } - function handleSubmit(data: BookingFormValues) { + const handleSubmit = form.handleSubmit((data) => { if (data.contractType === "renewal" && renewalValid !== true) { form.setError("previousContractRef", { type: "manual", @@ -124,15 +130,7 @@ export default function NewBookingPage() { } const me = getCurrentCustomer(); - const reference = - data.draftContractId || - data.previousContractRef || - `EDR-DRAFT-${Date.now()}`; - - const qtyCount = - data.cargoType === "container" - ? data.containers.reduce((acc, c) => acc + Number(c.qty || 0), 0) - : 1; + const reference = data.previousContractRef; const totalWeight = data.cargoType === "container" @@ -142,48 +140,13 @@ export default function NewBookingPage() { ) : Number(data.cargoWeight || 0); - const description = - data.cargoType === "container" - ? data.containers.map((c) => `${c.qty} × ${c.type}`).join(", ") - : data.freightType === "bulk" - ? `Bulk - ${data.bulkCommodity === "Others" ? data.bulkCommodityOther : data.bulkCommodity}` - : `Break-Bulk - ${data.breakBulkType === "Others" ? data.breakBulkTypeOther : data.breakBulkType}`; - - const newBooking = { - id: Date.now(), - reference, - customerId: me.id, - customer: me.company, - cargoType: (data.cargoType === "container" - ? "Containerized" - : "Bulk") as any, - originStation: data.originYard, - destinationStation: data.destinationYard, - transportMode: (data.serviceType === "rail" - ? "Rail" - : "Multimodal") as any, - containerType: (data.cargoType === "container" && - data.containers[0]?.type === "40ft" - ? "40FT" - : "20FT") as any, - containerCount: qtyCount, - weightTons: totalWeight, - requestedDate: new Date().toISOString().slice(0, 10), - priority: (data.isHazardous ? "High" : "Normal") as any, - cargoDescription: description, - specialInstructions: data.notes || "Standard handling required", - status: "Pending" as any, - }; - - addBooking(newBooking); - - // Call API using api.bookings.create.call const apiPayload = { reference, customerId: String(me.id), scheduledDate: new Date().toISOString().slice(0, 10), totalAmount: 0, - contractType: data.contractType.toUpperCase(), + contractType: + data.contractType.toUpperCase() as CreateBookingPayload["contractType"], previousContractId: data.previousContractRef || undefined, serviceType: data.serviceType === "rail" ? "RAIL_ONLY" : "RAIL_AND_FORWARDING", @@ -197,8 +160,8 @@ export default function NewBookingPage() { : undefined, equipmentReturn: data.equipmentReturn === "with_return" - ? "WITH_RETURN" - : "WITHOUT_RETURN", + ? ("WITH_RETURN" as const) + : ("WITHOUT_RETURN" as const), originStation: data.originYard, destinationStation: data.destinationYard, cargoTotalWeightVgm: totalWeight, @@ -220,31 +183,18 @@ export default function NewBookingPage() { ...(data.cargoType === "container" && data.containers.length > 0 ? { containers: data.containers.map((c) => ({ - type: c.type === "40ft" ? "40FT" as const : "20FT" as const, + type: c.type === "40ft" ? ("40FT" as const) : ("20FT" as const), qty: Number(c.qty || 1), vgm: Number(c.vgm || 0), })), } : {}), - }; + } satisfies CreateBookingPayload; - api.bookings.create - .call(apiPayload as CreateBookingPayload) - .then((created) => { - console.log("Successfully created booking via API:", created); - }) - .catch((err) => { - console.warn( - "API call failed (expected if API server is offline), falling back to mock storage:", - err, - ); - }); + createMutation.mutate(apiPayload); + }); - setSubmitted(true); - setTimeout(() => navigate("/bookings"), 2500); - } - - if (submitted) { + if (createMutation.isSuccess && createMutation.data) { return (
@@ -257,7 +207,7 @@ export default function NewBookingPage() { notified once approved.

- {contractId} + {createMutation.data.reference}

@@ -268,7 +218,7 @@ export default function NewBookingPage() {
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 1549606ed..2a34eb843 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 @@ -151,7 +151,6 @@ export const bookingFormSchema = z .object({ contractType: z.enum(["new", "renewal"], "Select a contract type."), previousContractRef: z.string(), - draftContractId: z.string(), serviceType: z.enum(["rail", "rail_forwarding"], "Select a service type."), firstMileEnabled: z.boolean(), pickUpAddress: z.string(), @@ -162,7 +161,7 @@ export const bookingFormSchema = z destinationYard: z.string(), cargoType: z.enum(["container", "bulk"], "Select a cargo type."), cargoWeight: z.string(), - freightType: z.enum(["bulk", "break_bulk", ""]).default(""), + freightType: z.enum(["bulk", "break_bulk"]), bulkCommodity: z.string(), bulkCommodityOther: z.string(), breakBulkType: z.string(), @@ -188,14 +187,6 @@ export const bookingFormSchema = z termsAccepted: z.boolean(), }) .superRefine((data, ctx) => { - if (data.contractType === "new" && !data.draftContractId.trim()) { - ctx.addIssue({ - code: "custom", - path: ["draftContractId"], - message: "A draft contract ID is required.", - }); - } - if (data.contractType === "renewal" && !data.previousContractRef.trim()) { ctx.addIssue({ code: "custom", @@ -360,7 +351,6 @@ export type BookingFormValues = z.infer; export const initialBookingFormValues: Partial = { previousContractRef: "", - draftContractId: "", firstMileEnabled: false, pickUpAddress: "", lastMileEnabled: false, @@ -383,7 +373,7 @@ export const initialBookingFormValues: Partial = { }; export const stepFields: Record> = { - 1: ["contractType", "previousContractRef", "draftContractId"], + 1: ["contractType", "previousContractRef"], 2: ["serviceType"], 3: [ "firstMileEnabled", 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 1343bdb2e..e8e045e5c 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,7 +1,7 @@ import { Controller, type UseFormReturn } from "react-hook-form"; import { FileText, Loader2, RefreshCw } from "lucide-react"; -import { Button, Field, FieldError, FieldLabel, Input } from "@edr/ui-common"; -import { type BookingFormValues, genContractId } from "./schema"; +import { Button, Field, FieldLabel, Input } from "@edr/ui-common"; +import { type BookingFormValues } from "./schema"; import { AlertBox, OptionCard, OptionFieldError, StepHeader } from "./shared"; type BookingForm = UseFormReturn; @@ -18,9 +18,7 @@ export function Step1ContractType({ onValidate: () => void; }) { const contractType = form.watch("contractType"); - const draftContractId = form.watch("draftContractId"); const previousContractRef = form.watch("previousContractRef"); - const errors = form.formState.errors; return (
@@ -40,12 +38,6 @@ export function Step1ContractType({ onClick={() => { field.onChange("new"); form.clearErrors(["contractType", "previousContractRef"]); - if (!draftContractId) { - form.setValue("draftContractId", genContractId(), { - shouldDirty: true, - shouldValidate: true, - }); - } }} >
@@ -55,11 +47,6 @@ export function Step1ContractType({

Blank contract form. A draft ID is auto-generated.

- {field.value === "new" && draftContractId && ( -

- {draftContractId} -

- )}
- )} />