feat(collection-pool): render supplier-grouped sections

This commit is contained in:
SJCoetz 2026-07-10 23:58:47 +02:00
parent 8e4289b104
commit 5984f2f814

View file

@ -118,6 +118,12 @@ export default function CollectionPoolPage() {
const [readyDialogRow, setReadyDialogRow] = useState<CollectionPoolVisibleRow | null>(null);
const [readyDialogError, setReadyDialogError] = useState<string | null>(null);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const [collapsedSuppliers, setCollapsedSuppliers] = useState<Set<string>>(new Set());
const [bulkAssignDialog, setBulkAssignDialog] = useState<{
supplierId: string;
supplierName: string;
rowIds: string[];
} | null>(null);
const toggleRowSelected = useCallback((rowId: string) => {
setSelectedIds((prev) => {
@ -141,6 +147,25 @@ export default function CollectionPoolPage() {
});
}, []);
const toggleCollapsed = useCallback((supplierId: string) => {
setCollapsedSuppliers((prev) => {
const next = new Set(prev);
if (next.has(supplierId)) next.delete(supplierId);
else next.add(supplierId);
return next;
});
}, []);
const openBulkAssign = useCallback(
(supplierId: string, supplierName: string, rowIds: string[]) => {
if (rowIds.length === 0) return;
setBulkAssignDialog({ supplierId, supplierName, rowIds });
},
[],
);
const closeBulkAssign = useCallback(() => setBulkAssignDialog(null), []);
const closeAssignDialog = useCallback(() => setAssignDialog(null), []);
const openAssignForRow = useCallback((row: CollectionPoolVisibleRow) => {
@ -270,6 +295,21 @@ export default function CollectionPoolPage() {
) as CollectionPoolVisibleRow[];
}, [rows, todayYmd, filterText, readyFilter, sortMode, clusterSortKeyForSupplier]);
const groups = useMemo<
| (Omit<CollectionPoolGroup, "rows"> & { rows: CollectionPoolVisibleRow[] })[]
| null
>(() => {
if (!visibleRows) return null;
// `buildSupplierGroups` types its rows as `CollectionPoolViewRow[]`,
// but every row in `visibleRows` is a `CollectionPoolVisibleRow` (a
// superset of `CollectionPoolViewRow` — see the cast above). The
// widening per-row is sound.
return buildSupplierGroups(visibleRows, supplierMarkerMap).map((g) => ({
...g,
rows: g.rows as CollectionPoolVisibleRow[],
}));
}, [visibleRows, supplierMarkerMap]);
const updateItemStatus = useCallback(
async (
itemId: string,
@ -483,51 +523,44 @@ export default function CollectionPoolPage() {
</div>
</div>
{rows === null && (
<div className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className="h-20 w-full animate-pulse rounded-md bg-muted/30"
aria-label="Loading..."
/>
))}
<div className="overflow-hidden rounded-xl border bg-card">
<div className="hidden grid-cols-[minmax(0,1.35fr)_minmax(0,0.95fr)_auto_auto] gap-4 border-b bg-muted/20 px-4 py-2 pl-[22px] text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground lg:grid">
<span>Collection Item</span>
<span>Order Detail</span>
<span>Status</span>
<span className="text-right">Actions</span>
</div>
)}
{rows !== null && rows.length === 0 && (
<EmptyState message="No items in the collection pool. Items land here after a PO email is sent successfully." />
)}
{rows !== null && rows.length > 0 && visibleRows && visibleRows.length === 0 && (
<EmptyState message="No items match the current filters." />
)}
{rows !== null && visibleRows && visibleRows.length > 0 && (
<div className="overflow-hidden rounded-xl border bg-card">
<div className="hidden grid-cols-[minmax(0,1.35fr)_minmax(0,0.95fr)_auto_auto] gap-4 border-b bg-muted/20 px-4 py-2 pl-[22px] text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground lg:grid">
<span>Collection Item</span>
<span>Order Detail</span>
<span>Status</span>
<span className="text-right">Actions</span>
</div>
<ul className="divide-y" data-od-id="collection-pool-unified-list">
{visibleRows.map((row) => (
<PoolRow
key={row.id}
row={row}
busy={busyId === row.id}
markerToken={supplierMarkerMap.get(row.supplierId)}
todayYmd={todayYmd}
selected={selectedIds.has(row.id)}
onToggleSelected={toggleRowSelected}
onOpenAssign={openAssignForRow}
onToggleReady={handleToggleReady}
{groups === null ? (
<div className="space-y-2 p-4">
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className="h-20 w-full animate-pulse rounded-md bg-muted/30"
aria-label="Loading..."
/>
))}
</ul>
</div>
)}
</div>
) : groups.length === 0 ? (
<EmptyState message="No items match the current filters." />
) : (
groups.map((group) => (
<SupplierGroup
key={group.supplierId}
group={group}
busy={busyId !== null}
todayYmd={todayYmd}
selectedIds={selectedIds}
collapsed={collapsedSuppliers.has(group.supplierId)}
onToggleRowSelected={toggleRowSelected}
onToggleSectionSelected={toggleSectionSelected}
onToggleCollapsed={toggleCollapsed}
onOpenAssign={openAssignForRow}
onOpenBulkAssign={openBulkAssign}
onToggleReady={handleToggleReady}
/>
))
)}
</div>
<AssignDriverDialog
open={assignDialog !== null}