@asteby/metacore-runtime-react 47.0.0 → 48.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 +42 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +24 -4
- package/dist/audit-info.d.ts +54 -0
- package/dist/audit-info.d.ts.map +1 -0
- package/dist/audit-info.js +142 -0
- package/dist/business/catalog-product-search.d.ts +25 -0
- package/dist/business/catalog-product-search.d.ts.map +1 -0
- package/dist/business/catalog-product-search.js +57 -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 +107 -0
- package/dist/business/document-editor-model.d.ts.map +1 -0
- package/dist/business/document-editor-model.js +279 -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 +445 -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 +7 -0
- package/dist/business/line-items.d.ts.map +1 -1
- package/dist/business/line-items.js +6 -1
- 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 +19 -8
- 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 +43 -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 +8 -2
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +21 -5
- package/dist/dynamic-crud-page.d.ts +6 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +14 -5
- 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 +81 -20
- package/dist/dynamic-kanban.d.ts +9 -3
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +39 -13
- package/dist/dynamic-line-items.d.ts.map +1 -1
- package/dist/dynamic-line-items.js +3 -0
- package/dist/dynamic-multi-select-field.d.ts.map +1 -1
- package/dist/dynamic-multi-select-field.js +2 -0
- 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-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +4 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -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/option-filter.d.ts +31 -0
- package/dist/option-filter.d.ts.map +1 -0
- package/dist/option-filter.js +49 -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 +82 -0
- package/dist/primitives/contributions.d.ts.map +1 -0
- package/dist/primitives/contributions.js +126 -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-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/types.d.ts +142 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-options-resolver.d.ts +8 -0
- package/dist/use-options-resolver.d.ts.map +1 -1
- package/dist/use-options-resolver.js +8 -2
- package/package.json +5 -5
- package/src/__tests__/action-modal-grid-layout.test.tsx +1 -1
- package/src/__tests__/apply-sidebar-layout.test.ts +1 -1
- package/src/__tests__/audit-info.test.tsx +103 -0
- package/src/__tests__/catalog-product-search.test.ts +61 -0
- package/src/__tests__/dependent-options.test.tsx +12 -5
- package/src/__tests__/document-editor-model.test.ts +167 -0
- package/src/__tests__/document-editor.test.tsx +261 -0
- package/src/__tests__/document-form-dialog.test.tsx +118 -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__/dynamic-table-infinite-scroll.test.tsx +4 -0
- package/src/__tests__/installed-addons-context.test.tsx +50 -0
- package/src/__tests__/line-item-totals.test.ts +28 -0
- package/src/__tests__/line-items-lock-rows.test.tsx +2 -1
- package/src/__tests__/option-filter.test.tsx +115 -0
- 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__/structured-view-value.test.tsx +5 -1
- package/src/__tests__/wizard-action-modal.test.tsx +1 -1
- package/src/action-modal-dispatcher.tsx +47 -4
- package/src/audit-info.tsx +219 -0
- package/src/business/catalog-product-search.ts +87 -0
- package/src/business/catalog.ts +2 -0
- package/src/business/document-editor-model.ts +325 -0
- package/src/business/document-editor-panels.tsx +164 -0
- package/src/business/document-editor.tsx +727 -0
- package/src/business/document-lines.ts +5 -1
- package/src/business/index.ts +5 -0
- package/src/business/line-items.ts +15 -1
- package/src/business/payment-allocator.tsx +110 -0
- package/src/business/product-search.ts +4 -0
- package/src/dialogs/dynamic-record.tsx +42 -6
- package/src/document-form-dialog.tsx +92 -12
- package/src/dropdown-menu-entry.test.ts +23 -0
- package/src/dynamic-columns-helpers.ts +22 -0
- package/src/dynamic-columns.tsx +23 -6
- package/src/dynamic-crud-page.tsx +22 -3
- package/src/dynamic-form-schema.ts +15 -2
- package/src/dynamic-form.tsx +120 -39
- package/src/dynamic-kanban.tsx +41 -10
- package/src/dynamic-line-items.tsx +3 -0
- package/src/dynamic-multi-select-field.tsx +2 -0
- package/src/dynamic-relation-helpers.ts +9 -0
- package/src/dynamic-relation.tsx +7 -2
- package/src/dynamic-select-field.tsx +4 -0
- package/src/index.ts +34 -1
- package/src/installed-addons-context.tsx +58 -0
- package/src/option-filter.ts +88 -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 +208 -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-prefill-bar.tsx +62 -0
- package/src/row-action-stage-gate.test.ts +33 -0
- package/src/types.ts +142 -0
- package/src/use-options-resolver.ts +19 -1
- package/vitest.config.ts +1 -0
- package/vitest.setup.ts +7 -0
|
@@ -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;
|
|
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"}
|
package/dist/dynamic-form.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
// Minimal standalone DynamicForm. Factored from the dynamic-record-dialog
|
|
3
3
|
// pattern + ActionFieldDef renderer so callers can reuse the form layout
|
|
4
4
|
// outside the full record-edit modal.
|
|
5
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { groupFieldsBySection } from './form-layout';
|
|
7
7
|
import { FieldSection, WizardProgress } from './form-layout-ui';
|
|
8
8
|
import { AssistInterview } from './assist-interview';
|
|
@@ -10,6 +10,7 @@ import { Input, Textarea, Label, Switch, Button, Select, SelectContent, SelectIt
|
|
|
10
10
|
import { buildZodSchema, resolveWidget, isLineItemsField, evaluateBalance, applyOptionWhen, getDependsOn, getVisibleWhen, evaluateVisibleWhen, } from './dynamic-form-schema';
|
|
11
11
|
import { ScanLine } from 'lucide-react';
|
|
12
12
|
import { BarcodeScanner } from './barcode-scanner';
|
|
13
|
+
import { getOptionFilter } from './option-filter';
|
|
13
14
|
import { useOptionsResolver } from './use-options-resolver';
|
|
14
15
|
import { DynamicLineItems } from './dynamic-line-items';
|
|
15
16
|
import { DynamicSelectField } from './dynamic-select-field';
|
|
@@ -26,8 +27,24 @@ export { DynamicDateField } from './dynamic-date-field';
|
|
|
26
27
|
export { UploadField } from './upload-field';
|
|
27
28
|
export { IconPickerField } from './icon-picker-field';
|
|
28
29
|
export { ColorPickerField, DEFAULT_ROLE_COLOR, normalizeHex } from './color-picker-field';
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Valores iniciales del formulario: el registro (edición) gana sobre el default
|
|
31
|
+
// del campo. Se calcula de forma síncrona en el primer render para que ningún
|
|
32
|
+
// widget (p.ej. un Select de Radix) monte con '' y emita un cambio espurio que
|
|
33
|
+
// pise el valor real del registro.
|
|
34
|
+
function seedValues(fields, initialValues) {
|
|
35
|
+
const defaults = {};
|
|
36
|
+
for (const f of fields) {
|
|
37
|
+
if (isLineItemsField(f)) {
|
|
38
|
+
defaults[f.key] = initialValues?.[f.key] ?? f.defaultValue ?? [];
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
defaults[f.key] = initialValues?.[f.key] ?? f.defaultValue ?? (f.type === 'boolean' ? false : '');
|
|
42
|
+
}
|
|
43
|
+
return defaults;
|
|
44
|
+
}
|
|
45
|
+
const isReadonlyField = (f) => !!f.readonly ||
|
|
46
|
+
!!f.readOnly;
|
|
47
|
+
export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel = 'Guardar', cancelLabel = 'Cancelar', disabled = false, formLayout, scrollableBody = false, }) {
|
|
31
48
|
const [errors, setErrors] = useState({});
|
|
32
49
|
const [submitting, setSubmitting] = useState(false);
|
|
33
50
|
// Readonly fields are system-owned (connection state, sync-written values):
|
|
@@ -35,7 +52,11 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
35
52
|
// instead of rendered disabled, matching the metadata contract
|
|
36
53
|
// (ColumnDef.Readonly). They are also excluded from defaults/submit so a
|
|
37
54
|
// create never posts e.g. `status: ""` over the column default.
|
|
38
|
-
const editableFields = useMemo(() => fields.filter((f) => !f
|
|
55
|
+
const editableFields = useMemo(() => fields.filter((f) => !isReadonlyField(f)), [fields]);
|
|
56
|
+
const [values, setValues] = useState(() => seedValues(editableFields, initialValues));
|
|
57
|
+
// Campos que el usuario (o un reset dependiente) cambió desde la siembra.
|
|
58
|
+
// En edición, un select que sigue vacío y nadie tocó no viaja en el PATCH.
|
|
59
|
+
const touched = useRef(new Set());
|
|
39
60
|
// Conditional visibility: a field carrying `visible_when` is rendered — and
|
|
40
61
|
// validated — only while the referenced sibling field's current value
|
|
41
62
|
// matches the predicate. Driving BOTH the schema and the render off the same
|
|
@@ -68,19 +89,22 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
68
89
|
useEffect(() => {
|
|
69
90
|
setStepIndex((i) => Math.min(i, Math.max(groups.length - 1, 0)));
|
|
70
91
|
}, [groups.length]);
|
|
92
|
+
// Re-siembra cuando cambia el registro o los campos DESPUÉS del montaje (el
|
|
93
|
+
// primer render ya quedó sembrado por el inicializador de useState).
|
|
94
|
+
const seededFrom = useRef({ editableFields, initialValues });
|
|
71
95
|
useEffect(() => {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
defaults[f.key] = initialValues?.[f.key] ?? f.defaultValue ?? (f.type === 'boolean' ? false : '');
|
|
79
|
-
}
|
|
80
|
-
setValues(defaults);
|
|
96
|
+
const prev = seededFrom.current;
|
|
97
|
+
if (prev.editableFields === editableFields && prev.initialValues === initialValues)
|
|
98
|
+
return;
|
|
99
|
+
seededFrom.current = { editableFields, initialValues };
|
|
100
|
+
touched.current = new Set();
|
|
101
|
+
setValues(seedValues(editableFields, initialValues));
|
|
81
102
|
setErrors({});
|
|
82
103
|
}, [editableFields, initialValues]);
|
|
83
|
-
const update = (k, v) =>
|
|
104
|
+
const update = (k, v) => {
|
|
105
|
+
touched.current.add(k);
|
|
106
|
+
setValues((prev) => ({ ...prev, [k]: v }));
|
|
107
|
+
};
|
|
84
108
|
const handleSubmit = async (e) => {
|
|
85
109
|
e.preventDefault();
|
|
86
110
|
if (balanceBlocked)
|
|
@@ -97,9 +121,23 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
97
121
|
return;
|
|
98
122
|
}
|
|
99
123
|
setErrors({});
|
|
124
|
+
const data = { ...result.data };
|
|
125
|
+
// Edición: un select vacío que el usuario no tocó no se manda — el update
|
|
126
|
+
// es parcial, así que omitirlo conserva lo que tenga el registro en vez de
|
|
127
|
+
// pisarlo con ''.
|
|
128
|
+
if (initialValues) {
|
|
129
|
+
for (const f of visibleFields) {
|
|
130
|
+
if (resolveWidget(f) !== 'select')
|
|
131
|
+
continue;
|
|
132
|
+
if (touched.current.has(f.key))
|
|
133
|
+
continue;
|
|
134
|
+
if (data[f.key] === '' || data[f.key] == null)
|
|
135
|
+
delete data[f.key];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
100
138
|
setSubmitting(true);
|
|
101
139
|
try {
|
|
102
|
-
await onSubmit(
|
|
140
|
+
await onSubmit(data);
|
|
103
141
|
}
|
|
104
142
|
finally {
|
|
105
143
|
setSubmitting(false);
|
|
@@ -109,6 +147,10 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
109
147
|
// header fields flow through it; line-items grids (and textareas) span full
|
|
110
148
|
// width so the row table / memo gets room. Shared by every layout mode.
|
|
111
149
|
const renderGrid = (groupFields) => (_jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: groupFields.map((field) => (_jsx(FieldRow, { field: field, value: values[field.key], onChange: (v) => update(field.key, v), values: values, error: errors[field.key], initialValues: initialValues }, field.key))) }));
|
|
150
|
+
// scrollableBody: el cuerpo hace scroll y el footer de botones queda fijo.
|
|
151
|
+
const formClass = scrollableBody ? 'flex min-h-0 flex-1 flex-col gap-4' : 'grid gap-4';
|
|
152
|
+
const bodyClass = scrollableBody ? '-mx-1 grid min-h-0 flex-1 gap-4 overflow-y-auto px-1' : 'grid gap-4';
|
|
153
|
+
const footerExtra = scrollableBody ? ' shrink-0 border-t' : '';
|
|
112
154
|
// ── Steps (wizard) mode ────────────────────────────────────────────────
|
|
113
155
|
// One step per section; Anterior/Siguiente navigate, submit only on the
|
|
114
156
|
// last step. Advancing validates just the current step's fields so a later
|
|
@@ -133,7 +175,7 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
133
175
|
setStepIndex((i) => Math.min(i + 1, groups.length - 1));
|
|
134
176
|
};
|
|
135
177
|
const goBack = () => setStepIndex((i) => Math.max(i - 1, 0));
|
|
136
|
-
return (_jsxs("form", { onSubmit: handleSubmit, className:
|
|
178
|
+
return (_jsxs("form", { onSubmit: handleSubmit, className: formClass, children: [_jsxs("div", { className: bodyClass, children: [_jsx(WizardProgress, { groups: groups, stepIndex: stepIndex, onStepClick: i => setStepIndex(i) }), step.assist ? (_jsx(AssistInterview, { assist: step.assist, values: values, eyebrow: step.title, autoStart: step.assist.trigger !== 'button', onApply: fields => setValues(prev => ({ ...prev, ...fields })) })) : (renderGrid(step.fields))] }), _jsxs("div", { className: 'flex justify-between gap-2 pt-2' + footerExtra, children: [stepIndex > 0 ? (_jsx(Button, { type: "button", variant: "outline", onClick: goBack, disabled: submitting || disabled, children: "Anterior" })) : onCancel ? (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: submitting || disabled, children: cancelLabel })) : (_jsx("span", {})), isLast ? (_jsx(Button, { type: "submit", disabled: submitting || disabled || balanceBlocked, children: submitLabel })) : (_jsx(Button, { type: "button", onClick: goNext, disabled: submitting || disabled, children: "Siguiente" }))] })] }));
|
|
137
179
|
}
|
|
138
180
|
// ── Sections / flat mode ───────────────────────────────────────────────
|
|
139
181
|
// Layout: scalar header fields flow through a responsive 2-column grid;
|
|
@@ -141,7 +183,7 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
141
183
|
// memo gets room. Mirrors the pro look of the federated journal modal but
|
|
142
184
|
// stays fully declarative — driven only by field shape. With no layout this
|
|
143
185
|
// is a single default group rendered without section chrome (unchanged).
|
|
144
|
-
return (_jsxs("form", { onSubmit: handleSubmit, className:
|
|
186
|
+
return (_jsxs("form", { onSubmit: handleSubmit, className: formClass, children: [_jsx("div", { className: bodyClass, children: groups.map((group) => (_jsx(FieldSection, { group: group, children: renderGrid(group.fields) }, group.key))) }), _jsxs("div", { className: 'flex justify-end gap-2 pt-2' + footerExtra, children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: submitting || disabled, children: cancelLabel })), _jsx(Button, { type: "submit", disabled: submitting || disabled || balanceBlocked, children: submitLabel })] })] }));
|
|
145
187
|
}
|
|
146
188
|
// One form field row: label + renderer + inline error. Encapsulated as its own
|
|
147
189
|
// component so a STATIC enum select whose options are all gated out by a sibling
|
|
@@ -242,8 +284,10 @@ function FieldRenderer({ field, value, onChange, initialValues, effectiveOptions
|
|
|
242
284
|
return _jsx(Textarea, { id: field.key, "data-widget": "richtext", value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
|
|
243
285
|
case 'color':
|
|
244
286
|
return (_jsx(ColorPickerField, { "aria-label": field.label || field.key, value: typeof value === 'string' ? value : '', onChange: onChange }));
|
|
245
|
-
case 'select':
|
|
246
|
-
|
|
287
|
+
case 'select': {
|
|
288
|
+
const opts = effectiveOptions ?? field.options;
|
|
289
|
+
return (_jsxs(Select, { value: value || '', onValueChange: guardEmptySelect(value, onChange, (opts ?? []).map((o) => o.value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: opts?.map((opt) => _jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })] }));
|
|
290
|
+
}
|
|
247
291
|
case 'switch':
|
|
248
292
|
return _jsx(Switch, { id: field.key, checked: !!value, onCheckedChange: onChange });
|
|
249
293
|
case 'number':
|
|
@@ -290,11 +334,28 @@ function ScannableInput({ field, value, onChange, type, }) {
|
|
|
290
334
|
// (default) porque suma varios productos seguidos.
|
|
291
335
|
continuous: false, position: "fixed", title: `Escanear ${field.label ?? ''}`.trim() })] }));
|
|
292
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Radix Select puede emitir `onValueChange('')` sin interacción del usuario (su
|
|
339
|
+
* <select> nativo oculto colapsa a '' si recibe el valor antes que sus
|
|
340
|
+
* <option>s, p.ej. dentro de un <form> en el primer render). Ese '' pisaría el
|
|
341
|
+
* valor real del registro y viajaría en el PATCH. Se ignora cuando ya hay un
|
|
342
|
+
* valor y '' no es una opción válida — el usuario nunca puede elegir '' así.
|
|
343
|
+
*/
|
|
344
|
+
export function guardEmptySelect(current, onChange, optionValues) {
|
|
345
|
+
return (v) => {
|
|
346
|
+
const hasValue = current !== undefined && current !== null && current !== '';
|
|
347
|
+
if (v === '' && hasValue && !optionValues.some((o) => String(o) === ''))
|
|
348
|
+
return;
|
|
349
|
+
onChange(v);
|
|
350
|
+
};
|
|
351
|
+
}
|
|
293
352
|
function RefSelect({ field, value, onChange }) {
|
|
294
353
|
const { options, loading } = useOptionsResolver({
|
|
295
354
|
modelKey: '', // unused — `ref` drives the URL
|
|
296
355
|
fieldKey: 'id',
|
|
297
356
|
ref: field.ref,
|
|
357
|
+
optionFilter: getOptionFilter(field),
|
|
358
|
+
keepValue: value,
|
|
298
359
|
});
|
|
299
|
-
return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: loading, children: [_jsx(SelectTrigger, { className: "w-full", 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)))) })] }));
|
|
360
|
+
return (_jsxs(Select, { value: value || '', onValueChange: guardEmptySelect(value, onChange, options.map((o) => String(o.id))), disabled: loading, children: [_jsx(SelectTrigger, { className: "w-full", 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)))) })] }));
|
|
300
361
|
}
|
package/dist/dynamic-kanban.d.ts
CHANGED
|
@@ -91,9 +91,15 @@ export declare function isCardOverdue(card: any, col: ColumnDefinition, now?: nu
|
|
|
91
91
|
* The stage machine (stages, transitions, smart lanes) belongs to the model's
|
|
92
92
|
* stage column (`stage_field`; older hosts that do not serve it: the served
|
|
93
93
|
* group_by): grouping by it keeps the machine, grouping by anything else drops
|
|
94
|
-
* it and the lanes come from the new column.
|
|
95
|
-
*
|
|
96
|
-
*
|
|
94
|
+
* it and the lanes come from the new column.
|
|
95
|
+
*
|
|
96
|
+
* The served group_by is the LAST kanban nav entry's, which need not be the
|
|
97
|
+
* stage column: a board opened without `?group_by=` (the list's view toggle)
|
|
98
|
+
* groups by the stage column, and a board grouped by the served column keeps
|
|
99
|
+
* the machine only when that column IS the stage column. Otherwise the stage
|
|
100
|
+
* lanes were painted over another column (by bay: every card under «Sin etapa»
|
|
101
|
+
* and a drop wrote `service_bay_id = 'diagnosis'`, 422). Returns the SAME
|
|
102
|
+
* object when there is nothing to change. Pure — exported for unit tests.
|
|
97
103
|
*/
|
|
98
104
|
export declare function withGroupBy(metadata: TableMetadata | null, groupBy: string | undefined): TableMetadata | null;
|
|
99
105
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"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"}
|
package/dist/dynamic-kanban.js
CHANGED
|
@@ -10,7 +10,14 @@ import { CSS } from '@dnd-kit/utilities';
|
|
|
10
10
|
import { arrayMove } from '@dnd-kit/sortable';
|
|
11
11
|
import { Calendar, CircleDot, GripVertical, Hash, ListFilter, MoreHorizontal, RotateCcw, Search, Settings2, Tag, ToggleLeft, Type, X, } from 'lucide-react';
|
|
12
12
|
import { toast } from 'sonner';
|
|
13
|
-
import { Badge, Button, Card, CardContent,
|
|
13
|
+
import { Badge, Button, Card, CardContent, Input, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, Skeleton, } from '@asteby/metacore-ui/primitives';
|
|
14
|
+
// The card "…" menu renders RowActionMenuItem (dynamic-columns), whose
|
|
15
|
+
// DropdownMenuItem comes from the ROOT '@asteby/metacore-ui' entry. The menu
|
|
16
|
+
// shell MUST come from that same entry: hosts share the root entry as a Module
|
|
17
|
+
// Federation singleton while '/primitives' is bundled locally, so mixing them
|
|
18
|
+
// yields two Radix Menu contexts and the click crashes the app with
|
|
19
|
+
// "`MenuItem` must be used within `Menu`" (Pitsline taller kanban, 2026-10-05).
|
|
20
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@asteby/metacore-ui';
|
|
14
21
|
import { ColumnFilterControl, FilterValueCombobox } from '@asteby/metacore-ui/data-table';
|
|
15
22
|
import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib';
|
|
16
23
|
import { useApi } from './api-context';
|
|
@@ -30,7 +37,7 @@ import { useRecordMutationTick } from './use-record-mutation-tick';
|
|
|
30
37
|
import { useOptionsResolver } from './use-options-resolver';
|
|
31
38
|
import { ActivityValueRenderer } from './activity-value-renderer';
|
|
32
39
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
33
|
-
import { aggregateOf, formatAggregateTotal, isRowActionVisible, relationKeyFor, RowActionMenuItem, } from './dynamic-columns';
|
|
40
|
+
import { aggregateOf, formatAggregateTotal, isRowActionVisible, lifecycleStageField, relationKeyFor, RowActionMenuItem, } from './dynamic-columns';
|
|
34
41
|
import { useCan, usePermissionsActive, resolveRowActions } from './permissions-context';
|
|
35
42
|
import { useDynamicRowActions } from './dynamic-row-actions';
|
|
36
43
|
import { useStageLayout } from './stage-layout';
|
|
@@ -244,18 +251,37 @@ export function isCardOverdue(card, col, now = Date.now()) {
|
|
|
244
251
|
* The stage machine (stages, transitions, smart lanes) belongs to the model's
|
|
245
252
|
* stage column (`stage_field`; older hosts that do not serve it: the served
|
|
246
253
|
* group_by): grouping by it keeps the machine, grouping by anything else drops
|
|
247
|
-
* it and the lanes come from the new column.
|
|
248
|
-
*
|
|
249
|
-
*
|
|
254
|
+
* it and the lanes come from the new column.
|
|
255
|
+
*
|
|
256
|
+
* The served group_by is the LAST kanban nav entry's, which need not be the
|
|
257
|
+
* stage column: a board opened without `?group_by=` (the list's view toggle)
|
|
258
|
+
* groups by the stage column, and a board grouped by the served column keeps
|
|
259
|
+
* the machine only when that column IS the stage column. Otherwise the stage
|
|
260
|
+
* lanes were painted over another column (by bay: every card under «Sin etapa»
|
|
261
|
+
* and a drop wrote `service_bay_id = 'diagnosis'`, 422). Returns the SAME
|
|
262
|
+
* object when there is nothing to change. Pure — exported for unit tests.
|
|
250
263
|
*/
|
|
251
264
|
export function withGroupBy(metadata, groupBy) {
|
|
252
|
-
if (!metadata
|
|
265
|
+
if (!metadata)
|
|
253
266
|
return metadata;
|
|
267
|
+
const hasMachine = (metadata.stages?.length ?? 0) > 0;
|
|
268
|
+
const stageColumn = metadata.stage_field ?? (hasMachine ? metadata.group_by : undefined);
|
|
269
|
+
if (!groupBy) {
|
|
270
|
+
if (!hasMachine || !stageColumn || stageColumn === metadata.group_by)
|
|
271
|
+
return metadata;
|
|
272
|
+
return { ...metadata, group_by: stageColumn };
|
|
273
|
+
}
|
|
274
|
+
if (groupBy === stageColumn) {
|
|
275
|
+
return groupBy === metadata.group_by ? metadata : { ...metadata, group_by: groupBy };
|
|
276
|
+
}
|
|
254
277
|
if (!metadata.columns.some((c) => c.key === groupBy))
|
|
255
278
|
return metadata;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
279
|
+
if (groupBy === metadata.group_by &&
|
|
280
|
+
!hasMachine &&
|
|
281
|
+
!metadata.transitions &&
|
|
282
|
+
!metadata.smart_lanes) {
|
|
283
|
+
return metadata;
|
|
284
|
+
}
|
|
259
285
|
return {
|
|
260
286
|
...metadata,
|
|
261
287
|
group_by: groupBy,
|
|
@@ -1543,7 +1569,7 @@ function DynamicKanbanBoard({ model, endpoint, refreshTrigger, realtime: realtim
|
|
|
1543
1569
|
extraFilters,
|
|
1544
1570
|
configurable: isConfigurable(stage),
|
|
1545
1571
|
onConfigure: () => openConfigStage(stage),
|
|
1546
|
-
children: loadingData && cards.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20 w-full" }), _jsx(Skeleton, { className: "h-20 w-full" })] })) : cards.length === 0 ? (_jsx("p", { className: "px-1 py-6 text-center text-xs text-muted-foreground", children: t('kanban.emptyLane', { defaultValue: 'Sin tarjetas' }) })) : (cards.map((card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, showAllFields: explicitCard, actions: rowActions, locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: handleInternalAction, pending: card.id != null && String(card.id) in moveOverrides }, String(card.id))))),
|
|
1572
|
+
children: loadingData && cards.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20 w-full" }), _jsx(Skeleton, { className: "h-20 w-full" })] })) : cards.length === 0 ? (_jsx("p", { className: "px-1 py-6 text-center text-xs text-muted-foreground", children: t('kanban.emptyLane', { defaultValue: 'Sin tarjetas' }) })) : (cards.map((card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, showAllFields: explicitCard, actions: rowActions, stageField: lifecycleStageField(metadata), locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: handleInternalAction, pending: card.id != null && String(card.id) in moveOverrides }, String(card.id))))),
|
|
1547
1573
|
};
|
|
1548
1574
|
};
|
|
1549
1575
|
// Props for one smart (virtual) lane — a read-only, filter-defined column.
|
|
@@ -1557,7 +1583,7 @@ function DynamicKanbanBoard({ model, endpoint, refreshTrigger, realtime: realtim
|
|
|
1557
1583
|
refreshTrigger,
|
|
1558
1584
|
onEdit: openEditStage,
|
|
1559
1585
|
onDelete: openDeleteStage,
|
|
1560
|
-
renderCard: (card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, showAllFields: explicitCard, actions: rowActions, locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: handleInternalAction, draggable: false })),
|
|
1586
|
+
renderCard: (card) => (_jsx(KanbanCard, { card: card, titleCol: titleCol, fieldCols: fieldCols, showAllFields: explicitCard, actions: rowActions, stageField: lifecycleStageField(metadata), locale: i18n.language, timeZone: timeZone, currency: currency, onClick: onCardClick, onAction: handleInternalAction, draggable: false })),
|
|
1561
1587
|
});
|
|
1562
1588
|
const showResetOrder = laneReorderEnabled && (stageLayout.hasCustomLayout || !!laneOrderOverride);
|
|
1563
1589
|
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", "data-testid": "kanban-filters", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: globalFilter, onChange: (e) => setGlobalFilter(e.target.value), placeholder: t('kanban.searchPlaceholder', { defaultValue: 'Buscar...' }), className: "h-8 w-52 pl-8 text-sm" })] }), filterFields.length > 0 && (_jsxs(Sheet, { open: filtersOpen, onOpenChange: setFiltersOpen, children: [_jsx(SheetTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5", children: [_jsx(ListFilter, { className: "h-3.5 w-3.5" }), t('kanban.filters', { defaultValue: 'Filtros' }), activeFilterCount > 0 && (_jsx(Badge, { variant: "secondary", className: "ml-0.5 h-4 min-w-4 justify-center rounded-full px-1 text-[10px] tabular-nums", children: activeFilterCount }))] }) }), _jsxs(SheetContent, { side: "right", className: "flex w-80 flex-col gap-0 p-0 sm:max-w-sm", children: [_jsx(SheetHeader, { className: "space-y-0 border-b px-4 py-3", children: _jsx(SheetTitle, { className: "text-sm", children: t('kanban.filters', { defaultValue: 'Filtros' }) }) }), _jsxs("div", { className: "flex flex-1 flex-col gap-1 overflow-y-auto px-4 py-3", children: [activeFields.length > 0 && (_jsxs(_Fragment, { children: [_jsx("p", { className: "px-0.5 pb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: t('kanban.activeFilters', {
|
|
@@ -1875,13 +1901,13 @@ function CardBody({ card, titleCol, fieldCols, showAllFields, locale, timeZone,
|
|
|
1875
1901
|
? 'font-medium text-destructive'
|
|
1876
1902
|
: 'text-muted-foreground'}`, "data-overdue": isCardOverdue(card, col) || undefined, children: [_jsxs("span", { className: "shrink-0 opacity-70", children: [col.label, ":"] }), _jsx("span", { className: "min-w-0 break-words", children: _jsx(ActivityValueRenderer, { value: cardCellValue(card, col), col: col, locale: locale, timeZone: timeZone, currency: currency }) })] }, col.key)))] }));
|
|
1877
1903
|
}
|
|
1878
|
-
function KanbanCard({ card, titleCol, fieldCols, showAllFields, actions, locale, timeZone, currency, onClick, onAction, draggable = true, pending = false, }) {
|
|
1904
|
+
function KanbanCard({ card, titleCol, fieldCols, showAllFields, actions, stageField, locale, timeZone, currency, onClick, onAction, draggable = true, pending = false, }) {
|
|
1879
1905
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
1880
1906
|
id: String(card.id),
|
|
1881
1907
|
data: { type: 'card' },
|
|
1882
1908
|
disabled: !draggable,
|
|
1883
1909
|
});
|
|
1884
|
-
const visibleActions = actions.filter((a) => isRowActionVisible(a, card));
|
|
1910
|
+
const visibleActions = actions.filter((a) => isRowActionVisible(a, card, stageField));
|
|
1885
1911
|
const menu = visibleActions.length > 0 ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-6 w-6 shrink-0 -mr-1 -mt-1",
|
|
1886
1912
|
// Don't start a drag / card click from the menu button.
|
|
1887
1913
|
onPointerDown: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }), _jsx(DropdownMenuContent, { align: "end", onClick: (e) => e.stopPropagation(), children: visibleActions.map((a) => (_jsx(RowActionMenuItem, { action: a, label: a.label, onSelect: (e) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-line-items.d.ts","sourceRoot":"","sources":["../src/dynamic-line-items.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-line-items.d.ts","sourceRoot":"","sources":["../src/dynamic-line-items.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAmB7C,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;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAyBD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAgB,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,qBAAqB,+BAoRvH"}
|
|
@@ -13,6 +13,7 @@ import { useEffect, useRef } from 'react';
|
|
|
13
13
|
import { Plus, Trash2, Check } from 'lucide-react';
|
|
14
14
|
import { resolveWidget, getItemFields, computeLineItemTotals, applyLineItemRowFormulas, autofillLineFromOption, evaluateBalance, toNumber, getDependsOn, resolveDependsValue, getOptionsConfig, resolveOptionsSource, applyOptionWhen, } from './dynamic-form-schema';
|
|
15
15
|
import { DynamicSelectField, DEFAULT_DEPENDS_HINT } from './dynamic-select-field';
|
|
16
|
+
import { getOptionFilter } from './option-filter';
|
|
16
17
|
import { useOptionsResolver } from './use-options-resolver';
|
|
17
18
|
const fmtNumber = (n) => n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
18
19
|
/** Numeric columns render right-aligned (debit/credit/amount feel). */
|
|
@@ -197,6 +198,8 @@ function RefCell({ field, value, onChange, onPick, disabled, formValues, rowValu
|
|
|
197
198
|
ref: optSource.ref,
|
|
198
199
|
endpoint: optSource.endpoint,
|
|
199
200
|
filterValue: dependsOn ? scope : undefined,
|
|
201
|
+
optionFilter: getOptionFilter(field),
|
|
202
|
+
keepValue: value,
|
|
200
203
|
enabled: !blockedByDependency,
|
|
201
204
|
});
|
|
202
205
|
// Clear the selection when the parent scope changes (skip initial mount).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-multi-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-multi-select-field.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-multi-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-multi-select-field.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,MAAM,WAAW,4BAA4B;IACzC,KAAK,EAAE,cAAc,CAAA;IACrB,qFAAqF;IACrF,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACtC;AAED,wBAAgB,uBAAuB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,4BAA4B,+BA2B/F"}
|
|
@@ -20,6 +20,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
20
20
|
// value instead.
|
|
21
21
|
import { useMemo } from 'react';
|
|
22
22
|
import { MultiSelect } from '@asteby/metacore-ui/primitives';
|
|
23
|
+
import { getOptionFilter } from './option-filter';
|
|
23
24
|
import { useOptionsResolver } from './use-options-resolver';
|
|
24
25
|
import { getFieldRef } from './dynamic-form-schema';
|
|
25
26
|
export function DynamicMultiSelectField({ field, value, onChange }) {
|
|
@@ -30,6 +31,7 @@ export function DynamicMultiSelectField({ field, value, onChange }) {
|
|
|
30
31
|
ref,
|
|
31
32
|
endpoint: !ref && field.searchEndpoint ? field.searchEndpoint : undefined,
|
|
32
33
|
limit: 200,
|
|
34
|
+
optionFilter: getOptionFilter(field),
|
|
33
35
|
});
|
|
34
36
|
const selected = useMemo(() => (Array.isArray(value) ? value.map(String) : []), [value]);
|
|
35
37
|
const uiOptions = useMemo(() => options.map((o) => ({ value: String(o.id), label: o.label })), [options]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-relation-helpers.d.ts","sourceRoot":"","sources":["../src/dynamic-relation-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAiB9E,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,cAAc,CAAA;AAoBhE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAK9D;AAUD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,MAAM,CAkC9F;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAGrB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3D,UAAU,EAAE,MAAM,GACnB,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"dynamic-relation-helpers.d.ts","sourceRoot":"","sources":["../src/dynamic-relation-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -151,10 +151,18 @@ export function deriveRelationFormFields(metadata, foreignKey) {
|
|
|
151
151
|
// than a raw uuid text input. Without this the column lost its `ref`
|
|
152
152
|
// crossing the column→field boundary and degraded to plain text.
|
|
153
153
|
ref: col.ref,
|
|
154
|
+
// Columnas de solo lectura (escritas por el servidor/sistema): el
|
|
155
|
+
// DynamicForm las oculta y no las manda en el submit. Sin copiarla,
|
|
156
|
+
// el renglón las exponía editables y el PATCH las pisaba.
|
|
157
|
+
...(isReadonlyColumn(col) ? { readonly: true } : {}),
|
|
154
158
|
});
|
|
155
159
|
}
|
|
156
160
|
return out;
|
|
157
161
|
}
|
|
162
|
+
function isReadonlyColumn(col) {
|
|
163
|
+
const c = col;
|
|
164
|
+
return c.readonly === true || c.readOnly === true || c.read_only === true;
|
|
165
|
+
}
|
|
158
166
|
function columnTypeToFieldType(col) {
|
|
159
167
|
switch (col.type) {
|
|
160
168
|
case 'number': return 'number';
|
package/dist/dynamic-relation.js
CHANGED
|
@@ -315,7 +315,7 @@ function OneToManyRelation({ kind, model, foreignKey, parentId, filters, endpoin
|
|
|
315
315
|
}) }, headerGroup.id))) }), _jsx(TableBody, { children: table.getRowModel().rows.map((row, idx) => (_jsx(TableRow, { children: row.getVisibleCells().map((cell) => (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: "py-2", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, relationRowKey(row.original, idx, foreignKey)))) })] }), loadingMore && (_jsx("div", { className: "p-2", children: _jsx(Skeleton, { className: "h-8 w-full" }) })), _jsx("div", { ref: sentinelRef, "aria-hidden": true, className: "h-px w-full" })] }) })), !loading && rows.length > 0 && total > rows.length && (_jsx("div", { className: "pt-2 text-end text-xs text-muted-foreground", children: labels.countLabel
|
|
316
316
|
.replace('{{loaded}}', String(rows.length))
|
|
317
317
|
.replace('{{total}}', String(total)) })), _jsx(Dialog, { open: formOpen, onOpenChange: (open) => { setFormOpen(open); if (!open)
|
|
318
|
-
setEditingRow(null); }, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: editingRow ? labels.editLabel : labels.addLabel }) }), _jsx(DynamicForm, { fields: formFields, initialValues: editingRow || undefined, onSubmit: handleSubmit, onCancel: () => { setFormOpen(false); setEditingRow(null); }, submitLabel: labels.saveLabel, cancelLabel: labels.cancelLabel, disabled: submitting })] }) }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: labels.confirmRemoveTitle }), _jsx(AlertDialogDescription, { children: labels.confirmRemoveDescription })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: submitting, children: labels.cancelLabel }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); handleDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: submitting, children: labels.removeLabel })] })] }) })] }));
|
|
318
|
+
setEditingRow(null); }, children: _jsxs(DialogContent, { className: "flex max-h-[90dvh] flex-col overflow-hidden", children: [_jsx(DialogHeader, { className: "shrink-0", children: _jsx(DialogTitle, { children: editingRow ? labels.editLabel : labels.addLabel }) }), _jsx(DynamicForm, { scrollableBody: true, fields: formFields, initialValues: editingRow || undefined, onSubmit: handleSubmit, onCancel: () => { setFormOpen(false); setEditingRow(null); }, submitLabel: labels.saveLabel, cancelLabel: labels.cancelLabel, disabled: submitting }, editingRow ? `edit-${editingRow.id ?? ''}` : 'new')] }) }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: labels.confirmRemoveTitle }), _jsx(AlertDialogDescription, { children: labels.confirmRemoveDescription })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: submitting, children: labels.cancelLabel }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); handleDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: submitting, children: labels.removeLabel })] })] }) })] }));
|
|
319
319
|
}
|
|
320
320
|
function ManyToManyRelation({ kind, through, references, foreignKey, referencesKey, parentId, filters, pivotEndpoint, referencesEndpoint, displayKey, canCreate = true, canDelete = true, readonly, readOnly, strings, className, onChange, }) {
|
|
321
321
|
// Read-only relation always wins over host-passed perms (AND).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AA6CA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,IAAI,EACJ,IAAS,GACZ,EAAE;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,+BA4BA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCA4BA;AAMD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B,yFAAyF;IACzF,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;IACtC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;IAChD;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,EAChB,aAAoB,EACpB,kBAA0B,EAC1B,UAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,OAAe,GAClB,EAAE,uBAAuB,+BA8TzB;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -23,6 +23,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
23
23
|
// value). A dedicated `?ids=` lookup is a follow-up; create flows — the common
|
|
24
24
|
// case — start empty and never hit this.
|
|
25
25
|
import { useEffect, useRef, useState } from 'react';
|
|
26
|
+
import { getOptionFilter } from './option-filter';
|
|
26
27
|
import { useTranslation } from 'react-i18next';
|
|
27
28
|
import { Badge, Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, InitialsAvatar, } from '@asteby/metacore-ui/primitives';
|
|
28
29
|
import { Check, ChevronsUpDown, Loader2, Plus, ScanLine } from 'lucide-react';
|
|
@@ -123,6 +124,7 @@ export function DynamicSelectField({ field, value, onChange, onPick, seedOption,
|
|
|
123
124
|
const scope = dependsValue ? String(dependsValue) : '';
|
|
124
125
|
const blockedByDependency = !!dependsOn && scope === '';
|
|
125
126
|
const useStatic = Array.isArray(staticOptions);
|
|
127
|
+
const optionFilter = getOptionFilter(field);
|
|
126
128
|
const { options: fetchedOptions, loading: fetchLoading } = useOptionsResolver({
|
|
127
129
|
modelKey: '',
|
|
128
130
|
fieldKey: source.fieldKey,
|
|
@@ -136,6 +138,8 @@ export function DynamicSelectField({ field, value, onChange, onPick, seedOption,
|
|
|
136
138
|
// Cascade scope forwarded as filter_value (only when this field
|
|
137
139
|
// declares a dependency). Re-fetches when the parent value changes.
|
|
138
140
|
filterValue: dependsOn ? scope : undefined,
|
|
141
|
+
optionFilter,
|
|
142
|
+
keepValue: value,
|
|
139
143
|
// Don't fetch until the popover opens (and keep fetching as the query
|
|
140
144
|
// changes while open). A picker blocked by an unset dependency never
|
|
141
145
|
// fetches. A readonly cell fetches eagerly so its value's label resolves
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { setFederatedActionLoader, type FederatedActionLoader } from './federate
|
|
|
32
32
|
export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
|
|
33
33
|
export { resolveListPrimaryAction, actionReplacesCreate, type ListPrimaryActionInput, type ListPrimaryActionResult, type ListActionRef, type CreateMode, } from './list-primary-action';
|
|
34
34
|
export { emitRecordMutation, subscribeRecordMutations, RECORD_MUTATION_EVENT, type RecordMutationKind, type RecordMutationDetail, } from './record-mutation-events';
|
|
35
|
-
export { DocumentFormDialog, resolveDocumentForms, type DocumentFormDialogProps, } from './document-form-dialog';
|
|
35
|
+
export { DocumentFormDialog, resolveDocumentForms, scopeDocumentFormsToFilter, type DocumentFormDialogProps, } from './document-form-dialog';
|
|
36
36
|
export { modelFromNavUrl, capabilityForNavItem, isNavItemAllowed, useNavItemVisible, type NavLeafLike, } from './nav-permissions';
|
|
37
37
|
export { CfdiStampPanel, CfdiStampResultDialog, extractStampResult, type CfdiStampResult, type CfdiStampPanelProps, type CfdiStampResultDialogProps, } from './cfdi-stamp-panel';
|
|
38
38
|
export { useRecordMutationTick } from './use-record-mutation-tick';
|
|
@@ -54,6 +54,7 @@ export { applySidebarLayout, collectFolderKeys, folderAccessCapability, coreRefK
|
|
|
54
54
|
export { SidebarLayoutManager, type SidebarLayoutManagerProps, } from './sidebar-layout-manager';
|
|
55
55
|
export * from './org-runtime-context';
|
|
56
56
|
export * from './business';
|
|
57
|
+
export * from './primitives';
|
|
57
58
|
export * from './org-runtime-provider';
|
|
58
59
|
export * from './navigation-builder';
|
|
59
60
|
export * from './i18n-provider';
|
|
@@ -71,7 +72,7 @@ export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederation
|
|
|
71
72
|
export * from './dynamic-icon';
|
|
72
73
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
73
74
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveAvatarSrc, resolveMissingActorLabel, resolveActorDisplayName, resolveRelationLabel, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
74
|
-
export { humanizeToken, translateMetadataLabel } from './dynamic-columns-helpers';
|
|
75
|
+
export { humanizeToken, localizeFieldLabel, translateMetadataLabel } from './dynamic-columns-helpers';
|
|
75
76
|
export type { MetadataTranslator } from './dynamic-columns-helpers';
|
|
76
77
|
export { UrlChip, FileChip, ImageThumbnail, MediaValue, RichText, linkifyText, classifyUrl, isImageUrl, isFileUrl, ensureHref, smartUrlLabel, fileNameFromUrl, splitTrailingPunct, type UrlKind, type LinkifyOptions, } from './rich-url';
|
|
77
78
|
export { ImageStack, OptionBadge, statusColorFor, useIsDarkTheme, } from './display-value';
|
|
@@ -100,6 +101,7 @@ export { registerValidator, getDependsOn, resolveDependsValue, getOptionsConfig,
|
|
|
100
101
|
export { BranchCreateGateProvider, useBranchCreateGate, type BranchCreateGateApi, } from './branch-create-gate';
|
|
101
102
|
export { ActivityValueRenderer, type ActivityValueRendererProps, } from './activity-value-renderer';
|
|
102
103
|
export { ActivityDiff, type ActivityEvent, type ActivityDiffProps, } from './activity-diff';
|
|
104
|
+
export { AuditInfo, readAuditMeta, resolveAuditActor, SYSTEM_ACTOR_ID, type AuditInfoProps, type AuditActor, } from './audit-info';
|
|
103
105
|
export { RecordHistory, type RecordHistoryProps, } from './record-history';
|
|
104
106
|
export { ActivityTimeline, type ActivityTimelineProps, } from './activity-timeline';
|
|
105
107
|
export { DashboardGrid, normalizeGroups, } from './dashboard-grid';
|
|
@@ -113,4 +115,8 @@ export { AssistInterview, AssistCardView } from './assist-interview';
|
|
|
113
115
|
export type { AssistSession, AssistTurn, AssistQuestion, AssistCard, AssistProgressStep } from './assist-interview';
|
|
114
116
|
export { useAttributeClasses, resolveAttributeClasses, resetAttributeClassCache, type AttributeClass, type AttributeClassSection, } from './attribute-classes';
|
|
115
117
|
export * from './document';
|
|
118
|
+
export { getOptionFilter, applyOptionFilter, optionPassesRule } from './option-filter';
|
|
119
|
+
export type { OptionFilter, OptionFilterRule } from './option-filter';
|
|
120
|
+
export { InstalledAddonsProvider, useAddonInstalled, useCapabilityProvided, useInstalledAddons, type InstalledAddonsValue, } from './installed-addons-context';
|
|
121
|
+
export { RecordPrefillBar, type RecordPrefillBarProps } from './record-prefill-bar';
|
|
116
122
|
//# sourceMappingURL=index.d.ts.map
|