@asteby/metacore-runtime-react 11.0.0 → 13.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 +74 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +21 -1
- package/dist/dialogs/create-record-dialog.d.ts +3 -0
- package/dist/dialogs/create-record-dialog.d.ts.map +1 -0
- package/dist/dialogs/create-record-dialog.js +20 -0
- package/dist/dialogs/dynamic-record.d.ts +38 -1
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +50 -12
- package/dist/dialogs/types.d.ts +115 -0
- package/dist/dialogs/types.d.ts.map +1 -0
- package/dist/dialogs/types.js +15 -0
- package/dist/dialogs/view-record-dialog.d.ts +3 -0
- package/dist/dialogs/view-record-dialog.d.ts.map +1 -0
- package/dist/dialogs/view-record-dialog.js +15 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +6 -2
- package/dist/dynamic-form-schema.d.ts +9 -0
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +22 -0
- package/dist/dynamic-form.d.ts +1 -0
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +12 -1
- package/dist/dynamic-line-items.d.ts +9 -0
- package/dist/dynamic-line-items.d.ts.map +1 -0
- package/dist/dynamic-line-items.js +64 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +7 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/model-action-toolbar.d.ts +27 -0
- package/dist/model-action-toolbar.d.ts.map +1 -0
- package/dist/model-action-toolbar.js +88 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/dynamic-form.test.ts +56 -1
- package/src/action-modal-dispatcher.tsx +22 -2
- package/src/dialogs/create-record-dialog.tsx +46 -0
- package/src/dialogs/dynamic-record.tsx +111 -15
- package/src/dialogs/types.ts +119 -0
- package/src/dialogs/view-record-dialog.tsx +37 -0
- package/src/dynamic-crud-page.tsx +11 -1
- package/src/dynamic-form-schema.ts +25 -0
- package/src/dynamic-form.tsx +12 -1
- package/src/dynamic-line-items.tsx +221 -0
- package/src/dynamic-table.tsx +7 -1
- package/src/index.ts +16 -0
- package/src/model-action-toolbar.tsx +154 -0
- package/src/types.ts +18 -0
package/dist/dynamic-form.js
CHANGED
|
@@ -4,9 +4,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// outside the full record-edit modal.
|
|
5
5
|
import { useEffect, useMemo, useState } from 'react';
|
|
6
6
|
import { Input, Textarea, Label, Switch, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@asteby/metacore-ui/primitives';
|
|
7
|
-
import { buildZodSchema, resolveWidget } from './dynamic-form-schema';
|
|
7
|
+
import { buildZodSchema, resolveWidget, isLineItemsField } from './dynamic-form-schema';
|
|
8
8
|
import { useOptionsResolver } from './use-options-resolver';
|
|
9
|
+
import { DynamicLineItems } from './dynamic-line-items';
|
|
9
10
|
export { buildZodSchema, resolveWidget };
|
|
11
|
+
export { DynamicLineItems } from './dynamic-line-items';
|
|
10
12
|
export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel = 'Guardar', cancelLabel = 'Cancelar', disabled = false, }) {
|
|
11
13
|
const [values, setValues] = useState({});
|
|
12
14
|
const [errors, setErrors] = useState({});
|
|
@@ -15,6 +17,10 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
15
17
|
useEffect(() => {
|
|
16
18
|
const defaults = {};
|
|
17
19
|
for (const f of fields) {
|
|
20
|
+
if (isLineItemsField(f)) {
|
|
21
|
+
defaults[f.key] = initialValues?.[f.key] ?? f.defaultValue ?? [];
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
18
24
|
defaults[f.key] = initialValues?.[f.key] ?? f.defaultValue ?? (f.type === 'boolean' ? false : '');
|
|
19
25
|
}
|
|
20
26
|
setValues(defaults);
|
|
@@ -46,6 +52,11 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
46
52
|
return (_jsxs("form", { onSubmit: handleSubmit, className: "grid gap-4", children: [fields.map((field) => (_jsxs("div", { className: "grid gap-2", children: [_jsxs(Label, { htmlFor: field.key, children: [field.label, field.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), _jsx(FieldRenderer, { field: field, value: values[field.key], onChange: (v) => update(field.key, v) }), errors[field.key] && (_jsx("span", { className: "text-red-500 text-sm", role: "alert", children: errors[field.key] }))] }, field.key))), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: submitting || disabled, children: cancelLabel })), _jsx(Button, { type: "submit", disabled: submitting || disabled, children: submitLabel })] })] }));
|
|
47
53
|
}
|
|
48
54
|
function FieldRenderer({ field, value, onChange }) {
|
|
55
|
+
// Repeatable line-items group → render the row grid. Its value is an array
|
|
56
|
+
// of row objects rather than a scalar.
|
|
57
|
+
if (isLineItemsField(field)) {
|
|
58
|
+
return _jsx(DynamicLineItems, { field: field, value: value, onChange: onChange });
|
|
59
|
+
}
|
|
49
60
|
const widget = resolveWidget(field);
|
|
50
61
|
// Ref-driven select: hook into useOptionsResolver so the canonical
|
|
51
62
|
// /api/options/<ref>?field=id endpoint feeds the dropdown. This is
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ActionFieldDef } from './types';
|
|
2
|
+
export interface DynamicLineItemsProps {
|
|
3
|
+
field: ActionFieldDef;
|
|
4
|
+
value: any[] | undefined;
|
|
5
|
+
onChange: (rows: any[]) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function DynamicLineItems({ field, value, onChange, disabled }: DynamicLineItemsProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=dynamic-line-items.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-line-items.d.ts","sourceRoot":"","sources":["../src/dynamic-line-items.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAI7C,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,EAAE,GAAG,SAAS,CAAA;IACxB,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAUD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAgB,EAAE,EAAE,qBAAqB,2CAwEnG"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// DynamicLineItems — renders a repeatable line-items group: a table/grid of
|
|
3
|
+
// rows where each column is one of the field's `itemFields` (the v3
|
|
4
|
+
// `item_fields`). Powers declarative multi-line action modals (e.g. the item
|
|
5
|
+
// rows of a "Recibir mercancía" modal, or the debit/credit lines of a journal
|
|
6
|
+
// entry) without needing a custom federated modal.
|
|
7
|
+
//
|
|
8
|
+
// The value is an array of row objects keyed by the item field keys. Add/remove
|
|
9
|
+
// row controls mutate the array; each cell is a widget resolved via
|
|
10
|
+
// `resolveWidget`, matching the flat-field renderer in dynamic-form.tsx.
|
|
11
|
+
import { Input, Textarea, Switch, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@asteby/metacore-ui/primitives';
|
|
12
|
+
import { Plus, Trash2 } from 'lucide-react';
|
|
13
|
+
import { resolveWidget, getItemFields } from './dynamic-form-schema';
|
|
14
|
+
import { useOptionsResolver } from './use-options-resolver';
|
|
15
|
+
function emptyRow(itemFields) {
|
|
16
|
+
const row = {};
|
|
17
|
+
for (const f of itemFields) {
|
|
18
|
+
row[f.key] = f.defaultValue ?? (f.type === 'boolean' ? false : '');
|
|
19
|
+
}
|
|
20
|
+
return row;
|
|
21
|
+
}
|
|
22
|
+
export function DynamicLineItems({ field, value, onChange, disabled = false }) {
|
|
23
|
+
const itemFields = getItemFields(field);
|
|
24
|
+
const rows = Array.isArray(value) ? value : [];
|
|
25
|
+
const addRow = () => onChange([...rows, emptyRow(itemFields)]);
|
|
26
|
+
const removeRow = (idx) => onChange(rows.filter((_, i) => i !== idx));
|
|
27
|
+
const updateCell = (idx, key, cellValue) => onChange(rows.map((r, i) => (i === idx ? { ...r, [key]: cellValue } : r)));
|
|
28
|
+
return (_jsxs("div", { className: "grid gap-2", "data-widget": "line_items", children: [_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50", children: _jsxs("tr", { children: [itemFields.map((col) => (_jsxs("th", { className: "px-3 py-2 text-left font-medium", children: [col.label, col.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }, col.key))), _jsx("th", { className: "w-12 px-3 py-2", "aria-label": "acciones" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: itemFields.length + 1, className: "px-3 py-4 text-center text-muted-foreground", children: "Sin renglones" }) })), rows.map((row, idx) => (_jsxs("tr", { className: "border-t align-top", children: [itemFields.map((col) => (_jsx("td", { className: "px-2 py-1.5", children: _jsx(CellRenderer, { field: col, value: row?.[col.key], onChange: (v) => updateCell(idx, col.key, v), disabled: disabled }) }, col.key))), _jsx("td", { className: "px-2 py-1.5 text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeRow(idx), disabled: disabled, "aria-label": "Eliminar rengl\u00F3n", children: _jsx(Trash2, { className: "h-4 w-4 text-red-500" }) }) })] }, idx)))] })] }) }), _jsx("div", { children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addRow, disabled: disabled, children: [_jsx(Plus, { className: "mr-1 h-4 w-4" }), "Agregar rengl\u00F3n"] }) })] }));
|
|
29
|
+
}
|
|
30
|
+
// Per-cell widget. Mirrors the flat FieldRenderer in dynamic-form.tsx but
|
|
31
|
+
// without the per-field Label (the column header is the label) and sized for a
|
|
32
|
+
// table cell. Nested line-items inside a row are not supported (a row column is
|
|
33
|
+
// a scalar widget).
|
|
34
|
+
function CellRenderer({ field, value, onChange, disabled }) {
|
|
35
|
+
const widget = resolveWidget(field);
|
|
36
|
+
if (widget === 'select' && field.ref) {
|
|
37
|
+
return _jsx(RefCell, { field: field, value: value, onChange: onChange, disabled: disabled });
|
|
38
|
+
}
|
|
39
|
+
switch (widget) {
|
|
40
|
+
case 'textarea':
|
|
41
|
+
case 'richtext':
|
|
42
|
+
return (_jsx(Textarea, { value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: disabled, rows: 2 }));
|
|
43
|
+
case 'color':
|
|
44
|
+
return (_jsx(Input, { type: "color", value: value || '#000000', onChange: (e) => onChange(e.target.value), disabled: disabled }));
|
|
45
|
+
case 'select':
|
|
46
|
+
return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: disabled, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: field.options?.map((opt) => (_jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
47
|
+
case 'switch':
|
|
48
|
+
return _jsx(Switch, { checked: !!value, onCheckedChange: onChange, disabled: disabled });
|
|
49
|
+
case 'number':
|
|
50
|
+
return (_jsx(Input, { type: "number", value: value ?? '', onChange: (e) => onChange(e.target.valueAsNumber || ''), placeholder: field.placeholder, disabled: disabled }));
|
|
51
|
+
case 'date':
|
|
52
|
+
return (_jsx(Input, { type: "date", value: value || '', onChange: (e) => onChange(e.target.value), disabled: disabled }));
|
|
53
|
+
default:
|
|
54
|
+
return (_jsx(Input, { type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: disabled }));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function RefCell({ field, value, onChange, disabled }) {
|
|
58
|
+
const { options, loading } = useOptionsResolver({
|
|
59
|
+
modelKey: '',
|
|
60
|
+
fieldKey: 'id',
|
|
61
|
+
ref: field.ref,
|
|
62
|
+
});
|
|
63
|
+
return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: disabled || loading, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? 'Cargando…' : field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: options.map((opt) => (_jsx(SelectItem, { value: String(opt.id), children: opt.label }, String(opt.id)))) })] }));
|
|
64
|
+
}
|
|
@@ -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,2CAqtBnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -532,7 +532,13 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
532
532
|
const columns = useMemo(() => {
|
|
533
533
|
if (!metadata)
|
|
534
534
|
return [];
|
|
535
|
-
|
|
535
|
+
// Row-action column only renders per-row actions. Table-level placements
|
|
536
|
+
// ("table"/"create") are surfaced by <ModelActionToolbar> at the page
|
|
537
|
+
// level, so strip them here to avoid a meaningless per-row button.
|
|
538
|
+
const rowMetadata = metadata.actions?.some((a) => a.placement === 'table' || a.placement === 'create')
|
|
539
|
+
? { ...metadata, actions: metadata.actions.filter((a) => !a.placement || a.placement === 'row') }
|
|
540
|
+
: metadata;
|
|
541
|
+
const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs);
|
|
536
542
|
const filteredBase = baseColumns.filter((col) => !hiddenColumns.includes(col.id));
|
|
537
543
|
const actionsCol = filteredBase.find((c) => c.id === 'actions');
|
|
538
544
|
const otherCols = filteredBase.filter((c) => c.id !== 'actions');
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './options-context';
|
|
|
3
3
|
export * from './dynamic-table';
|
|
4
4
|
export * from './dynamic-form';
|
|
5
5
|
export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
|
|
6
|
+
export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
|
|
6
7
|
export * from './addon-loader';
|
|
7
8
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
|
|
8
9
|
export * from './slot';
|
|
@@ -17,6 +18,9 @@ export * from './dynamic-icon';
|
|
|
17
18
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
18
19
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
19
20
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
21
|
+
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
22
|
+
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
|
23
|
+
export type { ModelKey, ModelSchema, CreateResult, RecordDialogProps, CreateRecordDialogProps, ViewRecordDialogProps, } from './dialogs/types';
|
|
20
24
|
export { ExportDialog } from './dialogs/export';
|
|
21
25
|
export { ImportDialog } from './dialogs/import';
|
|
22
26
|
export { DynamicCRUDPage, type DynamicCRUDPageProps, type DynamicCRUDPageStrings, type DynamicCRUDPageClasses, } from './dynamic-crud-page';
|
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,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,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,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,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,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './options-context';
|
|
|
8
8
|
export * from './dynamic-table';
|
|
9
9
|
export * from './dynamic-form';
|
|
10
10
|
export { ActionModalDispatcher, } from './action-modal-dispatcher';
|
|
11
|
+
export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
|
|
11
12
|
export * from './addon-loader';
|
|
12
13
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
|
|
13
14
|
export * from './slot';
|
|
@@ -21,6 +22,8 @@ export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederation
|
|
|
21
22
|
export * from './dynamic-icon';
|
|
22
23
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, } from './dynamic-columns';
|
|
23
24
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
25
|
+
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
26
|
+
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
|
24
27
|
export { ExportDialog } from './dialogs/export';
|
|
25
28
|
export { ImportDialog } from './dialogs/import';
|
|
26
29
|
export { DynamicCRUDPage, } from './dynamic-crud-page';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ActionDefinition } from './types';
|
|
2
|
+
export type ActionPlacement = 'row' | 'table' | 'create';
|
|
3
|
+
export interface ModelActionToolbarProps {
|
|
4
|
+
/** Model key as registered on the backend (e.g. "JournalEntry"). */
|
|
5
|
+
model: string;
|
|
6
|
+
/** Data endpoint passed to the dispatcher. Defaults to `/data/<model>/me`. */
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Pre-fetched action definitions. When omitted the toolbar reads them from
|
|
10
|
+
* the metadata cache, falling back to `/metadata/table/<model>`. Pass this
|
|
11
|
+
* when the host page already holds the metadata to avoid a second fetch.
|
|
12
|
+
*/
|
|
13
|
+
actions?: ActionDefinition[];
|
|
14
|
+
/** Which placements to render. Defaults to `['table', 'create']`. */
|
|
15
|
+
placements?: ActionPlacement[];
|
|
16
|
+
/** Fired after an action's modal reports success. */
|
|
17
|
+
onChange?: () => void;
|
|
18
|
+
/** Extra classes on the button row container. */
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the model's actions matching the requested placements. Reads from the
|
|
23
|
+
* `actions` prop when provided, else the metadata cache, else fetches once.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useModelActions(model: string, placements?: ActionPlacement[], provided?: ActionDefinition[]): ActionDefinition[];
|
|
26
|
+
export declare function ModelActionToolbar({ model, endpoint, actions, placements, onChange, className, }: ModelActionToolbarProps): import("react/jsx-runtime").JSX.Element | null;
|
|
27
|
+
//# sourceMappingURL=model-action-toolbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-action-toolbar.d.ts","sourceRoot":"","sources":["../src/model-action-toolbar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,gBAAgB,EAAiC,MAAM,SAAS,CAAA;AAE9E,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACpC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAmBD;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,eAAe,EAAuB,EAClD,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAC9B,gBAAgB,EAAE,CA2BpB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,UAA+B,EAC/B,QAAQ,EACR,SAAS,GACZ,EAAE,uBAAuB,kDA4CzB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// ModelActionToolbar — renders page-level (toolbar) triggers for a model's
|
|
3
|
+
// declarative actions and owns the modal dispatch for them.
|
|
4
|
+
//
|
|
5
|
+
// A model's actions carry a `placement` hint (see manifest/v3 Action.placement):
|
|
6
|
+
// "row" (default) — rendered per-row inside <DynamicTable>'s action column.
|
|
7
|
+
// "table" — a plain toolbar button (no record context).
|
|
8
|
+
// "create" — a primary toolbar button that replaces the generic
|
|
9
|
+
// "create" button, for addons shipping a custom create
|
|
10
|
+
// experience (e.g. a journal entry with debit/credit lines).
|
|
11
|
+
//
|
|
12
|
+
// This component renders the buttons for the placements it's asked to surface
|
|
13
|
+
// (default: table + create) and mounts the <ActionModalDispatcher>, which
|
|
14
|
+
// resolves either a custom federated modal (registered via the action registry)
|
|
15
|
+
// or the generic declarative form. For create-style actions there is no record
|
|
16
|
+
// yet, so the dispatcher receives an empty record `{}`.
|
|
17
|
+
//
|
|
18
|
+
// It is the single generic primitive every host consumes — DynamicCRUDPage uses
|
|
19
|
+
// it internally, and bespoke host pages (e.g. ops `/m/$model`) mount it directly
|
|
20
|
+
// next to their own toolbar. Hosts never reimplement action-button plumbing.
|
|
21
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
22
|
+
import { Button } from '@asteby/metacore-ui/primitives';
|
|
23
|
+
import { useApi } from './api-context';
|
|
24
|
+
import { useMetadataCache } from './metadata-cache';
|
|
25
|
+
import { DynamicIcon } from './dynamic-icon';
|
|
26
|
+
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
27
|
+
const DEFAULT_PLACEMENTS = ['table', 'create'];
|
|
28
|
+
function toActionMetadata(a) {
|
|
29
|
+
return {
|
|
30
|
+
key: a.key,
|
|
31
|
+
label: a.label,
|
|
32
|
+
icon: a.icon || 'Zap',
|
|
33
|
+
color: a.color,
|
|
34
|
+
confirm: a.confirm,
|
|
35
|
+
confirmMessage: a.confirmMessage,
|
|
36
|
+
fields: a.fields,
|
|
37
|
+
requiresState: a.requiresState,
|
|
38
|
+
executable: a.executable,
|
|
39
|
+
placement: a.placement,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the model's actions matching the requested placements. Reads from the
|
|
44
|
+
* `actions` prop when provided, else the metadata cache, else fetches once.
|
|
45
|
+
*/
|
|
46
|
+
export function useModelActions(model, placements = DEFAULT_PLACEMENTS, provided) {
|
|
47
|
+
const api = useApi();
|
|
48
|
+
const cached = useMetadataCache((s) => s.getMetadata(model));
|
|
49
|
+
const [fetched, setFetched] = useState(null);
|
|
50
|
+
const haveSource = provided != null || cached != null;
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (haveSource)
|
|
53
|
+
return;
|
|
54
|
+
let cancelled = false;
|
|
55
|
+
api
|
|
56
|
+
.get(`/metadata/table/${model}`)
|
|
57
|
+
.then((res) => {
|
|
58
|
+
if (!cancelled)
|
|
59
|
+
setFetched((res.data?.data ?? res.data));
|
|
60
|
+
})
|
|
61
|
+
.catch(() => {
|
|
62
|
+
if (!cancelled)
|
|
63
|
+
setFetched(null);
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, [model, haveSource, api]);
|
|
69
|
+
const all = provided ?? cached?.actions ?? fetched?.actions ?? [];
|
|
70
|
+
return useMemo(() => all.filter((a) => placements.includes((a.placement ?? 'row'))), [all, placements]);
|
|
71
|
+
}
|
|
72
|
+
export function ModelActionToolbar({ model, endpoint, actions, placements = DEFAULT_PLACEMENTS, onChange, className, }) {
|
|
73
|
+
const surfaced = useModelActions(model, placements, actions);
|
|
74
|
+
const [active, setActive] = useState(null);
|
|
75
|
+
const dataEndpoint = endpoint ?? `/data/${model}/me`;
|
|
76
|
+
if (surfaced.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: className ?? 'flex items-center gap-2', children: surfaced.map((a) => {
|
|
79
|
+
const isCreate = (a.placement ?? 'row') === 'create';
|
|
80
|
+
return (_jsxs(Button, { variant: isCreate ? 'default' : 'outline', onClick: () => setActive(toActionMetadata(a)), style: a.color && !isCreate ? { borderColor: a.color, color: a.color } : undefined, children: [_jsx(DynamicIcon, { name: a.icon || (isCreate ? 'Plus' : 'Zap'), className: "mr-2 h-4 w-4" }), a.label] }, a.key));
|
|
81
|
+
}) }), active && (_jsx(ActionModalDispatcher, { open: !!active, onOpenChange: (open) => {
|
|
82
|
+
if (!open)
|
|
83
|
+
setActive(null);
|
|
84
|
+
}, action: active, model: model, record: {}, endpoint: dataEndpoint, onSuccess: () => {
|
|
85
|
+
setActive(null);
|
|
86
|
+
onChange?.();
|
|
87
|
+
} }))] }));
|
|
88
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -121,6 +121,16 @@ export interface ActionFieldDef {
|
|
|
121
121
|
* `useOptionsResolver` against `/api/options/<ref>?field=id`.
|
|
122
122
|
*/
|
|
123
123
|
ref?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Columns of a repeatable line-items group. Mirrors the kernel v3
|
|
126
|
+
* `ActionField.item_fields` (json `item_fields`). Present on a field
|
|
127
|
+
* with `type: "array"` — the multi-row container (e.g. the item rows
|
|
128
|
+
* of a "Recibir mercancía" modal, or the debit/credit lines of a
|
|
129
|
+
* journal entry). Each entry is itself an ActionFieldDef describing
|
|
130
|
+
* one column's cell widget. The field value is an array of objects
|
|
131
|
+
* keyed by these item field keys. Rendered by `DynamicLineItems`.
|
|
132
|
+
*/
|
|
133
|
+
itemFields?: ActionFieldDef[];
|
|
124
134
|
}
|
|
125
135
|
export interface ActionDefinition {
|
|
126
136
|
key: string;
|
|
@@ -137,6 +147,13 @@ export interface ActionDefinition {
|
|
|
137
147
|
fields?: ActionFieldDef[];
|
|
138
148
|
requiresState?: string[];
|
|
139
149
|
executable?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Where the host surfaces the trigger. Mirrors manifest/v3 Action.placement.
|
|
152
|
+
* "row" (default) — per-row table action.
|
|
153
|
+
* "table" — page toolbar button (no record context).
|
|
154
|
+
* "create" — toolbar button that replaces the generic create button.
|
|
155
|
+
*/
|
|
156
|
+
placement?: 'row' | 'table' | 'create';
|
|
140
157
|
}
|
|
141
158
|
export interface ApiResponse<T> {
|
|
142
159
|
success: boolean;
|
|
@@ -163,5 +180,6 @@ export interface ActionMetadata {
|
|
|
163
180
|
fields?: ActionFieldDef[];
|
|
164
181
|
requiresState?: string[];
|
|
165
182
|
executable?: boolean;
|
|
183
|
+
placement?: 'row' | 'table' | 'create';
|
|
166
184
|
}
|
|
167
185
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjF,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,qBAAqB,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,eAAe,GAAG,OAAO,CAAA;IAC3I,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3E;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;CAC/B;AAED,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAA;IACxC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC3B;AASD,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAID,MAAM,MAAM,WAAW,GACjB,MAAM,GACN,UAAU,GACV,UAAU,GACV,OAAO,GACP,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,QAAQ,CAAA;AAEd,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC5C,YAAY,CAAC,EAAE,GAAG,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"lucide-react": ">=0.460",
|
|
33
33
|
"date-fns": ">=3",
|
|
34
34
|
"react-day-picker": ">=8",
|
|
35
|
-
"@asteby/metacore-sdk": "^
|
|
36
|
-
"@asteby/metacore-ui": "^2.
|
|
35
|
+
"@asteby/metacore-sdk": "^3.1.0",
|
|
36
|
+
"@asteby/metacore-ui": "^2.1.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@tanstack/react-router": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"i18next": "^26.0.0",
|
|
53
53
|
"lucide-react": "^1.0.0",
|
|
54
54
|
"react": "^19.2.4",
|
|
55
|
-
"react-day-picker": "^
|
|
55
|
+
"react-day-picker": "^10.0.0",
|
|
56
56
|
"react-dom": "^19.2.4",
|
|
57
57
|
"react-i18next": "^17.0.0",
|
|
58
58
|
"sonner": "^2.0.0",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"typescript": "^6.0.0",
|
|
61
61
|
"vitest": "^4.0.0",
|
|
62
62
|
"zustand": "^5.0.0",
|
|
63
|
-
"@asteby/metacore-sdk": "
|
|
64
|
-
"@asteby/metacore-ui": "2.
|
|
63
|
+
"@asteby/metacore-sdk": "3.1.0",
|
|
64
|
+
"@asteby/metacore-ui": "2.1.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsc -p tsconfig.json",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { buildZodSchema, resolveWidget } from '../dynamic-form-schema'
|
|
2
|
+
import { buildZodSchema, resolveWidget, isLineItemsField, getItemFields } from '../dynamic-form-schema'
|
|
3
3
|
import type { ActionFieldDef } from '../types'
|
|
4
4
|
|
|
5
5
|
describe('buildZodSchema', () => {
|
|
@@ -102,3 +102,58 @@ describe('resolveWidget', () => {
|
|
|
102
102
|
expect(resolveWidget({ key: 'k', label: 'L', type: 'email' })).toBe('text')
|
|
103
103
|
})
|
|
104
104
|
})
|
|
105
|
+
|
|
106
|
+
describe('line-items (repeatable group)', () => {
|
|
107
|
+
const lineItemsField: ActionFieldDef = {
|
|
108
|
+
key: 'lines',
|
|
109
|
+
label: 'Renglones',
|
|
110
|
+
type: 'array',
|
|
111
|
+
itemFields: [
|
|
112
|
+
{ key: 'product_id', label: 'Producto', type: 'select', ref: 'product' },
|
|
113
|
+
{ key: 'quantity', label: 'Cantidad', type: 'number', required: true },
|
|
114
|
+
],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
it('detecta un campo line-items por sus itemFields', () => {
|
|
118
|
+
expect(isLineItemsField(lineItemsField)).toBe(true)
|
|
119
|
+
expect(isLineItemsField({ key: 'name', label: 'Nombre', type: 'string' })).toBe(false)
|
|
120
|
+
expect(getItemFields(lineItemsField)).toHaveLength(2)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('tolera item_fields snake_case crudo del kernel', () => {
|
|
124
|
+
const raw = {
|
|
125
|
+
key: 'lines',
|
|
126
|
+
label: 'Renglones',
|
|
127
|
+
type: 'array',
|
|
128
|
+
item_fields: [{ key: 'sku', label: 'SKU', type: 'string' }],
|
|
129
|
+
} as unknown as ActionFieldDef
|
|
130
|
+
expect(isLineItemsField(raw)).toBe(true)
|
|
131
|
+
expect(getItemFields(raw)).toHaveLength(1)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('valida como array de objetos por renglón', () => {
|
|
135
|
+
const schema = buildZodSchema([lineItemsField])
|
|
136
|
+
const ok = schema.safeParse({ lines: [{ product_id: 'p1', quantity: 3 }] })
|
|
137
|
+
expect(ok.success).toBe(true)
|
|
138
|
+
// No es un array → inválido (el valor del grupo debe ser una lista de renglones)
|
|
139
|
+
expect(schema.safeParse({ lines: 'nope' }).success).toBe(false)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('aplica reglas por columna dentro de cada renglón', () => {
|
|
143
|
+
const withBound: ActionFieldDef = {
|
|
144
|
+
key: 'lines',
|
|
145
|
+
label: 'Renglones',
|
|
146
|
+
type: 'array',
|
|
147
|
+
itemFields: [{ key: 'qty', label: 'Cantidad', type: 'number', required: true, validation: { min: 1 } }],
|
|
148
|
+
}
|
|
149
|
+
const schema = buildZodSchema([withBound])
|
|
150
|
+
expect(schema.safeParse({ lines: [{ qty: 5 }] }).success).toBe(true)
|
|
151
|
+
expect(schema.safeParse({ lines: [{ qty: 0 }] }).success).toBe(false)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('un grupo requerido exige al menos un renglón', () => {
|
|
155
|
+
const schema = buildZodSchema([{ ...lineItemsField, required: true }])
|
|
156
|
+
expect(schema.safeParse({ lines: [] }).success).toBe(false)
|
|
157
|
+
expect(schema.safeParse({ lines: [{ product_id: 'p1', quantity: 1 }] }).success).toBe(true)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
@@ -38,6 +38,9 @@ import { Loader2 } from 'lucide-react'
|
|
|
38
38
|
import { toast } from 'sonner'
|
|
39
39
|
import { useApi } from './api-context'
|
|
40
40
|
import { DynamicIcon } from './dynamic-icon'
|
|
41
|
+
import { DynamicLineItems } from './dynamic-line-items'
|
|
42
|
+
import { isLineItemsField } from './dynamic-form-schema'
|
|
43
|
+
import type { ActionFieldDef } from './types'
|
|
41
44
|
// Canonical registry lives in @asteby/metacore-sdk
|
|
42
45
|
import {
|
|
43
46
|
type ActionMetadata,
|
|
@@ -172,6 +175,10 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
172
175
|
if (open && action.fields) {
|
|
173
176
|
const defaults: Record<string, any> = {}
|
|
174
177
|
for (const field of action.fields) {
|
|
178
|
+
if (isLineItemsField(field)) {
|
|
179
|
+
defaults[field.key] = field.defaultValue ?? []
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
175
182
|
defaults[field.key] = field.defaultValue ?? (field.type === 'boolean' ? false : '')
|
|
176
183
|
}
|
|
177
184
|
setFormData(defaults)
|
|
@@ -183,7 +190,16 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
183
190
|
const execute = async () => {
|
|
184
191
|
if (action.fields) {
|
|
185
192
|
for (const field of action.fields) {
|
|
186
|
-
if (field.required
|
|
193
|
+
if (!field.required) continue
|
|
194
|
+
if (isLineItemsField(field)) {
|
|
195
|
+
const rows = formData[field.key]
|
|
196
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
197
|
+
toast.error(`${field.label} requiere al menos un renglón`)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if (!formData[field.key] && formData[field.key] !== false) {
|
|
187
203
|
toast.error(`${field.label} es requerido`)
|
|
188
204
|
return
|
|
189
205
|
}
|
|
@@ -247,10 +263,14 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
247
263
|
}
|
|
248
264
|
|
|
249
265
|
function renderField(
|
|
250
|
-
field:
|
|
266
|
+
field: ActionFieldDef,
|
|
251
267
|
value: any,
|
|
252
268
|
onChange: (value: any) => void,
|
|
253
269
|
) {
|
|
270
|
+
// Repeatable line-items group → row grid (value is an array of row objects).
|
|
271
|
+
if (isLineItemsField(field)) {
|
|
272
|
+
return <DynamicLineItems field={field} value={value} onChange={onChange} />
|
|
273
|
+
}
|
|
254
274
|
switch (field.type) {
|
|
255
275
|
case 'textarea':
|
|
256
276
|
return <Textarea id={field.key} value={value || ''} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)} placeholder={field.placeholder} />
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreateRecordDialog — generic record create/edit dialog.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around the underlying `DynamicRecordDialog` that exposes a
|
|
5
|
+
* narrower, intent-specific API (`onCreate` / `onUpdate` callbacks, `defaults`)
|
|
6
|
+
* matching the Wave 2.5 cleanup spec. Callers that need the full mode-switched
|
|
7
|
+
* dialog (including the read-only `view` mode) should reach for
|
|
8
|
+
* `DynamicRecordDialog` directly.
|
|
9
|
+
*
|
|
10
|
+
* When `recordId` is supplied, the dialog operates in edit mode; otherwise it
|
|
11
|
+
* starts in create mode. Callbacks (`onCreate`, `onUpdate`) are optional —
|
|
12
|
+
* when omitted, the dialog falls back to the configured `useApi()` transport
|
|
13
|
+
* with the default `/dynamic/${modelKey}` endpoint convention.
|
|
14
|
+
*/
|
|
15
|
+
import { DynamicRecordDialog } from './dynamic-record'
|
|
16
|
+
import type { CreateRecordDialogProps } from './types'
|
|
17
|
+
|
|
18
|
+
export function CreateRecordDialog({
|
|
19
|
+
modelKey,
|
|
20
|
+
open,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
recordId,
|
|
23
|
+
endpoint,
|
|
24
|
+
schema,
|
|
25
|
+
defaults,
|
|
26
|
+
onCreate,
|
|
27
|
+
onUpdate,
|
|
28
|
+
onSaved,
|
|
29
|
+
}: CreateRecordDialogProps) {
|
|
30
|
+
const mode = recordId ? 'edit' : 'create'
|
|
31
|
+
return (
|
|
32
|
+
<DynamicRecordDialog
|
|
33
|
+
open={open}
|
|
34
|
+
onOpenChange={onOpenChange}
|
|
35
|
+
mode={mode}
|
|
36
|
+
model={modelKey}
|
|
37
|
+
recordId={recordId}
|
|
38
|
+
endpoint={endpoint}
|
|
39
|
+
schema={schema}
|
|
40
|
+
defaults={defaults}
|
|
41
|
+
onCreate={onCreate}
|
|
42
|
+
onUpdate={onUpdate}
|
|
43
|
+
onSaved={onSaved}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|