@asteby/metacore-runtime-react 47.1.0 → 49.0.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 +51 -0
- package/dist/action-modal-dispatcher.d.ts +17 -3
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +80 -13
- package/dist/action-priority.d.ts +34 -0
- package/dist/action-priority.d.ts.map +1 -0
- package/dist/action-priority.js +60 -0
- package/dist/business/catalog-product-search.d.ts +32 -0
- package/dist/business/catalog-product-search.d.ts.map +1 -0
- package/dist/business/catalog-product-search.js +71 -0
- package/dist/business/catalog.d.ts.map +1 -1
- package/dist/business/catalog.js +2 -0
- package/dist/business/document-editor-model.d.ts +119 -0
- package/dist/business/document-editor-model.d.ts.map +1 -0
- package/dist/business/document-editor-model.js +317 -0
- package/dist/business/document-editor-panels.d.ts +64 -0
- package/dist/business/document-editor-panels.d.ts.map +1 -0
- package/dist/business/document-editor-panels.js +58 -0
- package/dist/business/document-editor.d.ts +27 -0
- package/dist/business/document-editor.d.ts.map +1 -0
- package/dist/business/document-editor.js +493 -0
- package/dist/business/document-lines.d.ts.map +1 -1
- package/dist/business/document-lines.js +5 -1
- package/dist/business/index.d.ts +5 -0
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/index.js +5 -0
- package/dist/business/line-items.d.ts +16 -1
- package/dist/business/line-items.d.ts.map +1 -1
- package/dist/business/line-items.js +14 -2
- package/dist/business/payment-allocator.d.ts +19 -0
- package/dist/business/payment-allocator.d.ts.map +1 -0
- package/dist/business/payment-allocator.js +31 -0
- package/dist/business/product-search.d.ts +4 -0
- package/dist/business/product-search.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +14 -7
- package/dist/document/document-page.d.ts.map +1 -1
- package/dist/document/document-page.js +8 -1
- package/dist/document/evaluate.d.ts.map +1 -1
- package/dist/document/evaluate.js +17 -1
- package/dist/document/index.d.ts +1 -0
- package/dist/document/index.d.ts.map +1 -1
- package/dist/document/index.js +1 -0
- package/dist/document/secondary-bar.d.ts +20 -0
- package/dist/document/secondary-bar.d.ts.map +1 -0
- package/dist/document/secondary-bar.js +44 -0
- package/dist/document/types.d.ts +8 -0
- package/dist/document/types.d.ts.map +1 -1
- package/dist/document-form-dialog.d.ts +31 -2
- package/dist/document-form-dialog.d.ts.map +1 -1
- package/dist/document-form-dialog.js +46 -11
- package/dist/dynamic-columns-helpers.d.ts +9 -0
- package/dist/dynamic-columns-helpers.d.ts.map +1 -1
- package/dist/dynamic-columns-helpers.js +19 -0
- package/dist/dynamic-columns.d.ts +9 -13
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +27 -21
- package/dist/dynamic-crud-page.d.ts +18 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +15 -6
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +15 -3
- package/dist/dynamic-form.d.ts +16 -1
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +78 -20
- package/dist/dynamic-kanban.d.ts +9 -3
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +31 -12
- package/dist/dynamic-relation-helpers.d.ts.map +1 -1
- package/dist/dynamic-relation-helpers.js +8 -0
- package/dist/dynamic-relation.js +1 -1
- package/dist/dynamic-row-actions.d.ts +8 -0
- package/dist/dynamic-row-actions.d.ts.map +1 -1
- package/dist/dynamic-row-actions.js +12 -2
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +64 -63
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/installed-addons-context.d.ts +15 -0
- package/dist/installed-addons-context.d.ts.map +1 -0
- package/dist/installed-addons-context.js +39 -0
- package/dist/org-runtime-context.d.ts +8 -0
- package/dist/org-runtime-context.d.ts.map +1 -1
- package/dist/org-runtime-context.js +8 -0
- package/dist/org-runtime-provider.d.ts +3 -1
- package/dist/org-runtime-provider.d.ts.map +1 -1
- package/dist/org-runtime-provider.js +3 -3
- package/dist/primitives/aging.d.ts +34 -0
- package/dist/primitives/aging.d.ts.map +1 -0
- package/dist/primitives/aging.js +48 -0
- package/dist/primitives/allocation.d.ts +42 -0
- package/dist/primitives/allocation.d.ts.map +1 -0
- package/dist/primitives/allocation.js +59 -0
- package/dist/primitives/approvals.d.ts +71 -0
- package/dist/primitives/approvals.d.ts.map +1 -0
- package/dist/primitives/approvals.js +48 -0
- package/dist/primitives/contributions.d.ts +93 -0
- package/dist/primitives/contributions.d.ts.map +1 -0
- package/dist/primitives/contributions.js +163 -0
- package/dist/primitives/document-kinds.d.ts +25 -0
- package/dist/primitives/document-kinds.d.ts.map +1 -0
- package/dist/primitives/document-kinds.js +26 -0
- package/dist/primitives/index.d.ts +8 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +23 -0
- package/dist/primitives/panels.d.ts +68 -0
- package/dist/primitives/panels.d.ts.map +1 -0
- package/dist/primitives/panels.js +3 -0
- package/dist/primitives/use-contributions.d.ts +15 -0
- package/dist/primitives/use-contributions.d.ts.map +1 -0
- package/dist/primitives/use-contributions.js +30 -0
- package/dist/record-actions.d.ts +21 -0
- package/dist/record-actions.d.ts.map +1 -0
- package/dist/record-actions.js +76 -0
- package/dist/record-prefill-bar.d.ts +11 -0
- package/dist/record-prefill-bar.d.ts.map +1 -0
- package/dist/record-prefill-bar.js +33 -0
- package/dist/row-actions-menu.d.ts +32 -0
- package/dist/row-actions-menu.d.ts.map +1 -0
- package/dist/row-actions-menu.js +59 -0
- package/dist/slot.d.ts +4 -21
- package/dist/slot.d.ts.map +1 -1
- package/dist/slot.js +14 -36
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/catalog-product-search.test.ts +61 -0
- package/src/__tests__/document-editor-model.test.ts +221 -0
- package/src/__tests__/document-editor.test.tsx +340 -0
- package/src/__tests__/document-form-dialog.test.tsx +158 -3
- package/src/__tests__/dynamic-form-select-edit.test.tsx +155 -0
- package/src/__tests__/dynamic-kanban.test.tsx +24 -3
- package/src/__tests__/dynamic-record-extension-label.test.tsx +88 -0
- package/src/__tests__/installed-addons-context.test.tsx +50 -0
- package/src/__tests__/line-item-totals.test.ts +28 -0
- package/src/__tests__/prefill-scalar-from-record.test.ts +52 -1
- package/src/__tests__/primitives-federated.test.tsx +156 -0
- package/src/__tests__/primitives.test.ts +224 -0
- package/src/__tests__/record-prefill-bar.test.tsx +50 -0
- package/src/__tests__/row-action-link-navigation.test.ts +17 -0
- package/src/__tests__/secondary-actions.test.tsx +261 -0
- package/src/action-modal-dispatcher.tsx +97 -12
- package/src/action-priority.ts +82 -0
- package/src/business/business.test.ts +1 -1
- package/src/business/catalog-product-search.ts +105 -0
- package/src/business/catalog.ts +2 -0
- package/src/business/document-editor-model.ts +367 -0
- package/src/business/document-editor-panels.tsx +164 -0
- package/src/business/document-editor.tsx +781 -0
- package/src/business/document-lines.ts +5 -1
- package/src/business/index.ts +5 -0
- package/src/business/line-items.ts +31 -2
- package/src/business/payment-allocator.tsx +110 -0
- package/src/business/product-search.ts +4 -0
- package/src/dialogs/dynamic-record.tsx +29 -6
- package/src/document/document-page.tsx +15 -0
- package/src/document/evaluate.ts +17 -1
- package/src/document/index.ts +1 -0
- package/src/document/secondary-bar.tsx +92 -0
- package/src/document/types.ts +8 -0
- package/src/document-form-dialog.tsx +96 -12
- package/src/dynamic-columns-helpers.ts +22 -0
- package/src/dynamic-columns.tsx +35 -62
- package/src/dynamic-crud-page.tsx +33 -5
- package/src/dynamic-form-schema.ts +15 -2
- package/src/dynamic-form.tsx +117 -39
- package/src/dynamic-kanban.tsx +34 -7
- package/src/dynamic-relation-helpers.ts +9 -0
- package/src/dynamic-relation.tsx +7 -2
- package/src/dynamic-row-actions.tsx +12 -2
- package/src/dynamic-table.tsx +3 -0
- package/src/index.ts +46 -1
- package/src/installed-addons-context.tsx +58 -0
- package/src/org-runtime-context.ts +10 -0
- package/src/org-runtime-provider.tsx +7 -2
- package/src/primitives/aging.ts +76 -0
- package/src/primitives/allocation.ts +104 -0
- package/src/primitives/approvals.ts +103 -0
- package/src/primitives/contributions.ts +255 -0
- package/src/primitives/document-kinds.ts +62 -0
- package/src/primitives/index.ts +23 -0
- package/src/primitives/panels.ts +59 -0
- package/src/primitives/use-contributions.ts +42 -0
- package/src/record-actions.ts +101 -0
- package/src/record-prefill-bar.tsx +62 -0
- package/src/row-action-stage-gate.test.ts +33 -0
- package/src/row-actions-menu.tsx +159 -0
- package/src/slot.tsx +18 -53
- package/src/types.ts +129 -0
|
@@ -16,6 +16,7 @@ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogH
|
|
|
16
16
|
import { Loader2 } from 'lucide-react';
|
|
17
17
|
import { toast } from 'sonner';
|
|
18
18
|
import { useApi } from './api-context';
|
|
19
|
+
import { useOrgTaxRate } from './org-runtime-context';
|
|
19
20
|
import { DynamicIcon } from './dynamic-icon';
|
|
20
21
|
import { FieldCell, FieldGrid, FieldLabel } from './field-grid';
|
|
21
22
|
import { FormErrorBanner } from './business/feedback';
|
|
@@ -26,21 +27,39 @@ import { extractFieldErrors, localizeFieldErrorMap } from './server-error';
|
|
|
26
27
|
import { validateValues, bagHasErrors } from './validator';
|
|
27
28
|
import { clearFieldErrorTree } from './field-validation-ui';
|
|
28
29
|
import { emitRecordMutation } from './record-mutation-events';
|
|
29
|
-
import { isLineItemsField, resolveWidget } from './dynamic-form-schema';
|
|
30
|
+
import { isLineItemsField, resolveWidget, scopeValueFromFilterToken } from './dynamic-form-schema';
|
|
31
|
+
import { createCatalogProductSearch, withDefaultTaxRate } from './business/catalog-product-search';
|
|
32
|
+
import { DocumentEditor } from './business/document-editor';
|
|
33
|
+
import { editorLinesConfig, isEditorLayout } from './business/document-editor-model';
|
|
30
34
|
/** Manifest efectivo: la prop gana sobre el metadata; sin tipos devuelve undefined. */
|
|
31
35
|
export function resolveDocumentForms(metadata, override) {
|
|
32
36
|
const m = override ?? metadata?.document_forms;
|
|
33
37
|
return m && Array.isArray(m.types) && m.types.length > 0 ? m : undefined;
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Acota el alta guiada a la vista filtrada. Un nav que filtra por la columna
|
|
41
|
+
* discriminadora (`type_field`) lista UN tipo de documento: «Crear» debe abrir
|
|
42
|
+
* ese tipo directo y, si el manifest no trae formulario para él, no ofrecerse
|
|
43
|
+
* (retest r3: «Documentos fiscales → Facturas → Crear» abría el REP, el único
|
|
44
|
+
* tipo declarado). Sin manifest, sin `type_field` o sin filtro sobre él, todo
|
|
45
|
+
* queda igual.
|
|
46
|
+
*/
|
|
47
|
+
export function scopeDocumentFormsToFilter(forms, filter) {
|
|
48
|
+
const field = forms?.type_field;
|
|
49
|
+
if (!forms || !field || !filter || !(field in filter))
|
|
50
|
+
return { forms, hideCreate: false };
|
|
51
|
+
const want = scopeValueFromFilterToken(filter[field]).trim().toLowerCase();
|
|
52
|
+
if (want === '')
|
|
53
|
+
return { forms, hideCreate: false };
|
|
54
|
+
const types = forms.types.filter((x) => String(x.value ?? x.key).trim().toLowerCase() === want);
|
|
55
|
+
if (types.length === 0)
|
|
56
|
+
return { forms: undefined, hideCreate: true };
|
|
57
|
+
return { forms: types.length === forms.types.length ? forms : { ...forms, types }, hideCreate: false };
|
|
40
58
|
}
|
|
41
|
-
export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms, initialType, onSaved, searchProducts, currency, }) {
|
|
59
|
+
export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms, initialType, onSaved, searchProducts, productModel, defaultTaxRate, currency, record, initialSource, }) {
|
|
42
60
|
const { t, i18n } = useTranslation();
|
|
43
61
|
const api = useApi();
|
|
62
|
+
const orgTaxRate = useOrgTaxRate();
|
|
44
63
|
const types = forms.types;
|
|
45
64
|
const single = types.length === 1 ? types[0] : undefined;
|
|
46
65
|
const [typeKey, setTypeKey] = useState(initialType ?? single?.key ?? null);
|
|
@@ -52,7 +71,15 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
52
71
|
const [formError, setFormError] = useState();
|
|
53
72
|
const [saving, setSaving] = useState(false);
|
|
54
73
|
const type = useMemo(() => types.find((x) => x.key === typeKey) ?? null, [types, typeKey]);
|
|
55
|
-
|
|
74
|
+
// Sin buscador inyectado, el paso de renglones solo ofrecía «Renglón libre»
|
|
75
|
+
// y la línea entraba sin precio (DynamicCRUDPage no pasa searchProducts).
|
|
76
|
+
// Default: el catálogo de productos de la org. El buscador del host también
|
|
77
|
+
// recibe el IVA de la org para los productos que no traen su tasa.
|
|
78
|
+
const taxRate = defaultTaxRate ?? orgTaxRate;
|
|
79
|
+
const lineSearch = useMemo(() => searchProducts
|
|
80
|
+
? withDefaultTaxRate(searchProducts, taxRate)
|
|
81
|
+
: createCatalogProductSearch(api, { model: productModel, defaultTaxRate: taxRate }), [searchProducts, api, productModel, taxRate]);
|
|
82
|
+
const lineCfg = type ? editorLinesConfig(type, forms) : undefined;
|
|
56
83
|
// Reset every time the dialog (re)opens.
|
|
57
84
|
useEffect(() => {
|
|
58
85
|
if (!open)
|
|
@@ -69,10 +96,10 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
69
96
|
useEffect(() => {
|
|
70
97
|
if (!type)
|
|
71
98
|
return;
|
|
72
|
-
setFormData({ ...buildFieldDefaults(type.fields,
|
|
99
|
+
setFormData({ ...buildFieldDefaults(type.fields, record), ...(type.defaults ?? {}) });
|
|
73
100
|
setFieldErrors({});
|
|
74
101
|
setFormError(undefined);
|
|
75
|
-
}, [type]);
|
|
102
|
+
}, [type, record]);
|
|
76
103
|
const updateField = (key, value) => {
|
|
77
104
|
setFormData((prev) => ({ ...prev, [key]: value }));
|
|
78
105
|
setFieldErrors((prev) => clearFieldErrorTree(prev, key));
|
|
@@ -175,6 +202,14 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
175
202
|
: s === 'fields'
|
|
176
203
|
? t('documentForm.step_data', { defaultValue: 'Datos' })
|
|
177
204
|
: tl(lineCfg?.title ?? 'Renglones');
|
|
205
|
+
// layout "editor": una sola pantalla por secciones (DocumentEditor). Sin él
|
|
206
|
+
// sigue el wizard de siempre (retrocompatible).
|
|
207
|
+
if (open && type && step !== 'type' && (isEditorLayout(type) || !!initialSource)) {
|
|
208
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex max-h-[92dvh] flex-col overflow-hidden", style: { maxHeight: '92dvh', maxWidth: '960px', width: '95vw' }, "data-slot": "document-editor-dialog", children: [_jsxs(DialogHeader, { className: "shrink-0", children: [_jsx(DialogTitle, { children: tl(type.label) }), type.description && _jsx(DialogDescription, { children: tl(type.description) })] }), _jsx(DocumentEditor, { model: model, endpoint: endpoint, forms: forms, type: type, record: record, initialSource: initialSource, searchProducts: lineSearch, currency: currency, onCancel: () => (canGoBackToTypes ? setStep('type') : onOpenChange(false)), onSaved: (rec) => {
|
|
209
|
+
onSaved?.(rec);
|
|
210
|
+
onOpenChange(false);
|
|
211
|
+
} }, type.key)] }) }));
|
|
212
|
+
}
|
|
178
213
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex max-h-[90dvh] flex-col overflow-hidden sm:max-w-2xl", style: { maxHeight: '90dvh', ...(step === 'lines' ? { maxWidth: '980px', width: '95vw' } : {}) }, "data-slot": "document-form-dialog", children: [_jsxs(DialogHeader, { className: "shrink-0", children: [_jsx(DialogTitle, { children: type ? tl(type.label) : t('documentForm.title', { defaultValue: 'Nuevo documento' }) }), _jsx(DialogDescription, { children: stepLabel(step) })] }), _jsxs("div", { className: "-mx-1 min-h-0 flex-1 space-y-3 overflow-y-auto px-1 py-3", children: [_jsx(FormErrorBanner, { message: formError }), step === 'type' && (_jsx("div", { role: "radiogroup", className: "grid gap-3 sm:grid-cols-2", children: types.map((x) => (_jsxs("button", { type: "button", role: "radio", "aria-checked": x.key === typeKey, "data-type-card": x.key, onClick: () => chooseType(x.key), className: "flex items-start gap-3 rounded-lg border p-4 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [x.icon && _jsx(DynamicIcon, { name: x.icon, className: "mt-0.5 size-5 shrink-0 text-primary" }), _jsxs("span", { children: [_jsx("span", { className: "block text-sm font-medium", children: tl(x.label) }), x.description && (_jsx("span", { className: "block text-xs text-muted-foreground", children: tl(x.description) }))] })] }, x.key))) })), step === 'fields' && type && (_jsx(FieldGrid, { children: type.fields.map((field) => {
|
|
179
214
|
const fullWidth = isLineItemsField(field) ||
|
|
180
215
|
resolveWidget(field) === 'textarea' ||
|
|
@@ -183,7 +218,7 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
183
218
|
}) })), step === 'lines' && type && lineCfg && (_jsx(DocumentLinesGrid, { value: lines, onChange: (next) => {
|
|
184
219
|
setLines(next);
|
|
185
220
|
setServerLineErrors({});
|
|
186
|
-
}, columns: lineCfg.columns ?? ['discount', 'tax'], priceSource: lineCfg.price_source ?? 'sale', serverErrors: serverLineErrors, search:
|
|
221
|
+
}, columns: lineCfg.columns ?? ['discount', 'tax'], priceSource: lineCfg.price_source ?? 'sale', discountMode: lineCfg.discount_mode, serverErrors: serverLineErrors, search: lineSearch, currency: currency }))] }), _jsxs(DialogFooter, { className: "shrink-0", children: [step === 'type' ? (_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: t('common.cancel', { defaultValue: 'Cancelar' }) })) : (_jsx(Button, { type: "button", variant: "outline", disabled: saving, onClick: () => {
|
|
187
222
|
if (step === 'lines')
|
|
188
223
|
setStep('fields');
|
|
189
224
|
else if (canGoBackToTypes)
|
|
@@ -27,4 +27,13 @@ export type MetadataTranslator = (key: string, options?: {
|
|
|
27
27
|
* machine path such as `models.orders.table.columns.payment_status`.
|
|
28
28
|
*/
|
|
29
29
|
export declare function translateMetadataLabel(value: string | undefined, translate?: MetadataTranslator): string;
|
|
30
|
+
/**
|
|
31
|
+
* Form-field label as the user should read it. The host usually serves labels
|
|
32
|
+
* already localized; when it could not (an extension field whose addon catalog
|
|
33
|
+
* the server did not resolve), the label arrives as the raw catalog key and the
|
|
34
|
+
* form printed `FISCAL_MEXICO.EXT.CUSTOMER.RFC_RECEPTOR`. Such a key is resolved
|
|
35
|
+
* with the client catalog and, failing that, humanized. Any other label is
|
|
36
|
+
* returned untouched.
|
|
37
|
+
*/
|
|
38
|
+
export declare function localizeFieldLabel(label: string | undefined, translate?: MetadataTranslator): string;
|
|
30
39
|
//# sourceMappingURL=dynamic-columns-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns-helpers.d.ts","sourceRoot":"","sources":["../src/dynamic-columns-helpers.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiBpD;AAED,MAAM,MAAM,kBAAkB,GAAG,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KAClC,MAAM,CAAA;AAEX;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,CAAC,EAAE,kBAAkB,GAC/B,MAAM,CAOR"}
|
|
1
|
+
{"version":3,"file":"dynamic-columns-helpers.d.ts","sourceRoot":"","sources":["../src/dynamic-columns-helpers.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiBpD;AAED,MAAM,MAAM,kBAAkB,GAAG,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KAClC,MAAM,CAAA;AAEX;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,CAAC,EAAE,kBAAkB,GAC/B,MAAM,CAOR;AAOD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,CAAC,EAAE,kBAAkB,GAC/B,MAAM,CAIR"}
|
|
@@ -72,3 +72,22 @@ export function translateMetadataLabel(value, translate) {
|
|
|
72
72
|
const translated = translate(value, { defaultValue: fallback });
|
|
73
73
|
return translated && translated !== value ? translated : fallback;
|
|
74
74
|
}
|
|
75
|
+
// A catalog key as addons write them: lowercase dotted segments
|
|
76
|
+
// ("fiscal_mexico.ext.customer.rfc_receptor"). Human labels have spaces,
|
|
77
|
+
// capitals or accents and never match.
|
|
78
|
+
const CATALOG_KEY_RE = /^[a-z0-9_]+(\.[a-z0-9_]+)+$/;
|
|
79
|
+
/**
|
|
80
|
+
* Form-field label as the user should read it. The host usually serves labels
|
|
81
|
+
* already localized; when it could not (an extension field whose addon catalog
|
|
82
|
+
* the server did not resolve), the label arrives as the raw catalog key and the
|
|
83
|
+
* form printed `FISCAL_MEXICO.EXT.CUSTOMER.RFC_RECEPTOR`. Such a key is resolved
|
|
84
|
+
* with the client catalog and, failing that, humanized. Any other label is
|
|
85
|
+
* returned untouched.
|
|
86
|
+
*/
|
|
87
|
+
export function localizeFieldLabel(label, translate) {
|
|
88
|
+
if (!label)
|
|
89
|
+
return '';
|
|
90
|
+
if (!CATALOG_KEY_RE.test(label))
|
|
91
|
+
return label;
|
|
92
|
+
return translateMetadataLabel(label, translate);
|
|
93
|
+
}
|
|
@@ -38,17 +38,7 @@ export declare const aggregateOf: (col: ColumnDefinition) => string | undefined;
|
|
|
38
38
|
* empty filtered set reads cleanly.
|
|
39
39
|
*/
|
|
40
40
|
export declare const formatAggregateTotal: (col: ColumnDefinition, value: unknown, currency?: string, locale?: string) => string;
|
|
41
|
-
|
|
42
|
-
* One entry of the row "…" menu. An action gated by a missing optional addon
|
|
43
|
-
* (`requiresAddon`) stays listed with a lock + tooltip; the click still goes to
|
|
44
|
-
* `onAction`, whose shared handler (useDynamicRowActions) opens the
|
|
45
|
-
* requires-addon dialog instead of executing.
|
|
46
|
-
*/
|
|
47
|
-
export declare function RowActionMenuItem({ action, label, onSelect, }: {
|
|
48
|
-
action: any;
|
|
49
|
-
label: React.ReactNode;
|
|
50
|
-
onSelect: (e: React.MouseEvent) => void;
|
|
51
|
-
}): React.JSX.Element;
|
|
41
|
+
export { RowActionMenuItem } from './row-actions-menu';
|
|
52
42
|
/**
|
|
53
43
|
* State-machine gate for per-row actions.
|
|
54
44
|
*
|
|
@@ -62,7 +52,7 @@ export declare function RowActionMenuItem({ action, label, onSelect, }: {
|
|
|
62
52
|
* - action without requiresState (or empty array) → always shown.
|
|
63
53
|
* - row with neither `status` nor `state` → all actions shown.
|
|
64
54
|
*/
|
|
65
|
-
export declare const isActionAllowedForRowState: (action: any, row: any) => boolean;
|
|
55
|
+
export declare const isActionAllowedForRowState: (action: any, row: any, stageField?: string) => boolean;
|
|
66
56
|
/**
|
|
67
57
|
* Declarative `condition` gate for a per-row action: shows the action only when
|
|
68
58
|
* the row's `field` satisfies the operator. Supports both the SDK dialect
|
|
@@ -89,7 +79,13 @@ export declare const isActionConditionMet: (action: any, row: any) => boolean;
|
|
|
89
79
|
* (`requiresState`) AND the declarative `condition` must pass. Shared by the
|
|
90
80
|
* table's action column and the kanban card menu so they hide/show identically.
|
|
91
81
|
*/
|
|
92
|
-
export declare const isRowActionVisible: (action: any, row: any) => boolean;
|
|
82
|
+
export declare const isRowActionVisible: (action: any, row: any, stageField?: string) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The column a model's `requiresState` gates read: the served `stage_field`
|
|
85
|
+
* (or, for hosts that predate it, the `group_by` of a model with stages).
|
|
86
|
+
* Undefined → the historical `status` / `state` lookup.
|
|
87
|
+
*/
|
|
88
|
+
export declare const lifecycleStageField: (metadata: any) => string | undefined;
|
|
93
89
|
/**
|
|
94
90
|
* Resolves the relation sibling object a backend serves alongside an FK column.
|
|
95
91
|
* For a column keyed `category_id` the data row also carries
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"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"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -16,8 +16,8 @@ import * as React from 'react';
|
|
|
16
16
|
import { format } from 'date-fns';
|
|
17
17
|
import { isCalendarDayValue, parseCalendarDate } from './calendar-date';
|
|
18
18
|
import { es, enUS } from 'date-fns/locale';
|
|
19
|
-
import { Calendar, Check, Copy, Mail, Minus
|
|
20
|
-
import { Avatar, AvatarFallback, AvatarImage, Badge,
|
|
19
|
+
import { Calendar, Check, Copy, Mail, Minus } from 'lucide-react';
|
|
20
|
+
import { Avatar, AvatarFallback, AvatarImage, Badge, Checkbox, InitialsAvatar, } from '@asteby/metacore-ui';
|
|
21
21
|
import { DataTableColumnHeader, FilterableColumnHeader, } from '@asteby/metacore-ui/data-table';
|
|
22
22
|
import { generateBadgeStyles, getInitials, relationChipStyles, } from '@asteby/metacore-ui/lib';
|
|
23
23
|
import { Progress } from './dialogs/_primitives';
|
|
@@ -30,7 +30,7 @@ import { DynamicIcon, isLucideIconName } from './dynamic-icon';
|
|
|
30
30
|
import { CollectionCell } from './collection-cell';
|
|
31
31
|
import { isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
32
32
|
import { useOptionsResolver } from './use-options-resolver';
|
|
33
|
-
import {
|
|
33
|
+
import { RowActionsMenu } from './row-actions-menu';
|
|
34
34
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
35
35
|
const defaultGetImageUrl = (path) => path;
|
|
36
36
|
const getNestedValue = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
@@ -109,23 +109,25 @@ const CodeCell = ({ text, maxLength }) => {
|
|
|
109
109
|
};
|
|
110
110
|
return (_jsxs("div", { className: "group flex items-center gap-1.5", children: [_jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/80", title: text, children: display }), _jsx("button", { type: "button", onClick: onCopy, className: "opacity-0 transition-opacity group-hover:opacity-100 text-muted-foreground hover:text-foreground", "aria-label": "Copiar", title: "Copiar", children: copied ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-500" })) : (_jsx(Copy, { className: "h-3.5 w-3.5" })) })] }));
|
|
111
111
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
* `onAction`, whose shared handler (useDynamicRowActions) opens the
|
|
116
|
-
* requires-addon dialog instead of executing.
|
|
117
|
-
*/
|
|
118
|
-
export function RowActionMenuItem({ action, label, onSelect, }) {
|
|
119
|
-
const requirement = resolveRequiresAddon(action);
|
|
120
|
-
const requiresAddonLabel = useRequiresAddonLabel();
|
|
121
|
-
return (_jsxs(DropdownMenuItem, { onClick: onSelect, title: requirement ? requiresAddonLabel(requirement) : undefined, "data-requires-addon": requirement?.key, children: [_jsx(DynamicIcon, { name: action.icon || 'Zap', className: "mr-2 h-4 w-4" }), label, requirement && _jsx(RequiresAddonLock, { requirement: requirement })] }));
|
|
122
|
-
}
|
|
112
|
+
// RowActionMenuItem vive en row-actions-menu.tsx (junto al menú que lo usa);
|
|
113
|
+
// se re-exporta aquí por compatibilidad (dynamic-kanban, hosts).
|
|
114
|
+
export { RowActionMenuItem } from './row-actions-menu';
|
|
123
115
|
/**
|
|
124
116
|
* Lifecycle column used by `requiresState`: prefer `status` (workshop, vehicles,
|
|
125
117
|
* …) and fall back to `state` (purchases, inventory transfers, …). Empty string
|
|
126
118
|
* is treated as missing so a blank `status` does not hide a populated `state`.
|
|
127
119
|
*/
|
|
128
|
-
const rowLifecycleState = (row) => {
|
|
120
|
+
const rowLifecycleState = (row, stageField) => {
|
|
121
|
+
// A model with a stage machine gates its actions on the stage column, the
|
|
122
|
+
// same field the kernel's checkRequiresState reads (dynamic/action.go).
|
|
123
|
+
// Without this, WorkOrder actions declared with stage keys
|
|
124
|
+
// (requiresState: ['reception', …]) were compared to `status` ('open') and
|
|
125
|
+
// hidden in every row, so an OT could never be advanced from the UI.
|
|
126
|
+
if (stageField) {
|
|
127
|
+
const stage = row?.[stageField];
|
|
128
|
+
if (stage !== undefined && stage !== null && stage !== '')
|
|
129
|
+
return stage;
|
|
130
|
+
}
|
|
129
131
|
const status = row?.status;
|
|
130
132
|
if (status !== undefined && status !== null && status !== '')
|
|
131
133
|
return status;
|
|
@@ -147,11 +149,11 @@ const rowLifecycleState = (row) => {
|
|
|
147
149
|
* - action without requiresState (or empty array) → always shown.
|
|
148
150
|
* - row with neither `status` nor `state` → all actions shown.
|
|
149
151
|
*/
|
|
150
|
-
export const isActionAllowedForRowState = (action, row) => {
|
|
152
|
+
export const isActionAllowedForRowState = (action, row, stageField) => {
|
|
151
153
|
const requires = action?.requiresState ?? action?.requires_state;
|
|
152
154
|
if (!Array.isArray(requires) || requires.length === 0)
|
|
153
155
|
return true;
|
|
154
|
-
const status = rowLifecycleState(row);
|
|
156
|
+
const status = rowLifecycleState(row, stageField);
|
|
155
157
|
if (status === undefined)
|
|
156
158
|
return true;
|
|
157
159
|
return requires.map(String).includes(String(status));
|
|
@@ -225,7 +227,13 @@ export const isActionConditionMet = (action, row) => {
|
|
|
225
227
|
* (`requiresState`) AND the declarative `condition` must pass. Shared by the
|
|
226
228
|
* table's action column and the kanban card menu so they hide/show identically.
|
|
227
229
|
*/
|
|
228
|
-
export const isRowActionVisible = (action, row) => isActionAllowedForRowState(action, row) && isActionConditionMet(action, row);
|
|
230
|
+
export const isRowActionVisible = (action, row, stageField) => isActionAllowedForRowState(action, row, stageField) && isActionConditionMet(action, row);
|
|
231
|
+
/**
|
|
232
|
+
* The column a model's `requiresState` gates read: the served `stage_field`
|
|
233
|
+
* (or, for hosts that predate it, the `group_by` of a model with stages).
|
|
234
|
+
* Undefined → the historical `status` / `state` lookup.
|
|
235
|
+
*/
|
|
236
|
+
export const lifecycleStageField = (metadata) => metadata?.stage_field ?? metadata?.stageField ?? (metadata?.stages?.length ? metadata?.group_by : undefined);
|
|
229
237
|
const lowerFirst = (value) => {
|
|
230
238
|
if (!value)
|
|
231
239
|
return value;
|
|
@@ -1033,9 +1041,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
|
|
|
1033
1041
|
size: 80,
|
|
1034
1042
|
maxSize: 80,
|
|
1035
1043
|
meta: {},
|
|
1036
|
-
cell: ({ row }) => (_jsx(
|
|
1037
|
-
.filter((action) => isRowActionVisible(action, row.original))
|
|
1038
|
-
.map((action) => (_jsx(RowActionMenuItem, { action: action, label: translateMetadataLabel(action.label, t), onSelect: () => onAction && onAction(action.key, row.original) }, action.key))) })] }) })),
|
|
1044
|
+
cell: ({ row }) => (_jsx(RowActionsMenu, { actions: resolvedActions.filter((action) => isRowActionVisible(action, row.original, lifecycleStageField(metadata))), row: row.original, onAction: onAction })),
|
|
1039
1045
|
});
|
|
1040
1046
|
}
|
|
1041
1047
|
return columns;
|
|
@@ -45,6 +45,24 @@ export interface DynamicCRUDPageProps {
|
|
|
45
45
|
* with neither, the generic record dialog is used as before.
|
|
46
46
|
*/
|
|
47
47
|
documentForms?: DocumentFormsManifest;
|
|
48
|
+
/**
|
|
49
|
+
* Filtro fijo de la vista (el `filter` del nav). Si va sobre el `type_field`
|
|
50
|
+
* del alta guiada, «Crear» abre ese tipo directo o se oculta cuando no hay
|
|
51
|
+
* formulario para él.
|
|
52
|
+
*/
|
|
53
|
+
filter?: Record<string, string>;
|
|
54
|
+
/**
|
|
55
|
+
* Abre el alta guiada al montar («crear desde»): tipo de documento y origen
|
|
56
|
+
* a precargar (`sources[].key` + id), p. ej. leídos de la URL
|
|
57
|
+
* `?create=<tipo>&from=<fuente>&from_id=<id>`. Solo con document_forms.
|
|
58
|
+
*/
|
|
59
|
+
initialCreate?: {
|
|
60
|
+
type?: string;
|
|
61
|
+
source?: {
|
|
62
|
+
key: string;
|
|
63
|
+
id: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
48
66
|
}
|
|
49
67
|
/**
|
|
50
68
|
* Page-level CRUD shell wired around <DynamicTable>. Hosts mount a single
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAad,OAAO,KAAK,EAAE,qBAAqB,EAAiB,MAAM,SAAS,CAAA;AAEnE,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAad,OAAO,KAAK,EAAE,qBAAqB,EAAiB,MAAM,SAAS,CAAA;AAEnE,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAC1E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,qBA6O1D"}
|
|
@@ -29,7 +29,7 @@ import { useApi } from './api-context';
|
|
|
29
29
|
import { useMetadataCache } from './metadata-cache';
|
|
30
30
|
import { DynamicTable } from './dynamic-table';
|
|
31
31
|
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
32
|
-
import { DocumentFormDialog, resolveDocumentForms } from './document-form-dialog';
|
|
32
|
+
import { DocumentFormDialog, resolveDocumentForms, scopeDocumentFormsToFilter } from './document-form-dialog';
|
|
33
33
|
import { ExportDialog } from './dialogs/export';
|
|
34
34
|
import { ImportDialog } from './dialogs/import';
|
|
35
35
|
import { getModelExtension } from './model-extension-registry';
|
|
@@ -48,7 +48,7 @@ const defaultStrings = {
|
|
|
48
48
|
* metadata fetch, dialogs and toolbar.
|
|
49
49
|
*/
|
|
50
50
|
export function DynamicCRUDPage(props) {
|
|
51
|
-
const { model, endpoint, title: titleOverride, newLabel, i18n, hideCreate, hideExport, hideImport, hideRefresh, headerExtras, toolbarExtras, classes, onChange, documentForms, } = props;
|
|
51
|
+
const { model, endpoint, title: titleOverride, newLabel, i18n, hideCreate, hideExport, hideImport, hideRefresh, headerExtras, toolbarExtras, classes, onChange, documentForms, filter, initialCreate, } = props;
|
|
52
52
|
const strings = { ...defaultStrings, ...(i18n ?? {}) };
|
|
53
53
|
const dataEndpoint = endpoint ?? `/dynamic/${model}`;
|
|
54
54
|
const ext = getModelExtension(model);
|
|
@@ -56,7 +56,7 @@ export function DynamicCRUDPage(props) {
|
|
|
56
56
|
const cachedMeta = useMetadataCache((s) => s.getMetadata(model));
|
|
57
57
|
const [metadata, setMetadata] = useState(cachedMeta ?? null);
|
|
58
58
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
59
|
-
const [openCreate, setOpenCreate] = useState(
|
|
59
|
+
const [openCreate, setOpenCreate] = useState(!!initialCreate);
|
|
60
60
|
const [openExport, setOpenExport] = useState(false);
|
|
61
61
|
const [openImport, setOpenImport] = useState(false);
|
|
62
62
|
useEffect(() => {
|
|
@@ -114,11 +114,20 @@ export function DynamicCRUDPage(props) {
|
|
|
114
114
|
// (useCan defaults to always-true), in which case create/export/import
|
|
115
115
|
// require `lowercase(model).create|export|import`.
|
|
116
116
|
const can = useCan();
|
|
117
|
-
const
|
|
117
|
+
const scoped = scopeDocumentFormsToFilter(resolveDocumentForms(metadata, documentForms), filter);
|
|
118
|
+
const guidedForms = scoped.forms;
|
|
119
|
+
const showCreate = primary.showGenericCreate && !scoped.hideCreate && can(modelCapability(model, 'create'));
|
|
118
120
|
const showImport = enableCRUD && !effectiveHideImport && can(modelCapability(model, 'import'));
|
|
119
121
|
const showExport = !effectiveHideExport && can(modelCapability(model, 'export'));
|
|
120
122
|
const showRefresh = !effectiveHideRefresh;
|
|
121
|
-
|
|
123
|
+
// El mismo filtro acota la tabla (forma `eq:<valor>` que espera DynamicTable).
|
|
124
|
+
const filterKey = filter ? JSON.stringify(filter) : '';
|
|
125
|
+
const tableFilters = useMemo(() => {
|
|
126
|
+
if (!filter || Object.keys(filter).length === 0)
|
|
127
|
+
return undefined;
|
|
128
|
+
return Object.fromEntries(Object.entries(filter).map(([k, v]) => [k, String(v).includes(':') ? String(v) : `eq:${v}`]));
|
|
129
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
130
|
+
}, [filterKey]);
|
|
122
131
|
const handleRefresh = useCallback(() => {
|
|
123
132
|
setRefreshKey((k) => k + 1);
|
|
124
133
|
onChange?.();
|
|
@@ -129,5 +138,5 @@ export function DynamicCRUDPage(props) {
|
|
|
129
138
|
const titleCls = classes?.title ?? 'text-2xl font-bold tracking-tight';
|
|
130
139
|
const toolbarCls = classes?.toolbar ?? 'flex items-center gap-2';
|
|
131
140
|
const tableWrapperCls = classes?.tableWrapper ?? 'flex-1 min-h-0';
|
|
132
|
-
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, _jsx(ModelActionToolbar, { model: model, endpoint: dataEndpoint, actions: metadata?.actions, primaryActionKey: ext?.primaryActionKey, onChange: handleRefresh }), showCreate && (_jsxs("button", { type: 'button', "data-primary": 'true', 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 && guidedForms && (_jsx(DocumentFormDialog, { open: openCreate, onOpenChange: setOpenCreate, model: model, endpoint: dataEndpoint, forms: guidedForms, onSaved: handleRefresh })), showCreate && !guidedForms && (_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 }))] }));
|
|
141
|
+
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, _jsx(ModelActionToolbar, { model: model, endpoint: dataEndpoint, actions: metadata?.actions, primaryActionKey: ext?.primaryActionKey, onChange: handleRefresh }), showCreate && (_jsxs("button", { type: 'button', "data-primary": 'true', 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, defaultFilters: tableFilters }, model) })] }), showCreate && guidedForms && (_jsx(DocumentFormDialog, { open: openCreate, onOpenChange: setOpenCreate, model: model, endpoint: dataEndpoint, forms: guidedForms, initialType: initialCreate?.type, initialSource: initialCreate?.source, onSaved: handleRefresh })), showCreate && !guidedForms && (_jsx(DynamicRecordDialog, { open: openCreate && !initialCreate?.source, 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 }))] }));
|
|
133
142
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAkBzF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,CAGrE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,cAAc,GACtB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAatG;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAkBzF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,CAGrE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,cAAc,GACtB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAatG;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBxB;AAwBD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACpC,UAAU,EAAE,cAAc,EAAE,EAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAuBrB;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,UAAU,EAAE,cAAc,EAAE,EAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACxB,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC3D,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqCrB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,YAAY,GAAG,SAAS,CAgB1B;AAqDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAiC3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAIrE;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAItE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,cAAc,EACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EACvC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GACvC,MAAM,CAQR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC3B,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,EAClD,SAAS,CAAC,EAAE,MAAM,GACnB,SAAS,EAAE,CAqBb;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE;IAAE,YAAY,CAAC,EAAE,WAAW,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACpF,WAAW,GAAG,SAAS,CAIzB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wBAAwB,CAAA;AAE1D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,EACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAiBT;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAqB9D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAChC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,GAC7D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiBxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAC3C,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,EACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,GAC9C,OAAO,CAQT;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,kBAAkB,GAAG,SAAS,CAGtF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACnB,CAQA;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB,CAaA"}
|
|
@@ -97,14 +97,26 @@ export function toNumber(v) {
|
|
|
97
97
|
* drift (0.1 + 0.2 noise) that would make a genuinely balanced entry look off.
|
|
98
98
|
*/
|
|
99
99
|
export function computeLineItemTotals(field, rows) {
|
|
100
|
-
const
|
|
100
|
+
const itemFields = getItemFields(field);
|
|
101
|
+
const cols = itemFields.filter((c) => c.total);
|
|
102
|
+
// A `total` UNIT PRICE column on a grid with a quantity and no line amount
|
|
103
|
+
// column sums the line amounts (qty × price − discount): adding unit prices
|
|
104
|
+
// is meaningless once a quantity exists (2 × 750 footed 750, not 1500).
|
|
105
|
+
const qtyKey = firstKey(itemFields, LINE_QTY_KEYS);
|
|
106
|
+
const lineValued = !!qtyKey && !firstKey(itemFields, LINE_AMOUNT_KEYS);
|
|
107
|
+
const discountKey = firstKey(itemFields, LINE_DISCOUNT_KEYS);
|
|
101
108
|
const totals = {};
|
|
102
109
|
for (const c of cols)
|
|
103
110
|
totals[c.key] = 0;
|
|
104
111
|
if (Array.isArray(rows)) {
|
|
105
112
|
for (const row of rows) {
|
|
106
|
-
for (const c of cols)
|
|
107
|
-
|
|
113
|
+
for (const c of cols) {
|
|
114
|
+
const v = toNumber(row?.[c.key]);
|
|
115
|
+
totals[c.key] +=
|
|
116
|
+
lineValued && LINE_PRICE_KEYS.has(c.key)
|
|
117
|
+
? toNumber(row?.[qtyKey]) * v - (discountKey ? toNumber(row?.[discountKey]) : 0)
|
|
118
|
+
: v;
|
|
119
|
+
}
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
for (const k of Object.keys(totals))
|
package/dist/dynamic-form.d.ts
CHANGED
|
@@ -25,6 +25,21 @@ export interface DynamicFormProps {
|
|
|
25
25
|
* legacy flat list (unchanged).
|
|
26
26
|
*/
|
|
27
27
|
formLayout?: FormLayout;
|
|
28
|
+
/**
|
|
29
|
+
* Para formularios dentro de un modal con alto acotado: el cuerpo de campos
|
|
30
|
+
* hace scroll propio y la barra de botones (Guardar/Cancelar) queda fija
|
|
31
|
+
* abajo, así nunca queda fuera de pantalla con muchos campos. El contenedor
|
|
32
|
+
* debe ser flex-col con alto máximo (p.ej. `max-h-[90dvh]`).
|
|
33
|
+
*/
|
|
34
|
+
scrollableBody?: boolean;
|
|
28
35
|
}
|
|
29
|
-
export declare function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel, cancelLabel, disabled, formLayout, }: DynamicFormProps): import("react").JSX.Element;
|
|
36
|
+
export declare function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel, cancelLabel, disabled, formLayout, scrollableBody, }: DynamicFormProps): import("react").JSX.Element;
|
|
37
|
+
/**
|
|
38
|
+
* Radix Select puede emitir `onValueChange('')` sin interacción del usuario (su
|
|
39
|
+
* <select> nativo oculto colapsa a '' si recibe el valor antes que sus
|
|
40
|
+
* <option>s, p.ej. dentro de un <form> en el primer render). Ese '' pisaría el
|
|
41
|
+
* valor real del registro y viajaría en el PATCH. Se ignora cuando ya hay un
|
|
42
|
+
* valor y '' no es una opción válida — el usuario nunca puede elegir '' así.
|
|
43
|
+
*/
|
|
44
|
+
export declare function guardEmptySelect(current: unknown, onChange: (v: any) => void, optionValues: readonly unknown[]): (v: string) => void;
|
|
30
45
|
//# sourceMappingURL=dynamic-form.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAerE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAOhB,MAAM,uBAAuB,CAAA;AAa9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEzF,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAerE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAOhB,MAAM,uBAAuB,CAAA;AAa9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEzF,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAsBD,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,EAChB,UAAU,EACV,cAAsB,GACzB,EAAE,gBAAgB,+BAiOlB;AA4PD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,EAC1B,YAAY,EAAE,SAAS,OAAO,EAAE,GACjC,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAMrB"}
|