@asteby/metacore-runtime-react 6.0.0 → 6.4.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 -2
- package/README.md +67 -44
- package/dist/dialogs/export.js +1 -1
- package/dist/dialogs/import.d.ts.map +1 -1
- package/dist/dialogs/import.js +1 -2
- package/dist/dynamic-columns.d.ts +29 -0
- package/dist/dynamic-columns.d.ts.map +1 -0
- package/dist/dynamic-columns.js +312 -0
- package/dist/dynamic-crud-page.d.ts +49 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -0
- package/dist/dynamic-crud-page.js +106 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +36 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/metadata-cache.js +3 -3
- package/dist/model-extension-registry.d.ts +19 -0
- package/dist/model-extension-registry.d.ts.map +1 -0
- package/dist/model-extension-registry.js +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/dialogs/export.tsx +1 -1
- package/src/dialogs/import.tsx +1 -2
- package/src/dynamic-columns.tsx +531 -0
- package/src/dynamic-crud-page.tsx +275 -0
- package/src/dynamic-table.tsx +34 -11
- package/src/index.ts +18 -0
- package/src/metadata-cache.ts +3 -3
- package/src/model-extension-registry.tsx +46 -0
- package/src/types.ts +3 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// DynamicCRUDPage — drop-in route component for any model that has
|
|
3
|
+
// `DefineTable` metadata on the backend. Pulls the title and CRUD flag
|
|
4
|
+
// from `/metadata/table/<model>`, mounts <DynamicTable>, wires
|
|
5
|
+
// `<DynamicRecordDialog>` for create, and exposes `<ExportDialog>` /
|
|
6
|
+
// `<ImportDialog>` from a single toolbar.
|
|
7
|
+
//
|
|
8
|
+
// The whole thing exists so apps stop reinventing the same ~150 LOC of
|
|
9
|
+
// page chrome around DynamicTable. Override anything via props:
|
|
10
|
+
//
|
|
11
|
+
// <DynamicCRUDPage
|
|
12
|
+
// model="customers"
|
|
13
|
+
// endpoint="/dynamic/customers" // default: /dynamic/<model>
|
|
14
|
+
// title="Mis clientes" // default: metadata.title
|
|
15
|
+
// newLabel="Nuevo cliente" // default: "New <singular>"
|
|
16
|
+
// hideExport hideImport hideCreate // toolbar trimming
|
|
17
|
+
// headerExtras={<MyBranchSwitcher />} // injected before the title row
|
|
18
|
+
// toolbarExtras={<MyExtraActions />} // injected before "Nuevo X"
|
|
19
|
+
// i18n={{ refresh: 'Refrescar', export: 'Exportar', ... }}
|
|
20
|
+
// onChange={() => analytics.track('table.refresh')}
|
|
21
|
+
// />
|
|
22
|
+
//
|
|
23
|
+
// Apps that only need to swap the create endpoint or hide a button keep
|
|
24
|
+
// the boilerplate to one line. Apps that need richer per-model headers
|
|
25
|
+
// register a model-extension registry and feed it via `headerExtras`.
|
|
26
|
+
import { useCallback, useEffect, useMemo, useState, } from 'react';
|
|
27
|
+
import { Plus, Download, Upload, RefreshCw } from 'lucide-react';
|
|
28
|
+
import { useApi } from './api-context';
|
|
29
|
+
import { useMetadataCache } from './metadata-cache';
|
|
30
|
+
import { DynamicTable } from './dynamic-table';
|
|
31
|
+
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
32
|
+
import { ExportDialog } from './dialogs/export';
|
|
33
|
+
import { ImportDialog } from './dialogs/import';
|
|
34
|
+
import { getModelExtension } from './model-extension-registry';
|
|
35
|
+
const defaultStrings = {
|
|
36
|
+
refresh: 'Refresh',
|
|
37
|
+
export: 'Export',
|
|
38
|
+
import: 'Import',
|
|
39
|
+
newPrefix: 'New',
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Page-level CRUD shell wired around <DynamicTable>. Hosts mount a single
|
|
43
|
+
* `<DynamicCRUDPage model="..." />` per route and the SDK takes care of
|
|
44
|
+
* metadata fetch, dialogs and toolbar.
|
|
45
|
+
*/
|
|
46
|
+
export function DynamicCRUDPage(props) {
|
|
47
|
+
const { model, endpoint, title: titleOverride, newLabel, i18n, hideCreate, hideExport, hideImport, hideRefresh, headerExtras, toolbarExtras, classes, onChange, } = props;
|
|
48
|
+
const strings = { ...defaultStrings, ...(i18n ?? {}) };
|
|
49
|
+
const dataEndpoint = endpoint ?? `/dynamic/${model}`;
|
|
50
|
+
const ext = getModelExtension(model);
|
|
51
|
+
const api = useApi();
|
|
52
|
+
const cachedMeta = useMetadataCache((s) => s.getMetadata(model));
|
|
53
|
+
const [metadata, setMetadata] = useState(cachedMeta ?? null);
|
|
54
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
55
|
+
const [openCreate, setOpenCreate] = useState(false);
|
|
56
|
+
const [openExport, setOpenExport] = useState(false);
|
|
57
|
+
const [openImport, setOpenImport] = useState(false);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (cachedMeta) {
|
|
60
|
+
setMetadata(cachedMeta);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let cancelled = false;
|
|
64
|
+
api
|
|
65
|
+
.get(`/metadata/table/${model}`)
|
|
66
|
+
.then((res) => {
|
|
67
|
+
if (cancelled)
|
|
68
|
+
return;
|
|
69
|
+
const meta = (res.data?.data ?? res.data);
|
|
70
|
+
setMetadata(meta ?? null);
|
|
71
|
+
})
|
|
72
|
+
.catch(() => {
|
|
73
|
+
if (!cancelled)
|
|
74
|
+
setMetadata(null);
|
|
75
|
+
});
|
|
76
|
+
return () => {
|
|
77
|
+
cancelled = true;
|
|
78
|
+
};
|
|
79
|
+
}, [model, cachedMeta, api]);
|
|
80
|
+
const title = titleOverride ?? ext?.title ?? metadata?.title ?? model;
|
|
81
|
+
const resolvedNewLabel = newLabel ?? ext?.newLabel;
|
|
82
|
+
const singular = useMemo(() => {
|
|
83
|
+
const t = title.replace(/s$/i, '');
|
|
84
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
85
|
+
}, [title]);
|
|
86
|
+
const enableCRUD = metadata?.enableCRUDActions ?? false;
|
|
87
|
+
const effectiveHideCreate = hideCreate || ext?.hideCreate;
|
|
88
|
+
const effectiveHideExport = hideExport || ext?.hideExport;
|
|
89
|
+
const effectiveHideImport = hideImport || ext?.hideImport;
|
|
90
|
+
const effectiveHideRefresh = hideRefresh || ext?.hideRefresh;
|
|
91
|
+
const showCreate = enableCRUD && !effectiveHideCreate;
|
|
92
|
+
const showImport = enableCRUD && !effectiveHideImport;
|
|
93
|
+
const showExport = !effectiveHideExport;
|
|
94
|
+
const showRefresh = !effectiveHideRefresh;
|
|
95
|
+
const handleRefresh = useCallback(() => {
|
|
96
|
+
setRefreshKey((k) => k + 1);
|
|
97
|
+
onChange?.();
|
|
98
|
+
}, [onChange]);
|
|
99
|
+
const rootCls = classes?.root ?? 'flex flex-col h-full overflow-hidden';
|
|
100
|
+
const containerCls = classes?.container ?? 'flex flex-col flex-1 p-6 lg:p-8 gap-4 overflow-hidden';
|
|
101
|
+
const headerCls = classes?.header ?? 'flex items-center justify-between shrink-0';
|
|
102
|
+
const titleCls = classes?.title ?? 'text-2xl font-bold tracking-tight';
|
|
103
|
+
const toolbarCls = classes?.toolbar ?? 'flex items-center gap-2';
|
|
104
|
+
const tableWrapperCls = classes?.tableWrapper ?? 'flex-1 min-h-0';
|
|
105
|
+
return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, showCreate && (_jsxs("button", { type: 'button', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey }, model) })] }), showCreate && (_jsx(DynamicRecordDialog, { open: openCreate, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
|
|
106
|
+
}
|
|
@@ -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;
|
|
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;AASnF,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,2CA4qBnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// DynamicTable — metadata-driven CRUD table used by every metacore host.
|
|
3
|
-
//
|
|
4
|
-
// for metacore packages + context-injected peer deps:
|
|
3
|
+
// Originally extracted from a host app and generalized so the host-specific
|
|
4
|
+
// aliases are swapped for metacore packages + context-injected peer deps:
|
|
5
5
|
// * `@/lib/api` → <ApiProvider> (see api-context.tsx)
|
|
6
6
|
// * `@/stores/branch-store` → <BranchProvider> (optional)
|
|
7
7
|
// * `@/stores/metadata-cache` → internal ./metadata-cache zustand store
|
|
@@ -24,6 +24,7 @@ import { toast } from 'sonner';
|
|
|
24
24
|
import { Progress } from './dialogs/_primitives';
|
|
25
25
|
import { useMetadataCache } from './metadata-cache';
|
|
26
26
|
import { useApi, useCurrentBranch } from './api-context';
|
|
27
|
+
import { defaultGetDynamicColumns } from './dynamic-columns';
|
|
27
28
|
import { OptionsContext } from './options-context';
|
|
28
29
|
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
29
30
|
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
@@ -436,9 +437,13 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
436
437
|
}, []);
|
|
437
438
|
const columnFilterConfigs = useMemo(() => {
|
|
438
439
|
const map = new Map();
|
|
439
|
-
if (!metadata
|
|
440
|
+
if (!metadata)
|
|
440
441
|
return map;
|
|
441
|
-
|
|
442
|
+
// Explicit `metadata.filters` wins. When the backend does not emit
|
|
443
|
+
// them, derive a filter chip from every column flagged
|
|
444
|
+
// `filterable: true` — keeps the kernel API minimal (one flag on the
|
|
445
|
+
// column) while still rendering the FilterableColumnHeader.
|
|
446
|
+
for (const f of metadata.filters ?? []) {
|
|
442
447
|
const fType = f.type;
|
|
443
448
|
let options = [];
|
|
444
449
|
if (f.options && f.options.length > 0) {
|
|
@@ -459,6 +464,33 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
459
464
|
searchEndpoint: f.searchEndpoint,
|
|
460
465
|
});
|
|
461
466
|
}
|
|
467
|
+
for (const c of metadata.columns ?? []) {
|
|
468
|
+
if (!c.filterable || map.has(c.key))
|
|
469
|
+
continue;
|
|
470
|
+
const hasStaticOptions = (c.options?.length ?? 0) > 0;
|
|
471
|
+
const hasEndpoint = !!c.searchEndpoint;
|
|
472
|
+
if (!hasStaticOptions && !hasEndpoint && c.type !== 'boolean')
|
|
473
|
+
continue;
|
|
474
|
+
const options = hasStaticOptions
|
|
475
|
+
? c.options.map(o => ({
|
|
476
|
+
label: o.label,
|
|
477
|
+
value: String(o.value),
|
|
478
|
+
icon: o.icon,
|
|
479
|
+
color: o.color,
|
|
480
|
+
}))
|
|
481
|
+
: hasEndpoint && filterOptionsMap.has(c.searchEndpoint)
|
|
482
|
+
? filterOptionsMap.get(c.searchEndpoint) || []
|
|
483
|
+
: [];
|
|
484
|
+
map.set(c.key, {
|
|
485
|
+
filterType: 'select',
|
|
486
|
+
filterKey: c.key,
|
|
487
|
+
options,
|
|
488
|
+
selectedValues: dynamicFilters[c.key] || [],
|
|
489
|
+
onFilterChange: handleDynamicFilterChange,
|
|
490
|
+
loading: hasEndpoint && !filterOptionsMap.has(c.searchEndpoint),
|
|
491
|
+
searchEndpoint: c.searchEndpoint,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
462
494
|
return map;
|
|
463
495
|
}, [metadata, filterOptionsMap, dynamicFilters, handleDynamicFilterChange]);
|
|
464
496
|
const columns = useMemo(() => {
|
|
@@ -508,9 +540,3 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
508
540
|
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));
|
|
509
541
|
}) }, 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: '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"] }) })] }));
|
|
510
542
|
}
|
|
511
|
-
/** Sensible default when hosts don't provide their own getDynamicColumns. */
|
|
512
|
-
const defaultGetDynamicColumns = (metadata, _handleAction, _t, _lang, _filters) => (metadata.columns ?? []).map((col) => ({
|
|
513
|
-
accessorKey: col.name,
|
|
514
|
-
header: col.label ?? col.name,
|
|
515
|
-
enableSorting: col.sortable ?? false,
|
|
516
|
-
}));
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,10 @@ export * from './api-context';
|
|
|
12
12
|
export * from './metadata-cache';
|
|
13
13
|
export * from './dynamic-icon';
|
|
14
14
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
15
|
+
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
15
16
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
16
17
|
export { ExportDialog } from './dialogs/export';
|
|
17
18
|
export { ImportDialog } from './dialogs/import';
|
|
19
|
+
export { DynamicCRUDPage, type DynamicCRUDPageProps, type DynamicCRUDPageStrings, type DynamicCRUDPageClasses, } from './dynamic-crud-page';
|
|
20
|
+
export { registerModelExtension, getModelExtension, clearModelExtensions, type ModelExtension, type ModelExtensionProps, } from './model-extension-registry';
|
|
18
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,9 @@ export * from './i18n-provider';
|
|
|
16
16
|
export * from './api-context';
|
|
17
17
|
export * from './metadata-cache';
|
|
18
18
|
export * from './dynamic-icon';
|
|
19
|
+
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, } from './dynamic-columns';
|
|
19
20
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
20
21
|
export { ExportDialog } from './dialogs/export';
|
|
21
22
|
export { ImportDialog } from './dialogs/import';
|
|
23
|
+
export { DynamicCRUDPage, } from './dynamic-crud-page';
|
|
24
|
+
export { registerModelExtension, getModelExtension, clearModelExtensions, } from './model-extension-registry';
|
package/dist/metadata-cache.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Metadata cache — a zustand store that memoizes table/modal metadata
|
|
2
|
-
// responses across dynamic-table mounts.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// responses across dynamic-table mounts. Generalized from a host-app
|
|
3
|
+
// metadata-cache store so the runtime-react package no longer depends on
|
|
4
|
+
// a host-specific alias.
|
|
5
5
|
//
|
|
6
6
|
// The prefetchAll() method needs an `api` client (axios-like); we keep that
|
|
7
7
|
// as an injectable parameter so the store stays host-agnostic. If a caller
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ModelExtensionProps {
|
|
3
|
+
model: string;
|
|
4
|
+
onRefresh: () => void;
|
|
5
|
+
}
|
|
6
|
+
export interface ModelExtension {
|
|
7
|
+
headerExtras?: React.ComponentType<ModelExtensionProps>;
|
|
8
|
+
toolbarExtras?: React.ComponentType<ModelExtensionProps>;
|
|
9
|
+
hideCreate?: boolean;
|
|
10
|
+
hideExport?: boolean;
|
|
11
|
+
hideImport?: boolean;
|
|
12
|
+
hideRefresh?: boolean;
|
|
13
|
+
title?: string;
|
|
14
|
+
newLabel?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function registerModelExtension(model: string, ext: ModelExtension): void;
|
|
17
|
+
export declare function getModelExtension(model: string): ModelExtension | undefined;
|
|
18
|
+
export declare function clearModelExtensions(): void;
|
|
19
|
+
//# sourceMappingURL=model-extension-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-extension-registry.d.ts","sourceRoot":"","sources":["../src/model-extension-registry.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC3B,YAAY,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAA;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAA;IACxD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAID,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAE/E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE3E;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const registry = new Map();
|
|
2
|
+
export function registerModelExtension(model, ext) {
|
|
3
|
+
registry.set(model, ext);
|
|
4
|
+
}
|
|
5
|
+
export function getModelExtension(model) {
|
|
6
|
+
return registry.get(model);
|
|
7
|
+
}
|
|
8
|
+
export function clearModelExtensions() {
|
|
9
|
+
registry.clear();
|
|
10
|
+
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/dialogs/export.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ExportDialog — lets users pick format (csv/json) + columns and kicks off
|
|
2
2
|
// either a sync download or an async export job (polled via /exports/:id/status).
|
|
3
|
-
//
|
|
3
|
+
// Axios-like client is provided by <ApiProvider>.
|
|
4
4
|
import { useState, useEffect, useCallback } from 'react'
|
|
5
5
|
import {
|
|
6
6
|
Dialog,
|
package/src/dialogs/import.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// ImportDialog — three-step CSV/JSON import flow (upload → validate → import
|
|
2
|
-
// with per-row error report).
|
|
3
|
-
// is provided by <ApiProvider>.
|
|
2
|
+
// with per-row error report). Axios-like client is provided by <ApiProvider>.
|
|
4
3
|
import { useState, useEffect, useRef } from 'react'
|
|
5
4
|
import {
|
|
6
5
|
Dialog,
|