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 a9a0d52c4..68ce805df 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx @@ -385,6 +385,7 @@ const FirstMilePage = () => { const [rowSelection, setRowSelection] = useState>({}); const [assignOpen, setAssignOpen] = useState(false); + const [bulkMode, setBulkMode] = useState(false); const [detailOpen, setDetailOpen] = useState(false); const [tripSlipOpen, setTripSlipOpen] = useState(false); const [tripSlipJob, setTripSlipJob] = useState(null); @@ -396,6 +397,11 @@ const FirstMilePage = () => { [jobs, activeJobId], ); + const selectedIds = useMemo( + () => Object.keys(rowSelection).filter((id) => rowSelection[id]), + [rowSelection], + ); + const matchesStatusFilter = (job: FirstMileJob) => { switch (statusFilter) { case "ALL": @@ -445,11 +451,19 @@ const FirstMilePage = () => { const openAssign = (jobId: string | null) => { const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null; + setBulkMode(false); setActiveJobId(resolved); setVehicleValue(null); setAssignOpen(true); }; + const openBulkAssign = () => { + setBulkMode(true); + setActiveJobId(null); + setVehicleValue(null); + setAssignOpen(true); + }; + const openDetail = (jobId: string) => { setActiveJobId(jobId); setDetailOpen(true); @@ -457,6 +471,7 @@ const FirstMilePage = () => { const closeAssign = () => { setAssignOpen(false); + setBulkMode(false); setActiveJobId(null); setVehicleValue(null); }; @@ -467,8 +482,7 @@ const FirstMilePage = () => { }; const handleAssign = () => { - const jobId = activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id; - if (!jobId || !vehicleValue) { + if (!vehicleValue) { toast({ title: "Select a vehicle", description: "Choose a vehicle to assign to this pickup.", @@ -480,15 +494,30 @@ const FirstMilePage = () => { const vehicleLabel = VEHICLE_OPTIONS.find((option) => option.value === vehicleValue)?.label ?? vehicleValue; + const targetIds = bulkMode + ? selectedIds + : [activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id].filter( + (id): id is string => Boolean(id), + ); + + if (!targetIds.length) return; + + const targetSet = new Set(targetIds); setJobs((current) => current.map((job) => - job.id === jobId + targetSet.has(job.id) ? { ...job, status: "ASSIGNED", assignedVehicle: vehicleLabel } : job, ), ); - toast({ title: "Vehicle assigned", description: vehicleLabel }); + toast({ + title: "Vehicle assigned", + description: bulkMode + ? `${targetIds.length} pickups → ${vehicleLabel}` + : vehicleLabel, + }); + if (bulkMode) setRowSelection({}); closeAssign(); }; @@ -708,12 +737,23 @@ const FirstMilePage = () => { onChange={(e) => setSearch(e.currentTarget.value)} w={260} /> - + + {selectedIds.length > 0 && ( + + )} + + {FILTER_OPTIONS.map((option) => { @@ -777,7 +817,15 @@ const FirstMilePage = () => { centered > - {activeJob ? ( + {bulkMode ? ( + + Assigning a vehicle to{" "} + + {selectedIds.length} + {" "} + selected {selectedIds.length === 1 ? "pickup" : "pickups"}. + + ) : activeJob ? ( ) : ( @@ -797,8 +845,11 @@ const FirstMilePage = () => { - 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 48181873e..cd5191c5c 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx @@ -385,6 +385,7 @@ const LastMilePage = () => { const [rowSelection, setRowSelection] = useState>({}); const [assignOpen, setAssignOpen] = useState(false); + const [bulkMode, setBulkMode] = useState(false); const [detailOpen, setDetailOpen] = useState(false); const [tripSlipOpen, setTripSlipOpen] = useState(false); const [tripSlipJob, setTripSlipJob] = useState(null); @@ -396,6 +397,11 @@ const LastMilePage = () => { [jobs, activeJobId], ); + const selectedIds = useMemo( + () => Object.keys(rowSelection).filter((id) => rowSelection[id]), + [rowSelection], + ); + const matchesStatusFilter = (job: LastMileJob) => { switch (statusFilter) { case "ALL": @@ -445,11 +451,19 @@ const LastMilePage = () => { const openAssign = (jobId: string | null) => { const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null; + setBulkMode(false); setActiveJobId(resolved); setVehicleValue(null); setAssignOpen(true); }; + const openBulkAssign = () => { + setBulkMode(true); + setActiveJobId(null); + setVehicleValue(null); + setAssignOpen(true); + }; + const openDetail = (jobId: string) => { setActiveJobId(jobId); setDetailOpen(true); @@ -457,6 +471,7 @@ const LastMilePage = () => { const closeAssign = () => { setAssignOpen(false); + setBulkMode(false); setActiveJobId(null); setVehicleValue(null); }; @@ -467,8 +482,7 @@ const LastMilePage = () => { }; const handleAssign = () => { - const jobId = activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id; - if (!jobId || !vehicleValue) { + if (!vehicleValue) { toast({ title: "Select a vehicle", description: "Choose a vehicle to assign to this delivery.", @@ -480,15 +494,30 @@ const LastMilePage = () => { const vehicleLabel = VEHICLE_OPTIONS.find((option) => option.value === vehicleValue)?.label ?? vehicleValue; + const targetIds = bulkMode + ? selectedIds + : [activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id].filter( + (id): id is string => Boolean(id), + ); + + if (!targetIds.length) return; + + const targetSet = new Set(targetIds); setJobs((current) => current.map((job) => - job.id === jobId + targetSet.has(job.id) ? { ...job, status: "ASSIGNED", assignedVehicle: vehicleLabel } : job, ), ); - toast({ title: "Vehicle assigned", description: vehicleLabel }); + toast({ + title: "Vehicle assigned", + description: bulkMode + ? `${targetIds.length} deliveries → ${vehicleLabel}` + : vehicleLabel, + }); + if (bulkMode) setRowSelection({}); closeAssign(); }; @@ -708,12 +737,23 @@ const LastMilePage = () => { onChange={(e) => setSearch(e.currentTarget.value)} w={260} /> - + + {selectedIds.length > 0 && ( + + )} + + {FILTER_OPTIONS.map((option) => { @@ -777,7 +817,15 @@ const LastMilePage = () => { centered > - {activeJob ? ( + {bulkMode ? ( + + Assigning a vehicle to{" "} + + {selectedIds.length} + {" "} + selected {selectedIds.length === 1 ? "delivery" : "deliveries"}. + + ) : activeJob ? ( ) : ( @@ -797,8 +845,11 @@ const LastMilePage = () => { -