@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/action-modal-dispatcher.d.ts.map +1 -1
  3. package/dist/action-modal-dispatcher.js +21 -1
  4. package/dist/dialogs/create-record-dialog.d.ts +3 -0
  5. package/dist/dialogs/create-record-dialog.d.ts.map +1 -0
  6. package/dist/dialogs/create-record-dialog.js +20 -0
  7. package/dist/dialogs/dynamic-record.d.ts +38 -1
  8. package/dist/dialogs/dynamic-record.d.ts.map +1 -1
  9. package/dist/dialogs/dynamic-record.js +50 -12
  10. package/dist/dialogs/types.d.ts +115 -0
  11. package/dist/dialogs/types.d.ts.map +1 -0
  12. package/dist/dialogs/types.js +15 -0
  13. package/dist/dialogs/view-record-dialog.d.ts +3 -0
  14. package/dist/dialogs/view-record-dialog.d.ts.map +1 -0
  15. package/dist/dialogs/view-record-dialog.js +15 -0
  16. package/dist/dynamic-crud-page.d.ts.map +1 -1
  17. package/dist/dynamic-crud-page.js +6 -2
  18. package/dist/dynamic-form-schema.d.ts +9 -0
  19. package/dist/dynamic-form-schema.d.ts.map +1 -1
  20. package/dist/dynamic-form-schema.js +22 -0
  21. package/dist/dynamic-form.d.ts +1 -0
  22. package/dist/dynamic-form.d.ts.map +1 -1
  23. package/dist/dynamic-form.js +12 -1
  24. package/dist/dynamic-line-items.d.ts +9 -0
  25. package/dist/dynamic-line-items.d.ts.map +1 -0
  26. package/dist/dynamic-line-items.js +64 -0
  27. package/dist/dynamic-table.d.ts.map +1 -1
  28. package/dist/dynamic-table.js +7 -1
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -0
  32. package/dist/model-action-toolbar.d.ts +27 -0
  33. package/dist/model-action-toolbar.d.ts.map +1 -0
  34. package/dist/model-action-toolbar.js +88 -0
  35. package/dist/types.d.ts +18 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/package.json +6 -6
  38. package/src/__tests__/dynamic-form.test.ts +56 -1
  39. package/src/action-modal-dispatcher.tsx +22 -2
  40. package/src/dialogs/create-record-dialog.tsx +46 -0
  41. package/src/dialogs/dynamic-record.tsx +111 -15
  42. package/src/dialogs/types.ts +119 -0
  43. package/src/dialogs/view-record-dialog.tsx +37 -0
  44. package/src/dynamic-crud-page.tsx +11 -1
  45. package/src/dynamic-form-schema.ts +25 -0
  46. package/src/dynamic-form.tsx +12 -1
  47. package/src/dynamic-line-items.tsx +221 -0
  48. package/src/dynamic-table.tsx +7 -1
  49. package/src/index.ts +16 -0
  50. package/src/model-action-toolbar.tsx +154 -0
  51. package/src/types.ts +18 -0
@@ -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,2CA+sBnB"}
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"}
@@ -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
- const baseColumns = getDynamicColumns(metadata, handleInternalAction, t, i18n.language, columnFilterConfigs);
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';
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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": "11.0.0",
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": "^2.6.0",
36
- "@asteby/metacore-ui": "^2.0.0"
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": "^9.0.0",
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": "2.6.0",
64
- "@asteby/metacore-ui": "2.0.0"
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 && !formData[field.key] && formData[field.key] !== false) {
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: { key: string; type: string; options?: { value: string; label: string }[]; placeholder?: string },
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
+ }