mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
165 lines
4.7 KiB
TypeScript
165 lines
4.7 KiB
TypeScript
import { useState, useMemo } from "react";
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
import {
|
|
Box,
|
|
Button,
|
|
Group,
|
|
Loader,
|
|
Select,
|
|
Stack,
|
|
Table,
|
|
Text,
|
|
Alert,
|
|
} from "@mantine/core";
|
|
import { AlertCircle } from "lucide-react";
|
|
import toast from "react-hot-toast";
|
|
|
|
import { vehiclesService } from "@/services/vehicles.service";
|
|
|
|
export interface ContainerAllocationRow {
|
|
id: string;
|
|
type: string;
|
|
qty: number;
|
|
}
|
|
|
|
export interface FirstMileContainerAllocationTableProps {
|
|
firstMileId: string;
|
|
containers: ContainerAllocationRow[];
|
|
onSave: (allocations: Array<{ containerId: string; vehicleId: string }>) => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Manual container-to-vehicle allocation table for first-mile pickups.
|
|
* Displays containers with type/qty, vehicle dropdown per row, and save action.
|
|
*/
|
|
export function FirstMileContainerAllocationTable({
|
|
firstMileId,
|
|
containers,
|
|
onSave,
|
|
}: FirstMileContainerAllocationTableProps) {
|
|
const [allocations, setAllocations] = useState<Record<string, string | null>>(
|
|
() => containers.reduce((acc, c) => ({ ...acc, [c.id]: null }), {}),
|
|
);
|
|
|
|
const { data: vehicles = [], isLoading: vehiclesLoading } = useQuery({
|
|
queryKey: ["vehicles", "active"],
|
|
queryFn: () => vehiclesService.getAll({ status: "ACTIVE" }),
|
|
});
|
|
|
|
const vehicleOptions = useMemo(
|
|
() =>
|
|
vehicles.map((v) => ({
|
|
value: v.id,
|
|
label: `${v.plateNumber} (${v.vehicleType})`,
|
|
description: `${v.model} · ${v.manufacturer}`,
|
|
})),
|
|
[vehicles],
|
|
);
|
|
|
|
const saveAllocation = useMutation({
|
|
mutationFn: async () => {
|
|
const mappings = containers
|
|
.filter((c) => allocations[c.id])
|
|
.map((c) => ({
|
|
containerId: c.id,
|
|
vehicleId: allocations[c.id]!,
|
|
}));
|
|
|
|
if (mappings.length === 0) {
|
|
throw new Error("No containers allocated to vehicles");
|
|
}
|
|
|
|
await onSave(mappings);
|
|
},
|
|
onSuccess: () => {
|
|
toast.success("Container allocations saved");
|
|
setAllocations(
|
|
containers.reduce((acc, c) => ({ ...acc, [c.id]: null }), {}),
|
|
);
|
|
},
|
|
onError: (error) => {
|
|
toast.error(
|
|
error instanceof Error ? error.message : "Failed to save allocations",
|
|
);
|
|
},
|
|
});
|
|
|
|
const allocatedCount = Object.values(allocations).filter(Boolean).length;
|
|
const allAllocated = allocatedCount === containers.length;
|
|
|
|
if (vehiclesLoading) {
|
|
return (
|
|
<Box display="flex" justifyContent="center" p="xl">
|
|
<Loader size="sm" />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Stack gap="md">
|
|
{vehicles.length === 0 && (
|
|
<Alert icon={<AlertCircle size={16} />} color="yellow">
|
|
No active vehicles available. Add vehicles before allocating containers.
|
|
</Alert>
|
|
)}
|
|
|
|
<Box style={{ overflowX: "auto" }}>
|
|
<Table verticalSpacing="md" horizontalSpacing="md" highlightOnHover>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>Container ID</Table.Th>
|
|
<Table.Th>Type</Table.Th>
|
|
<Table.Th>Qty</Table.Th>
|
|
<Table.Th>Assigned Vehicle</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{containers.map((container) => (
|
|
<Table.Tr key={container.id}>
|
|
<Table.Td>
|
|
<Text fw={600} size="sm">
|
|
{container.id}
|
|
</Text>
|
|
</Table.Td>
|
|
<Table.Td>{container.type}</Table.Td>
|
|
<Table.Td>{container.qty}</Table.Td>
|
|
<Table.Td>
|
|
<Select
|
|
placeholder="Select vehicle"
|
|
data={vehicleOptions}
|
|
value={allocations[container.id] ?? null}
|
|
onChange={(value) =>
|
|
setAllocations((prev) => ({
|
|
...prev,
|
|
[container.id]: value,
|
|
}))
|
|
}
|
|
searchable
|
|
clearable
|
|
disabled={vehicles.length === 0}
|
|
style={{ minWidth: 200 }}
|
|
/>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
))}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</Box>
|
|
|
|
<Group justify="space-between">
|
|
<Text size="sm" c="dimmed">
|
|
{allocatedCount} of {containers.length} containers allocated
|
|
</Text>
|
|
<Button
|
|
color="edr-green"
|
|
loading={saveAllocation.isPending}
|
|
disabled={allocatedCount === 0 || vehicles.length === 0}
|
|
onClick={() => saveAllocation.mutate()}
|
|
>
|
|
Save Allocations
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
);
|
|
}
|