From 4e926c0d390a486ae35e4e76372ed97a4f52f685 Mon Sep 17 00:00:00 2001 From: natib21 Date: Sat, 20 Jun 2026 12:46:02 +0000 Subject: [PATCH] last mile --- .../src/pages/operations/LastMilePage.tsx | 168 ++++++++++++++++-- 1 file changed, 153 insertions(+), 15 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx index e879685be..075d73705 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx @@ -1,15 +1,19 @@ import { useMemo, useState } from "react"; -import { Truck } from "lucide-react"; +import { Eye, MoreHorizontal, RefreshCw, Truck } from "lucide-react"; import type { ColumnDef } from "@edr/ui-common"; import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common"; import { + ActionIcon, Badge, Box, Button, Card, + Divider, Group, + Menu, Modal, Select, + SimpleGrid, Stack, Text, TextInput, @@ -28,6 +32,13 @@ interface LastMileJob { cargo: string; status: LastMileStatus; assignedVehicle: string | null; + // Booking info shown in the Assign / View Detail modals. + serviceType: string; + weight: string; + originYard: string; + contactName: string; + contactPhone: string; + requestedDate: string; } // Placeholder data — replace with a real last-mile service once the API exists. @@ -40,6 +51,12 @@ const PLACEHOLDER_JOBS: LastMileJob[] = [ cargo: "20ft container · Electronics", status: "UNASSIGNED", assignedVehicle: null, + serviceType: "Door-to-door (Last Mile)", + weight: "12.4 t", + originYard: "Indode Dry Port", + contactName: "Selam Bekele", + contactPhone: "+251 911 234 567", + requestedDate: "2026-06-22", }, { id: "2", @@ -49,6 +66,12 @@ const PLACEHOLDER_JOBS: LastMileJob[] = [ cargo: "Bulk · 18t Cement", status: "ASSIGNED", assignedVehicle: "Isuzu FVR (3-AA-45821)", + serviceType: "Terminal-to-door (Last Mile)", + weight: "18.0 t", + originYard: "Dire Dawa Terminal", + contactName: "Yonas Tadesse", + contactPhone: "+251 912 887 010", + requestedDate: "2026-06-21", }, { id: "3", @@ -58,6 +81,12 @@ const PLACEHOLDER_JOBS: LastMileJob[] = [ cargo: "40ft container · Machinery", status: "UNASSIGNED", assignedVehicle: null, + serviceType: "Door-to-door (Last Mile)", + weight: "24.7 t", + originYard: "Mojo Dry Port", + contactName: "Hanna Girma", + contactPhone: "+251 913 445 221", + requestedDate: "2026-06-23", }, ]; @@ -68,6 +97,44 @@ const VEHICLE_OPTIONS = [ { value: "mercedes-actros-90113", label: "Mercedes Actros (3-AA-90113)" }, ]; +const InfoRow = ({ label, value }: { label: string; value: string }) => ( + + + {label} + + {value} + +); + +const BookingInfo = ({ job }: { job: LastMileJob }) => ( + + + + {job.bookingRef} + + {job.status === "ASSIGNED" ? "Assigned" : "Unassigned"} + + + + + + + + + + + + + + + + +); + const LastMilePage = () => { const { toast } = useToast(); const { pagination, setPagination } = usePagination({ pageSize: 10 }); @@ -76,9 +143,15 @@ const LastMilePage = () => { const [search, setSearch] = useState(""); const [assignOpen, setAssignOpen] = useState(false); + const [detailOpen, setDetailOpen] = useState(false); const [activeJobId, setActiveJobId] = useState(null); const [vehicleValue, setVehicleValue] = useState(null); + const activeJob = useMemo( + () => jobs.find((job) => job.id === activeJobId) ?? null, + [jobs, activeJobId], + ); + const filteredJobs = useMemo(() => { const term = search.trim().toLowerCase(); if (!term) return jobs; @@ -97,17 +170,28 @@ const LastMilePage = () => { }, [filteredJobs, pagination.pageIndex, pagination.pageSize]); const openAssign = (jobId: string | null) => { - setActiveJobId(jobId); + const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null; + setActiveJobId(resolved); setVehicleValue(null); setAssignOpen(true); }; + const openDetail = (jobId: string) => { + setActiveJobId(jobId); + setDetailOpen(true); + }; + const closeAssign = () => { setAssignOpen(false); setActiveJobId(null); setVehicleValue(null); }; + const closeDetail = () => { + setDetailOpen(false); + setActiveJobId(null); + }; + const handleAssign = () => { const jobId = activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id; if (!jobId || !vehicleValue) { @@ -191,18 +275,43 @@ const LastMilePage = () => { id: "actions", header: "Actions", meta: { headerClassName, cellClassName: `${cellClassName} whitespace-nowrap` }, - cell: ({ row }) => ( - - - - ), + cell: ({ row }) => { + const isAssigned = row.original.status === "ASSIGNED"; + return ( + + + + + + + + + } + disabled={isAssigned} + onClick={() => openAssign(row.original.id)} + > + Assign + + } + disabled={!isAssigned} + onClick={() => openAssign(row.original.id)} + > + Reassign + + + } + onClick={() => openDetail(row.original.id)} + > + View detail + + + + + ); + }, }, ]; }, []); @@ -268,10 +377,19 @@ const LastMilePage = () => { opened={assignOpen} onClose={closeAssign} title={Assign Vehicle} + size="lg" radius="lg" centered > + {activeJob ? ( + + ) : ( + + No unassigned deliveries available. + + )} +