add checkbox

This commit is contained in:
natib21
2026-06-20 14:25:31 +00:00
parent db5e1568df
commit a3f9eabfdd
2 changed files with 128 additions and 26 deletions

View File

@@ -385,6 +385,7 @@ const FirstMilePage = () => {
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const [assignOpen, setAssignOpen] = useState(false); const [assignOpen, setAssignOpen] = useState(false);
const [bulkMode, setBulkMode] = useState(false);
const [detailOpen, setDetailOpen] = useState(false); const [detailOpen, setDetailOpen] = useState(false);
const [tripSlipOpen, setTripSlipOpen] = useState(false); const [tripSlipOpen, setTripSlipOpen] = useState(false);
const [tripSlipJob, setTripSlipJob] = useState<FirstMileJob | null>(null); const [tripSlipJob, setTripSlipJob] = useState<FirstMileJob | null>(null);
@@ -396,6 +397,11 @@ const FirstMilePage = () => {
[jobs, activeJobId], [jobs, activeJobId],
); );
const selectedIds = useMemo(
() => Object.keys(rowSelection).filter((id) => rowSelection[id]),
[rowSelection],
);
const matchesStatusFilter = (job: FirstMileJob) => { const matchesStatusFilter = (job: FirstMileJob) => {
switch (statusFilter) { switch (statusFilter) {
case "ALL": case "ALL":
@@ -445,11 +451,19 @@ const FirstMilePage = () => {
const openAssign = (jobId: string | null) => { const openAssign = (jobId: string | null) => {
const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null; const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null;
setBulkMode(false);
setActiveJobId(resolved); setActiveJobId(resolved);
setVehicleValue(null); setVehicleValue(null);
setAssignOpen(true); setAssignOpen(true);
}; };
const openBulkAssign = () => {
setBulkMode(true);
setActiveJobId(null);
setVehicleValue(null);
setAssignOpen(true);
};
const openDetail = (jobId: string) => { const openDetail = (jobId: string) => {
setActiveJobId(jobId); setActiveJobId(jobId);
setDetailOpen(true); setDetailOpen(true);
@@ -457,6 +471,7 @@ const FirstMilePage = () => {
const closeAssign = () => { const closeAssign = () => {
setAssignOpen(false); setAssignOpen(false);
setBulkMode(false);
setActiveJobId(null); setActiveJobId(null);
setVehicleValue(null); setVehicleValue(null);
}; };
@@ -467,8 +482,7 @@ const FirstMilePage = () => {
}; };
const handleAssign = () => { const handleAssign = () => {
const jobId = activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id; if (!vehicleValue) {
if (!jobId || !vehicleValue) {
toast({ toast({
title: "Select a vehicle", title: "Select a vehicle",
description: "Choose a vehicle to assign to this pickup.", description: "Choose a vehicle to assign to this pickup.",
@@ -480,15 +494,30 @@ const FirstMilePage = () => {
const vehicleLabel = const vehicleLabel =
VEHICLE_OPTIONS.find((option) => option.value === vehicleValue)?.label ?? vehicleValue; 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) => setJobs((current) =>
current.map((job) => current.map((job) =>
job.id === jobId targetSet.has(job.id)
? { ...job, status: "ASSIGNED", assignedVehicle: vehicleLabel } ? { ...job, status: "ASSIGNED", assignedVehicle: vehicleLabel }
: job, : job,
), ),
); );
toast({ title: "Vehicle assigned", description: vehicleLabel }); toast({
title: "Vehicle assigned",
description: bulkMode
? `${targetIds.length} pickups → ${vehicleLabel}`
: vehicleLabel,
});
if (bulkMode) setRowSelection({});
closeAssign(); closeAssign();
}; };
@@ -708,12 +737,23 @@ const FirstMilePage = () => {
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch(e.currentTarget.value)}
w={260} w={260}
/> />
<Button <Group gap="sm">
leftSection={<Truck size={16} />} {selectedIds.length > 0 && (
onClick={() => openAssign(null)} <Button
> variant="light"
Assign Mile leftSection={<Truck size={16} />}
</Button> onClick={openBulkAssign}
>
Assign vehicle ({selectedIds.length})
</Button>
)}
<Button
leftSection={<Truck size={16} />}
onClick={() => openAssign(null)}
>
Assign Mile
</Button>
</Group>
</Group> </Group>
<Group gap="xs" wrap="wrap"> <Group gap="xs" wrap="wrap">
{FILTER_OPTIONS.map((option) => { {FILTER_OPTIONS.map((option) => {
@@ -777,7 +817,15 @@ const FirstMilePage = () => {
centered centered
> >
<Stack gap="md"> <Stack gap="md">
{activeJob ? ( {bulkMode ? (
<Text size="sm">
Assigning a vehicle to{" "}
<Text span fw={600}>
{selectedIds.length}
</Text>{" "}
selected {selectedIds.length === 1 ? "pickup" : "pickups"}.
</Text>
) : activeJob ? (
<BookingInfo job={activeJob} /> <BookingInfo job={activeJob} />
) : ( ) : (
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
@@ -797,8 +845,11 @@ const FirstMilePage = () => {
<Button variant="default" onClick={closeAssign}> <Button variant="default" onClick={closeAssign}>
Cancel Cancel
</Button> </Button>
<Button onClick={handleAssign} disabled={!activeJob}> <Button
{activeJob?.status === "ASSIGNED" ? "Reassign" : "Assign"} onClick={handleAssign}
disabled={bulkMode ? selectedIds.length === 0 : !activeJob}
>
{!bulkMode && activeJob?.status === "ASSIGNED" ? "Reassign" : "Assign"}
</Button> </Button>
</Group> </Group>
</Stack> </Stack>

View File

@@ -385,6 +385,7 @@ const LastMilePage = () => {
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({}); const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const [assignOpen, setAssignOpen] = useState(false); const [assignOpen, setAssignOpen] = useState(false);
const [bulkMode, setBulkMode] = useState(false);
const [detailOpen, setDetailOpen] = useState(false); const [detailOpen, setDetailOpen] = useState(false);
const [tripSlipOpen, setTripSlipOpen] = useState(false); const [tripSlipOpen, setTripSlipOpen] = useState(false);
const [tripSlipJob, setTripSlipJob] = useState<LastMileJob | null>(null); const [tripSlipJob, setTripSlipJob] = useState<LastMileJob | null>(null);
@@ -396,6 +397,11 @@ const LastMilePage = () => {
[jobs, activeJobId], [jobs, activeJobId],
); );
const selectedIds = useMemo(
() => Object.keys(rowSelection).filter((id) => rowSelection[id]),
[rowSelection],
);
const matchesStatusFilter = (job: LastMileJob) => { const matchesStatusFilter = (job: LastMileJob) => {
switch (statusFilter) { switch (statusFilter) {
case "ALL": case "ALL":
@@ -445,11 +451,19 @@ const LastMilePage = () => {
const openAssign = (jobId: string | null) => { const openAssign = (jobId: string | null) => {
const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null; const resolved = jobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id ?? null;
setBulkMode(false);
setActiveJobId(resolved); setActiveJobId(resolved);
setVehicleValue(null); setVehicleValue(null);
setAssignOpen(true); setAssignOpen(true);
}; };
const openBulkAssign = () => {
setBulkMode(true);
setActiveJobId(null);
setVehicleValue(null);
setAssignOpen(true);
};
const openDetail = (jobId: string) => { const openDetail = (jobId: string) => {
setActiveJobId(jobId); setActiveJobId(jobId);
setDetailOpen(true); setDetailOpen(true);
@@ -457,6 +471,7 @@ const LastMilePage = () => {
const closeAssign = () => { const closeAssign = () => {
setAssignOpen(false); setAssignOpen(false);
setBulkMode(false);
setActiveJobId(null); setActiveJobId(null);
setVehicleValue(null); setVehicleValue(null);
}; };
@@ -467,8 +482,7 @@ const LastMilePage = () => {
}; };
const handleAssign = () => { const handleAssign = () => {
const jobId = activeJobId ?? filteredJobs.find((job) => job.status === "UNASSIGNED")?.id; if (!vehicleValue) {
if (!jobId || !vehicleValue) {
toast({ toast({
title: "Select a vehicle", title: "Select a vehicle",
description: "Choose a vehicle to assign to this delivery.", description: "Choose a vehicle to assign to this delivery.",
@@ -480,15 +494,30 @@ const LastMilePage = () => {
const vehicleLabel = const vehicleLabel =
VEHICLE_OPTIONS.find((option) => option.value === vehicleValue)?.label ?? vehicleValue; 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) => setJobs((current) =>
current.map((job) => current.map((job) =>
job.id === jobId targetSet.has(job.id)
? { ...job, status: "ASSIGNED", assignedVehicle: vehicleLabel } ? { ...job, status: "ASSIGNED", assignedVehicle: vehicleLabel }
: job, : job,
), ),
); );
toast({ title: "Vehicle assigned", description: vehicleLabel }); toast({
title: "Vehicle assigned",
description: bulkMode
? `${targetIds.length} deliveries → ${vehicleLabel}`
: vehicleLabel,
});
if (bulkMode) setRowSelection({});
closeAssign(); closeAssign();
}; };
@@ -708,12 +737,23 @@ const LastMilePage = () => {
onChange={(e) => setSearch(e.currentTarget.value)} onChange={(e) => setSearch(e.currentTarget.value)}
w={260} w={260}
/> />
<Button <Group gap="sm">
leftSection={<Truck size={16} />} {selectedIds.length > 0 && (
onClick={() => openAssign(null)} <Button
> variant="light"
Assign Mile leftSection={<Truck size={16} />}
</Button> onClick={openBulkAssign}
>
Assign vehicle ({selectedIds.length})
</Button>
)}
<Button
leftSection={<Truck size={16} />}
onClick={() => openAssign(null)}
>
Assign Mile
</Button>
</Group>
</Group> </Group>
<Group gap="xs" wrap="wrap"> <Group gap="xs" wrap="wrap">
{FILTER_OPTIONS.map((option) => { {FILTER_OPTIONS.map((option) => {
@@ -777,7 +817,15 @@ const LastMilePage = () => {
centered centered
> >
<Stack gap="md"> <Stack gap="md">
{activeJob ? ( {bulkMode ? (
<Text size="sm">
Assigning a vehicle to{" "}
<Text span fw={600}>
{selectedIds.length}
</Text>{" "}
selected {selectedIds.length === 1 ? "delivery" : "deliveries"}.
</Text>
) : activeJob ? (
<BookingInfo job={activeJob} /> <BookingInfo job={activeJob} />
) : ( ) : (
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
@@ -797,8 +845,11 @@ const LastMilePage = () => {
<Button variant="default" onClick={closeAssign}> <Button variant="default" onClick={closeAssign}>
Cancel Cancel
</Button> </Button>
<Button onClick={handleAssign} disabled={!activeJob}> <Button
{activeJob?.status === "ASSIGNED" ? "Reassign" : "Assign"} onClick={handleAssign}
disabled={bulkMode ? selectedIds.length === 0 : !activeJob}
>
{!bulkMode && activeJob?.status === "ASSIGNED" ? "Reassign" : "Assign"}
</Button> </Button>
</Group> </Group>
</Stack> </Stack>