@asteby/metacore-runtime-react 18.11.0 → 18.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +29 -0
- package/dist/dynamic-table.d.ts +9 -3
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +6 -5
- package/package.json +1 -1
- package/src/dialogs/dynamic-record.tsx +48 -0
- package/src/dynamic-table.tsx +23 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 18.12.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e568344: ViewValue: render structured jsonb values (objects/arrays without a label/name/title) as readable key→value pairs instead of "[object Object]" — e.g. a `fiscal_data` jsonb column on the record detail page. Plain objects become a humanized key/value list, primitive arrays a comma-joined line, nested structures a pretty-printed JSON block; empty structures render the "—" empty marker.
|
|
8
|
+
|
|
9
|
+
## 18.12.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- e661c1f: Add `onRowClick` prop to `DynamicTable` — when provided, each data row becomes clickable (cursor-pointer) and calls `onRowClick(row)` on click. Clicks on the checkbox (select column) and action buttons are stopped from propagating so they do not trigger the row handler. Behaviour is unchanged when the prop is not supplied.
|
|
14
|
+
|
|
3
15
|
## 18.11.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
|
|
@@ -123,6 +123,9 @@ function formatDisplayValue(rawValue, field) {
|
|
|
123
123
|
// when no declared option matches the value.
|
|
124
124
|
return match?.label ?? humanizeToken(value);
|
|
125
125
|
}
|
|
126
|
+
// Structured value with no label — JSON beats "[object Object]".
|
|
127
|
+
if (typeof value === 'object')
|
|
128
|
+
return JSON.stringify(value);
|
|
126
129
|
return String(value);
|
|
127
130
|
}
|
|
128
131
|
const MODE_CONFIG = {
|
|
@@ -574,12 +577,38 @@ export function ViewValue({ field, value: rawValue, record, getImageUrl: getImag
|
|
|
574
577
|
};
|
|
575
578
|
return (_jsxs(Badge, { variant: "secondary", className: "w-fit flex items-center gap-1", style: opt.color && !opt.icon ? { backgroundColor: opt.color, color: '#fff', borderColor: 'transparent' } : undefined, children: [_jsx(OptionLead, { option: lead, size: 16 }), opt.label] }));
|
|
576
579
|
}
|
|
580
|
+
// Structured value (jsonb column, e.g. fiscal_data) with no label/name/title
|
|
581
|
+
// to surface — render readable key/value pairs instead of falling through to
|
|
582
|
+
// String(value) ("[object Object]").
|
|
583
|
+
if (value !== null && typeof value === 'object') {
|
|
584
|
+
return _jsx(StructuredViewValue, { value: value });
|
|
585
|
+
}
|
|
577
586
|
const display = formatDisplayValue(value, field);
|
|
578
587
|
if (field.type === 'textarea') {
|
|
579
588
|
return (_jsx("p", { className: "text-sm whitespace-pre-wrap rounded-md bg-muted/40 p-3 min-h-[60px]", children: display }));
|
|
580
589
|
}
|
|
581
590
|
return _jsx("p", { className: "text-sm py-1", children: display });
|
|
582
591
|
}
|
|
592
|
+
// StructuredViewValue renders a jsonb object/array that has no resolvable label:
|
|
593
|
+
// plain objects become a key→value list (keys humanized), primitive arrays a
|
|
594
|
+
// comma-joined line, and anything deeper a pretty-printed JSON block. Empty
|
|
595
|
+
// structures render the same "—" marker as null scalars.
|
|
596
|
+
function StructuredViewValue({ value }) {
|
|
597
|
+
if (Array.isArray(value)) {
|
|
598
|
+
if (value.length === 0) {
|
|
599
|
+
return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
|
|
600
|
+
}
|
|
601
|
+
if (value.every(v => v === null || typeof v !== 'object')) {
|
|
602
|
+
return _jsx("p", { className: "text-sm py-1", children: value.map(v => String(v ?? '—')).join(', ') });
|
|
603
|
+
}
|
|
604
|
+
return (_jsx("pre", { className: "text-xs whitespace-pre-wrap rounded-md bg-muted/40 p-3 overflow-x-auto", children: JSON.stringify(value, null, 2) }));
|
|
605
|
+
}
|
|
606
|
+
const entries = Object.entries(value).filter(([, v]) => v !== null && v !== undefined && v !== '');
|
|
607
|
+
if (entries.length === 0) {
|
|
608
|
+
return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
|
|
609
|
+
}
|
|
610
|
+
return (_jsx("dl", { className: "text-sm py-1 space-y-0.5", children: entries.map(([k, v]) => (_jsxs("div", { className: "flex gap-2", children: [_jsxs("dt", { className: "text-muted-foreground shrink-0", children: [humanizeToken(k), ":"] }), _jsx("dd", { className: "break-words", children: typeof v === 'object' ? JSON.stringify(v) : String(v) })] }, k))) }));
|
|
611
|
+
}
|
|
583
612
|
function EditField({ field, value, onChange }) {
|
|
584
613
|
if (field.type === 'boolean') {
|
|
585
614
|
return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx(Switch, { checked: !!value, onCheckedChange: onChange }), _jsx("span", { className: "text-sm text-muted-foreground", children: value ? 'Sí' : 'No' })] }));
|
package/dist/dynamic-table.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { type ColumnDef } from '@tanstack/react-table';
|
|
2
2
|
import type { GetDynamicColumns } from './dynamic-columns-shim';
|
|
3
|
-
interface DynamicTableProps {
|
|
3
|
+
export interface DynamicTableProps {
|
|
4
4
|
model: string;
|
|
5
5
|
endpoint?: string;
|
|
6
6
|
enableUrlSync?: boolean;
|
|
7
7
|
hiddenColumns?: string[];
|
|
8
8
|
onAction?: (action: string, row: any) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Called when the user clicks anywhere on a data row (not on a checkbox,
|
|
11
|
+
* action button, or interactive element inside the cell). When provided,
|
|
12
|
+
* each row becomes focusable (cursor-pointer). Absent → rows are not
|
|
13
|
+
* clickable and the behaviour is unchanged.
|
|
14
|
+
*/
|
|
15
|
+
onRowClick?: (row: any) => void;
|
|
9
16
|
refreshTrigger?: any;
|
|
10
17
|
defaultFilters?: Record<string, any>;
|
|
11
18
|
extraColumns?: ColumnDef<any>[];
|
|
@@ -30,6 +37,5 @@ interface DynamicTableProps {
|
|
|
30
37
|
*/
|
|
31
38
|
currency?: string;
|
|
32
39
|
}
|
|
33
|
-
export declare function DynamicTable({ model, endpoint, enableUrlSync, hiddenColumns, onAction, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, timeZone, currency, }: DynamicTableProps): import("react").JSX.Element;
|
|
34
|
-
export {};
|
|
40
|
+
export declare function DynamicTable({ model, endpoint, enableUrlSync, hiddenColumns, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, timeZone, currency, }: DynamicTableProps): import("react").JSX.Element;
|
|
35
41
|
//# sourceMappingURL=dynamic-table.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,GACX,EAAE,iBAAiB,+BA+3BnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -31,7 +31,7 @@ import { getSearchableColumnKeys } from './column-visibility';
|
|
|
31
31
|
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
32
32
|
import { ExportDialog } from './dialogs/export';
|
|
33
33
|
import { ImportDialog } from './dialogs/import';
|
|
34
|
-
export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColumns = [], onAction, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, }) {
|
|
34
|
+
export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColumns = [], onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, }) {
|
|
35
35
|
const { t, i18n } = useTranslation();
|
|
36
36
|
const api = useApi();
|
|
37
37
|
const currentBranch = useCurrentBranch();
|
|
@@ -623,9 +623,10 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
623
623
|
return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [metadata.canExport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), metadata.canImport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsx("div", { className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
|
|
624
624
|
const isActionsColumn = header.id === 'actions';
|
|
625
625
|
return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
626
|
-
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (_jsxs(_Fragment, { children: [table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', children: row.getVisibleCells().map((cell) => {
|
|
626
|
+
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (_jsxs(_Fragment, { children: [table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
|
|
627
627
|
const isActionsColumn = cell.column.id === 'actions';
|
|
628
|
-
|
|
628
|
+
const isSelectColumn = cell.column.id === 'select';
|
|
629
|
+
return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), onClick: (isActionsColumn || isSelectColumn) ? (e) => e.stopPropagation() : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
629
630
|
}) }, row.id))), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0' }) }))] })) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })] }) }) })) }), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableFooter, { className: "bg-transparent", children: _jsx(TableRow, { className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((leaf, idx) => {
|
|
630
631
|
const col = (metadata?.columns ?? []).find((c) => c.key === leaf.id);
|
|
631
632
|
const isFirst = idx === 0;
|
|
@@ -640,10 +641,10 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
640
641
|
const cells = row.getVisibleCells();
|
|
641
642
|
const actionsCell = cells.find((c) => c.column.id === 'actions');
|
|
642
643
|
const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
|
|
643
|
-
return (_jsxs("div", { "data-state": row.getIsSelected() && 'selected', className: 'flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', children: [dataCells.map((cell) => {
|
|
644
|
+
return (_jsxs("div", { "data-state": row.getIsSelected() && 'selected', className: cn('flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: [dataCells.map((cell) => {
|
|
644
645
|
const header = cell.column.columnDef.header;
|
|
645
646
|
const label = typeof header === 'string' ? header : cell.column.id;
|
|
646
647
|
return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 break-words text-right font-medium', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
|
|
647
|
-
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
648
|
+
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
648
649
|
})) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })) }), _jsx("div", { className: 'shrink-0 pt-4', children: _jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions }) })] }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEst\u00E1 absolutamente seguro?" }), _jsx(AlertDialogDescription, { children: "Esta acci\u00F3n no se puede deshacer. Esto eliminar\u00E1 permanentemente el registro seleccionado de nuestros servidores." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: isDeleting, children: isDeleting ? 'Eliminando...' : 'Eliminar' })] })] }) }), _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), _jsx(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: handleRefresh }), metadata.canExport && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), metadata.canImport && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), actionModal.action && (_jsx(ActionModalDispatcher, { open: actionModal.open, onOpenChange: (open) => setActionModal((prev) => ({ ...prev, open })), action: actionModal.action, model: model, record: actionModal.record, endpoint: endpoint, onSuccess: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
|
|
649
650
|
}
|
package/package.json
CHANGED
|
@@ -323,6 +323,9 @@ function formatDisplayValue(rawValue: any, field: FieldDef): string {
|
|
|
323
323
|
return match?.label ?? humanizeToken(value)
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
// Structured value with no label — JSON beats "[object Object]".
|
|
327
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
328
|
+
|
|
326
329
|
return String(value)
|
|
327
330
|
}
|
|
328
331
|
|
|
@@ -1048,6 +1051,13 @@ export function ViewValue({
|
|
|
1048
1051
|
)
|
|
1049
1052
|
}
|
|
1050
1053
|
|
|
1054
|
+
// Structured value (jsonb column, e.g. fiscal_data) with no label/name/title
|
|
1055
|
+
// to surface — render readable key/value pairs instead of falling through to
|
|
1056
|
+
// String(value) ("[object Object]").
|
|
1057
|
+
if (value !== null && typeof value === 'object') {
|
|
1058
|
+
return <StructuredViewValue value={value} />
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1051
1061
|
const display = formatDisplayValue(value, field)
|
|
1052
1062
|
|
|
1053
1063
|
if (field.type === 'textarea') {
|
|
@@ -1061,6 +1071,44 @@ export function ViewValue({
|
|
|
1061
1071
|
return <p className="text-sm py-1">{display}</p>
|
|
1062
1072
|
}
|
|
1063
1073
|
|
|
1074
|
+
// StructuredViewValue renders a jsonb object/array that has no resolvable label:
|
|
1075
|
+
// plain objects become a key→value list (keys humanized), primitive arrays a
|
|
1076
|
+
// comma-joined line, and anything deeper a pretty-printed JSON block. Empty
|
|
1077
|
+
// structures render the same "—" marker as null scalars.
|
|
1078
|
+
function StructuredViewValue({ value }: { value: any }) {
|
|
1079
|
+
if (Array.isArray(value)) {
|
|
1080
|
+
if (value.length === 0) {
|
|
1081
|
+
return <p className="text-sm py-1 text-muted-foreground">—</p>
|
|
1082
|
+
}
|
|
1083
|
+
if (value.every(v => v === null || typeof v !== 'object')) {
|
|
1084
|
+
return <p className="text-sm py-1">{value.map(v => String(v ?? '—')).join(', ')}</p>
|
|
1085
|
+
}
|
|
1086
|
+
return (
|
|
1087
|
+
<pre className="text-xs whitespace-pre-wrap rounded-md bg-muted/40 p-3 overflow-x-auto">
|
|
1088
|
+
{JSON.stringify(value, null, 2)}
|
|
1089
|
+
</pre>
|
|
1090
|
+
)
|
|
1091
|
+
}
|
|
1092
|
+
const entries = Object.entries(value).filter(
|
|
1093
|
+
([, v]) => v !== null && v !== undefined && v !== '',
|
|
1094
|
+
)
|
|
1095
|
+
if (entries.length === 0) {
|
|
1096
|
+
return <p className="text-sm py-1 text-muted-foreground">—</p>
|
|
1097
|
+
}
|
|
1098
|
+
return (
|
|
1099
|
+
<dl className="text-sm py-1 space-y-0.5">
|
|
1100
|
+
{entries.map(([k, v]) => (
|
|
1101
|
+
<div key={k} className="flex gap-2">
|
|
1102
|
+
<dt className="text-muted-foreground shrink-0">{humanizeToken(k)}:</dt>
|
|
1103
|
+
<dd className="break-words">
|
|
1104
|
+
{typeof v === 'object' ? JSON.stringify(v) : String(v)}
|
|
1105
|
+
</dd>
|
|
1106
|
+
</div>
|
|
1107
|
+
))}
|
|
1108
|
+
</dl>
|
|
1109
|
+
)
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1064
1112
|
function EditField({ field, value, onChange }: {
|
|
1065
1113
|
field: FieldDef
|
|
1066
1114
|
value: any
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -75,12 +75,19 @@ import { DynamicRecordDialog } from './dialogs/dynamic-record'
|
|
|
75
75
|
import { ExportDialog } from './dialogs/export'
|
|
76
76
|
import { ImportDialog } from './dialogs/import'
|
|
77
77
|
|
|
78
|
-
interface DynamicTableProps {
|
|
78
|
+
export interface DynamicTableProps {
|
|
79
79
|
model: string
|
|
80
80
|
endpoint?: string
|
|
81
81
|
enableUrlSync?: boolean
|
|
82
82
|
hiddenColumns?: string[]
|
|
83
83
|
onAction?: (action: string, row: any) => void
|
|
84
|
+
/**
|
|
85
|
+
* Called when the user clicks anywhere on a data row (not on a checkbox,
|
|
86
|
+
* action button, or interactive element inside the cell). When provided,
|
|
87
|
+
* each row becomes focusable (cursor-pointer). Absent → rows are not
|
|
88
|
+
* clickable and the behaviour is unchanged.
|
|
89
|
+
*/
|
|
90
|
+
onRowClick?: (row: any) => void
|
|
84
91
|
refreshTrigger?: any
|
|
85
92
|
defaultFilters?: Record<string, any>
|
|
86
93
|
extraColumns?: ColumnDef<any>[]
|
|
@@ -112,6 +119,7 @@ export function DynamicTable({
|
|
|
112
119
|
enableUrlSync = true,
|
|
113
120
|
hiddenColumns = [],
|
|
114
121
|
onAction,
|
|
122
|
+
onRowClick,
|
|
115
123
|
refreshTrigger,
|
|
116
124
|
defaultFilters,
|
|
117
125
|
extraColumns = [],
|
|
@@ -781,14 +789,21 @@ export function DynamicTable({
|
|
|
781
789
|
) : table.getRowModel().rows?.length ? (
|
|
782
790
|
<>
|
|
783
791
|
{table.getRowModel().rows.map((row: Row<any>) => (
|
|
784
|
-
<TableRow
|
|
792
|
+
<TableRow
|
|
793
|
+
key={row.id}
|
|
794
|
+
data-state={row.getIsSelected() && 'selected'}
|
|
795
|
+
className={cn(onRowClick && 'cursor-pointer')}
|
|
796
|
+
onClick={onRowClick ? () => onRowClick(row.original) : undefined}
|
|
797
|
+
>
|
|
785
798
|
{row.getVisibleCells().map((cell: Cell<any, unknown>) => {
|
|
786
799
|
const isActionsColumn = cell.column.id === 'actions'
|
|
800
|
+
const isSelectColumn = cell.column.id === 'select'
|
|
787
801
|
return (
|
|
788
802
|
<TableCell
|
|
789
803
|
key={cell.id}
|
|
790
804
|
style={cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined}
|
|
791
805
|
className={cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]')}
|
|
806
|
+
onClick={(isActionsColumn || isSelectColumn) ? (e: React.MouseEvent) => e.stopPropagation() : undefined}
|
|
792
807
|
>
|
|
793
808
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
794
809
|
</TableCell>
|
|
@@ -888,7 +903,8 @@ export function DynamicTable({
|
|
|
888
903
|
<div
|
|
889
904
|
key={row.id}
|
|
890
905
|
data-state={row.getIsSelected() && 'selected'}
|
|
891
|
-
className='flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40'
|
|
906
|
+
className={cn('flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', onRowClick && 'cursor-pointer')}
|
|
907
|
+
onClick={onRowClick ? () => onRowClick(row.original) : undefined}
|
|
892
908
|
>
|
|
893
909
|
{dataCells.map((cell: Cell<any, unknown>) => {
|
|
894
910
|
const header = cell.column.columnDef.header
|
|
@@ -903,7 +919,10 @@ export function DynamicTable({
|
|
|
903
919
|
)
|
|
904
920
|
})}
|
|
905
921
|
{actionsCell && (
|
|
906
|
-
<div
|
|
922
|
+
<div
|
|
923
|
+
className='flex justify-end border-t pt-2'
|
|
924
|
+
onClick={onRowClick ? (e: React.MouseEvent) => e.stopPropagation() : undefined}
|
|
925
|
+
>
|
|
907
926
|
{flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext())}
|
|
908
927
|
</div>
|
|
909
928
|
)}
|