From 0184db904053b67d6782d0ef012392e4e28ea26e Mon Sep 17 00:00:00 2001 From: ghost2023 Date: Fri, 5 Jun 2026 09:50:09 +0300 Subject: [PATCH] feat(bookings): Introduce DRAFT booking workflow with comprehensive features for pricing, document management, submission, and cancellation. --- .../src/pages/bookings/BookingDetailPage.tsx | 531 +++++++++++++++++- .../src/pages/bookings/NewBookingPage.tsx | 66 +-- .../pages/bookings/new-booking-form/schema.ts | 106 +--- .../new-booking-form/step5-cargo-details.tsx | 117 ++-- .../portal/src/services/api.ts | 31 +- .../portal/src/services/bookings.service.ts | 51 ++ .../services/files/file_upload_settings.ts | 3 - 7 files changed, 649 insertions(+), 256 deletions(-) delete mode 100644 apps/edr-freight-web/portal/src/services/files/file_upload_settings.ts diff --git a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx index f8ea6c496..73977fc7c 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx @@ -1,6 +1,8 @@ +import { useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { + AlertCircle, Calendar, MapPin, Package, @@ -24,21 +26,29 @@ import { FileSignature, PackageCheck, LoaderCircle, + DollarSign, + Upload, + XCircle, + Building2, + FileUp, } from "lucide-react"; import Breadcrumbs from "@/components/Breadcrumbs"; import { api } from "@/services/api"; import type { Freight } from "@edr/types"; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, +import type { GeneratePriceResponse } from "@/services/bookings.service"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, Badge, + Button, Separator, } from "@edr/ui-common"; import { cn } from "@/lib/utils"; +import useAuth from "@/hooks/useAuth"; const PROGRESS_STAGES = [ { label: "Request", icon: FileText, statuses: ["DRAFT"] }, @@ -55,9 +65,17 @@ const STATUS_MAP: Record(); const navigate = useNavigate(); + const queryClient = useQueryClient(); const { data: booking, isLoading, isError, error } = useQuery( api.bookings.get.queryOptions({ @@ -66,6 +84,10 @@ export default function BookingDetailPage() { }), ); + const refetchBooking = () => { + queryClient.invalidateQueries({ queryKey: api.bookings.get.queryKey({ id: id! }) }); + }; + if (isLoading) { return (
@@ -110,17 +132,474 @@ export default function BookingDetailPage() { ); } + if (booking.status === "DRAFT") { + return ; + } + + return ; +} + +function DraftBookingView({ + booking, + onBookingUpdated, +}: { + booking: Freight.IBooking; + onBookingUpdated: () => void; +}) { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const { customer } = useAuth(); + const fileInputRefs = useRef>({}); + + const [pricingData, setPricingData] = useState(null); + const [selectedFiles, setSelectedFiles] = useState>({}); + const [cancelReason, setCancelReason] = useState(""); + + const anyFileSelected = Object.values(selectedFiles).some(Boolean); + const allDocsProvided = !anyFileSelected; + + const priceMutation = useMutation({ + mutationFn: () => api.bookings.generatePrice.call({ id: booking.id }), + onSuccess: (data) => { + setPricingData(data); + queryClient.invalidateQueries({ queryKey: api.bookings.get.queryKey({ id: booking.id }) }); + }, + }); + + const uploadMutation = useMutation({ + mutationFn: (files: Record) => + api.bookings.uploadDocuments.call({ id: booking.id, files }), + onSuccess: () => { + setSelectedFiles({}); + onBookingUpdated(); + }, + }); + + const submitMutation = useMutation({ + mutationFn: () => api.bookings.submit.call({ id: booking.id }), + onSuccess: () => { + onBookingUpdated(); + queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() }); + }, + }); + + const cancelMutation = useMutation({ + mutationFn: (reason: string) => + api.bookings.cancel.call({ id: booking.id, reason }), + onSuccess: () => { + onBookingUpdated(); + }, + }); + + function handleFileSelect(key: string, file: File | null) { + setSelectedFiles((prev) => ({ ...prev, [key]: file })); + } + + function handleUploadAll() { + const filesToUpload: Record = {}; + for (const doc of REQUIRED_DOC_FIELDS) { + if (selectedFiles[doc.key]) { + filesToUpload[doc.key] = selectedFiles[doc.key]!; + } + } + if (Object.keys(filesToUpload).length === 0) return; + uploadMutation.mutate(filesToUpload); + } + + function handleCancel() { + const reason = cancelReason.trim() || "Cancelled by customer"; + cancelMutation.mutate(reason); + } + + const canConfirm = !!pricingData && !uploadMutation.isPending && !submitMutation.isPending; + + const companyName = (customer as any)?.company?.name ?? "—"; + const companyTin = (customer as any)?.company?.tin ?? "—"; + const contactName = (customer as any)?.profile + ? `${(customer as any).profile.firstName ?? ""} ${(customer as any).profile.lastName ?? ""}`.trim() || "—" + : "—"; + const contactEmail = (customer as any)?.profile?.email ?? "—"; + + return ( +
+
+ + + + +
+
+ +
+
+
+

+ {booking.reference} +

+ +
+

+ Complete the steps below to submit your booking request. +

+
+
+
+
+ + {priceMutation.isError && ( +
+ +
+

Pricing failed

+

+ {priceMutation.error instanceof Error + ? priceMutation.error.message + : "An unexpected error occurred."} +

+
+
+ )} + + {uploadMutation.isError && ( +
+ +
+

Document upload failed

+

+ {uploadMutation.error instanceof Error + ? uploadMutation.error.message + : "An unexpected error occurred."} +

+
+
+ )} + + {submitMutation.isError && ( +
+ +
+

Submission failed

+

+ {submitMutation.error instanceof Error + ? submitMutation.error.message + : "An unexpected error occurred."} +

+
+
+ )} + + {cancelMutation.isError && ( +
+ +
+

Cancel failed

+

+ {cancelMutation.error instanceof Error + ? cancelMutation.error.message + : "An unexpected error occurred."} +

+
+
+ )} + + + + + + Pricing Estimation + + + Generate a price estimate based on your booking details. + + + + {pricingData ? ( +
+
+ + + + + + + + + {pricingData.lineItems.map((item, i) => ( + + + + + ))} + + + + + +
DescriptionAmount
{item.description} + {item.amount.toLocaleString()} {item.currency} +
Total Estimated Cost + {pricingData.totalAmount.toLocaleString()} {pricingData.currency} +
+
+ + {pricingData.warnings.length > 0 && ( +
+ {pricingData.warnings.map((w, i) => ( +

+ + {w} +

+ ))} +
+ )} + +
+ +
+
+ ) : ( +
+
+ +
+
+

No pricing yet

+

+ Generate a price estimate to review before submitting. +

+
+ +
+ )} +
+
+ + + + + + Required Documents + + + Provide the necessary documents for this booking. Some information is + pre-filled from your company profile. + + + +
+

+ + Company Information (from profile) +

+
+ + + + +
+

+ To update your company information, go to{" "} + + . +

+
+ + + +
+

+ + Upload Booking Documents +

+
+ {REQUIRED_DOC_FIELDS.map((doc) => ( +
+ +
+ { + fileInputRefs.current[doc.key] = el; + }} + type="file" + accept=".pdf,.jpg,.jpeg,.png" + className="hidden" + onChange={(e) => { + handleFileSelect(doc.key, e.target.files?.[0] ?? null); + }} + /> + + {selectedFiles[doc.key] && ( + + )} +
+
+ ))} +
+ +
+ + {uploadMutation.isSuccess && ( +

+ + Documents uploaded successfully +

+ )} +
+
+
+
+ + + + + + Cancel Booking + + + If you no longer need this booking, you can cancel it. + + + + setCancelReason(e.target.value)} + /> + + + + +
+
+ {!canConfirm && ( +

+ {!pricingData + ? "Request pricing estimation before confirming." + : "Upload documents before confirming."} +

+ )} + + +
+
+
+
+ ); +} + +function ReadonlyBookingView({ booking }: { booking: Freight.IBooking }) { + const navigate = useNavigate(); + const normalizedStatus = booking.status as keyof typeof STATUS_MAP; const statusConfig = STATUS_MAP[normalizedStatus] || STATUS_MAP.DRAFT; const currentStageIndex = statusConfig.stage; - const containerCount = booking.containers?.reduce((sum, c) => sum + c.qty, 0) ?? 0; - const containerType = booking.containers?.[0]?.type ?? null; - return (
- +
-
= 0 ? `${(currentStageIndex / (PROGRESS_STAGES.length - 1)) * 100}%` : '0%' }} />
@@ -193,13 +672,13 @@ export default function BookingDetailPage() { {PROGRESS_STAGES.map((stage, idx) => { const isCompleted = idx < currentStageIndex; const isActive = idx === currentStageIndex; - + return (
{isCompleted ? : } @@ -435,14 +914,14 @@ function RouteEndpoint({ ); } -function InfoItem({ - icon, - label, - value -}: { - icon?: React.ReactNode; - label: string; - value?: string | number | null +function InfoItem({ + icon, + label, + value +}: { + icon?: React.ReactNode; + label: string; + value?: string | number | null }) { return (
@@ -465,8 +944,8 @@ function StatusBadge({ status }: { status: string }) { }; return ( - {status.replace(/_/g, ' ')} 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 d69258e0d..9b5a4cf73 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx @@ -6,14 +6,12 @@ import { useNavigate } from "react-router-dom"; import { AlertCircle, Check, - CheckCircle2, ChevronLeft, ChevronRight, LoaderCircle, } from "lucide-react"; import { Button } from "@edr/ui-common"; import { api } from "@/services/api"; -import { Freight } from "@edr/types"; import type { CreateBookingPayload } from "@/services/bookings.service"; import { BookingFormInputValues, @@ -32,13 +30,11 @@ import { Step5CargoDetails, Step8Review, } from "./new-booking-form/steps"; -import useAuth from "@/hooks/useAuth"; export default function NewBookingPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const [step, setStep] = useState(1); - const { customer } = useAuth(); const { data: referenceData, isLoading: refDataLoading } = useQuery( api.bookings.referenceData.queryOptions(), ); @@ -48,7 +44,7 @@ export default function NewBookingPage() { api.bookings.create.call(payload), onSuccess: (booking) => { queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() }); - setTimeout(() => navigate(`/bookings/${booking.id}`), 2500); + navigate(`/bookings/${booking.id}`); }, }); @@ -132,23 +128,27 @@ export default function NewBookingPage() { return ""; }; + const selectedChild = + data.cargoType !== "container" && data.bulkCommoditytype + ? cargoTree + .find((g) => g.code.toLowerCase() === data.freightType) + ?.children?.find((c) => c.name === data.bulkCommoditytype) + : undefined; + const cargoTypeId = data.cargoType === "container" ? findContainerCargoTypeId() - : (findCargoTypeId( - data.freightType === "bulk" - ? data.bulkCommodity - : data.breakBulkType, - ) ?? ""); + : (findCargoTypeId(data.bulkCommoditytype) ?? + cargoTree.find((g) => g.code.toLowerCase() === data.freightType) + ?.id ?? + ""); const cargoFreeText = data.cargoType === "container" ? undefined - : data.freightType === "bulk" && data.bulkCommodity === "Others" - ? data.bulkCommodityOther - : data.freightType === "break_bulk" && data.breakBulkType === "Others" - ? data.breakBulkTypeOther - : undefined; + : selectedChild?.show_free_text_box + ? data.bulkCommoditytype + : undefined; // ── Build API payload ─────────────────────────────────────────────── const apiPayload: CreateBookingPayload = { @@ -168,15 +168,16 @@ export default function NewBookingPage() { : direction === "domestic" ? "DOMESTIC" : "IMPORT", - freightType: - data.cargoType === "container" - ? Freight.FreightType.Container - : Freight.FreightType.Bulk, cargoTypeId: data.cargoType === "container" ? undefined : cargoTypeId, cargoTotalWeightVgm: totalWeight, isHazardous: data.isHazardous, paymentCurrency: "USD", allowConsolidation: data.consolidationEnabled, + // @ts-ignore + freightType: + data.cargoType === "container" + ? ("CONTAINER" as const) + : ("BULK" as const), containers: data.cargoType === "container" ? data.containers.map((c) => ({ @@ -185,7 +186,6 @@ export default function NewBookingPage() { vgmPerUnitTons: Number(c.vgm || 0), })) : [], - ...(customer?.company?.id ? { companyId: customer.company.id } : {}), ...(data.previousContractRef ? { previousContractId: data.previousContractRef } : {}), @@ -207,26 +207,6 @@ export default function NewBookingPage() { createMutation.mutate(apiPayload); }); - if (createMutation.isSuccess) { - return ( -
-
-
- -
-

Contract Submitted

-

- Your request is queued for review by EDR Line Staff. You will be - notified once approved. -

-

- {createMutation.data?.reference} -

-
-
- ); - } - return (
-

Submission failed

+

Failed to save draft

{createMutation.error instanceof Error ? createMutation.error.message @@ -306,9 +286,7 @@ export default function NewBookingPage() { ) : ( )} - {createMutation.isPending - ? "Submitting..." - : "Submit Contract Request"} + {createMutation.isPending ? "Saving Draft..." : "Save as Draft"} )}

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 d994f93f7..be443d081 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,19 +1,6 @@ import { DeepPartial, Path } from "react-hook-form"; import * as z from "zod"; -export const STATIONS = [ - "Addis Ababa", - "Adama", - "Mojo", - "Awash", - "Mieso", - "Dire Dawa", - "Aysha", - "Ali Sabieh", - "Holhol", - "Djibouti City", -] as const; - export const ETHIOPIA_STATIONS = new Set([ "Addis Ababa", "Adama", @@ -23,19 +10,6 @@ export const ETHIOPIA_STATIONS = new Set([ "Dire Dawa", ]); -export const BULK_COMMODITIES = [ - "Coffee", - "Beans", - "Fertilizer", - "Sugar", - "Oil", - "Livestock", - "Steel", - "Others", -] as const; - -export const BREAK_BULK_TYPES = ["Machinery", "Ro-Ro", "Others"] as const; - export const MOCK_VALID_CONTRACTS = [ "EDR-2024-10001", "EDR-2024-10002", @@ -43,31 +17,6 @@ export const MOCK_VALID_CONTRACTS = [ "EDR-2022-55442", ]; -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 & Mile", short: "Service" }, @@ -108,11 +57,8 @@ export const bookingFormSchema = z shippingLine: z.string(), cargoType: z.enum(["container", "bulk"], "Select a cargo type."), cargoWeight: z.string(), - freightType: z.enum(["bulk", "break_bulk"]).optional(), - bulkCommodity: z.string(), - bulkCommodityOther: z.string(), - breakBulkType: z.string(), - breakBulkTypeOther: z.string(), + freightType: z.string(), // parent group + bulkCommoditytype: z.string(), isHazardous: z.boolean(), isRefrigerated: z.boolean(), containers: z.array( @@ -171,42 +117,10 @@ export const bookingFormSchema = z (data) => !( data.cargoType === "bulk" && - data.freightType === "bulk" && - !data.bulkCommodity + data.freightType && + !data.bulkCommoditytype ), - { 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"], - }, + { message: "Select a commodity.", path: ["bulkCommoditytype"] }, ) .refine( (data) => { @@ -268,10 +182,7 @@ export const initialBookingFormValues: DeepPartial = { destinationYard: "", shippingLine: "", cargoWeight: "", - bulkCommodity: "", - bulkCommodityOther: "", - breakBulkType: "", - breakBulkTypeOther: "", + bulkCommoditytype: "", isHazardous: false, isRefrigerated: false, containers: [{ type: "20ft", containerType: "", qty: "1", vgm: "" }], @@ -300,10 +211,7 @@ export const stepFields: Record>> = { "cargoType", "cargoWeight", "freightType", - "bulkCommodity", - "bulkCommodityOther", - "breakBulkType", - "breakBulkTypeOther", + "bulkCommoditytype", "containers", "consolidationEnabled", ], 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 ee54196e8..cbd70f5ac 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 @@ -44,8 +44,7 @@ export function Step5CargoDetails({ }) { const cargoType = form.watch("cargoType"); const freightType = form.watch("freightType"); - const bulkCommodity = form.watch("bulkCommodity"); - const breakBulkType = form.watch("breakBulkType"); + const bulkCommoditytype = form.watch("bulkCommoditytype"); const containers = form.watch("containers"); const { fields, append, remove } = useFieldArray({ @@ -60,13 +59,21 @@ export function Step5CargoDetails({ ); }, [referenceData]); - const bulkCommodityOptions = useMemo(() => { + const freightTypeGroups = useMemo(() => { if (!referenceData?.cargo_type) return []; - return referenceData.cargo_type.flatMap( - (group) => group.children?.map((c) => c.name) ?? [], + return referenceData.cargo_type.filter( + (g) => g.code !== "CONTAINER", ); }, [referenceData]); + const commodityOptions = useMemo(() => { + if (!referenceData?.cargo_type || !freightType) return []; + const group = referenceData.cargo_type.find( + (g) => g.code.toLowerCase() === freightType, + ); + return group?.children?.map((c) => c.name) ?? []; + }, [referenceData, freightType]); + function getOverweightAlert( type: "20ft" | "40ft", vgm: number, @@ -122,7 +129,7 @@ export function Step5CargoDetails({ selected={cargoType === "container"} onClick={() => { field.onChange("container"); - form.setValue("freightType", undefined, { + form.setValue("freightType", "", { shouldDirty: true, }); }} @@ -194,82 +201,42 @@ export function Step5CargoDetails({ render={({ field, fieldState }) => (
- field.onChange("bulk")} - > -

Bulk

-

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

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

Break-Bulk

-

- Machinery, vehicles, project cargo, etc. -

-
+ {freightTypeGroups.map((group) => { + const val = group.code.toLowerCase(); + return ( + { + field.onChange(val); + form.setValue("bulkCommoditytype", "", { + shouldDirty: true, + }); + }} + > +

{group.name}

+
+ ); + })}
)} /> - {freightType === "bulk" && ( + {freightType && commodityOptions.length > 0 && (
( - {bulkCommodityOptions.map((option) => ( - - {option} - - ))} - - )} - /> - {bulkCommodity === "Others" && ( - ( - - - - - )} - /> - )} -
- )} - - {freightType === "break_bulk" && ( -
- ( - - {bulkCommodityOptions.map((option) => ( + {commodityOptions.map((option) => ( {option} @@ -277,22 +244,6 @@ export function Step5CargoDetails({ )} /> - {breakBulkType === "Others" && ( - ( - - - - - )} - /> - )}
)}
diff --git a/apps/edr-freight-web/portal/src/services/api.ts b/apps/edr-freight-web/portal/src/services/api.ts index baca5cd34..672b1f562 100644 --- a/apps/edr-freight-web/portal/src/services/api.ts +++ b/apps/edr-freight-web/portal/src/services/api.ts @@ -8,7 +8,11 @@ import type { UpdateFileUploadFieldDto, UpdateFileUploadSettingDto, } from "@/types/fileUploadSettings"; -import { bookingsService, CreateBookingPayload } from "./bookings.service"; +import { + bookingsService, + CreateBookingPayload, + GeneratePriceResponse, +} from "./bookings.service"; import { consignmentsService } from "./consignments.service"; import { trackingService } from "./tracking.service"; import { fileUploadSettingsService } from "./fileUploadSettings.service"; @@ -144,6 +148,31 @@ export const api = { remove: endpoint<{ id: string }, void>("bookings", "remove", ({ id }) => bookingsService.remove(id), ), + + cancel: endpoint<{ id: string; reason: string }, Freight.IBooking>( + "bookings", + "cancel", + ({ id, reason }) => bookingsService.cancel(id, reason), + ), + + generatePrice: endpoint<{ id: string }, GeneratePriceResponse>( + "bookings", + "generatePrice", + ({ id }) => bookingsService.generatePrice(id), + ), + + submit: endpoint<{ id: string }, Freight.IBooking>( + "bookings", + "submit", + ({ id }) => bookingsService.submit(id), + ), + + uploadDocuments: endpoint< + { id: string; files: Record }, + Freight.IBooking + >("bookings", "uploadDocuments", ({ id, files }) => + bookingsService.uploadDocuments(id, files), + ), }, consignments: { diff --git a/apps/edr-freight-web/portal/src/services/bookings.service.ts b/apps/edr-freight-web/portal/src/services/bookings.service.ts index 2dc1ba235..c3ea58ad9 100644 --- a/apps/edr-freight-web/portal/src/services/bookings.service.ts +++ b/apps/edr-freight-web/portal/src/services/bookings.service.ts @@ -25,6 +25,21 @@ export interface ContractView { }>; } +export interface PriceLineItem { + code: string; + description: string; + amount: number; + currency: string; +} + +export interface GeneratePriceResponse { + bookingId: string; + totalAmount: number; + currency: string; + lineItems: PriceLineItem[]; + warnings: string[]; +} + export interface SignContractPayload { role: "CUSTOMER" | "STAFF"; signatureImageBase64: string; @@ -53,6 +68,42 @@ export const bookingsService = { await client.delete(`/api/bookings/${id}`); }, + cancel: async (id: string, reason: string): Promise => { + const { data } = await client.post(`/api/bookings/${id}/cancel`, { reason }); + return data.data; + }, + + generatePrice: async (id: string): Promise => { + const { data } = await client.post(`/api/bookings/${id}/generate-price`); + return data.data; + }, + + submit: async (id: string): Promise => { + const { data } = await client.post(`/api/bookings/${id}/submit`); + return data.data; + }, + + uploadDocuments: async ( + id: string, + files: Record, + ): Promise => { + const formData = new FormData(); + for (const [key, fileOrFiles] of Object.entries(files)) { + if (!fileOrFiles) continue; + if (Array.isArray(fileOrFiles)) { + for (const f of fileOrFiles) formData.append(key, f); + } else { + formData.append(key, fileOrFiles); + } + } + const { data } = await client.post( + `/api/bookings/${id}/documents`, + formData, + { headers: { "Content-Type": "multipart/form-data" } }, + ); + return data.data; + }, + getContractView: async (id: string): Promise => { const { data } = await client.get(B.CONTRACT_VIEW(id)); return data.data ?? data; diff --git a/apps/edr-freight-web/portal/src/services/files/file_upload_settings.ts b/apps/edr-freight-web/portal/src/services/files/file_upload_settings.ts deleted file mode 100644 index c0ee8b1dc..000000000 --- a/apps/edr-freight-web/portal/src/services/files/file_upload_settings.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { api } from "../crud"; - -import { URL_CONSTANTS } from "../../constants/URLS" \ No newline at end of file