.
This commit is contained in:
parent
2a1a42fd9c
commit
5bacdcc2fc
@ -1,57 +1,50 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Table } from "@tanstack/react-table"
|
|
||||||
import { Settings2 } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button, DropdownMenu,
|
Button,
|
||||||
|
DropdownMenu,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger,
|
||||||
} from '@repo/shadcn-ui/components'
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import type { Table } from "@tanstack/react-table";
|
||||||
|
import { Settings2 } from "lucide-react";
|
||||||
|
|
||||||
export function DataTableViewOptions<TData>({
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
table,
|
|
||||||
}: {
|
export function DataTableViewOptions<TData>({ table }: { table: Table<TData> }) {
|
||||||
table: Table<TData>
|
const { t } = useTranslation();
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button className="ml-auto hidden h-8 lg:flex" size="sm" type="button" variant="outline">
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="ml-auto hidden h-8 lg:flex"
|
|
||||||
>
|
|
||||||
<Settings2 />
|
<Settings2 />
|
||||||
View
|
{t("components.datatable_view_options.view_button")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[150px]">
|
<DropdownMenuContent align="end" className="w-[150px]">
|
||||||
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
|
{t("components.datatable_view_options.toggle_columns")}
|
||||||
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{table
|
{table
|
||||||
.getAllColumns()
|
.getAllColumns()
|
||||||
.filter(
|
.filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide())
|
||||||
(column) =>
|
|
||||||
typeof column.accessorFn !== "undefined" && column.getCanHide()
|
|
||||||
)
|
|
||||||
.map((column) => {
|
.map((column) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
key={column.id}
|
|
||||||
className="capitalize"
|
|
||||||
checked={column.getIsVisible()}
|
checked={column.getIsVisible()}
|
||||||
|
className="capitalize"
|
||||||
|
key={column.id}
|
||||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||||
>
|
>
|
||||||
{column.id}
|
{column.id}
|
||||||
</DropdownMenuCheckboxItem>
|
</DropdownMenuCheckboxItem>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,4 @@
|
|||||||
"use client"
|
"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 {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -34,16 +14,36 @@ import {
|
|||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow
|
TableRow,
|
||||||
} from '@repo/shadcn-ui/components'
|
} from "@repo/shadcn-ui/components";
|
||||||
import { DataTablePagination } from './data-table-pagination.tsx'
|
import {
|
||||||
import { DataTableToolbar } from "./data-table-toolbar.tsx"
|
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> = {
|
export type DataTableOps<TData> = {
|
||||||
onAdd?: (table: Table<TData>) => void;
|
onAdd?: (table: Table<TData>) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type DataTableRowOps<TData> = {
|
export type DataTableRowOps<TData> = {
|
||||||
duplicate?(index: number, table: Table<TData>): void;
|
duplicate?(index: number, table: Table<TData>): void;
|
||||||
@ -61,25 +61,25 @@ export type DataTableBulkRowOps<TData> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DataTableMeta<TData> = TableMeta<TData> & {
|
export type DataTableMeta<TData> = TableMeta<TData> & {
|
||||||
totalItems?: number; // para paginación server-side
|
totalItems?: number; // para paginación server-side
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
|
||||||
tableOps?: DataTableOps<TData>
|
tableOps?: DataTableOps<TData>;
|
||||||
rowOps?: DataTableRowOps<TData>
|
rowOps?: DataTableRowOps<TData>;
|
||||||
bulkOps?: DataTableBulkRowOps<TData>
|
bulkOps?: DataTableBulkRowOps<TData>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface DataTableProps<TData, TValue> {
|
export interface DataTableProps<TData, TValue> {
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[];
|
||||||
data: TData[]
|
data: TData[];
|
||||||
meta?: DataTableMeta<TData>
|
meta?: DataTableMeta<TData>;
|
||||||
|
|
||||||
// Configuración
|
// Configuración
|
||||||
readOnly?: boolean
|
readOnly?: boolean;
|
||||||
enablePagination?: boolean
|
enablePagination?: boolean;
|
||||||
pageSize?: number
|
pageSize?: number;
|
||||||
enableRowSelection?: boolean
|
enableRowSelection?: boolean;
|
||||||
EditorComponent?: React.ComponentType<{ row: TData; index: number; onClose: () => void }>
|
EditorComponent?: React.ComponentType<{ row: TData; index: number; onClose: () => void }>;
|
||||||
|
|
||||||
getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string;
|
getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string;
|
||||||
|
|
||||||
@ -155,9 +155,7 @@ export function DataTable<TData, TValue>({
|
|||||||
|
|
||||||
// Propagar cambios al padre
|
// Propagar cambios al padre
|
||||||
onPaginationChange: (updater) => {
|
onPaginationChange: (updater) => {
|
||||||
const next = typeof updater === "function"
|
const next = typeof updater === "function" ? updater({ pageIndex, pageSize }) : updater;
|
||||||
? updater({ pageIndex, pageSize })
|
|
||||||
: updater;
|
|
||||||
|
|
||||||
if (typeof next.pageIndex === "number") onPageChange?.(next.pageIndex);
|
if (typeof next.pageIndex === "number") onPageChange?.(next.pageIndex);
|
||||||
if (typeof next.pageSize === "number") onPageSizeChange?.(next.pageSize);
|
if (typeof next.pageSize === "number") onPageSizeChange?.(next.pageSize);
|
||||||
@ -175,17 +173,15 @@ export function DataTable<TData, TValue>({
|
|||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFacetedRowModel: getFacetedRowModel(),
|
getFacetedRowModel: getFacetedRowModel(),
|
||||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||||
})
|
});
|
||||||
|
|
||||||
const handleCloseEditor = React.useCallback(() => setEditIndex(null), [])
|
const handleCloseEditor = React.useCallback(() => setEditIndex(null), []);
|
||||||
|
|
||||||
// Render principal
|
// Render principal
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="transition-[max-height] duration-300 ease-in-out">
|
||||||
className="transition-[max-height] duration-300 ease-in-out"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-0">
|
<div className="flex flex-col gap-0">
|
||||||
<DataTableToolbar table={table} showViewOptions={!readOnly} />
|
<DataTableToolbar showViewOptions={!readOnly} table={table} />
|
||||||
|
|
||||||
<div className="overflow-hidden rounded-md border">
|
<div className="overflow-hidden rounded-md border">
|
||||||
<TableComp className="w-full text-sm">
|
<TableComp className="w-full text-sm">
|
||||||
@ -199,8 +195,8 @@ export function DataTable<TData, TValue>({
|
|||||||
const maxW = h.column.columnDef.maxSize;
|
const maxW = h.column.columnDef.maxSize;
|
||||||
return (
|
return (
|
||||||
<TableHead
|
<TableHead
|
||||||
key={h.id}
|
|
||||||
colSpan={h.colSpan}
|
colSpan={h.colSpan}
|
||||||
|
key={h.id}
|
||||||
style={{
|
style={{
|
||||||
width: w ? `${w}px` : undefined,
|
width: w ? `${w}px` : undefined,
|
||||||
minWidth: typeof minW === "number" ? `${minW}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.length ? (
|
||||||
table.getRowModel().rows.map((row, rowIndex) => (
|
table.getRowModel().rows.map((row, rowIndex) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={row.id}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
className={"group bg-background cursor-pointer"}
|
className={"group bg-background cursor-pointer"}
|
||||||
onKeyDown={(e) => {
|
data-state={row.getIsSelected() && "selected"}
|
||||||
if (e.key === "Enter" || e.key === " ") onRowClick?.(row.original, rowIndex, e as any);
|
key={row.id}
|
||||||
}}
|
|
||||||
onClick={(e) => onRowClick?.(row.original, rowIndex, e)}
|
onClick={(e) => onRowClick?.(row.original, rowIndex, e)}
|
||||||
onDoubleClick={
|
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) => {
|
{row.getVisibleCells().map((cell) => {
|
||||||
const w = cell.column.getSize();
|
const w = cell.column.getSize();
|
||||||
const minW = cell.column.columnDef.minSize;
|
const minW = cell.column.columnDef.minSize;
|
||||||
const maxW = cell.column.columnDef.maxSize;
|
const maxW = cell.column.columnDef.maxSize;
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={cell.id}
|
|
||||||
className="align-top"
|
className="align-top"
|
||||||
|
key={cell.id}
|
||||||
style={{
|
style={{
|
||||||
width: w ? `${w}px` : undefined,
|
width: w ? `${w}px` : undefined,
|
||||||
minWidth: typeof minW === "number" ? `${minW}px` : undefined,
|
minWidth: typeof minW === "number" ? `${minW}px` : undefined,
|
||||||
@ -258,7 +256,10 @@ export function DataTable<TData, TValue>({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<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")}
|
{t("components.datatable.empty")}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -270,18 +271,21 @@ export function DataTable<TData, TValue>({
|
|||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={100}>
|
<TableCell colSpan={100}>
|
||||||
<DataTablePagination table={table} onPageChange={onPageChange} onPageSizeChange={onPageSizeChange} />
|
<DataTablePagination
|
||||||
|
onPageChange={onPageChange}
|
||||||
|
onPageSizeChange={onPageSizeChange}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableFooter>)
|
</TableFooter>
|
||||||
}
|
)}
|
||||||
|
|
||||||
</TableComp>
|
</TableComp>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Editor modal */}
|
{/* Editor modal */}
|
||||||
{EditorComponent && editIndex !== null && (
|
{EditorComponent && editIndex !== null && (
|
||||||
<Dialog open onOpenChange={handleCloseEditor}>
|
<Dialog onOpenChange={handleCloseEditor} open>
|
||||||
<DialogContent className="max-w-3xl">
|
<DialogContent className="max-w-3xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("components.datatable.editor.title")}</DialogTitle>
|
<DialogTitle>{t("components.datatable.editor.title")}</DialogTitle>
|
||||||
@ -290,14 +294,14 @@ export function DataTable<TData, TValue>({
|
|||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<EditorComponent
|
<EditorComponent
|
||||||
row={data[editIndex]}
|
|
||||||
index={editIndex}
|
index={editIndex}
|
||||||
onClose={handleCloseEditor}
|
onClose={handleCloseEditor}
|
||||||
|
row={data[editIndex]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button type="button" variant="secondary" onClick={handleCloseEditor}>
|
<Button onClick={handleCloseEditor} type="button" variant="secondary">
|
||||||
{t("common.close")}
|
{t("common.close")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@ -306,5 +310,5 @@ export function DataTable<TData, TValue>({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
|
|
||||||
export const AppContent = ({
|
export const AppContent = ({
|
||||||
className,
|
className,
|
||||||
@ -8,10 +8,8 @@ export const AppContent = ({
|
|||||||
}: PropsWithChildren<{ className?: string }>) => {
|
}: PropsWithChildren<{ className?: string }>) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn("app-content flex flex-1 flex-col gap-4 p-4 pt-6 min-h-screen", className)}
|
||||||
"app-content flex flex-1 flex-col gap-4 p-4 pt-6 bg-primary/5 min-h-screen",
|
style={{ backgroundColor: "#fdfdfd" }}
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { SidebarInset, SidebarProvider } from "@repo/shadcn-ui/components";
|
import { SidebarInset, SidebarProvider } from "@repo/shadcn-ui/components";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
|
|
||||||
import { AppSidebar } from "./app-sidebar.tsx";
|
import { AppSidebar } from "./app-sidebar.tsx";
|
||||||
|
|
||||||
export const AppLayout = () => {
|
export const AppLayout = () => {
|
||||||
@ -12,9 +13,9 @@ export const AppLayout = () => {
|
|||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AppSidebar variant='inset' />
|
<AppSidebar variant="inset" />
|
||||||
{/* Aquí está el MAIN */}
|
{/* Aquí está el MAIN */}
|
||||||
<SidebarInset className='app-main'>
|
<SidebarInset className="app-main">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|||||||
@ -35,6 +35,10 @@
|
|||||||
"rows_selected": "{{count}} of {{total}} selected rows"
|
"rows_selected": "{{count}} of {{total}} selected rows"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"datatable_view_options": {
|
||||||
|
"view_button": "View",
|
||||||
|
"toggle_columns": "Toggle columns"
|
||||||
|
},
|
||||||
"loading_indicator": {
|
"loading_indicator": {
|
||||||
"title": "Loading...",
|
"title": "Loading...",
|
||||||
"subtitle": "This may take a few seconds. Please do not close this page."
|
"subtitle": "This may take a few seconds. Please do not close this page."
|
||||||
|
|||||||
@ -38,6 +38,10 @@
|
|||||||
"rows_selected": "{{count}} de {{total}} filas seleccionadas"
|
"rows_selected": "{{count}} de {{total}} filas seleccionadas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"datatable_view_options": {
|
||||||
|
"view_button": "Ver",
|
||||||
|
"toggle_columns": "Alternar columnas"
|
||||||
|
},
|
||||||
"loading_indicator": {
|
"loading_indicator": {
|
||||||
"title": "Cargando...",
|
"title": "Cargando...",
|
||||||
"subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página."
|
"subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página."
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user