FactuGES_ERP/packages/rdx-ui/src/components/datatable/data-table-toolbar.tsx

198 lines
6.8 KiB
TypeScript
Raw Normal View History

2025-11-16 21:15:23 +00:00
import {
Button,
Separator,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@repo/shadcn-ui/components";
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";
2025-10-18 19:57:52 +00:00
2025-10-16 11:18:55 +00:00
interface DataTableToolbarProps<TData> {
2025-10-18 19:57:52 +00:00
table: Table<TData>;
showViewOptions?: boolean;
className?: string;
2025-10-16 11:18:55 +00:00
}
export function DataTableToolbar<TData>({
table,
2025-10-16 17:59:13 +00:00
showViewOptions = true,
2025-11-16 21:15:23 +00:00
className,
2025-10-16 11:18:55 +00:00
}: DataTableToolbarProps<TData>) {
2025-10-18 19:57:52 +00:00
const { t } = useTranslation();
const meta = table.options.meta as DataTableMeta<TData> | undefined;
2025-10-16 17:59:13 +00:00
2025-10-18 19:57:52 +00:00
const readOnly = meta?.readOnly ?? false;
2025-10-16 17:59:13 +00:00
2025-10-18 19:57:52 +00:00
// Modelos y conteos
const allRows = table.getFilteredRowModel().rows;
const selectedRows = table.getSelectedRowModel().rows;
const totalCount = allRows.length;
const selectedCount = selectedRows.length;
2025-10-16 17:59:13 +00:00
const hasSelection = selectedCount > 0;
2025-10-18 19:57:52 +00:00
// Índices seleccionados (memoizado)
2025-11-16 21:15:23 +00:00
const selectedIndexes = React.useMemo(() => selectedRows.map((r) => r.index), [selectedRows]);
2025-10-18 19:57:52 +00:00
const handleAdd = React.useCallback(() => {
if (!readOnly) meta?.tableOps?.onAdd?.(table);
}, [meta, table, readOnly]);
const handleDuplicateSelected = React.useCallback(() => {
if (!readOnly) meta?.bulkOps?.duplicateSelected?.(selectedIndexes, table);
}, [meta, selectedIndexes, table, readOnly]);
const handleMoveSelectedUp = React.useCallback(() => {
if (!readOnly) meta?.bulkOps?.moveSelectedUp?.(selectedIndexes, table);
}, [meta, selectedIndexes, table, readOnly]);
const handleMoveSelectedDown = React.useCallback(() => {
if (!readOnly) meta?.bulkOps?.moveSelectedDown?.(selectedIndexes, table);
}, [meta, selectedIndexes, table, readOnly]);
2025-10-16 11:18:55 +00:00
2025-10-18 19:57:52 +00:00
const handleRemoveSelected = React.useCallback(() => {
if (!readOnly) meta?.bulkOps?.removeSelected?.(selectedIndexes, table);
}, [meta, selectedIndexes, table, readOnly]);
const handleClearSelection = React.useCallback(() => {
table.resetRowSelection();
}, [table]);
// Render principal
2025-10-16 11:18:55 +00:00
return (
2025-11-19 16:05:01 +00:00
<div className={cn("flex items-center justify-between gap-2 py-4 bg-transparent", className)}>
2025-10-18 19:57:52 +00:00
{/* IZQUIERDA: acciones + contador */}
<div className="flex flex-1 items-center gap-3 flex-wrap">
{/* Botón añadir */}
{!readOnly && meta?.tableOps?.onAdd && (
2025-10-16 17:59:13 +00:00
<Button
aria-label={t("components.datatable.actions.add")}
2025-11-16 21:15:23 +00:00
className="cursor-pointer"
onClick={handleAdd}
size="sm"
type="button"
variant={"outline"}
2025-10-16 17:59:13 +00:00
>
2025-11-16 21:15:23 +00:00
<PlusIcon aria-hidden="true" className="size-4 mr-1" />
2025-10-16 17:59:13 +00:00
<span>{t("components.datatable.actions.add")}</span>
</Button>
)}
2025-10-18 19:57:52 +00:00
{/* Acciones sobre selección */}
2025-10-16 17:59:13 +00:00
{hasSelection && (
<>
2025-11-16 21:15:23 +00:00
<Separator className="h-5 mx-1" orientation="vertical" />
2025-10-16 17:59:13 +00:00
2025-10-18 19:57:52 +00:00
{!readOnly && meta?.bulkOps?.duplicateSelected && (
2025-10-16 17:59:13 +00:00
<Button
2025-11-16 21:15:23 +00:00
aria-label={t("components.datatable.actions.duplicate")}
className="cursor-pointer"
onClick={handleDuplicateSelected}
2025-10-16 17:59:13 +00:00
size="sm"
2025-11-16 21:15:23 +00:00
type="button"
2025-10-16 17:59:13 +00:00
variant="outline"
>
2025-11-16 21:15:23 +00:00
<CopyPlusIcon aria-hidden="true" className="size-4 mr-1" />
2025-10-16 17:59:13 +00:00
<span>{t("components.datatable.actions.duplicate")}</span>
</Button>
)}
2025-10-16 11:18:55 +00:00
2025-10-18 19:57:52 +00:00
{!readOnly && meta?.bulkOps?.moveSelectedUp && (
2025-10-16 17:59:13 +00:00
<Tooltip>
<TooltipTrigger>
2025-10-16 17:59:13 +00:00
<Button
2025-11-16 21:15:23 +00:00
aria-label={t("components.datatable.actions.move_up")}
onClick={handleMoveSelectedUp}
2025-10-16 17:59:13 +00:00
size="sm"
2025-11-16 21:15:23 +00:00
type="button"
2025-10-16 17:59:13 +00:00
variant="outline"
>
2025-11-16 21:15:23 +00:00
<ArrowUpIcon aria-hidden="true" className="size-4" />
2025-10-16 17:59:13 +00:00
</Button>
</TooltipTrigger>
2025-11-16 21:15:23 +00:00
<TooltipContent>{t("components.datatable.actions.move_up")}</TooltipContent>
2025-10-16 17:59:13 +00:00
</Tooltip>
)}
2025-10-18 19:57:52 +00:00
{!readOnly && meta?.bulkOps?.moveSelectedDown && (
2025-10-16 17:59:13 +00:00
<Tooltip>
<TooltipTrigger>
2025-10-16 17:59:13 +00:00
<Button
2025-11-16 21:15:23 +00:00
aria-label={t("components.datatable.actions.move_down")}
onClick={handleMoveSelectedDown}
2025-10-16 17:59:13 +00:00
size="sm"
2025-11-16 21:15:23 +00:00
type="button"
2025-10-16 17:59:13 +00:00
variant="outline"
>
2025-11-16 21:15:23 +00:00
<ArrowDownIcon aria-hidden="true" className="size-4" />
2025-10-16 17:59:13 +00:00
</Button>
</TooltipTrigger>
2025-11-16 21:15:23 +00:00
<TooltipContent>{t("components.datatable.actions.move_down")}</TooltipContent>
2025-10-16 17:59:13 +00:00
</Tooltip>
)}
2025-10-18 19:57:52 +00:00
{!readOnly && meta?.bulkOps?.removeSelected && (
2025-10-16 17:59:13 +00:00
<>
2025-11-16 21:15:23 +00:00
<Separator className="h-5 mx-1 w-[1px] bg-red-500/70" orientation="vertical" />
2025-10-16 17:59:13 +00:00
<Button
2025-11-16 21:15:23 +00:00
aria-label={t("components.datatable.actions.remove")}
onClick={handleRemoveSelected}
2025-10-16 17:59:13 +00:00
size="sm"
2025-11-16 21:15:23 +00:00
type="button"
2025-10-16 17:59:13 +00:00
variant="destructive"
>
2025-11-16 21:15:23 +00:00
<TrashIcon aria-hidden="true" className="size-4 mr-1" />
2025-10-16 17:59:13 +00:00
<span>{t("components.datatable.actions.remove")}</span>
</Button>
</>
)}
2025-11-16 21:15:23 +00:00
<Separator className="h-6 mx-1 bg-muted/50" orientation="vertical" />
2025-10-18 19:57:52 +00:00
2025-10-16 17:59:13 +00:00
<Tooltip>
<TooltipTrigger>
2025-11-16 21:15:23 +00:00
<Button onClick={handleClearSelection} size="sm" type="button" variant="outline">
<ScanIcon aria-hidden="true" className="size-4 mr-1" />
2025-10-18 19:57:52 +00:00
<span>{t("components.datatable.actions.clear_selection")}</span>
2025-10-16 17:59:13 +00:00
</Button>
</TooltipTrigger>
2025-11-16 21:15:23 +00:00
<TooltipContent>{t("components.datatable.actions.clear_selection")}</TooltipContent>
2025-10-16 17:59:13 +00:00
</Tooltip>
</>
)}
2025-10-18 19:57:52 +00:00
{/* Contador de selección */}
2025-11-16 21:15:23 +00:00
<div aria-live="polite" className="text-sm text-muted-foreground ml-2">
2025-10-18 19:57:52 +00:00
{hasSelection
? t("components.datatable.selection_summary", {
2025-11-16 21:15:23 +00:00
count: selectedCount,
total: totalCount,
})
2025-10-18 19:57:52 +00:00
: t("components.datatable.selection_none", { total: totalCount })}
</div>
2025-10-16 11:18:55 +00:00
</div>
2025-10-16 17:59:13 +00:00
2025-10-18 19:57:52 +00:00
{/* DERECHA: opciones de vista */}
2025-10-16 11:18:55 +00:00
<div className="flex items-center gap-2">
2025-10-18 19:57:52 +00:00
{showViewOptions && !readOnly && <DataTableViewOptions table={table} />}
2025-10-16 11:18:55 +00:00
</div>
</div>
2025-10-18 19:57:52 +00:00
);
2025-10-16 11:18:55 +00:00
}
2025-10-18 19:57:52 +00:00
2025-11-16 21:15:23 +00:00
export const MemoizedDataTableToolbar = React.memo(DataTableToolbar) as typeof DataTableToolbar;