183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
|
|
"use client"
|
||
|
|
|
||
|
|
import {
|
||
|
|
ColumnDef,
|
||
|
|
ColumnFiltersState,
|
||
|
|
ColumnSizingState,
|
||
|
|
SortingState,
|
||
|
|
VisibilityState,
|
||
|
|
flexRender,
|
||
|
|
getCoreRowModel,
|
||
|
|
getFacetedRowModel,
|
||
|
|
getFacetedUniqueValues,
|
||
|
|
getFilteredRowModel,
|
||
|
|
getPaginationRowModel,
|
||
|
|
getSortedRowModel,
|
||
|
|
useReactTable
|
||
|
|
} from "@tanstack/react-table"
|
||
|
|
import * as React from "react"
|
||
|
|
|
||
|
|
import {
|
||
|
|
Dialog,
|
||
|
|
DialogContent,
|
||
|
|
Table, TableBody,
|
||
|
|
TableCell,
|
||
|
|
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"
|
||
|
|
|
||
|
|
|
||
|
|
type DataTableRowOps = {
|
||
|
|
duplicate?(index: number): void;
|
||
|
|
remove?(index: number): void;
|
||
|
|
move?(from: number, to: number): void;
|
||
|
|
canMoveUp?(index: number): boolean;
|
||
|
|
canMoveDown?(index: number, lastIndex: number): boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
interface DataTableProps<TData, TValue> {
|
||
|
|
columns: ColumnDef<TData, TValue>[]
|
||
|
|
data: TData[],
|
||
|
|
meta?: Record<string, any>,
|
||
|
|
|
||
|
|
getRowId?: (row: TData, index: number) => string;
|
||
|
|
pageSize?: number;
|
||
|
|
enableRowSelection?: boolean;
|
||
|
|
|
||
|
|
renderRowEditor?: (index: number, close: () => void) => React.ReactNode; // editor modal opcional. Se muestra dentro de un Dialog.
|
||
|
|
}
|
||
|
|
|
||
|
|
export function DataTable<TData, TValue>({
|
||
|
|
columns,
|
||
|
|
data,
|
||
|
|
meta,
|
||
|
|
getRowId,
|
||
|
|
pageSize = 25,
|
||
|
|
enableRowSelection = true,
|
||
|
|
renderRowEditor,
|
||
|
|
}: DataTableProps<TData, TValue>) {
|
||
|
|
const { t } = useTranslation();
|
||
|
|
|
||
|
|
const [rowSelection, setRowSelection] = React.useState({});
|
||
|
|
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||
|
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
||
|
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||
|
|
const [colSizes, setColSizes] = React.useState<ColumnSizingState>({});
|
||
|
|
const [editIndex, setEditIndex] = React.useState<number | null>(null);
|
||
|
|
|
||
|
|
const openEditor = React.useCallback((i: number) => setEditIndex(i), []);
|
||
|
|
const closeEditor = React.useCallback(() => setEditIndex(null), []);
|
||
|
|
|
||
|
|
|
||
|
|
const table = useReactTable({
|
||
|
|
data,
|
||
|
|
columns,
|
||
|
|
columnResizeMode: "onChange",
|
||
|
|
onColumnSizingChange: setColSizes,
|
||
|
|
getRowId: getRowId ?? ((row: any, idx) => (row?.id ? String(row.id) : String(idx))),
|
||
|
|
state: {
|
||
|
|
columnSizing: colSizes,
|
||
|
|
sorting,
|
||
|
|
columnVisibility,
|
||
|
|
rowSelection,
|
||
|
|
columnFilters,
|
||
|
|
},
|
||
|
|
initialState: { pagination: { pageSize } },
|
||
|
|
meta: { ...meta, openEditor },
|
||
|
|
|
||
|
|
enableRowSelection,
|
||
|
|
getCoreRowModel: getCoreRowModel(),
|
||
|
|
onRowSelectionChange: setRowSelection,
|
||
|
|
onSortingChange: setSorting,
|
||
|
|
onColumnFiltersChange: setColumnFilters,
|
||
|
|
onColumnVisibilityChange: setColumnVisibility,
|
||
|
|
getFilteredRowModel: getFilteredRowModel(),
|
||
|
|
getPaginationRowModel: getPaginationRowModel(),
|
||
|
|
getSortedRowModel: getSortedRowModel(),
|
||
|
|
getFacetedRowModel: getFacetedRowModel(),
|
||
|
|
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||
|
|
})
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="flex flex-col gap-4">
|
||
|
|
<DataTableToolbar table={table} />
|
||
|
|
|
||
|
|
<div className="overflow-hidden rounded-md border">
|
||
|
|
<Table className="w-full text-sm">
|
||
|
|
<TableHeader className="sticky top-0 bg-muted hover:bg-muted z-10">
|
||
|
|
{table.getHeaderGroups().map((hg) => (
|
||
|
|
<TableRow key={hg.id}>
|
||
|
|
{hg.headers.map((h) => {
|
||
|
|
const w = h.getSize(); // px
|
||
|
|
const minW = h.column.columnDef.minSize;
|
||
|
|
const maxW = h.column.columnDef.maxSize;
|
||
|
|
return (
|
||
|
|
<TableHead
|
||
|
|
key={h.id}
|
||
|
|
colSpan={h.colSpan}
|
||
|
|
style={{
|
||
|
|
width: w ? `${w}px` : undefined,
|
||
|
|
minWidth: typeof minW === "number" ? `${minW}px` : undefined,
|
||
|
|
maxWidth: typeof maxW === "number" ? `${maxW}px` : undefined,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())}
|
||
|
|
</TableHead>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</TableRow>
|
||
|
|
))}
|
||
|
|
</TableHeader>
|
||
|
|
|
||
|
|
<TableBody>
|
||
|
|
{table.getRowModel().rows.length ? (
|
||
|
|
table.getRowModel().rows.map((row) => (
|
||
|
|
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
|
||
|
|
{row.getVisibleCells().map((cell) => {
|
||
|
|
const w = cell.column.getSize();
|
||
|
|
const minW = cell.column.columnDef.minSize;
|
||
|
|
const maxW = cell.column.columnDef.maxSize;
|
||
|
|
return (
|
||
|
|
<TableCell
|
||
|
|
key={cell.id}
|
||
|
|
className="align-top"
|
||
|
|
style={{
|
||
|
|
width: w ? `${w}px` : undefined,
|
||
|
|
minWidth: typeof minW === "number" ? `${minW}px` : undefined,
|
||
|
|
maxWidth: typeof maxW === "number" ? `${maxW}px` : undefined,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||
|
|
</TableCell>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</TableRow>
|
||
|
|
))
|
||
|
|
) : (
|
||
|
|
<TableRow>
|
||
|
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||
|
|
{t("components.datatabla.empty")}
|
||
|
|
</TableCell>
|
||
|
|
</TableRow>
|
||
|
|
)}
|
||
|
|
</TableBody>
|
||
|
|
</Table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<DataTablePagination table={table} />
|
||
|
|
|
||
|
|
{!!renderRowEditor && editIndex !== null && (
|
||
|
|
<Dialog open onOpenChange={(open) => (!open ? closeEditor() : null)}>
|
||
|
|
<DialogContent className="max-w-xl">
|
||
|
|
{renderRowEditor(editIndex, closeEditor)}
|
||
|
|
</DialogContent>
|
||
|
|
</Dialog>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|