@@ -83,7 +83,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
8383 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
8484 const [ sortColumn , setSortColumn ] = useState < string | null > ( null ) ;
8585 const [ sortDirection , setSortDirection ] = useState < SortDirection > ( null ) ;
86- const [ selectedRows , setSelectedRows ] = useState < Set < number > > ( new Set ( ) ) ;
86+ const [ selectedRowIds , setSelectedRowIds ] = useState < Set < any > > ( new Set ( ) ) ;
8787 const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
8888 const [ pageSize , setPageSize ] = useState ( initialPageSize ) ;
8989
@@ -120,6 +120,12 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
120120 ? sortedData . slice ( ( currentPage - 1 ) * pageSize , currentPage * pageSize )
121121 : sortedData ;
122122
123+ // Generate unique row ID
124+ const getRowId = ( row : any , index : number ) => {
125+ // Try to use 'id' field, fall back to index
126+ return row . id !== undefined ? row . id : `row-${ index } ` ;
127+ } ;
128+
123129 // Handlers
124130 const handleSort = ( columnKey : string ) => {
125131 if ( ! sortable ) return ;
@@ -138,21 +144,43 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
138144 } ;
139145
140146 const handleSelectAll = ( checked : boolean ) => {
147+ const newSelected = new Set < any > ( ) ;
141148 if ( checked ) {
142- setSelectedRows ( new Set ( paginatedData . map ( ( _ , idx ) => idx ) ) ) ;
143- } else {
144- setSelectedRows ( new Set ( ) ) ;
149+ paginatedData . forEach ( ( row , idx ) => {
150+ const globalIndex = ( currentPage - 1 ) * pageSize + idx ;
151+ const rowId = getRowId ( row , globalIndex ) ;
152+ newSelected . add ( rowId ) ;
153+ } ) ;
154+ }
155+ setSelectedRowIds ( newSelected ) ;
156+
157+ // Call callback if provided
158+ if ( schema . onSelectionChange ) {
159+ const selectedData = sortedData . filter ( ( row , idx ) => {
160+ const rowId = getRowId ( row , idx ) ;
161+ return newSelected . has ( rowId ) ;
162+ } ) ;
163+ schema . onSelectionChange ( selectedData ) ;
145164 }
146165 } ;
147166
148- const handleSelectRow = ( index : number , checked : boolean ) => {
149- const newSelected = new Set ( selectedRows ) ;
167+ const handleSelectRow = ( rowId : any , checked : boolean ) => {
168+ const newSelected = new Set ( selectedRowIds ) ;
150169 if ( checked ) {
151- newSelected . add ( index ) ;
170+ newSelected . add ( rowId ) ;
152171 } else {
153- newSelected . delete ( index ) ;
172+ newSelected . delete ( rowId ) ;
173+ }
174+ setSelectedRowIds ( newSelected ) ;
175+
176+ // Call callback if provided
177+ if ( schema . onSelectionChange ) {
178+ const selectedData = sortedData . filter ( ( row , idx ) => {
179+ const id = getRowId ( row , idx ) ;
180+ return newSelected . has ( id ) ;
181+ } ) ;
182+ schema . onSelectionChange ( selectedData ) ;
154183 }
155- setSelectedRows ( newSelected ) ;
156184 } ;
157185
158186 const handleExport = ( ) => {
@@ -182,8 +210,18 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
182210 return < ChevronDown className = "h-4 w-4 ml-1" /> ;
183211 } ;
184212
185- const allSelected = paginatedData . length > 0 && selectedRows . size === paginatedData . length ;
186- const someSelected = selectedRows . size > 0 && selectedRows . size < paginatedData . length ;
213+ // Check if all rows on current page are selected
214+ const allPageRowsSelected = paginatedData . length > 0 && paginatedData . every ( ( row , idx ) => {
215+ const globalIndex = ( currentPage - 1 ) * pageSize + idx ;
216+ const rowId = getRowId ( row , globalIndex ) ;
217+ return selectedRowIds . has ( rowId ) ;
218+ } ) ;
219+
220+ const somePageRowsSelected = paginatedData . some ( ( row , idx ) => {
221+ const globalIndex = ( currentPage - 1 ) * pageSize + idx ;
222+ const rowId = getRowId ( row , globalIndex ) ;
223+ return selectedRowIds . has ( rowId ) ;
224+ } ) && ! allPageRowsSelected ;
187225
188226 return (
189227 < div className = { `space-y-4 ${ className || '' } ` } >
@@ -219,9 +257,9 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
219257 </ Button >
220258 ) }
221259
222- { selectable && selectedRows . size > 0 && (
260+ { selectable && selectedRowIds . size > 0 && (
223261 < div className = "text-sm text-muted-foreground" >
224- { selectedRows . size } selected
262+ { selectedRowIds . size } selected
225263 </ div >
226264 ) }
227265 </ div >
@@ -236,7 +274,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
236274 { selectable && (
237275 < TableHead className = "w-12" >
238276 < Checkbox
239- checked = { allSelected ? true : someSelected ? 'indeterminate' : false }
277+ checked = { allPageRowsSelected ? true : somePageRowsSelected ? 'indeterminate' : false }
240278 onCheckedChange = { handleSelectAll }
241279 />
242280 </ TableHead >
@@ -270,43 +308,49 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
270308 </ TableCell >
271309 </ TableRow >
272310 ) : (
273- paginatedData . map ( ( row , rowIndex ) => (
274- < TableRow key = { rowIndex } data-state = { selectedRows . has ( rowIndex ) ? 'selected' : undefined } >
275- { selectable && (
276- < TableCell >
277- < Checkbox
278- checked = { selectedRows . has ( rowIndex ) }
279- onCheckedChange = { ( checked ) => handleSelectRow ( rowIndex , checked as boolean ) }
280- />
281- </ TableCell >
282- ) }
283- { columns . map ( ( col , colIndex ) => (
284- < TableCell key = { colIndex } className = { col . cellClassName } >
285- { row [ col . accessorKey ] }
286- </ TableCell >
287- ) ) }
288- { rowActions && (
289- < TableCell className = "text-right" >
290- < div className = "flex items-center justify-end gap-1" >
291- < Button
292- variant = "ghost"
293- size = "icon-sm"
294- onClick = { ( ) => schema . onRowEdit ?.( row ) }
295- >
296- < Edit className = "h-4 w-4" />
297- </ Button >
298- < Button
299- variant = "ghost"
300- size = "icon-sm"
301- onClick = { ( ) => schema . onRowDelete ?.( row ) }
302- >
303- < Trash2 className = "h-4 w-4 text-destructive" />
304- </ Button >
305- </ div >
306- </ TableCell >
307- ) }
308- </ TableRow >
309- ) )
311+ paginatedData . map ( ( row , rowIndex ) => {
312+ const globalIndex = ( currentPage - 1 ) * pageSize + rowIndex ;
313+ const rowId = getRowId ( row , globalIndex ) ;
314+ const isSelected = selectedRowIds . has ( rowId ) ;
315+
316+ return (
317+ < TableRow key = { rowId } data-state = { isSelected ? 'selected' : undefined } >
318+ { selectable && (
319+ < TableCell >
320+ < Checkbox
321+ checked = { isSelected }
322+ onCheckedChange = { ( checked ) => handleSelectRow ( rowId , checked as boolean ) }
323+ />
324+ </ TableCell >
325+ ) }
326+ { columns . map ( ( col , colIndex ) => (
327+ < TableCell key = { colIndex } className = { col . cellClassName } >
328+ { row [ col . accessorKey ] }
329+ </ TableCell >
330+ ) ) }
331+ { rowActions && (
332+ < TableCell className = "text-right" >
333+ < div className = "flex items-center justify-end gap-1" >
334+ < Button
335+ variant = "ghost"
336+ size = "icon-sm"
337+ onClick = { ( ) => schema . onRowEdit ?.( row ) }
338+ >
339+ < Edit className = "h-4 w-4" />
340+ </ Button >
341+ < Button
342+ variant = "ghost"
343+ size = "icon-sm"
344+ onClick = { ( ) => schema . onRowDelete ?.( row ) }
345+ >
346+ < Trash2 className = "h-4 w-4 text-destructive" />
347+ </ Button >
348+ </ div >
349+ </ TableCell >
350+ ) }
351+ </ TableRow >
352+ ) ;
353+ } )
310354 ) }
311355 </ TableBody >
312356 </ Table >
0 commit comments