feat(collection-pool): add per-row selection state and checkbox
- selectedIds Set state plus toggleRowSelected / toggleSectionSelected callbacks - PoolRow signature extended with selected + onToggleSelected - New flex wrapper around supplier/description block with leading checkbox input - bg-primary/5 row tint applied when selected - aria-label composes supplier + description for screen readers
This commit is contained in:
parent
7ce4445865
commit
b6ed9f2685
1 changed files with 59 additions and 14 deletions
|
|
@ -114,6 +114,29 @@ export default function CollectionPoolPage() {
|
|||
const [assignDialog, setAssignDialog] = useState<CollectionPoolAssignDialogState | null>(null);
|
||||
const [readyDialogRow, setReadyDialogRow] = useState<CollectionPoolVisibleRow | null>(null);
|
||||
const [readyDialogError, setReadyDialogError] = useState<string | null>(null);
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleRowSelected = useCallback((rowId: string) => {
|
||||
setSelectedIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(rowId)) next.delete(rowId);
|
||||
else next.add(rowId);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleSectionSelected = useCallback((rowIds: string[]) => {
|
||||
setSelectedIds((prev) => {
|
||||
const allSelected = rowIds.every((id) => prev.has(id));
|
||||
const next = new Set(prev);
|
||||
if (allSelected) {
|
||||
for (const id of rowIds) next.delete(id);
|
||||
} else {
|
||||
for (const id of rowIds) next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const closeAssignDialog = useCallback(() => setAssignDialog(null), []);
|
||||
|
||||
|
|
@ -493,6 +516,8 @@ export default function CollectionPoolPage() {
|
|||
busy={busyId === row.id}
|
||||
markerToken={supplierMarkerMap.get(row.supplierId)}
|
||||
todayYmd={todayYmd}
|
||||
selected={selectedIds.has(row.id)}
|
||||
onToggleSelected={toggleRowSelected}
|
||||
onOpenAssign={openAssignForRow}
|
||||
onToggleReady={handleToggleReady}
|
||||
/>
|
||||
|
|
@ -553,6 +578,8 @@ function PoolRow({
|
|||
busy,
|
||||
markerToken,
|
||||
todayYmd,
|
||||
selected,
|
||||
onToggleSelected,
|
||||
onOpenAssign,
|
||||
onToggleReady,
|
||||
}: {
|
||||
|
|
@ -560,6 +587,8 @@ function PoolRow({
|
|||
busy: boolean;
|
||||
markerToken?: string;
|
||||
todayYmd: string;
|
||||
selected: boolean;
|
||||
onToggleSelected: (rowId: string) => void;
|
||||
onOpenAssign: (row: CollectionPoolVisibleRow) => void;
|
||||
onToggleReady: (row: CollectionPoolVisibleRow, ready: boolean) => void;
|
||||
}) {
|
||||
|
|
@ -576,24 +605,40 @@ function PoolRow({
|
|||
: null;
|
||||
|
||||
return (
|
||||
<li className="grid grid-cols-[6px_minmax(0,1fr)]" aria-busy={busy}>
|
||||
<li
|
||||
className={cn(
|
||||
"grid grid-cols-[6px_minmax(0,1fr)]",
|
||||
selected && "bg-primary/5",
|
||||
)}
|
||||
aria-busy={busy}
|
||||
>
|
||||
<span className={cn("min-h-full", markerTokenToClassName(markerToken))} aria-hidden />
|
||||
|
||||
<div className="grid gap-4 p-4 lg:grid-cols-[minmax(0,1.35fr)_minmax(0,0.95fr)_auto_auto] lg:items-center">
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-semibold text-foreground">{row.supplierName}</p>
|
||||
<p className="mt-1 truncate text-sm text-foreground">{row.description || "(no description)"}</p>
|
||||
<div className="mt-1 flex flex-wrap gap-x-2 gap-y-1 text-xs text-muted-foreground">
|
||||
<span>{row.customerName || "No customer"}</span>
|
||||
<span className="text-border">/</span>
|
||||
<span>{row.companyName || "No company"}</span>
|
||||
</div>
|
||||
{!row.purchase_order && (
|
||||
<div className="mt-2 flex items-start gap-2 rounded-md border border-amber-200/80 bg-amber-50/70 px-2.5 py-2 text-[11px] leading-5 text-amber-900">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<span>This row has no purchase order link. The PO may have been deleted.</span>
|
||||
<div className="flex min-w-0 items-start gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
onChange={() => onToggleSelected(row.id)}
|
||||
disabled={busy}
|
||||
aria-label={`Select ${row.description ?? "item"} at ${row.supplierName ?? "unknown supplier"}`}
|
||||
className="mt-1 size-4 shrink-0 cursor-pointer accent-primary disabled:cursor-not-allowed"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-semibold text-foreground">{row.supplierName}</p>
|
||||
<p className="mt-1 truncate text-sm text-foreground">{row.description || "(no description)"}</p>
|
||||
<div className="mt-1 flex flex-wrap gap-x-2 gap-y-1 text-xs text-muted-foreground">
|
||||
<span>{row.customerName || "No customer"}</span>
|
||||
<span className="text-border">/</span>
|
||||
<span>{row.companyName || "No company"}</span>
|
||||
</div>
|
||||
)}
|
||||
{!row.purchase_order && (
|
||||
<div className="mt-2 flex items-start gap-2 rounded-md border border-amber-200/80 bg-amber-50/70 px-2.5 py-2 text-[11px] leading-5 text-amber-900">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
<span>This row has no purchase order link. The PO may have been deleted.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 text-sm">
|
||||
|
|
|
|||
Loading…
Reference in a new issue