mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 19:58:11 +00:00
242 lines
9.0 KiB
TypeScript
242 lines
9.0 KiB
TypeScript
import { ActionIcon, Badge, Button, Checkbox, Group, Table, Text, Tooltip } from '@mantine/core';
|
|
import { ArrowRightLeft, ClipboardList, Coins, Eye, FileText, History, MapPin } from 'lucide-react';
|
|
|
|
import {
|
|
getNextInventoryAction,
|
|
type InventoryAction,
|
|
type WarehouseInventoryItem,
|
|
} from '@/types/warehouse';
|
|
import { InventoryStatusBadge } from './badges';
|
|
import { formatDate, formatNumber, humanizeEnum } from './options';
|
|
|
|
interface WarehouseInventoryTableProps {
|
|
items: WarehouseInventoryItem[];
|
|
busyId?: string | null;
|
|
onAdvance: (item: WarehouseInventoryItem, action: InventoryAction) => void;
|
|
onMove: (item: WarehouseInventoryItem) => void;
|
|
onHistory: (item: WarehouseInventoryItem) => void;
|
|
onView?: (item: WarehouseInventoryItem) => void;
|
|
onInspect?: (item: WarehouseInventoryItem) => void;
|
|
onFeePreview?: (item: WarehouseInventoryItem) => void;
|
|
onReleaseDocument?: (item: WarehouseInventoryItem) => void;
|
|
onLastMile?: (item: WarehouseInventoryItem) => void;
|
|
selectedIds?: Set<string>;
|
|
onToggleSelect?: (id: string) => void;
|
|
onToggleSelectAll?: () => void;
|
|
allSelected?: boolean;
|
|
someSelected?: boolean;
|
|
}
|
|
|
|
const itemKind = (item: WarehouseInventoryItem) => {
|
|
if (item.containerId) return { label: 'Container', color: 'blue' };
|
|
if (item.cargoId) return { label: 'Cargo', color: 'grape' };
|
|
if (item.goodsId) return { label: 'Goods', color: 'orange' };
|
|
return { label: '-', color: 'gray' };
|
|
};
|
|
|
|
const actionColor: Record<InventoryAction, string> = {
|
|
store: 'blue',
|
|
reserve: 'grape',
|
|
'ready-for-loading': 'cyan',
|
|
load: 'teal',
|
|
dispatch: 'edr-green',
|
|
'ready-for-pickup': 'orange',
|
|
release: 'yellow',
|
|
deliver: 'green',
|
|
};
|
|
|
|
export function WarehouseInventoryTable({
|
|
items,
|
|
busyId,
|
|
onAdvance,
|
|
onMove,
|
|
onHistory,
|
|
onView,
|
|
onInspect,
|
|
onFeePreview,
|
|
onReleaseDocument,
|
|
onLastMile,
|
|
selectedIds,
|
|
onToggleSelect,
|
|
onToggleSelectAll,
|
|
allSelected,
|
|
someSelected,
|
|
}: WarehouseInventoryTableProps) {
|
|
const selectable = Boolean(onToggleSelect);
|
|
|
|
if (items.length === 0) {
|
|
return (
|
|
<Text size="sm" c="dimmed" ta="center" py="xl">
|
|
No inventory items found.
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Table.ScrollContainer minWidth={1150}>
|
|
<Table highlightOnHover verticalSpacing="sm" striped>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
{selectable && (
|
|
<Table.Th w={40}>
|
|
<Checkbox
|
|
aria-label="Select all"
|
|
checked={allSelected}
|
|
indeterminate={someSelected}
|
|
onChange={onToggleSelectAll}
|
|
/>
|
|
</Table.Th>
|
|
)}
|
|
<Table.Th>Booking</Table.Th>
|
|
<Table.Th>Facility</Table.Th>
|
|
<Table.Th>Warehouse</Table.Th>
|
|
<Table.Th>Yard</Table.Th>
|
|
<Table.Th>Zone</Table.Th>
|
|
<Table.Th>Item</Table.Th>
|
|
<Table.Th>Qty</Table.Th>
|
|
<Table.Th>Weight</Table.Th>
|
|
<Table.Th>Status</Table.Th>
|
|
<Table.Th>Arrived</Table.Th>
|
|
<Table.Th ta="right">Actions</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{items.map((item) => {
|
|
const kind = itemKind(item);
|
|
const busy = busyId === item.id;
|
|
const nextAction = getNextInventoryAction(item);
|
|
|
|
return (
|
|
<Table.Tr key={item.id}>
|
|
{selectable && (
|
|
<Table.Td>
|
|
<Checkbox
|
|
aria-label={`Select ${item.bookingId ?? item.id}`}
|
|
checked={selectedIds?.has(item.id) ?? false}
|
|
onChange={() => onToggleSelect?.(item.id)}
|
|
/>
|
|
</Table.Td>
|
|
)}
|
|
<Table.Td>
|
|
{item.bookingId ? (
|
|
<Tooltip label={item.bookingId} withArrow>
|
|
<Text size="sm" fw={600}>
|
|
{item.bookingId.slice(0, 8)}...
|
|
</Text>
|
|
</Tooltip>
|
|
) : (
|
|
<Text size="sm" c="dimmed">
|
|
-
|
|
</Text>
|
|
)}
|
|
</Table.Td>
|
|
<Table.Td>{item.warehouse?.facility?.name ?? '-'}</Table.Td>
|
|
<Table.Td>{item.warehouse?.code ?? '-'}</Table.Td>
|
|
<Table.Td>{item.yard?.code ?? '-'}</Table.Td>
|
|
<Table.Td>{item.zone?.code ?? '-'}</Table.Td>
|
|
<Table.Td>
|
|
<Badge color={kind.color} variant="light" size="sm" radius="md">
|
|
{kind.label}
|
|
</Badge>
|
|
</Table.Td>
|
|
<Table.Td>{formatNumber(item.quantity)}</Table.Td>
|
|
<Table.Td>{formatNumber(item.weight)}</Table.Td>
|
|
<Table.Td>
|
|
<InventoryStatusBadge status={item.status} />
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Text size="xs">{formatDate(item.arrivedAt)}</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
|
{onView && (
|
|
<Tooltip label="View details" withArrow>
|
|
<ActionIcon variant="subtle" color="gray" onClick={() => onView(item)}>
|
|
<Eye size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
{nextAction && (
|
|
<Button
|
|
size="compact-xs"
|
|
variant="light"
|
|
color={actionColor[nextAction]}
|
|
loading={busy}
|
|
onClick={() => onAdvance(item, nextAction)}
|
|
>
|
|
{nextAction === 'release' ? 'Exit Inspection' : humanizeEnum(nextAction.replace(/-/g, '_'))}
|
|
</Button>
|
|
)}
|
|
{item.status === 'READY_FOR_PICKUP' && (
|
|
<>
|
|
<Button
|
|
size="compact-xs"
|
|
variant="light"
|
|
color="blue"
|
|
loading={busy}
|
|
onClick={() => onAdvance(item, 'store')}
|
|
>
|
|
Store
|
|
</Button>
|
|
<Button
|
|
size="compact-xs"
|
|
variant="light"
|
|
color="green"
|
|
loading={busy}
|
|
onClick={() => onAdvance(item, 'dispatch')}
|
|
>
|
|
Dispatch
|
|
</Button>
|
|
</>
|
|
)}
|
|
{item.status !== 'DISPATCHED' && (
|
|
<Tooltip label="Move" withArrow>
|
|
<ActionIcon variant="subtle" color="gray" onClick={() => onMove(item)}>
|
|
<ArrowRightLeft size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
{onInspect && (
|
|
<Tooltip label="Inspection / Report" withArrow>
|
|
<ActionIcon variant="subtle" color="orange" onClick={() => onInspect(item)}>
|
|
<ClipboardList size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
{onFeePreview && (
|
|
<Tooltip label="Storage / Demurrage preview" withArrow>
|
|
<ActionIcon variant="subtle" color="teal" onClick={() => onFeePreview(item)}>
|
|
<Coins size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
{onReleaseDocument && item.releaseDate && (
|
|
<Tooltip label="View release exit paper" withArrow>
|
|
<ActionIcon variant="subtle" color="orange" onClick={() => onReleaseDocument(item)}>
|
|
<FileText size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
{onLastMile && item.booking?.lastMileDeliveryAddress && (
|
|
<Tooltip label="Last mile delivery" withArrow>
|
|
<ActionIcon variant="subtle" color="blue" onClick={() => onLastMile(item)}>
|
|
<MapPin size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
)}
|
|
<Tooltip label="History" withArrow>
|
|
<ActionIcon variant="subtle" color="gray" onClick={() => onHistory(item)}>
|
|
<History size={16} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
</Group>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
);
|
|
})}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</Table.ScrollContainer>
|
|
);
|
|
}
|