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 { readColumnVisibility, writeColumnVisibility, } from "../lib/entity-column-visibility-storage"; 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, locale, search, onSearchChange, filterKey, columnVisibilityStorageKey, }: { items: readonly T[]; columns: readonly EntityColumn[]; locale: string; search: string | undefined; onSearchChange: ((value: string) => void) | undefined; filterKey: string; columnVisibilityStorageKey?: string | undefined; }) { const [localSearch, setLocalSearch] = useState(""); const currentSearch = search ?? localSearch; const sortableColumns = columns.filter((column) => column.id !== "actions" && column.value); const defaultColumn = sortableColumns.find((column) => column.defaultSort) ?? sortableColumns.find((column) => column.id === "name") ?? sortableColumns[0]; const initialSorting: SortingState = defaultColumn ? [{ id: defaultColumn.id, desc: defaultColumn.defaultSort === "desc" }] : []; const [sorting, setSorting] = useState(initialSorting); const [visibility, setVisibility] = useState(() => readColumnVisibility(columnVisibilityStorageKey) ); const [previousStorageKey, setPreviousStorageKey] = useState(columnVisibilityStorageKey); if (previousStorageKey !== columnVisibilityStorageKey) { setPreviousStorageKey(columnVisibilityStorageKey); setVisibility(readColumnVisibility(columnVisibilityStorageKey)); } useEffect(() => { if (previousStorageKey === columnVisibilityStorageKey) { writeColumnVisibility(columnVisibilityStorageKey, visibility); } }, [columnVisibilityStorageKey, previousStorageKey, visibility]); 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 nameCollator = useMemo( () => new Intl.Collator(locale, { sensitivity: "base", numeric: true }), [locale] ); const definitions = useMemo[]>( () => columns.map((column) => ({ id: column.id, header: column.label, accessorFn: column.value ?? (() => ""), sortingFn: column.id === "name" ? (first, second, columnId) => nameCollator.compare( String(first.getValue(columnId) ?? ""), String(second.getValue(columnId) ?? "") ) : "auto", enableSorting: column.id !== "actions" && !!column.value, enableGlobalFilter: column.id !== "actions" && !!column.value, enableHiding: column.id !== "actions" && column.hideable !== false, })), [columns, nameCollator] ); const table = useReactTable({ data: [...items], columns: definitions, initialState: { sorting: initialSorting }, enableSortingRemoval: false, getRowId: (item) => item.id, state: { sorting, columnVisibility: Object.fromEntries( columns.map((column) => [ column.id, column.hideable === false || column.id === "actions" ? true : (visibility[column.id] ?? column.defaultVisible ?? true), ]) ), 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, }; }