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, memo, ReactNode, UIEvent, useCallback, useRef } from 'react' import { useHotKey } from 'hooks/ui/useHotKey' import { Button, cn } from 'ui' import { formatCompactNumber } from './DataTable.utils' import { useDataTable } from './providers/DataTableProvider' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './Table' // 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 } export function DataTableInfinite({ columns, defaultColumnVisibility = {}, isFetching, isLoading, fetchNextPage, hasNextPage, totalRows = 0, filterRows = 0, totalRowsFetched = 0, renderLiveRow, setColumnOrder, setColumnVisibility, searchParamsParser, }: DataTableInfiniteProps) { const { table } = useDataTable() const tableRef = useRef(null) const headerGroups = table.getHeaderGroups() const onScroll = useCallback( (e: UIEvent) => { const onPageBottom = Math.ceil(e.currentTarget.scrollTop + e.currentTarget.clientHeight) >= e.currentTarget.scrollHeight if (onPageBottom && !isFetching && totalRowsFetched < filterRows) { fetchNextPage() } }, [fetchNextPage, isFetching, filterRows, totalRowsFetched] ) useHotKey(() => { setColumnOrder([]) setColumnVisibility(defaultColumnVisibility) }, 'u') return ( <> {headerGroups.map((headerGroup) => ( {headerGroup.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' )} /> )} ) })} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().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?.()} No results. )} {hasNextPage || isFetching || isLoading ? (

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 }) { // REMINDER: rerender the row when live mode is toggled - used to opacity the row // via the `getRowClassName` prop - but for some reasons it wil render the row on data fetch useQueryState('live', searchParamsParser.live) const rowClassName = (table.options.meta as any)?.getRowClassName?.(row) return ( row.toggleSelected()} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault() row.toggleSelected() } }} className={cn(rowClassName)} > {row.getVisibleCells().map((cell) => { const cellClassName = (cell.column.columnDef.meta as any)?.cellClassName return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ) })} ) } const MemoizedRow = memo( DataTableRow, (prev, next) => prev.row.id === next.row.id && prev.selected === next.selected ) as typeof DataTableRow