/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import React, { type ReactElement, useEffect, useState, useMemo, Fragment, } from "react"; import { type Column, flexRender, type RowData, type Table, Row, } from "@tanstack/react-table"; import { Box, LoadingOverlay, Table as MTable, type TableProps, Pagination, Group, Text, Select, TextInput, type TextInputProps, MultiSelect, Flex, type SelectProps, ActionIcon, type MultiSelectProps, type MantineStyleProp, type ComboboxData, ScrollArea, type AutocompleteProps, Autocomplete, } from "@mantine/core"; import { IconChevronDown, IconChevronRight, IconChevronUp, IconX, } from "@tabler/icons-react"; import "./style.css"; import { isEmpty, uniq } from "lodash"; import { useElementOuterSize } from "./hooks"; declare module "@tanstack/table-core" { interface ColumnMeta { cellStyle?: React.CSSProperties; headerStyle?: React.CSSProperties; filter?: | { type: "expiration"; placeholder?: string; } | { type: "text" | "number"; placeholder?: string; } | { type: "autocomplete"; options?: ComboboxData; placeholder?: string; autocompleteProps?: Partial>; } | { type: "select"; options?: ComboboxData; placeholder?: string; selectProps?: Partial>; } | { type: "multi-select"; options?: ComboboxData; placeholder?: string; selectProps?: Partial>; }; } } const defaultLabels = { rowSingle: "row", rowPlural: "rows", rowsPerPage: "Rows per page", of: "of", }; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Props> = TableProps & { table: T; renderSubComponent?: (props: { row: ReturnType; }) => ReactElement; onRowClick?: (row: ReturnType) => void; paginate?: boolean; sortable?: boolean; loading?: boolean; columnFilters?: boolean; // labels?: Partial; perPageOptions?: number[]; rowStyles?: (row: ReturnType) => MantineStyleProp; stickyTop?: number | null; stickyFoot?: number | null; stickyBorderRadius?: string | null; showSummary?: boolean; header?: React.ReactNode; scrollHeight?: string | number; rowCount?: number; groupIndividualRows?: boolean; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any export default function TanstackTable>({ table, onRowClick, paginate, loading = false, sortable = false, columnFilters = false, stickyTop = null, stickyFoot = null, stickyBorderRadius = null, rowStyles = () => [], showSummary = false, scrollHeight = "100%", header, rowCount, renderSubComponent, groupIndividualRows = true, // labels = {}, perPageOptions = [10, 25, 50], ...rest }: Props) { const footerGroups = table.getFooterGroups(); const rows = paginate ?? sortable ? table.getRowModel() : table.getCoreRowModel(); const totalRowCount = rowCount ?? table.getFilteredRowModel().rows.length; const { ref: scrollViewportRef } = useElementOuterSize(); const { ref: headerRef, height: headerHeight } = useElementOuterSize(); const { ref: tableRef } = useElementOuterSize(); const { ref: footerRef } = useElementOuterSize(); const { ref: paginationRef, height: paginationHeight } = useElementOuterSize(); const textLabels = defaultLabels; // if a filter lowers the page count and you are now viewing a page out of range, reset const watchedPageCount = table.getPageCount(); useEffect(() => { if (table.getState().pagination.pageIndex > watchedPageCount - 1) { table.resetPageIndex(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [watchedPageCount]); return (
{showSummary || paginate ? ( {showSummary ? ( paginate ? ( {table.getState().pagination.pageSize * table.getState().pagination.pageIndex + 1} - {Math.min( totalRowCount, table.getState().pagination.pageSize * (table.getState().pagination.pageIndex + 1) )}{" "} of {totalRowCount}{" "} {totalRowCount === 1 ? textLabels.rowSingle : textLabels.rowPlural} ) : ( {totalRowCount}{" "} {totalRowCount === 1 ? textLabels.rowSingle : textLabels.rowPlural} ) ) : null} {paginate && ( Per page: { setStringValue(value); column.setFilterValue(value); }} {...filterOptions.selectProps} /> ); } if (filterOptions.type === "multi-select") { return ( { if (Array.isArray(values)) { setStringArrayValue(values); column.setFilterValue(values); } else { setStringArrayValue([values]); column.setFilterValue([values]); } }} {...filterOptions.selectProps} /> ); } return ( column.setFilterValue(value)} style={{ flex: 1 }} placeholder="Filter..." /> ); } // A debounced input react component function DebouncedInput({ value: initialValue, onChange, debounce = 500, ...props }: { value: string | number; onChange: (value: string | number) => void; debounce?: number; } & Omit) { const [value, setValue] = React.useState(initialValue); React.useEffect(() => { setValue(initialValue); }, [initialValue]); React.useEffect(() => { const timeout = setTimeout(() => { onChange(value); }, debounce); return () => clearTimeout(timeout); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); const rightSection = isEmpty(value) ? null : ( { setValue(""); onChange(""); }} > ); return ( setValue(e.target.value)} /> ); }