@asteby/metacore-runtime-react 13.6.0 → 13.8.0
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/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +29 -8
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +11 -2
- package/package.json +3 -3
- package/src/dynamic-select-field.tsx +41 -3
- package/src/dynamic-table.tsx +61 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 13.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ab80937: feat(dynamic-select): inline "+" to create the referenced record. A dynamic_select with a `ref` now shows a "+" button that opens the referenced model's OWN create modal (via a decoupled `metacore:create-record` window event the host handles) and auto-selects the newly created record. Lets users add a missing Category/Brand/etc. without leaving the form.
|
|
8
|
+
|
|
9
|
+
## 13.7.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 33165d2: feat(dynamic-table): card-per-row layout on mobile. On phones the multi-column data table forced a wide horizontal scroll; below the `sm` breakpoint the table is now replaced by a stacked card list (one card per row, columns shown as label : value pairs, row actions pinned at the bottom). Desktop keeps the classic table.
|
|
14
|
+
|
|
3
15
|
## 13.6.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAW7C,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAW7C,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CAgJrF;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -24,7 +24,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
24
24
|
// case — start empty and never hit this.
|
|
25
25
|
import { useEffect, useState } from 'react';
|
|
26
26
|
import { Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, } from '@asteby/metacore-ui/primitives';
|
|
27
|
-
import { Check, ChevronsUpDown, Loader2 } from 'lucide-react';
|
|
27
|
+
import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
|
|
28
28
|
import { useOptionsResolver } from './use-options-resolver';
|
|
29
29
|
function useDebounced(value, ms) {
|
|
30
30
|
const [debounced, setDebounced] = useState(value);
|
|
@@ -63,12 +63,33 @@ export function DynamicSelectField({ field, value, onChange }) {
|
|
|
63
63
|
setOpen(false);
|
|
64
64
|
setSearch('');
|
|
65
65
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
// Inline-create: the "+" opens the REFERENCED model's own create modal (the
|
|
67
|
+
// real one the host renders for that model — full fields, not a duplicate),
|
|
68
|
+
// via a decoupled window event the host listens for. On success the host
|
|
69
|
+
// hands back the new record and we select it immediately. No host import →
|
|
70
|
+
// no circular dependency; works for ANY dynamic_select with a `ref`.
|
|
71
|
+
const openCreate = () => {
|
|
72
|
+
if (!field.ref || typeof window === 'undefined')
|
|
73
|
+
return;
|
|
74
|
+
window.dispatchEvent(new CustomEvent('metacore:create-record', {
|
|
75
|
+
detail: {
|
|
76
|
+
model: field.ref,
|
|
77
|
+
onCreated: (rec) => {
|
|
78
|
+
if (rec && rec.id != null) {
|
|
79
|
+
const id = String(rec.id);
|
|
80
|
+
const label = String(rec.name ?? rec.label ?? rec.title ?? rec.id);
|
|
81
|
+
handlePick({ id, value: id, label, name: label });
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
};
|
|
87
|
+
return (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, className: "w-full justify-between font-normal", "data-empty": !value, children: [_jsx("span", { className: 'truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: selectedLabel || field.placeholder || 'Buscar…' }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
|
|
88
|
+
// Match the trigger width without an arbitrary Tailwind class
|
|
89
|
+
// (those don't always survive a consuming app's Tailwind scan).
|
|
90
|
+
style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: field.placeholder || 'Buscar…', value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: debounced ? 'Sin resultados' : 'Escribí para buscar…' })), !loading && options.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: options.map((opt) => {
|
|
91
|
+
const isSel = String(opt.id) === String(value);
|
|
92
|
+
return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 ' + (isSel ? 'opacity-100' : 'opacity-0') }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: opt.label }), opt.description && (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: opt.description }))] })] }, String(opt.id)));
|
|
93
|
+
}) }))] })] }) })] }), field.ref && (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: openCreate, title: `Crear ${field.label ?? field.ref}`, "aria-label": `Crear ${field.label ?? field.ref}`, children: _jsx(Plus, { className: "size-4" }) }))] }));
|
|
73
94
|
}
|
|
74
95
|
export default DynamicSelectField;
|
|
@@ -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;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,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,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;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,
|
|
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;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,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,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;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CAixBnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -574,11 +574,20 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
574
574
|
if (!metadata) {
|
|
575
575
|
return _jsx("div", { className: "text-center text-muted-foreground py-8", children: "Error al cargar la configuraci\u00F3n de la tabla." });
|
|
576
576
|
}
|
|
577
|
-
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: 'flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { noWrapper: true, className: "min-w-max w-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) => {
|
|
577
|
+
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: "min-w-max w-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) => {
|
|
578
578
|
const isActionsColumn = header.id === 'actions';
|
|
579
579
|
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));
|
|
580
580
|
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', children: row.getVisibleCells().map((cell) => {
|
|
581
581
|
const isActionsColumn = cell.column.id === 'actions';
|
|
582
582
|
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)]'), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
583
|
-
}) }, row.id)))) : (_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." })] })] }) }) })) })] }) }), _jsx("div", { className: '
|
|
583
|
+
}) }, row.id)))) : (_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." })] })] }) }) })) })] }) }), _jsx("div", { className: 'flex flex-1 min-h-0 flex-col gap-2 overflow-y-auto sm:hidden', children: loadingData && data.length === 0 ? (Array.from({ length: 5 }).map((_, i) => (_jsxs("div", { className: 'rounded-lg border bg-card p-3', children: [_jsx(Skeleton, { className: 'h-4 w-24' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-40' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-32' })] }, i)))) : table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => {
|
|
584
|
+
const cells = row.getVisibleCells();
|
|
585
|
+
const actionsCell = cells.find((c) => c.column.id === 'actions');
|
|
586
|
+
const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
|
|
587
|
+
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) => {
|
|
588
|
+
const header = cell.column.columnDef.header;
|
|
589
|
+
const label = typeof header === 'string' ? header : cell.column.id;
|
|
590
|
+
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));
|
|
591
|
+
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
592
|
+
})) : (_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"] }) })] }));
|
|
584
593
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.8.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"date-fns": ">=3",
|
|
35
35
|
"react-day-picker": ">=8",
|
|
36
36
|
"@asteby/metacore-sdk": "^3.1.0",
|
|
37
|
-
"@asteby/metacore-ui": "^2.1.
|
|
37
|
+
"@asteby/metacore-ui": "^2.1.1"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@tanstack/react-router": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"vitest": "^4.0.0",
|
|
63
63
|
"zustand": "^5.0.0",
|
|
64
64
|
"@asteby/metacore-sdk": "3.1.0",
|
|
65
|
-
"@asteby/metacore-ui": "2.1.
|
|
65
|
+
"@asteby/metacore-ui": "2.1.1"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
PopoverContent,
|
|
35
35
|
PopoverTrigger,
|
|
36
36
|
} from '@asteby/metacore-ui/primitives'
|
|
37
|
-
import { Check, ChevronsUpDown, Loader2 } from 'lucide-react'
|
|
37
|
+
import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react'
|
|
38
38
|
import { useOptionsResolver, type ResolvedOption } from './use-options-resolver'
|
|
39
39
|
import type { ActionFieldDef } from './types'
|
|
40
40
|
|
|
@@ -87,8 +87,32 @@ export function DynamicSelectField({ field, value, onChange }: DynamicSelectFiel
|
|
|
87
87
|
setSearch('')
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Inline-create: the "+" opens the REFERENCED model's own create modal (the
|
|
91
|
+
// real one the host renders for that model — full fields, not a duplicate),
|
|
92
|
+
// via a decoupled window event the host listens for. On success the host
|
|
93
|
+
// hands back the new record and we select it immediately. No host import →
|
|
94
|
+
// no circular dependency; works for ANY dynamic_select with a `ref`.
|
|
95
|
+
const openCreate = () => {
|
|
96
|
+
if (!field.ref || typeof window === 'undefined') return
|
|
97
|
+
window.dispatchEvent(
|
|
98
|
+
new CustomEvent('metacore:create-record', {
|
|
99
|
+
detail: {
|
|
100
|
+
model: field.ref,
|
|
101
|
+
onCreated: (rec: any) => {
|
|
102
|
+
if (rec && rec.id != null) {
|
|
103
|
+
const id = String(rec.id)
|
|
104
|
+
const label = String(rec.name ?? rec.label ?? rec.title ?? rec.id)
|
|
105
|
+
handlePick({ id, value: id, label, name: label })
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
90
113
|
return (
|
|
91
|
-
<
|
|
114
|
+
<div className="flex items-center gap-1.5">
|
|
115
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
92
116
|
<PopoverTrigger asChild>
|
|
93
117
|
<Button
|
|
94
118
|
type="button"
|
|
@@ -157,7 +181,21 @@ export function DynamicSelectField({ field, value, onChange }: DynamicSelectFiel
|
|
|
157
181
|
</CommandList>
|
|
158
182
|
</Command>
|
|
159
183
|
</PopoverContent>
|
|
160
|
-
|
|
184
|
+
</Popover>
|
|
185
|
+
{field.ref && (
|
|
186
|
+
<Button
|
|
187
|
+
type="button"
|
|
188
|
+
variant="outline"
|
|
189
|
+
size="icon"
|
|
190
|
+
className="size-9 shrink-0"
|
|
191
|
+
onClick={openCreate}
|
|
192
|
+
title={`Crear ${field.label ?? field.ref}`}
|
|
193
|
+
aria-label={`Crear ${field.label ?? field.ref}`}
|
|
194
|
+
>
|
|
195
|
+
<Plus className="size-4" />
|
|
196
|
+
</Button>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
161
199
|
)
|
|
162
200
|
}
|
|
163
201
|
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -687,7 +687,10 @@ export function DynamicTable({
|
|
|
687
687
|
}
|
|
688
688
|
/>
|
|
689
689
|
</div>
|
|
690
|
-
|
|
690
|
+
{/* Desktop: classic horizontal-scroll table. Hidden on phones —
|
|
691
|
+
a 7-column table forces a wide horizontal scroll there, so we
|
|
692
|
+
render a card-per-row list instead (see MobileCards below). */}
|
|
693
|
+
<div className='hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card'>
|
|
691
694
|
<Table noWrapper className="min-w-max w-full">
|
|
692
695
|
<TableHeader className='sticky top-0 z-10'>
|
|
693
696
|
{table.getHeaderGroups().map((headerGroup: HeaderGroup<any>) => (
|
|
@@ -746,6 +749,63 @@ export function DynamicTable({
|
|
|
746
749
|
</TableBody>
|
|
747
750
|
</Table>
|
|
748
751
|
</div>
|
|
752
|
+
|
|
753
|
+
{/* Mobile: one card per row — no horizontal scroll. Each card
|
|
754
|
+
stacks its columns as label : value pairs with the row actions
|
|
755
|
+
pinned at the bottom. */}
|
|
756
|
+
<div className='flex flex-1 min-h-0 flex-col gap-2 overflow-y-auto sm:hidden'>
|
|
757
|
+
{loadingData && data.length === 0 ? (
|
|
758
|
+
Array.from({ length: 5 }).map((_, i) => (
|
|
759
|
+
<div key={i} className='rounded-lg border bg-card p-3'>
|
|
760
|
+
<Skeleton className='h-4 w-24' />
|
|
761
|
+
<Skeleton className='mt-2 h-4 w-40' />
|
|
762
|
+
<Skeleton className='mt-2 h-4 w-32' />
|
|
763
|
+
</div>
|
|
764
|
+
))
|
|
765
|
+
) : table.getRowModel().rows?.length ? (
|
|
766
|
+
table.getRowModel().rows.map((row: Row<any>) => {
|
|
767
|
+
const cells = row.getVisibleCells()
|
|
768
|
+
const actionsCell = cells.find((c: Cell<any, unknown>) => c.column.id === 'actions')
|
|
769
|
+
const dataCells = cells.filter(
|
|
770
|
+
(c: Cell<any, unknown>) => c.column.id !== 'actions' && c.column.id !== 'select',
|
|
771
|
+
)
|
|
772
|
+
return (
|
|
773
|
+
<div
|
|
774
|
+
key={row.id}
|
|
775
|
+
data-state={row.getIsSelected() && 'selected'}
|
|
776
|
+
className='flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40'
|
|
777
|
+
>
|
|
778
|
+
{dataCells.map((cell: Cell<any, unknown>) => {
|
|
779
|
+
const header = cell.column.columnDef.header
|
|
780
|
+
const label = typeof header === 'string' ? header : cell.column.id
|
|
781
|
+
return (
|
|
782
|
+
<div key={cell.id} className='flex items-start justify-between gap-3 text-sm'>
|
|
783
|
+
<span className='shrink-0 text-muted-foreground'>{label}</span>
|
|
784
|
+
<span className='min-w-0 break-words text-right font-medium'>
|
|
785
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
786
|
+
</span>
|
|
787
|
+
</div>
|
|
788
|
+
)
|
|
789
|
+
})}
|
|
790
|
+
{actionsCell && (
|
|
791
|
+
<div className='flex justify-end border-t pt-2'>
|
|
792
|
+
{flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext())}
|
|
793
|
+
</div>
|
|
794
|
+
)}
|
|
795
|
+
</div>
|
|
796
|
+
)
|
|
797
|
+
})
|
|
798
|
+
) : (
|
|
799
|
+
<div className='flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground'>
|
|
800
|
+
<div className='flex h-16 w-16 items-center justify-center rounded-full bg-muted/50'>
|
|
801
|
+
<Inbox className='h-8 w-8' />
|
|
802
|
+
</div>
|
|
803
|
+
<h3 className='text-base font-semibold text-foreground'>No se encontraron resultados</h3>
|
|
804
|
+
<p className='text-sm text-muted-foreground'>No hay datos para mostrar en este momento.</p>
|
|
805
|
+
</div>
|
|
806
|
+
)}
|
|
807
|
+
</div>
|
|
808
|
+
|
|
749
809
|
<div className='shrink-0 pt-4'>
|
|
750
810
|
<DataTablePagination
|
|
751
811
|
table={table}
|