diff --git a/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx b/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx index d9e4c372c..7842adfeb 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx @@ -37,7 +37,9 @@ import { type FirstMileRecord, firstMileService, } from "@/services/first-mile.service"; +import { bookingsService } from "@/services/bookings.service"; import { vehiclesService } from "@/services/vehicles.service"; +import type { BookingDetail } from "@/types/booking"; const formatPrice = (amount: number) => `ETB ${amount.toLocaleString("en-US", { @@ -308,6 +310,12 @@ const FirstMilePage = () => { const [activeId, setActiveId] = useState(null); const [vehicleValue, setVehicleValue] = useState(null); + const [acceptOpen, setAcceptOpen] = useState(false); + const [acceptStep, setAcceptStep] = useState<1 | 2>(1); + const [selectedBooking, setSelectedBooking] = useState(null); + const [acceptVehicleValue, setAcceptVehicleValue] = useState(null); + const [bookingSearch, setBookingSearch] = useState(""); + const { data: listData, isLoading } = useQuery({ queryKey: QUERY_KEYS.FIRST_MILE.list(), queryFn: async () => { @@ -324,6 +332,13 @@ const FirstMilePage = () => { }, }); + const { data: paidBookingsData, isLoading: bookingsLoading } = useQuery({ + queryKey: QUERY_KEYS.BOOKINGS.list({ status: "PAID" }), + queryFn: () => bookingsService.list({ status: "PAID", pageSize: 100 }), + enabled: acceptOpen, + }); + const paidBookings = paidBookingsData?.items ?? []; + const records = listData?.data ?? []; const vehicleOptions = useMemo( @@ -346,6 +361,23 @@ const FirstMilePage = () => { }, }); + const acceptMutation = useMutation({ + mutationFn: async ({ reference, vehicleId }: { reference: string; vehicleId: string | null }) => { + const res = await firstMileService.accept(reference); + const created = res.data; + if (vehicleId) await firstMileService.update(created.id, { vehicleId }); + return created; + }, + onSuccess: () => { + void qc.invalidateQueries({ queryKey: QUERY_KEYS.FIRST_MILE.ROOT }); + toast({ title: "Booking accepted", description: "First-mile leg created successfully." }); + closeAccept(); + }, + onError: () => { + toast({ title: "Accept failed", variant: "destructive" }); + }, + }); + const activeRecord = useMemo( () => records.find((r) => r.id === activeId) ?? null, [records, activeId], @@ -356,6 +388,38 @@ const FirstMilePage = () => { [rowSelection], ); + const filteredPaidBookings = useMemo(() => { + const term = bookingSearch.trim().toLowerCase(); + if (!term) return paidBookings; + return paidBookings.filter((b) => + [b.reference, b.company?.name, b.company?.companyName] + .join(" ") + .toLowerCase() + .includes(term), + ); + }, [paidBookings, bookingSearch]); + + const openAccept = () => { + setAcceptOpen(true); + setAcceptStep(1); + setSelectedBooking(null); + setAcceptVehicleValue(null); + setBookingSearch(""); + }; + + const closeAccept = () => { + setAcceptOpen(false); + setAcceptStep(1); + setSelectedBooking(null); + setAcceptVehicleValue(null); + setBookingSearch(""); + }; + + const handleAcceptConfirm = () => { + if (!selectedBooking) return; + acceptMutation.mutate({ reference: selectedBooking.reference, vehicleId: acceptVehicleValue }); + }; + const matchesFilter = (r: FirstMileRecord) => { switch (statusFilter) { case "ALL": return true; @@ -641,7 +705,7 @@ const FirstMilePage = () => { Assign vehicle ({selectedIds.length}) )} - @@ -773,6 +837,119 @@ const FirstMilePage = () => { + + {/* Accept Booking modal — step 1: booking list, step 2: details + vehicle */} + + {acceptStep === 1 ? "Select a Paid Booking" : "Accept Booking"} + + } + size="xl" + radius="lg" + centered + > + {acceptStep === 1 ? ( + + setBookingSearch(e.currentTarget.value)} + /> + + {bookingsLoading ? ( + Loading bookings… + ) : filteredPaidBookings.length === 0 ? ( + No paid bookings found. + ) : ( + filteredPaidBookings.map((b) => ( + { + setSelectedBooking(b); + setAcceptStep(2); + }} + > + + + {b.reference} + {b.company?.name ?? b.company?.companyName ?? "—"} + + + + {b.originYard?.name ?? "—"} → {b.destinationYard?.name ?? "—"} + + {formatPrice(b.totalAmount)} + + {b.scheduledDate ? b.scheduledDate.slice(0, 10) : "—"} + + + + + )) + )} + + + + + + ) : ( + + {selectedBooking && ( + + + + {selectedBooking.reference} + PAID + + + + + + + + + + + + + + + + + )} + +