"use client" import { ColumnDef, ColumnFiltersState, ColumnSizingState, Row, SortingState, Table, TableMeta, VisibilityState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table" import * as React from "react" import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, TableBody, TableCell, Table as TableComp, TableFooter, TableHead, TableHeader, TableRow } from '@repo/shadcn-ui/components' import { DataTablePagination } from './data-table-pagination.tsx' import { DataTableToolbar } from "./data-table-toolbar.tsx" import { useTranslation } from "../../locales/i18n.ts" export type DataTableOps = { onAdd?: (table: Table) => void; } export type DataTableRowOps = { duplicate?(index: number, table: Table): void; remove?(index: number, table: Table): void; move?(from: number, to: number, table: Table): void; canMoveUp?(index: number, table: Table): boolean; canMoveDown?(index: number, lastIndex: number, table: Table): boolean; }; export type DataTableBulkRowOps = { duplicateSelected?: (indexes: number[], table: Table) => void; removeSelected?: (indexes: number[], table: Table) => void; moveSelectedUp?: (indexes: number[], table: Table) => void; moveSelectedDown?: (indexes: number[], table: Table) => void; }; export type DataTableMeta = TableMeta & { totalItems?: number; // para paginación server-side readOnly?: boolean; tableOps?: DataTableOps rowOps?: DataTableRowOps bulkOps?: DataTableBulkRowOps } export interface DataTableProps { columns: ColumnDef[] data: TData[] meta?: DataTableMeta // Configuración readOnly?: boolean enablePagination?: boolean pageSize?: number enableRowSelection?: boolean EditorComponent?: React.ComponentType<{ row: TData; index: number; onClose: () => void }> getRowId?: (originalRow: TData, index: number, parent?: Row) => string; // Soporte para paginación server-side manualPagination?: boolean; pageIndex?: number; // 0-based totalItems?: number; onPageChange?: (pageIndex: number) => void; onPageSizeChange?: (pageSize: number) => void; // Acción al hacer click en una fila onRowClick?: (row: TData, index: number, event: React.MouseEvent) => void; } export function DataTable({ columns, data, meta, readOnly = false, enablePagination = true, pageSize = 10, enableRowSelection = false, EditorComponent, getRowId, manualPagination, pageIndex = 0, totalItems, onPageChange, onPageSizeChange, onRowClick, }: DataTableProps) { const { t } = useTranslation(); const [rowSelection, setRowSelection] = React.useState({}); const [sorting, setSorting] = React.useState([]); const [columnVisibility, setColumnVisibility] = React.useState({}); const [columnFilters, setColumnFilters] = React.useState([]); const [colSizes, setColSizes] = React.useState({}); const [editIndex, setEditIndex] = React.useState(null); // Configuración TanStack const table = useReactTable({ data, columns, columnResizeMode: "onChange", onColumnSizingChange: setColSizes, meta: { ...meta, totalItems, readOnly }, getRowId: getRowId ?? ((originalRow: TData, i: number) => typeof (originalRow as any).id !== "undefined" ? String((originalRow as any).id) : String(i)), state: { columnSizing: colSizes, sorting, columnVisibility, rowSelection, columnFilters, pagination: { pageIndex, pageSize }, }, manualPagination, pageCount: manualPagination ? Math.ceil((totalItems ?? data.length) / (pageSize ?? 25)) : undefined, // Propagar cambios al padre onPaginationChange: (updater) => { const next = typeof updater === "function" ? updater({ pageIndex, pageSize }) : updater; if (typeof next.pageIndex === "number") onPageChange?.(next.pageIndex); if (typeof next.pageSize === "number") onPageSizeChange?.(next.pageSize); }, enableRowSelection, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: manualPagination ? undefined : getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), }) const handleCloseEditor = React.useCallback(() => setEditIndex(null), []) // Render principal return (
{/* CABECERA */} {table.getHeaderGroups().map((hg) => ( {hg.headers.map((h) => { const w = h.getSize(); const minW = h.column.columnDef.minSize; const maxW = h.column.columnDef.maxSize; return (
{h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())}
); })}
))}
{/* CUERPO */} {table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row, rowIndex) => ( { if (e.key === "Enter" || e.key === " ") onRowClick?.(row.original, rowIndex, e as any); }} onClick={(e) => onRowClick?.(row.original, rowIndex, e)} onDoubleClick={ !readOnly && !onRowClick ? () => setEditIndex(rowIndex) : undefined } > {row.getVisibleCells().map((cell) => { const w = cell.column.getSize(); const minW = cell.column.columnDef.minSize; const maxW = cell.column.columnDef.maxSize; return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ); })} )) ) : ( {t("components.datatable.empty")} )} {/* Paginación */} {enablePagination && ( ) }
{/* Editor modal */} {EditorComponent && editIndex !== null && ( {t("components.datatable.editor.title")} {t("components.datatable.editor.subtitle")}
)}
) }