import { type FetchNextPageOptions } from '@tanstack/react-query' import type { ColumnDef, Row, Table as TTable, VisibilityState } from '@tanstack/react-table' import { flexRender } from '@tanstack/react-table' import { LoaderCircle } from 'lucide-react' import { useQueryState } from 'nuqs' import { Fragment, ReactNode, UIEvent, useCallback, useRef } from 'react' import { Button, cn } from 'ui' import { formatCompactNumber } from './DataTable.utils' import { useDataTable } from './providers/DataTableProvider' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './Table' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' // TODO: add a possible chartGroupBy export interface DataTableInfiniteProps { columns: ColumnDef[] defaultColumnVisibility?: VisibilityState totalRows?: number filterRows?: number totalRowsFetched?: number isFetching?: boolean isLoading?: boolean hasNextPage?: boolean fetchNextPage: (options?: FetchNextPageOptions | undefined) => Promise renderLiveRow?: (props?: { row: Row }) => ReactNode setColumnOrder: (columnOrder: string[]) => void setColumnVisibility: (columnVisibility: VisibilityState) => void // [Joshen] See if we can type this properly searchParamsParser: any } // [Joshen] JFYI this component is NOT virtualized and hence will struggle handling many data points export function DataTableInfinite({ columns, defaultColumnVisibility = {}, fetchNextPage, hasNextPage, totalRows = 0, filterRows = 0, totalRowsFetched = 0, renderLiveRow, setColumnOrder, setColumnVisibility, searchParamsParser, }: DataTableInfiniteProps) { const { table, isLoading, isFetching } = useDataTable() const tableRef = useRef(null) const headerGroups = table.getHeaderGroups() const headers = headerGroups[0].headers const rows = table.getRowModel().rows ?? [] const onScroll = useCallback( (e: UIEvent) => { const onPageBottom = Math.ceil(e.currentTarget.scrollTop + e.currentTarget.clientHeight) >= e.currentTarget.scrollHeight if (onPageBottom && !isFetching && totalRows > totalRowsFetched) { fetchNextPage() } }, [fetchNextPage, isFetching, totalRows, totalRowsFetched] ) useShortcut(SHORTCUT_IDS.DATA_TABLE_RESET_COLUMNS, () => { setColumnOrder([]) setColumnVisibility(defaultColumnVisibility) }) return ( {headers.map((header) => { const sort = header.column.getIsSorted() const canResize = header.column.getCanResize() const onResize = header.getResizeHandler() const headerClassName = (header.column.columnDef.meta as any)?.headerClassName return ( {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} {canResize && (
header.column.resetSize()} onMouseDown={onResize} onTouchStart={onResize} className={cn( 'user-select-none absolute -right-2 top-0 z-10 flex h-full w-4 cursor-col-resize touch-none justify-center', 'before:absolute before:inset-y-0 before:w-px before:translate-x-px before:bg-border' )} /> )} ) })} {rows.length ? ( rows.map((row) => ( // REMINDER: if we want to add arrow navigation https://github.com/TanStack/table/discussions/2752#discussioncomment-192558 {renderLiveRow?.({ row: row as any })} )) ) : ( {renderLiveRow?.()}
{isLoading ? ( <>

Retrieving logs...

) : (

No results found

)}
)} {/* Only show load more section if we have rows OR if we're not in initial loading state */} {(rows.length > 0 || (!isLoading && !rows.length)) && ( {hasNextPage || isFetching ? (

Showing{' '} {formatCompactNumber(totalRowsFetched)} {' '} of{' '} {formatCompactNumber(totalRows)}{' '} rows

) : (

No more data to load ( {formatCompactNumber(filterRows)}{' '} of {formatCompactNumber(totalRows)}{' '} rows)

)}
)}
) } /** * REMINDER: this is the heaviest component in the table if lots of rows * Some other components are rendered more often necessary, but are fixed size (not like rows that can grow in height) * e.g. DataTableFilterControls, DataTableFilterCommand, DataTableToolbar, DataTableHeader */ function DataTableRow({ row, table, selected, searchParamsParser, }: { row: Row table: TTable selected?: boolean searchParamsParser: any }) { useQueryState('live', searchParamsParser.live) const rowClassName = (table.options.meta as any)?.getRowClassName?.(row) const cells = row.getVisibleCells() return ( row.toggleSelected()} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault() row.toggleSelected() } }} className={cn(rowClassName)} > {cells.map((cell) => { const cellClassName = (cell.column.columnDef.meta as any)?.cellClassName return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ) })} ) }