This commit is contained in:
David Arranz 2025-11-16 22:15:23 +01:00
parent 99f8b5fb8e
commit 07047f9984
2 changed files with 66 additions and 77 deletions

View File

@ -1,12 +1,26 @@
import { Button, Separator, Tooltip, TooltipContent, TooltipTrigger } from '@repo/shadcn-ui/components' import {
import { cn } from '@repo/shadcn-ui/lib/utils' Button,
import { Table } from "@tanstack/react-table" Separator,
import { ArrowDownIcon, ArrowUpIcon, CopyPlusIcon, PlusIcon, ScanIcon, TrashIcon } from 'lucide-react' Tooltip,
import React from 'react' TooltipContent,
import { useTranslation } from "../../locales/i18n.ts" TooltipTrigger,
import { DataTableViewOptions } from './data-table-view-options.tsx' } from "@repo/shadcn-ui/components";
import { DataTableMeta } from './data-table.tsx' import { cn } from "@repo/shadcn-ui/lib/utils";
import type { Table } from "@tanstack/react-table";
import {
ArrowDownIcon,
ArrowUpIcon,
CopyPlusIcon,
PlusIcon,
ScanIcon,
TrashIcon,
} from "lucide-react";
import React from "react";
import { useTranslation } from "../../locales/i18n.ts";
import type { DataTableMeta } from "./data-table.tsx";
import { DataTableViewOptions } from "./data-table-view-options.tsx";
interface DataTableToolbarProps<TData> { interface DataTableToolbarProps<TData> {
table: Table<TData>; table: Table<TData>;
@ -17,7 +31,7 @@ interface DataTableToolbarProps<TData> {
export function DataTableToolbar<TData>({ export function DataTableToolbar<TData>({
table, table,
showViewOptions = true, showViewOptions = true,
className className,
}: DataTableToolbarProps<TData>) { }: DataTableToolbarProps<TData>) {
const { t } = useTranslation(); const { t } = useTranslation();
const meta = table.options.meta as DataTableMeta<TData> | undefined; const meta = table.options.meta as DataTableMeta<TData> | undefined;
@ -32,10 +46,7 @@ export function DataTableToolbar<TData>({
const hasSelection = selectedCount > 0; const hasSelection = selectedCount > 0;
// Índices seleccionados (memoizado) // Índices seleccionados (memoizado)
const selectedIndexes = React.useMemo( const selectedIndexes = React.useMemo(() => selectedRows.map((r) => r.index), [selectedRows]);
() => selectedRows.map((r) => r.index),
[selectedRows]
);
const handleAdd = React.useCallback(() => { const handleAdd = React.useCallback(() => {
if (!readOnly) meta?.tableOps?.onAdd?.(table); if (!readOnly) meta?.tableOps?.onAdd?.(table);
@ -63,25 +74,20 @@ export function DataTableToolbar<TData>({
// Render principal // Render principal
return ( return (
<div <div className={cn("flex items-center justify-between gap-2 py-2 bg-transparent", className)}>
className={cn(
"flex items-center justify-between gap-2 py-2 bg-transparent",
className
)}
>
{/* IZQUIERDA: acciones + contador */} {/* IZQUIERDA: acciones + contador */}
<div className="flex flex-1 items-center gap-3 flex-wrap"> <div className="flex flex-1 items-center gap-3 flex-wrap">
{/* Botón añadir */} {/* Botón añadir */}
{!readOnly && meta?.tableOps?.onAdd && ( {!readOnly && meta?.tableOps?.onAdd && (
<Button <Button
className='cursor-pointer'
type="button"
size="sm"
variant={'outline'}
onClick={handleAdd}
aria-label={t("components.datatable.actions.add")} aria-label={t("components.datatable.actions.add")}
className="cursor-pointer"
onClick={handleAdd}
size="sm"
type="button"
variant={"outline"}
> >
<PlusIcon className="size-4 mr-1" aria-hidden="true" /> <PlusIcon aria-hidden="true" className="size-4 mr-1" />
<span>{t("components.datatable.actions.add")}</span> <span>{t("components.datatable.actions.add")}</span>
</Button> </Button>
)} )}
@ -89,18 +95,18 @@ export function DataTableToolbar<TData>({
{/* Acciones sobre selección */} {/* Acciones sobre selección */}
{hasSelection && ( {hasSelection && (
<> <>
<Separator orientation="vertical" className="h-5 mx-1" /> <Separator className="h-5 mx-1" orientation="vertical" />
{!readOnly && meta?.bulkOps?.duplicateSelected && ( {!readOnly && meta?.bulkOps?.duplicateSelected && (
<Button <Button
className='cursor-pointer'
type="button"
size="sm"
variant="outline"
onClick={handleDuplicateSelected}
aria-label={t("components.datatable.actions.duplicate")} aria-label={t("components.datatable.actions.duplicate")}
className="cursor-pointer"
onClick={handleDuplicateSelected}
size="sm"
type="button"
variant="outline"
> >
<CopyPlusIcon className="size-4 mr-1" aria-hidden="true" /> <CopyPlusIcon aria-hidden="true" className="size-4 mr-1" />
<span>{t("components.datatable.actions.duplicate")}</span> <span>{t("components.datatable.actions.duplicate")}</span>
</Button> </Button>
)} )}
@ -109,18 +115,16 @@ export function DataTableToolbar<TData>({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
type="button"
size="sm"
variant="outline"
onClick={handleMoveSelectedUp}
aria-label={t("components.datatable.actions.move_up")} aria-label={t("components.datatable.actions.move_up")}
onClick={handleMoveSelectedUp}
size="sm"
type="button"
variant="outline"
> >
<ArrowUpIcon className="size-4" aria-hidden="true" /> <ArrowUpIcon aria-hidden="true" className="size-4" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>{t("components.datatable.actions.move_up")}</TooltipContent>
{t("components.datatable.actions.move_up")}
</TooltipContent>
</Tooltip> </Tooltip>
)} )}
@ -128,71 +132,56 @@ export function DataTableToolbar<TData>({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
type="button"
size="sm"
variant="outline"
onClick={handleMoveSelectedDown}
aria-label={t("components.datatable.actions.move_down")} aria-label={t("components.datatable.actions.move_down")}
onClick={handleMoveSelectedDown}
size="sm"
type="button"
variant="outline"
> >
<ArrowDownIcon className="size-4" aria-hidden="true" /> <ArrowDownIcon aria-hidden="true" className="size-4" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>{t("components.datatable.actions.move_down")}</TooltipContent>
{t("components.datatable.actions.move_down")}
</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{!readOnly && meta?.bulkOps?.removeSelected && ( {!readOnly && meta?.bulkOps?.removeSelected && (
<> <>
<Separator <Separator className="h-5 mx-1 w-[1px] bg-red-500/70" orientation="vertical" />
orientation="vertical"
className="h-5 mx-1 w-[1px] bg-red-500/70"
/>
<Button <Button
type="button"
size="sm"
variant="destructive"
onClick={handleRemoveSelected}
aria-label={t("components.datatable.actions.remove")} aria-label={t("components.datatable.actions.remove")}
onClick={handleRemoveSelected}
size="sm"
type="button"
variant="destructive"
> >
<TrashIcon className="size-4 mr-1" aria-hidden="true" /> <TrashIcon aria-hidden="true" className="size-4 mr-1" />
<span>{t("components.datatable.actions.remove")}</span> <span>{t("components.datatable.actions.remove")}</span>
</Button> </Button>
</> </>
)} )}
<Separator orientation="vertical" className="h-6 mx-1 bg-muted/50" /> <Separator className="h-6 mx-1 bg-muted/50" orientation="vertical" />
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button onClick={handleClearSelection} size="sm" type="button" variant="outline">
type="button" <ScanIcon aria-hidden="true" className="size-4 mr-1" />
size="sm"
variant="outline"
onClick={handleClearSelection}
>
<ScanIcon className="size-4 mr-1" aria-hidden="true" />
<span>{t("components.datatable.actions.clear_selection")}</span> <span>{t("components.datatable.actions.clear_selection")}</span>
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>{t("components.datatable.actions.clear_selection")}</TooltipContent>
{t("components.datatable.actions.clear_selection")}
</TooltipContent>
</Tooltip> </Tooltip>
</> </>
)} )}
{/* Contador de selección */} {/* Contador de selección */}
<div <div aria-live="polite" className="text-sm text-muted-foreground ml-2">
className="text-sm text-muted-foreground ml-2"
aria-live="polite"
>
{hasSelection {hasSelection
? t("components.datatable.selection_summary", { ? t("components.datatable.selection_summary", {
count: selectedCount, count: selectedCount,
total: totalCount, total: totalCount,
}) })
: t("components.datatable.selection_none", { total: totalCount })} : t("components.datatable.selection_none", { total: totalCount })}
</div> </div>
</div> </div>
@ -205,4 +194,4 @@ export function DataTableToolbar<TData>({
); );
} }
export const MemoizedDataTableToolbar = React.memo(DataTableToolbar) as typeof DataTableToolbar; export const MemoizedDataTableToolbar = React.memo(DataTableToolbar) as typeof DataTableToolbar;

View File

@ -189,7 +189,7 @@ export function DataTable<TData, TValue>({
<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">
{/* CABECERA */} {/* CABECERA */}
<TableHeader className="sticky top-0 z-10 bg-muted"> <TableHeader className="sticky top-0 z-10 bg-muted/50">
{table.getHeaderGroups().map((hg) => ( {table.getHeaderGroups().map((hg) => (
<TableRow key={hg.id}> <TableRow key={hg.id}>
{hg.headers.map((h) => { {hg.headers.map((h) => {