diff --git a/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts b/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts index 98f8d3627..5f4be50c1 100644 --- a/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts +++ b/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts @@ -13,7 +13,13 @@ import { UseInterceptors, } from "@nestjs/common"; import { AnyFilesInterceptor } from "@nestjs/platform-express"; -import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from "@nestjs/swagger"; +import { + ApiBearerAuth, + ApiBody, + ApiConsumes, + ApiOperation, + ApiTags, +} from "@nestjs/swagger"; import { BookingsService } from "./bookings.service"; import { CreateBookingDto } from "./dto/create-booking.dto"; @@ -23,8 +29,9 @@ import { UpdateStatusDto } from "./dto/update-status.dto"; @ApiTags("bookings") @Controller("bookings") +@ApiBearerAuth() export class BookingsController { - constructor(private readonly bookingsService: BookingsService) {} + constructor(private readonly bookingsService: BookingsService) { } // ── 1. Create booking (multipart/form-data) ────────────────────────── @Post() @@ -46,7 +53,16 @@ export class BookingsController { @Body() dto: CreateBookingDto, @UploadedFiles() files: Express.Multer.File[], ) { - console.log('[BookingsController] Files received:', files?.length, files?.map(f => ({ fieldname: f.fieldname, originalname: f.originalname, size: f.size, mimetype: f.mimetype }))); + console.log( + "[BookingsController] Files received:", + files?.length, + files?.map((f) => ({ + fieldname: f.fieldname, + originalname: f.originalname, + size: f.size, + mimetype: f.mimetype, + })), + ); return this.bookingsService.create(dto, files ?? []); } @@ -56,7 +72,8 @@ export class BookingsController { @ApiConsumes("multipart/form-data") @ApiOperation({ summary: "Update a draft booking", - description: "Only DRAFT bookings can be updated. New files are merged into existing documents.", + description: + "Only DRAFT bookings can be updated. New files are merged into existing documents.", }) @ApiBody({ type: UpdateBookingDto }) update( @@ -141,7 +158,8 @@ export class BookingsController { @Delete(":id/consolidation") @ApiOperation({ summary: "Remove consolidation pairing", - description: "Unpairs both bookings and returns them to PENDING_CONSOLIDATION status.", + description: + "Unpairs both bookings and returns them to PENDING_CONSOLIDATION status.", }) removeConsolidation(@Param("id", ParseUUIDPipe) id: string) { return this.bookingsService.removeConsolidation(id); @@ -151,7 +169,8 @@ export class BookingsController { @Get(":id/consolidation") @ApiOperation({ summary: "Get consolidation details", - description: "Returns partner booking details and split billing information.", + description: + "Returns partner booking details and split billing information.", }) getConsolidationDetails(@Param("id", ParseUUIDPipe) id: string) { return this.bookingsService.getConsolidationDetails(id); diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx index da19b7599..111df497b 100644 --- a/apps/edr-freight-web/portal/src/App.tsx +++ b/apps/edr-freight-web/portal/src/App.tsx @@ -22,6 +22,7 @@ import { } from "lucide-react"; import BookingsPage from "./pages/bookings/BookingsPage"; +import MyBookings from "./pages/bookings/MyBookings"; import BookingDetailPage from "./pages/bookings/BookingDetailPage"; import NewBookingPage from "./pages/bookings/NewBookingPage"; import ConsignmentsPage from "./pages/consignments/ConsignmentsPage"; @@ -49,7 +50,7 @@ const sidebarItems: SidebarItem[] = [ { label: "My Portal", href: "/portal", icon: }, { label: "Dashboard", href: "/", icon: }, { label: "Customers", href: "/customers", icon: }, - { label: "Bookings", href: "/bookings", icon: }, + { label: "My Bookings", href: "/bookings", icon: }, { label: "Consignments", href: "/consignments", icon: }, { label: "Tracking", href: "/tracking", icon: }, { label: "Stations", href: "/stations", icon: }, @@ -115,7 +116,8 @@ const App = () => { } /> } /> - } /> + } /> + } /> } /> } /> } /> @@ -128,10 +130,7 @@ const App = () => { } /> } /> } /> - } - /> + } /> } 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 7eb038196..1ce8f76d5 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage.tsx @@ -15,7 +15,7 @@ import { import Breadcrumbs from "@/components/Breadcrumbs"; import DeleteBookingDialog from "./DeleteBookingDialog"; -import { getBookingById, type BookingStatus } from "./bookings.mock"; +import { getBookingById, deleteBooking, type BookingStatus } from "./bookings.mock"; import { Button, Card } from "@edr/ui-common"; export default function BookingDetailPage() { @@ -88,7 +88,10 @@ export default function BookingDetailPage() { navigate("/bookings")} + onConfirm={() => { + deleteBooking(booking.id); + navigate("/bookings"); + }} > diff --git a/apps/edr-freight-web/portal/src/pages/bookings/MyBookings.tsx b/apps/edr-freight-web/portal/src/pages/bookings/MyBookings.tsx new file mode 100644 index 000000000..dd7a54900 --- /dev/null +++ b/apps/edr-freight-web/portal/src/pages/bookings/MyBookings.tsx @@ -0,0 +1,333 @@ +import { useMemo, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { + ArrowRight, + Clock, + Eye, + Filter, + MoreHorizontal, + Package, + Plus, + Search, + Trash2, + Truck, +} from "lucide-react"; + +import Breadcrumbs from "@/components/Breadcrumbs"; +import DeleteBookingDialog from "./DeleteBookingDialog"; +import { getMyBookings } from "@/lib/currentCustomer"; +import { deleteBooking, type Booking, type BookingStatus } from "./bookings.mock"; +import { + DataTable, + DataTableFooter, + type ColumnDef, + usePagination, + Button, + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + Input, + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, +} from "@edr/ui-common"; + +export default function MyBookings() { + const navigate = useNavigate(); + const { pagination, setPagination } = usePagination({ pageSize: 10 }); + const [searchTerm, setSearchTerm] = useState(""); + const [myBookings, setMyBookings] = useState(() => getMyBookings()); + + const handleDeleteConfirm = (id: number) => { + deleteBooking(id); + setMyBookings(getMyBookings()); + }; + + const filteredData = useMemo(() => { + return myBookings.filter((b) => { + const term = searchTerm.toLowerCase(); + return ( + b.reference.toLowerCase().includes(term) || + b.originStation.toLowerCase().includes(term) || + b.destinationStation.toLowerCase().includes(term) || + b.cargoDescription.toLowerCase().includes(term) || + b.status.toLowerCase().includes(term) + ); + }); + }, [myBookings, searchTerm]); + + const total = filteredData.length; + const pageCount = Math.ceil(total / pagination.pageSize); + const start = pagination.pageIndex * pagination.pageSize; + const end = Math.min(start + pagination.pageSize, total); + + const paginatedData = useMemo(() => filteredData.slice(start, end), [filteredData, start, end]); + + const activeCount = useMemo(() => { + return myBookings.filter( + (b) => b.status === "Confirmed" || b.status === "In Transit", + ).length; + }, [myBookings]); + + const pendingCount = useMemo(() => { + return myBookings.filter((b) => b.status === "Pending").length; + }, [myBookings]); + + const columns: ColumnDef[] = [ + { + accessorKey: "reference", + header: "Reference", + cell: ({ row }) => { + const booking = row.original; + return ( + + + + + + {booking.reference} + {booking.requestedDate} + + + ); + }, + }, + { + id: "route", + header: "Route", + cell: ({ row }) => ( + + {row.original.originStation} + + {row.original.destinationStation} + + ), + }, + { + id: "cargo", + header: "Cargo", + cell: ({ row }) => { + const b = row.original; + return ( + + {b.cargoType} + + {b.containerCount > 0 ? `${b.containerCount} × ${b.containerType} · ` : ""}{b.weightTons}t + + + ); + }, + }, + { + accessorKey: "transportMode", + header: "Transport", + cell: ({ row }) => ( + + {row.original.transportMode} + + ), + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => , + }, + { + id: "actions", + size: 40, + cell: ({ row }) => { + const booking = row.original; + return ( + e.stopPropagation()} + > + + + + + + + + navigate(`/bookings/${booking.id}`)} + > + + View + + + handleDeleteConfirm(booking.id)} + > + e.preventDefault()} + variant="destructive" + > + + Delete + + + + + + ); + }, + }, + ]; + + return ( + + + + + {/* Header Section Card */} + + + + My Bookings + + + View and manage your freight booking requests. + + + + + + + setSearchTerm(e.target.value)} + className="pl-8!" + /> + + + + + + New Booking + + + + + + {/* Stat Cards */} + + + + + Total Bookings + + {myBookings.length} + + + + + + + + + + + + Active Bookings + + {activeCount} + + + + + + + + + + + + Pending Approval + + {pendingCount} + + + + + + + + + + {/* Data Table */} + + + + Recent Requests + + A list of your recent freight bookings and their statuses. + + + + + + Filter + + + + + {total === 0 ? ( + + + No bookings found + + {searchTerm ? "No bookings match your current search filter." : "You haven't requested any bookings yet."} + + + ) : ( + navigate(`/bookings/${(row as Booking).id}`)} + pagination={{ + pageIndex: pagination.pageIndex, + pageSize: pagination.pageSize, + pageCount: pageCount, + totalCount: total, + }} + tableOptions={{ + state: { pagination }, + onPaginationChange: setPagination, + }} + containerClassName="border-b shadow-none" + footer={DataTableFooter} + /> + )} + + + + + ); +} + +function StatusBadge({ status }: { status: BookingStatus }) { + const styles: Record = { + Pending: "bg-amber-100 text-amber-700", + Confirmed: "bg-sky-100 text-sky-700", + "In Transit": "bg-indigo-100 text-indigo-700", + Delivered: "bg-emerald-100 text-emerald-700", + Cancelled: "bg-red-100 text-red-700", + }; + + return ( + + {status} + + ); +} 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 e23b44cb2..49d233167 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx @@ -5,6 +5,10 @@ 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"; import { MOCK_VALID_CONTRACTS, STEPS, @@ -35,8 +39,8 @@ export default function NewBookingPage() { const [submitted, setSubmitted] = useState(false); const form = useForm({ - resolver: zodResolver(bookingFormSchema), defaultValues: initialBookingFormValues, + resolver: zodResolver(bookingFormSchema), mode: "onChange", }); @@ -119,6 +123,123 @@ export default function NewBookingPage() { return; } + 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 totalWeight = + data.cargoType === "container" + ? data.containers.reduce( + (acc, c) => acc + Number(c.vgm || 0) * Number(c.qty || 0), + 0, + ) + : 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(), + 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, + equipmentReturn: + data.equipmentReturn === "with_return" + ? "WITH_RETURN" + : "WITHOUT_RETURN", + originStation: data.originYard, + destinationStation: data.destinationYard, + cargoTotalWeightVgm: totalWeight, + freightType: data.cargoType === "container" ? "BREAK_BULK" : "BULK", + freightSubtype: + data.cargoType === "container" + ? undefined + : data.freightType === "bulk" + ? data.bulkCommodity + : data.breakBulkType, + isHazardous: data.isHazardous, + isRefrigerated: data.isRefrigerated, + tradeDirection: + getRouteDirection(data.originYard, data.destinationYard) === "export" + ? "EXPORT" + : "IMPORT", + paymentCurrency: "USD", + allowConsolidation: data.consolidationEnabled, + ...(data.cargoType === "container" && data.containers.length > 0 + ? { + containers: data.containers.map((c) => ({ + type: c.type === "40ft" ? "40FT" as const : "20FT" as const, + qty: Number(c.qty || 1), + vgm: Number(c.vgm || 0), + })), + } + : {}), + }; + + 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, + ); + }); + setSubmitted(true); setTimeout(() => navigate("/bookings"), 2500); } diff --git a/apps/edr-freight-web/portal/src/pages/bookings/bookings.mock.ts b/apps/edr-freight-web/portal/src/pages/bookings/bookings.mock.ts index 7740f7364..11bb4fc1c 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/bookings.mock.ts +++ b/apps/edr-freight-web/portal/src/pages/bookings/bookings.mock.ts @@ -83,7 +83,7 @@ function pickStation(i: number, offset: number) { return stations[(i + offset) % stations.length] as string; } -export const bookings: Booking[] = Array.from({ length: 22 }, (_, i) => { +const INITIAL_BOOKINGS: Booking[] = Array.from({ length: 22 }, (_, i) => { const customer = customers[i % customers.length] as (typeof customers)[number]; const id = i + 1; const requested = new Date(2026, 4, 1 + (i % 28)); @@ -125,6 +125,43 @@ export const bookings: Booking[] = Array.from({ length: 22 }, (_, i) => { }; }); +const getStoredBookings = (): Booking[] => { + if (typeof window === "undefined" || !window.localStorage) { + return INITIAL_BOOKINGS; + } + const data = localStorage.getItem("edr_bookings"); + if (!data) { + localStorage.setItem("edr_bookings", JSON.stringify(INITIAL_BOOKINGS)); + return INITIAL_BOOKINGS; + } + try { + return JSON.parse(data); + } catch (e) { + return INITIAL_BOOKINGS; + } +}; + +export const bookings: Booking[] = getStoredBookings(); + +export function saveBookingsToStorage() { + if (typeof window !== "undefined" && window.localStorage) { + localStorage.setItem("edr_bookings", JSON.stringify(bookings)); + } +} + +export function addBooking(booking: Booking) { + bookings.unshift(booking); + saveBookingsToStorage(); +} + +export function deleteBooking(id: number) { + const index = bookings.findIndex((b) => b.id === id); + if (index !== -1) { + bookings.splice(index, 1); + saveBookingsToStorage(); + } +} + export function getBookingById(id: number | string): Booking | undefined { const numericId = typeof id === "string" ? Number(id) : id; return bookings.find((b) => b.id === numericId); 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 7cb5844c4..1549606ed 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 @@ -46,7 +46,7 @@ export const REQUIRED_DOC_KEYS = [ "tin_certificate", "business_license", "business_registration", - "national_id", + // "national_id", ] as const; export const STEPS = [ @@ -141,9 +141,6 @@ export const BOOKING_DOCS_SETTING = { ], }; -const requiredString = (message: string) => - z.string().trim().min(1, { message }); - const fileValueSchema = z.union([ z.custom(), z.array(z.custom()), @@ -152,10 +149,10 @@ const fileValueSchema = z.union([ export const bookingFormSchema = z .object({ - contractType: z.enum(["new", "renewal", ""]), + contractType: z.enum(["new", "renewal"], "Select a contract type."), previousContractRef: z.string(), draftContractId: z.string(), - serviceType: z.enum(["rail", "rail_forwarding", ""]), + serviceType: z.enum(["rail", "rail_forwarding"], "Select a service type."), firstMileEnabled: z.boolean(), pickUpAddress: z.string(), lastMileEnabled: z.boolean(), @@ -163,9 +160,9 @@ export const bookingFormSchema = z equipmentReturn: z.enum(["with_return", "without_return"]), originYard: z.string(), destinationYard: z.string(), - cargoType: z.enum(["container", "bulk", ""]), + cargoType: z.enum(["container", "bulk"], "Select a cargo type."), cargoWeight: z.string(), - freightType: z.enum(["bulk", "break_bulk", ""]), + freightType: z.enum(["bulk", "break_bulk", ""]).default(""), bulkCommodity: z.string(), bulkCommodityOther: z.string(), breakBulkType: z.string(), @@ -191,14 +188,6 @@ export const bookingFormSchema = z termsAccepted: z.boolean(), }) .superRefine((data, ctx) => { - if (!data.contractType) { - ctx.addIssue({ - code: "custom", - path: ["contractType"], - message: "Select a contract type.", - }); - } - if (data.contractType === "new" && !data.draftContractId.trim()) { ctx.addIssue({ code: "custom", @@ -215,14 +204,6 @@ export const bookingFormSchema = z }); } - if (!data.serviceType) { - ctx.addIssue({ - code: "custom", - path: ["serviceType"], - message: "Select a service type.", - }); - } - if (data.firstMileEnabled && !data.pickUpAddress.trim()) { ctx.addIssue({ code: "custom", @@ -267,14 +248,6 @@ export const bookingFormSchema = z }); } - if (!data.cargoType) { - ctx.addIssue({ - code: "custom", - path: ["cargoType"], - message: "Select a cargo type.", - }); - } - if (data.cargoType === "bulk") { if (!data.freightType) { ctx.addIssue({ @@ -385,11 +358,9 @@ export const bookingFormSchema = z export type BookingFormValues = z.infer; -export const initialBookingFormValues: BookingFormValues = { - contractType: "", +export const initialBookingFormValues: Partial = { previousContractRef: "", draftContractId: "", - serviceType: "", firstMileEnabled: false, pickUpAddress: "", lastMileEnabled: false, @@ -397,9 +368,7 @@ export const initialBookingFormValues: BookingFormValues = { equipmentReturn: "with_return", originYard: "", destinationYard: "", - cargoType: "", cargoWeight: "", - freightType: "", bulkCommodity: "", bulkCommodityOther: "", breakBulkType: "", @@ -511,4 +480,4 @@ export function calcWagons(containers: ContainerConfig[]): WagonCalcResult { ft20Wagons: Ft20Wagons, wagonLayout, }; -} \ No newline at end of file +} diff --git a/apps/edr-freight-web/portal/src/services/api.ts b/apps/edr-freight-web/portal/src/services/api.ts index e368ed675..ba2b227c6 100644 --- a/apps/edr-freight-web/portal/src/services/api.ts +++ b/apps/edr-freight-web/portal/src/services/api.ts @@ -8,7 +8,7 @@ import type { UpdateFileUploadFieldDto, UpdateFileUploadSettingDto, } from "@/types/fileUploadSettings"; -import { bookingsService } from "./bookings.service"; +import { bookingsService, CreateBookingPayload } from "./bookings.service"; import { consignmentsService } from "./consignments.service"; import { trackingService } from "./tracking.service"; import { fileUploadSettingsService } from "./fileUploadSettings.service"; @@ -40,16 +40,11 @@ export const api = { ({ id }) => bookingsService.get(id), ), - create: endpoint< - { - reference: string; - customerId: string; - scheduledDate: string; - totalAmount: number; - trainId?: string; - }, - Freight.IBooking - >("bookings", "create", (input) => bookingsService.create(input)), + create: endpoint( + "bookings", + "create", + bookingsService.create, + ), remove: endpoint<{ id: string }, void>("bookings", "remove", ({ id }) => bookingsService.remove(id), 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 dafb071f2..45070a6f9 100644 --- a/apps/edr-freight-web/portal/src/services/bookings.service.ts +++ b/apps/edr-freight-web/portal/src/services/bookings.service.ts @@ -2,13 +2,7 @@ import type { Freight, PaginatedResponse } from "@edr/types"; import { api } from "./crud"; -export interface CreateBookingPayload { - reference: string; - customerId: string; - scheduledDate: string; - totalAmount: number; - trainId?: string; -} +export type CreateBookingPayload = Freight.CreateBookingDto; export const bookingsService = { list: async (): Promise> => { @@ -20,7 +14,16 @@ export const bookingsService = { return data.data; }, create: async (payload: CreateBookingPayload): Promise => { - const { data } = await api.post("/bookings", payload); + const fd = new FormData(); + for (const [key, value] of Object.entries(payload)) { + if (value === undefined || value === null) continue; + if (Array.isArray(value) || typeof value === "object") { + fd.append(key, JSON.stringify(value)); + } else { + fd.append(key, String(value)); + } + } + const { data } = await api.post("/api/bookings", fd); return data.data; }, remove: async (id: string): Promise => { diff --git a/apps/edr-freight-web/portal/src/services/consignments.service.ts b/apps/edr-freight-web/portal/src/services/consignments.service.ts index 44becc2e7..fdaf3a667 100644 --- a/apps/edr-freight-web/portal/src/services/consignments.service.ts +++ b/apps/edr-freight-web/portal/src/services/consignments.service.ts @@ -1,14 +1,14 @@ import type { Freight, PaginatedResponse } from "@edr/types"; -import { api } from "../utils/api"; +import { client } from "../utils/api"; export const consignmentsService = { list: async (): Promise> => { - const { data } = await api.get("/consignments"); + const { data } = await client.get("/consignments"); return data.data; }, get: async (id: string): Promise => { - const { data } = await api.get(`/consignments/${id}`); + const { data } = await client.get(`/consignments/${id}`); return data.data; }, }; diff --git a/apps/edr-freight-web/portal/src/services/tracking.service.ts b/apps/edr-freight-web/portal/src/services/tracking.service.ts index 07026cf0a..af3f5f4b7 100644 --- a/apps/edr-freight-web/portal/src/services/tracking.service.ts +++ b/apps/edr-freight-web/portal/src/services/tracking.service.ts @@ -1,12 +1,12 @@ import type { Freight } from "@edr/types"; -import { api } from "../utils/api"; +import { client } from "../utils/api"; export const trackingService = { forConsignment: async ( consignmentId: string, ): Promise => { - const { data } = await api.get(`/tracking/${consignmentId}`); + const { data } = await client.get(`/tracking/${consignmentId}`); return data.data; }, }; diff --git a/packages/types/src/freight/index.ts b/packages/types/src/freight/index.ts index 52511b73a..b6d1e6dfa 100644 --- a/packages/types/src/freight/index.ts +++ b/packages/types/src/freight/index.ts @@ -124,3 +124,45 @@ export interface IInvoice extends BaseEntity { issuedAt: string; dueAt: string; } + +export interface CreateBookingDto { + reference: string; + customerId: string; + trainId?: string; + scheduledDate: string; + totalAmount: number; + paymentStatus?: string; + contractType: "NEW" | "RENEWAL"; + previousContractId?: string; + serviceType: "RAIL_ONLY" | "RAIL_AND_FORWARDING"; + + firstMileEnabled?: boolean; + firstMilePickupAddress?: string; + lastMileEnabled?: boolean; + lastMileDeliveryAddress?: string; + + equipmentReturn: "WITH_RETURN" | "WITHOUT_RETURN"; + originStation: string; + destinationStation: string; + cargoTotalWeightVgm: number; + + freightType: "BULK" | "BREAK_BULK"; + freightSubtype?: string; + + isHazardous?: boolean; + isRefrigerated?: boolean; + + tradeDirection: "IMPORT" | "EXPORT"; + paymentCurrency: string; + allowConsolidation?: boolean; + + startDate?: string; + endDate?: string; + financialTerms?: string; + + containers?: Array<{ + type: "20FT" | "40FT"; + qty: number; + vgm: number; + }>; +}
{booking.reference}
{booking.requestedDate}
{b.cargoType}
+ {b.containerCount > 0 ? `${b.containerCount} × ${b.containerType} · ` : ""}{b.weightTons}t +
+ View and manage your freight booking requests. +
Total Bookings
Active Bookings
Pending Approval
+ {searchTerm ? "No bookings match your current search filter." : "You haven't requested any bookings yet."} +