fix(data-table): preserve prior rows when multi-selecting

onRowSelectionChange computed the next selection from the render-time
`rowSelection` closure, which could be stale by the time a second row was
toggled. The updater then produced a selection containing only the latest
row, and the persistedMap sync deleted the earlier one — so checking a
second part unchecked the first, making multi-select behave as single-select.

Track the latest selection in a ref and derive the updater from it, so each
toggle builds on the current selection. Fixes adding multiple parts/services/
expense items in invoices, estimates, and job cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ERP-System 2026-06-08 16:28:58 +04:00
parent 29d90b5e71
commit 75e44f5b26

View File

@ -39,8 +39,13 @@ export function DataTable<TData>({
// Persisted map of id → original row data across all pages // Persisted map of id → original row data across all pages
const persistedMap = useRef<Map<string, TData>>(new Map()) const persistedMap = useRef<Map<string, TData>>(new Map())
// Current-page selection state that TanStack Table controls // Current-page selection state that TanStack Table controls.
// A ref mirrors the latest value so onRowSelectionChange never derives the
// next selection from a stale closure (which would drop previously-checked
// rows and make multi-select behave like single-select).
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}) const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
const rowSelectionRef = useRef<RowSelectionState>({})
rowSelectionRef.current = rowSelection
// When the page/data changes, restore selection state for the new page from the map // When the page/data changes, restore selection state for the new page from the map
useEffect(() => { useEffect(() => {
@ -50,6 +55,7 @@ export function DataTable<TData>({
const id = String((row as Record<string, unknown>)[rowKeyStr]) const id = String((row as Record<string, unknown>)[rowKeyStr])
if (persistedMap.current.has(id)) restored[id] = true if (persistedMap.current.has(id)) restored[id] = true
}) })
rowSelectionRef.current = restored
setRowSelection(restored) setRowSelection(restored)
}, [data]) // eslint-disable-line react-hooks/exhaustive-deps }, [data]) // eslint-disable-line react-hooks/exhaustive-deps
@ -128,7 +134,10 @@ export function DataTable<TData>({
}) })
}, },
onRowSelectionChange: (updater) => { onRowSelectionChange: (updater) => {
const next = typeof updater === "function" ? updater(rowSelection) : updater // Derive from the ref (always current), not the render-time closure,
// so toggling a second row preserves earlier selections.
const next = typeof updater === "function" ? updater(rowSelectionRef.current) : updater
rowSelectionRef.current = next
setRowSelection(next) setRowSelection(next)
if (selection) { if (selection) {
// Sync current page into the persisted map // Sync current page into the persisted map