This commit is contained in:
David Arranz 2025-11-13 19:57:45 +01:00
parent 2a1a42fd9c
commit 5bacdcc2fc
6 changed files with 109 additions and 105 deletions

View File

@ -1,57 +1,50 @@
"use client"
import { Table } from "@tanstack/react-table"
import { Settings2 } from "lucide-react"
"use client";
import {
Button, DropdownMenu,
Button,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@repo/shadcn-ui/components'
DropdownMenuTrigger,
} from "@repo/shadcn-ui/components";
import type { Table } from "@tanstack/react-table";
import { Settings2 } from "lucide-react";
export function DataTableViewOptions<TData>({
table,
}: {
table: Table<TData>
}) {
import { useTranslation } from "../../locales/i18n.ts";
export function DataTableViewOptions<TData>({ table }: { table: Table<TData> }) {
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex"
>
<Button className="ml-auto hidden h-8 lg:flex" size="sm" type="button" variant="outline">
<Settings2 />
View
{t("components.datatable_view_options.view_button")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[150px]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuLabel>
{t("components.datatable_view_options.toggle_columns")}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide()
)
.filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
className="capitalize"
key={column.id}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
)
);
})}
</DropdownMenuContent>
</DropdownMenu>
)
);
}

View File

@ -1,24 +1,4 @@
"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"
"use client";
import {
Button,
@ -34,16 +14,36 @@ import {
TableFooter,
TableHead,
TableHeader,
TableRow
} from '@repo/shadcn-ui/components'
import { DataTablePagination } from './data-table-pagination.tsx'
import { DataTableToolbar } from "./data-table-toolbar.tsx"
TableRow,
} from "@repo/shadcn-ui/components";
import {
type ColumnDef,
type ColumnFiltersState,
type ColumnSizingState,
type Row,
type SortingState,
type Table,
type TableMeta,
type VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import * as React from "react";
import { useTranslation } from "../../locales/i18n.ts"
import { useTranslation } from "../../locales/i18n.ts";
import { DataTablePagination } from "./data-table-pagination.tsx";
import { DataTableToolbar } from "./data-table-toolbar.tsx";
export type DataTableOps<TData> = {
onAdd?: (table: Table<TData>) => void;
}
};
export type DataTableRowOps<TData> = {
duplicate?(index: number, table: Table<TData>): void;
@ -61,25 +61,25 @@ export type DataTableBulkRowOps<TData> = {
};
export type DataTableMeta<TData> = TableMeta<TData> & {
totalItems?: number; // para paginación server-side
totalItems?: number; // para paginación server-side
readOnly?: boolean;
tableOps?: DataTableOps<TData>
rowOps?: DataTableRowOps<TData>
bulkOps?: DataTableBulkRowOps<TData>
}
tableOps?: DataTableOps<TData>;
rowOps?: DataTableRowOps<TData>;
bulkOps?: DataTableBulkRowOps<TData>;
};
export interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
meta?: DataTableMeta<TData>
columns: ColumnDef<TData, TValue>[];
data: TData[];
meta?: DataTableMeta<TData>;
// Configuración
readOnly?: boolean
enablePagination?: boolean
pageSize?: number
enableRowSelection?: boolean
EditorComponent?: React.ComponentType<{ row: TData; index: number; onClose: () => void }>
readOnly?: boolean;
enablePagination?: boolean;
pageSize?: number;
enableRowSelection?: boolean;
EditorComponent?: React.ComponentType<{ row: TData; index: number; onClose: () => void }>;
getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string;
@ -155,9 +155,7 @@ export function DataTable<TData, TValue>({
// Propagar cambios al padre
onPaginationChange: (updater) => {
const next = typeof updater === "function"
? updater({ pageIndex, pageSize })
: 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);
@ -175,17 +173,15 @@ export function DataTable<TData, TValue>({
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
})
});
const handleCloseEditor = React.useCallback(() => setEditIndex(null), [])
const handleCloseEditor = React.useCallback(() => setEditIndex(null), []);
// Render principal
return (
<div
className="transition-[max-height] duration-300 ease-in-out"
>
<div className="transition-[max-height] duration-300 ease-in-out">
<div className="flex flex-col gap-0">
<DataTableToolbar table={table} showViewOptions={!readOnly} />
<DataTableToolbar showViewOptions={!readOnly} table={table} />
<div className="overflow-hidden rounded-md border">
<TableComp className="w-full text-sm">
@ -199,8 +195,8 @@ export function DataTable<TData, TValue>({
const maxW = h.column.columnDef.maxSize;
return (
<TableHead
key={h.id}
colSpan={h.colSpan}
key={h.id}
style={{
width: w ? `${w}px` : undefined,
minWidth: typeof minW === "number" ? `${minW}px` : undefined,
@ -224,26 +220,28 @@ export function DataTable<TData, TValue>({
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row, rowIndex) => (
<TableRow
key={row.id}
role="button"
tabIndex={0}
data-state={row.getIsSelected() && "selected"}
className={"group bg-background cursor-pointer"}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") onRowClick?.(row.original, rowIndex, e as any);
}}
data-state={row.getIsSelected() && "selected"}
key={row.id}
onClick={(e) => onRowClick?.(row.original, rowIndex, e)}
onDoubleClick={
!readOnly && !onRowClick ? () => setEditIndex(rowIndex) : undefined
} >
readOnly || onRowClick ? undefined : () => setEditIndex(rowIndex)
}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ")
onRowClick?.(row.original, rowIndex, e as any);
}}
role="button"
tabIndex={0}
>
{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"
key={cell.id}
style={{
width: w ? `${w}px` : undefined,
minWidth: typeof minW === "number" ? `${minW}px` : undefined,
@ -258,7 +256,10 @@ export function DataTable<TData, TValue>({
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center text-muted-foreground">
<TableCell
className="h-24 text-center text-muted-foreground"
colSpan={columns.length}
>
{t("components.datatable.empty")}
</TableCell>
</TableRow>
@ -270,18 +271,21 @@ export function DataTable<TData, TValue>({
<TableFooter>
<TableRow>
<TableCell colSpan={100}>
<DataTablePagination table={table} onPageChange={onPageChange} onPageSizeChange={onPageSizeChange} />
<DataTablePagination
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
table={table}
/>
</TableCell>
</TableRow>
</TableFooter>)
}
</TableFooter>
)}
</TableComp>
</div>
{/* Editor modal */}
{EditorComponent && editIndex !== null && (
<Dialog open onOpenChange={handleCloseEditor}>
<Dialog onOpenChange={handleCloseEditor} open>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>{t("components.datatable.editor.title")}</DialogTitle>
@ -290,14 +294,14 @@ export function DataTable<TData, TValue>({
<div className="mt-4">
<EditorComponent
row={data[editIndex]}
index={editIndex}
onClose={handleCloseEditor}
row={data[editIndex]}
/>
</div>
<DialogFooter>
<Button type="button" variant="secondary" onClick={handleCloseEditor}>
<Button onClick={handleCloseEditor} type="button" variant="secondary">
{t("common.close")}
</Button>
</DialogFooter>
@ -306,5 +310,5 @@ export function DataTable<TData, TValue>({
)}
</div>
</div>
)
);
}

View File

@ -1,5 +1,5 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
import { PropsWithChildren } from "react";
import type { PropsWithChildren } from "react";
export const AppContent = ({
className,
@ -8,10 +8,8 @@ export const AppContent = ({
}: PropsWithChildren<{ className?: string }>) => {
return (
<div
className={cn(
"app-content flex flex-1 flex-col gap-4 p-4 pt-6 bg-primary/5 min-h-screen",
className
)}
className={cn("app-content flex flex-1 flex-col gap-4 p-4 pt-6 min-h-screen", className)}
style={{ backgroundColor: "#fdfdfd" }}
{...props}
>
{children}

View File

@ -1,5 +1,6 @@
import { SidebarInset, SidebarProvider } from "@repo/shadcn-ui/components";
import { Outlet } from "react-router";
import { AppSidebar } from "./app-sidebar.tsx";
export const AppLayout = () => {
@ -12,9 +13,9 @@ export const AppLayout = () => {
} as React.CSSProperties
}
>
<AppSidebar variant='inset' />
<AppSidebar variant="inset" />
{/* Aquí está el MAIN */}
<SidebarInset className='app-main'>
<SidebarInset className="app-main">
<Outlet />
</SidebarInset>
</SidebarProvider>

View File

@ -35,6 +35,10 @@
"rows_selected": "{{count}} of {{total}} selected rows"
}
},
"datatable_view_options": {
"view_button": "View",
"toggle_columns": "Toggle columns"
},
"loading_indicator": {
"title": "Loading...",
"subtitle": "This may take a few seconds. Please do not close this page."

View File

@ -38,6 +38,10 @@
"rows_selected": "{{count}} de {{total}} filas seleccionadas"
}
},
"datatable_view_options": {
"view_button": "Ver",
"toggle_columns": "Alternar columnas"
},
"loading_indicator": {
"title": "Cargando...",
"subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página."