import type { ColumnDef, PaginationState, SortingState, VisibilityState, } from "@tanstack/react-table"; import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; import { useEffect, useMemo, useState } from "react"; import type { EntityColumn } from "../components/entity-list"; import { ENTITY_PAGE_SIZES, entityListRange } from "../lib/entity-list"; const ENTITY_PAGE_SIZE_STORAGE_KEY = "entity-page-size"; function storedPageSize(): number { if (typeof window === "undefined") { return ENTITY_PAGE_SIZES[0]; } try { const value = Number(window.localStorage.getItem(ENTITY_PAGE_SIZE_STORAGE_KEY)); return (ENTITY_PAGE_SIZES as readonly number[]).includes(value) ? value : ENTITY_PAGE_SIZES[0]; } catch { return ENTITY_PAGE_SIZES[0]; } } export function useEntityTable({ items, columns, search, onSearchChange, filterKey, }: { items: readonly T[]; columns: readonly EntityColumn[]; search: string | undefined; onSearchChange: ((value: string) => void) | undefined; filterKey: string; }) { const [localSearch, setLocalSearch] = useState(""); const currentSearch = search ?? localSearch; const [sorting, setSorting] = useState([]); const [visibility, setVisibility] = useState({}); const [pagination, setPagination] = useState(() => ({ pageIndex: 0, pageSize: storedPageSize(), })); useEffect(() => { try { window.localStorage.setItem(ENTITY_PAGE_SIZE_STORAGE_KEY, String(pagination.pageSize)); } catch { // La paginación sigue funcionando aunque el almacenamiento no esté disponible. } }, [pagination.pageSize]); const resetKey = `${currentSearch}\0${filterKey}`; const [previousKey, setPreviousKey] = useState(resetKey); if (previousKey !== resetKey) { setPreviousKey(resetKey); setPagination((previous) => ({ ...previous, pageIndex: 0 })); } const definitions = useMemo[]>( () => columns.map((column) => ({ id: column.id, header: column.label, accessorFn: column.value ?? (() => ""), enableSorting: column.id !== "actions" && !!column.value, enableGlobalFilter: column.id !== "actions" && !!column.value, enableHiding: column.id !== "actions" && column.hideable !== false, })), [columns] ); const table = useReactTable({ data: [...items], columns: definitions, getRowId: (item) => item.id, state: { sorting, columnVisibility: visibility, columnPinning: { right: columns.filter((column) => column.id === "actions").map((column) => column.id), }, pagination, globalFilter: onSearchChange ? "" : currentSearch.trim(), }, onSortingChange: setSorting, onColumnVisibilityChange: setVisibility, onPaginationChange: setPagination, autoResetPageIndex: false, globalFilterFn: "includesString", getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), }); const total = table.getFilteredRowModel().rows.length; const range = entityListRange(total, pagination.pageIndex, pagination.pageSize); // Una actualización puede reducir el conjunto sin cambiar los filtros. if (pagination.pageIndex !== range.page) { setPagination({ ...pagination, pageIndex: range.page }); } return { table, total, range, pagination, setPagination, sorting, setSorting, currentSearch, setLocalSearch, }; }