metis-desk/packages/rdx-ui/src/components/entity-table-content.tsx
David ccb52fb534 feat: add catalog field picker dialog and list components
- Introduced `CatalogFieldPickerDialog` and `CatalogFieldPickerList` for selecting catalog fields.
- Implemented `appendTemplateFields` helper function for managing template fields.
- Added tests for catalog field selection and template field management.
- Created reusable components for field picking and reordering in the UI library.
- Enhanced drag-and-drop functionality for reordering items in lists.
2026-10-15 15:58:01 +02:00

156 lines
5.2 KiB
TypeScript

import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@repo/shadcn-ui/components/table";
import type { Table as TableModel } from "@tanstack/react-table";
import type { EntityColumn } from "./entity-list";
export function EntityTableContent<T extends { id: string }>({
table,
columns,
title,
loading,
unavailable,
message,
rowClassName,
onRowClick,
onSort,
}: {
table: TableModel<T>;
columns: readonly EntityColumn<T>[];
title: string;
loading: boolean;
unavailable: boolean;
message: string;
rowClassName: ((item: T) => string) | undefined;
onRowClick: ((item: T) => void) | undefined;
onSort: () => void;
}) {
const visibleColumns = [
...table.getCenterVisibleLeafColumns(),
...table.getRightVisibleLeafColumns(),
];
const total = table.getFilteredRowModel().rows.length;
return (
<Table aria-label={title}>
<TableHeader>
<TableRow>
{visibleColumns.map((column) => {
const direction = column.getIsSorted();
return (
<TableHead
aria-sort={
direction === "asc"
? "ascending"
: direction === "desc"
? "descending"
: undefined
}
className={entityColumnClass(column.id, column.getIsPinned() === "right", true)}
data-pinned={column.getIsPinned() || undefined}
key={column.id}
scope="col"
>
{column.getCanSort() ? (
<button
className="inline-flex items-center gap-2 rounded py-1 text-left focus-visible:outline-2 focus-visible:outline-ring"
onClick={() => {
column.toggleSorting();
onSort();
}}
type="button"
>
{String(column.columnDef.header)}
<span aria-hidden="true">
{direction === "asc" ? "↑" : direction === "desc" ? "↓" : "↕"}
</span>
</button>
) : (
<span>{String(column.columnDef.header)}</span>
)}
</TableHead>
);
})}
</TableRow>
</TableHeader>
<TableBody>
{loading || unavailable ? (
<TableRow>
<TableCell colSpan={visibleColumns.length}>
<span className="text-muted-foreground">{message}</span>
</TableCell>
</TableRow>
) : (
table.getRowModel().rows.map((row) => (
<TableRow
className={`group/entity-row ${onRowClick ? "cursor-pointer" : ""} ${rowClassName?.(row.original) ?? ""}`}
key={row.id}
onClick={
onRowClick
? (event) => {
// Los controles de la fila conservan su interacción propia.
if (
event.defaultPrevented ||
!(event.target instanceof Element) ||
event.target.closest(
'a, button, input, select, textarea, label, [role="button"], [role="menuitem"], [role="checkbox"], [role="radio"]'
)
) {
return;
}
onRowClick(row.original);
}
: undefined
}
>
{visibleColumns.map((column) => (
<TableCell
className={entityColumnClass(column.id, column.getIsPinned() === "right", false)}
data-pinned={column.getIsPinned() || undefined}
key={column.id}
>
{columns.find((item) => item.id === column.id)?.render(row.original)}
</TableCell>
))}
</TableRow>
))
)}
{!(loading || unavailable || total) && (
<TableRow>
<TableCell className="p-6 text-muted-foreground" colSpan={visibleColumns.length}>
{message}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
);
}
function entityColumnClass(columnId: string, pinned: boolean, header: boolean) {
if (columnId === "selection") {
const labelVisibility = header ? "[&>span]:sr-only" : "";
return `w-px min-w-0 py-3 pr-1 pl-4 text-center whitespace-nowrap [&:has([role=checkbox])]:pr-1 ${labelVisibility}`;
}
if (pinned) {
return header
? "sticky right-0 z-20 w-20 min-w-20 bg-card pr-4 text-right"
: "sticky right-0 z-10 h-10 w-20 min-w-20 bg-card px-3 py-3 pr-4 text-right whitespace-nowrap group-hover/entity-row:bg-[color-mix(in_oklch,var(--muted)_50%,var(--card))] group-has-aria-expanded/entity-row:bg-[color-mix(in_oklch,var(--muted)_50%,var(--card))] ";
}
const nameWidth = columnId === "name" ? "min-w-40" : "first:min-w-40";
return header
? `${nameWidth} bg-background font-medium`
: `h-14 ${nameWidth} max-w-lg whitespace-normal break-words px-3 py-3`;
}