metis-desk/packages/rdx-ui/src/hooks/use-entity-table.ts

132 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-10-07 15:27:23 +00:00
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";
2026-10-07 15:27:23 +00:00
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));
2026-10-10 10:01:58 +00:00
return (ENTITY_PAGE_SIZES as readonly number[]).includes(value) ? value : ENTITY_PAGE_SIZES[0];
} catch {
return ENTITY_PAGE_SIZES[0];
}
}
2026-10-10 10:01:58 +00:00
2026-10-07 15:27:23 +00:00
export function useEntityTable<T extends { id: string }>({
items,
columns,
search,
onSearchChange,
filterKey,
}: {
items: readonly T[];
columns: readonly EntityColumn<T>[];
search: string | undefined;
onSearchChange: ((value: string) => void) | undefined;
filterKey: string;
}) {
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[0];
const initialSorting: SortingState = defaultColumn
? [{ id: defaultColumn.id, desc: defaultColumn.defaultSort === "desc" }]
: [];
const [sorting, setSorting] = useState<SortingState>(initialSorting);
2026-10-07 15:27:23 +00:00
const [visibility, setVisibility] = useState<VisibilityState>({});
const [pagination, setPagination] = useState<PaginationState>(() => ({
pageIndex: 0,
pageSize: storedPageSize(),
}));
2026-10-10 10:01:58 +00:00
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]);
2026-10-07 15:27:23 +00:00
const resetKey = `${currentSearch}\0${filterKey}`;
const [previousKey, setPreviousKey] = useState(resetKey);
2026-10-10 10:01:58 +00:00
2026-10-07 15:27:23 +00:00
if (previousKey !== resetKey) {
setPreviousKey(resetKey);
setPagination((previous) => ({ ...previous, pageIndex: 0 }));
}
const definitions = useMemo<ColumnDef<T>[]>(
() =>
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,
2026-10-07 15:27:23 +00:00
})),
[columns]
);
const table = useReactTable({
data: [...items],
columns: definitions,
initialState: { sorting: initialSorting },
enableSortingRemoval: false,
2026-10-07 15:27:23 +00:00
getRowId: (item) => item.id,
state: {
sorting,
columnVisibility: visibility,
columnPinning: {
right: columns.filter((column) => column.id === "actions").map((column) => column.id),
},
2026-10-07 15:27:23 +00:00
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);
2026-10-10 10:01:58 +00:00
2026-10-07 15:27:23 +00:00
// 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,
};
}