import { OnChangeFn, PaginationState, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getSortedRowModel, useReactTable, type ColumnDef, type ColumnFiltersState, type SortingState, type VisibilityState, } from "@tanstack/react-table"; import { getDataTableSelectionColumn } from "@/components"; import React, { useCallback, useMemo, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { DataTableFilterField } from "../../types"; import { usePaginationParams } from "../usePagination"; //import { useDebounce } from "@/hooks/use-debounce"; interface UseDataTableProps { /** * The data for the table. * @default [] * @type TData[] */ data: TData[]; /** * The columns of the table. * @default [] * @type ColumnDef[] */ columns: ColumnDef[]; /** * The number of pages in the table. * @type number */ pageCount: number; /** * Enable sorting columns. * @default false * @type boolean */ enableSorting?: boolean; /** * Enable hiding columns. * @default false * @type boolean */ enableHiding?: boolean; /** * Enable selection rows. * @default false * @type boolean */ enableRowSelection?: boolean; /** * Defines filter fields for the table. Supports both dynamic faceted filters and search filters. * - Faceted filters are rendered when `options` are provided for a filter field. * - Otherwise, search filters are rendered. * * The indie filter field `value` represents the corresponding column name in the database table. * @default [] * @type { label: string, value: keyof TData, placeholder?: string, options?: { label: string, value: string, icon?: React.ComponentType<{ className?: string }> }[] }[] * @example * ```ts * // Render a search filter * const filterFields = [ * { label: "Title", value: "title", placeholder: "Search titles" } * ]; * // Render a faceted filter * const filterFields = [ * { * label: "Status", * value: "status", * options: [ * { label: "Todo", value: "todo" }, * { label: "In Progress", value: "in-progress" }, * { label: "Done", value: "done" }, * { label: "Canceled", value: "canceled" } * ] * } * ]; * ``` */ filterFields?: DataTableFilterField[]; /** * Enable notion like column filters. * Advanced filters and column filters cannot be used at the same time. * @default false * @type boolean */ enableAdvancedFilter?: boolean; } export function useDataTable({ data, columns, pageCount, enableSorting = false, enableHiding = false, enableRowSelection = false, filterFields = [], enableAdvancedFilter = false, }: UseDataTableProps) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [searchParams, setSearchParams] = useSearchParams(); const [pagination, setPagination] = usePaginationParams(); const [sorting, setSorting] = useState([]); // Memoize computation of searchableColumns and filterableColumns const { searchableColumns, filterableColumns } = useMemo(() => { return { searchableColumns: filterFields.filter((field) => !field.options), filterableColumns: filterFields.filter((field) => field.options), }; }, [filterFields]); // Create query string /*const createQueryString = useCallback( (params: Record) => { const newSearchParams = new URLSearchParams(searchParams?.toString()); for (const [key, value] of Object.entries(params)) { if (value === null) { newSearchParams.delete(key); } else { newSearchParams.set(key, String(value)); } } return newSearchParams.toString(); }, [searchParams] );*/ // Initial column filters const initialColumnFilters: ColumnFiltersState = useMemo(() => { return Array.from(searchParams.entries()).reduce( (filters, [key, value]) => { const filterableColumn = filterableColumns.find( (column) => column.value === key ); const searchableColumn = searchableColumns.find( (column) => column.value === key ); if (filterableColumn) { filters.push({ id: key, value: value.split("."), }); } else if (searchableColumn) { filters.push({ id: key, value: [value], }); } return filters; }, [] ); }, [filterableColumns, searchableColumns, searchParams]); // Table states const [rowSelection, setRowSelection] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({}); const [columnFilters, setColumnFilters] = React.useState(initialColumnFilters); const paginationUpdater: OnChangeFn = (updater) => { if (typeof updater === "function") { const newPagination = updater(pagination); console.log(newPagination); setPagination(newPagination); } }; const sortingUpdater: OnChangeFn = (updater) => { if (typeof updater === "function") { setSorting(updater(sorting)); } }; // Handle server-side filtering /*const debouncedSearchableColumnFilters = JSON.parse( useDebounce( JSON.stringify( columnFilters.filter((filter) => { return searchableColumns.find((column) => column.value === filter.id); }) ), 500 ) ) as ColumnFiltersState;*/ /*const filterableColumnFilters = columnFilters.filter((filter) => { return filterableColumns.find((column) => column.value === filter.id); }); const [mounted, setMounted] = useState(false);*/ /*useEffect(() => { // Opt out when advanced filter is enabled, because it contains additional params if (enableAdvancedFilter) return; // Prevent resetting the page on initial render if (!mounted) { setMounted(true); return; } // Initialize new params const newParamsObject = { page: 1, }; // Handle debounced searchable column filters for (const column of debouncedSearchableColumnFilters) { if (typeof column.value === "string") { Object.assign(newParamsObject, { [column.id]: typeof column.value === "string" ? column.value : null, }); } } // Handle filterable column filters for (const column of filterableColumnFilters) { if (typeof column.value === "object" && Array.isArray(column.value)) { Object.assign(newParamsObject, { [column.id]: column.value.join(".") }); } } // Remove deleted values for (const key of searchParams.keys()) { if ( (searchableColumns.find((column) => column.value === key) && !debouncedSearchableColumnFilters.find( (column) => column.id === key )) || (filterableColumns.find((column) => column.value === key) && !filterableColumnFilters.find((column) => column.id === key)) ) { Object.assign(newParamsObject, { [key]: null }); } } // After cumulating all the changes, push new params navigate(`${location.pathname}?${createQueryString(newParamsObject)}`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ // eslint-disable-next-line react-hooks/exhaustive-deps //JSON.stringify(debouncedSearchableColumnFilters), // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(filterableColumnFilters), ]);*/ const getTableColumns = useCallback(() => { const _columns = columns; if (enableRowSelection) { _columns.unshift(getDataTableSelectionColumn()); } return _columns; }, [columns, enableRowSelection]); const table = useReactTable({ columns: getTableColumns(), data, getCoreRowModel: getCoreRowModel(), //getPaginationRowModel: getPaginationRowModel(), state: { pagination, sorting, columnVisibility, rowSelection, columnFilters, }, enableRowSelection, onRowSelectionChange: setRowSelection, manualSorting: true, enableSorting, getSortedRowModel: getSortedRowModel(), onSortingChange: sortingUpdater, enableHiding, onColumnVisibilityChange: setColumnVisibility, manualPagination: true, pageCount: pageCount ?? -1, onPaginationChange: paginationUpdater, getFilteredRowModel: getFilteredRowModel(), manualFiltering: true, onColumnFiltersChange: setColumnFilters, getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), }); return { table }; }