"use client"; import type { Row, VisibilityState } from "@tanstack/react-table"; import { noop } from "lodash"; import { useEffect, useRef } from "react"; import { useColumnFilters } from "~/data-table/hooks/useColumnFilters"; import { useDataTable } from "~/data-table/hooks/useDataTable"; import { useFetchMoreOnBottomReached } from "~/data-table/hooks/useFetchMoreOnBottomReached"; import type { DataTablePropsFromWrapper } from "./DataTable"; import { DataTable } from "./DataTable"; import { DataTablePagination } from "./DataTablePagination"; type BaseDataTableWrapperProps = Omit< DataTablePropsFromWrapper, "paginationMode" | "tableContainerRef" > & { totalRowCount?: number; ToolbarLeft?: React.ReactNode; ToolbarRight?: React.ReactNode; EmptyView?: React.ReactNode; LoaderView?: React.ReactNode; ErrorView?: React.ReactNode; hasError?: boolean; tableContainerRef?: React.RefObject; onRowMouseclick?: (row: Row) => void; rowTestId?: string | ((row: Row) => string | undefined); rowDataAttributes?: (row: Row) => Record | undefined; }; type InfinitePaginationProps = BaseDataTableWrapperProps & { paginationMode: "infinite"; hasNextPage: boolean; fetchNextPage: () => void; isFetching: boolean; }; type StandardPaginationProps = BaseDataTableWrapperProps & { paginationMode: "standard"; hasNextPage?: never; fetchNextPage?: never; isFetching?: never; }; export type DataTableWrapperProps = InfinitePaginationProps | StandardPaginationProps; export function DataTableWrapper({ testId, bodyTestId, table, isPending, hasNextPage, fetchNextPage, isFetching, totalRowCount, variant, ToolbarLeft, ToolbarRight, EmptyView, LoaderView, ErrorView, hasError, className, containerClassName, headerClassName, rowClassName, rowTestId, rowDataAttributes, children, tableContainerRef: externalRef, paginationMode, onRowMouseclick, hideSeparatorsOnSort, hideSeparatorsOnFilter, separatorClassName, }: DataTableWrapperProps) { const internalRef = useRef(null); const tableContainerRef = externalRef || internalRef; const fetchMoreOnBottomReached = useFetchMoreOnBottomReached({ tableContainerRef, hasNextPage: paginationMode === "infinite" ? hasNextPage : false, fetchNextPage: paginationMode === "infinite" ? fetchNextPage : noop, isFetching: paginationMode === "infinite" ? isFetching : false, enabled: paginationMode === "infinite", }); const { sorting, setSorting, columnVisibility, setColumnVisibility } = useDataTable(); const columnFilters = useColumnFilters(); useEffect(() => { const mergedColumnVisibility = { ...table.initialState?.columnVisibility, ...columnVisibility, } satisfies VisibilityState; table.setState((prev) => ({ ...prev, sorting, columnFilters, columnVisibility: mergedColumnVisibility, })); table.setOptions((prev) => ({ ...prev, onSortingChange: setSorting, onColumnVisibilityChange: setColumnVisibility, })); }, [table, sorting, columnFilters, columnVisibility, setSorting, setColumnVisibility]); // Scroll to top when table data changes useScrollToTopOnDataChange({ tableData: table.options.data, paginationMode, }); let view: "loader" | "empty" | "error" | "table" = "table"; if (hasError && ErrorView) { view = "error"; } else if (isPending && LoaderView) { view = "loader"; } else if (table.getRowCount() === 0 && EmptyView) { view = "empty"; } return ( <> {(ToolbarLeft || ToolbarRight || children) && (
{ToolbarLeft}
{ToolbarRight}
{children}
)} {view === "error" && ErrorView} {view === "loader" && LoaderView} {view === "empty" && EmptyView} {view === "table" && ( , "target">) => fetchMoreOnBottomReached(e.target as HTMLDivElement) : undefined }>
table={table} totalRowCount={totalRowCount} paginationMode={paginationMode} />
)} ); } /** * Scrolls to top when table data changes (pagination, filters, etc.) * Only active for standard pagination mode where we show discrete pages */ function useScrollToTopOnDataChange({ tableData, paginationMode, }: { tableData: TData[]; paginationMode: "standard" | "infinite"; }) { const previousDataRef = useRef(tableData); const isInitialMount = useRef(true); useEffect(() => { // Skip on initial mount if (isInitialMount.current) { isInitialMount.current = false; return; } const hasDataChanged = previousDataRef.current !== tableData; previousDataRef.current = tableData; if (paginationMode === "standard" && hasDataChanged) { window.scrollTo(0, 0); } }, [tableData, paginationMode]); }