- 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.
156 lines
5.2 KiB
TypeScript
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`;
|
|
}
|