"use client"; import type { Row } from "@tanstack/react-table"; import { flexRender } from "@tanstack/react-table"; import type { Table as ReactTableType, Header } from "@tanstack/react-table"; import { useVirtualizer, type Virtualizer } from "@tanstack/react-virtual"; // eslint-disable-next-line no-restricted-imports import kebabCase from "lodash/kebabCase"; import { usePathname } from "next/navigation"; import { useEffect, useState, memo } from "react"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow, Popover, PopoverTrigger, PopoverContent, Command, CommandList, CommandItem, Icon, } from "@calcom/ui"; import { useColumnSizingVars } from "../hooks"; import { usePersistentColumnResizing } from "../lib/resizing"; export type DataTableProps = { table: ReactTableType; tableContainerRef: React.RefObject; isPending?: boolean; onRowMouseclick?: (row: Row) => void; onScroll?: (e: Pick, "target">) => void; tableOverlay?: React.ReactNode; variant?: "default" | "compact"; testId?: string; bodyTestId?: string; hideHeader?: boolean; children?: React.ReactNode; identifier?: string; enableColumnResizing?: boolean; className?: string; containerClassName?: string; }; export function DataTable({ table, tableContainerRef, isPending, variant, onRowMouseclick, onScroll, children, hideHeader, identifier: _identifier, enableColumnResizing, testId, bodyTestId, className, containerClassName, ...rest }: DataTableProps & React.ComponentPropsWithoutRef<"div">) { const pathname = usePathname() as string | null; const identifier = _identifier ?? pathname ?? undefined; const { rows } = table.getRowModel(); // https://stackblitz.com/github/tanstack/table/tree/main/examples/react/virtualized-infinite-scrolling const rowVirtualizer = useVirtualizer({ count: rows.length, estimateSize: () => 100, getScrollElement: () => tableContainerRef.current, // measure dynamic row height, except in firefox because it measures table border height incorrectly measureElement: typeof window !== "undefined" && navigator.userAgent.indexOf("Firefox") === -1 ? (element) => element?.getBoundingClientRect().height : undefined, overscan: 10, }); useEffect(() => { if (rowVirtualizer.getVirtualItems().length >= rows.length && tableContainerRef.current) { const target = tableContainerRef.current; // Right after the last row is rendered, tableContainer's scrollHeight is // temporarily larger than the actual height of the table, so we need to // wait for a short time before calling onScroll to ensure the scrollHeight // is correct. setTimeout(() => { onScroll?.({ target }); }, 100); } }, [rowVirtualizer.getVirtualItems().length, rows.length, tableContainerRef.current]); const columnSizingVars = useColumnSizingVars({ table }); usePersistentColumnResizing({ enabled: Boolean(enableColumnResizing && identifier), table, tableContainerRef, identifier, }); return (
{/* Invalidate left & right properties for <= sm screen size, because we pin columns only for >= sm screen sizes. */}
{!hideHeader && ( {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const { column } = header; return ( {Boolean(enableColumnResizing) && header.column.getCanResize() && (
)} ); })} ))} )} {/* When resizing any column we will render this special memoized version of our table body */} {table.getState().columnSizingInfo.isResizingColumn ? ( ) : ( )}
{children}
); } const MemoizedTableBody = memo( DataTableBody, (prev, next) => prev.table.options.data === next.table.options.data && prev.rowVirtualizer === next.rowVirtualizer && prev.rows === next.rows && prev.testId === next.testId && prev.variant === next.variant && prev.isPending === next.isPending && prev.onRowMouseclick === next.onRowMouseclick ) as typeof DataTableBody; type DataTableBodyProps = { table: ReactTableType; rowVirtualizer: Virtualizer; rows: Row[]; testId?: string; variant?: "default" | "compact"; isPending?: boolean; onRowMouseclick?: (row: Row) => void; }; function DataTableBody({ table, rowVirtualizer, rows, testId, variant, isPending, onRowMouseclick, }: DataTableBodyProps) { const virtualRows = rowVirtualizer.getVirtualItems(); return ( {virtualRows && !isPending ? ( virtualRows.map((virtualRow) => { const row = rows[virtualRow.index] as Row; return ( rowVirtualizer.measureElement(node)} //measure dynamic row height key={row.id} data-index={virtualRow.index} //needed for dynamic row height measurement data-state={row.getIsSelected() && "selected"} onClick={() => onRowMouseclick && onRowMouseclick(row)} style={{ display: "flex", position: "absolute", transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll width: "100%", }} className={classNames(onRowMouseclick && "hover:cursor-pointer", "group")}> {row.getVisibleCells().map((cell) => { const column = cell.column; const meta = column?.columnDef.meta; return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ); })} ); }) ) : ( No results. )} ); } const TableHeadLabel = ({ header }: { header: Header }) => { const [open, setOpen] = useState(false); const { t } = useLocale(); const canHide = header.column.getCanHide(); const canSort = header.column.getCanSort(); if (!canSort && !canHide) { return (
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
); } return ( {canSort && ( <> { if (header.column.getIsSorted() === "asc") { header.column.clearSorting(); } else { header.column.toggleSorting(false, true); } }}> {t("asc")}
{header.column.getIsSorted() === "asc" && } { if (header.column.getIsSorted() === "desc") { header.column.clearSorting(); } else { header.column.toggleSorting(true, true); } }}> {t("desc")}
{header.column.getIsSorted() === "desc" && } )} {canHide && ( { header.column.toggleVisibility(false); setOpen(false); }}> {t("hide")} )} ); };