@asteby/metacore-runtime-react 23.9.0 → 23.9.1
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 +13 -0
- package/dist/dynamic-form-schema.d.ts +18 -1
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +43 -0
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +30 -9
- package/dist/dynamic-kanban.d.ts +8 -0
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +25 -4
- package/dist/dynamic-line-items.d.ts.map +1 -1
- package/dist/dynamic-line-items.js +24 -3
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +5 -0
- package/dist/types.d.ts +33 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/dependent-enum-form.test.tsx +84 -0
- package/src/__tests__/dependent-enum-options.test.ts +67 -0
- package/src/dynamic-form-schema.ts +44 -1
- package/src/dynamic-form.tsx +76 -28
- package/src/dynamic-kanban.tsx +26 -5
- package/src/dynamic-line-items.tsx +26 -2
- package/src/dynamic-table.tsx +4 -0
- package/src/types.ts +35 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 23.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3db80ab: fix(data-table/kanban): indicadores de filtro sin duplicados y tarjetas kanban con relaciones resueltas
|
|
8
|
+
- El botón "Limpiar filtros" del toolbar ya no muestra badge de conteo y solo aparece para el estado propio de la tabla (column filters / búsqueda); los filtros dinámicos ya tienen su fila de chips con "Limpiar todo".
|
|
9
|
+
- El icono de filtro del header de columna ya no superpone un badge de conteo: el icono en color primary es el único indicador activo.
|
|
10
|
+
- `f_<col>=eq:<valor>` en la URL se desenvuelve al valor plano, por lo que el chip muestra la etiqueta de la opción ("Estado: Recepción") en vez de `eq:reception`.
|
|
11
|
+
- Las tarjetas del kanban resuelven columnas FK (`<rel>_id`) al objeto hermano resuelto por el backend (nombre/label) en vez del UUID crudo, y un FK UUID-cero se muestra como vacío (—).
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [3db80ab]
|
|
14
|
+
- @asteby/metacore-ui@2.9.2
|
|
15
|
+
|
|
3
16
|
## 23.9.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type { ActionFieldDef, FieldOptionsConfig } from './types';
|
|
2
|
+
import type { ActionFieldDef, FieldOptionsConfig, OptionDef } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Apps register validator implementations by slug. The slug is the value
|
|
5
5
|
* `OrgConfig.validators[<key>]` returns for a $org.<key> reference.
|
|
@@ -80,6 +80,23 @@ export declare function getDependsOn(field: ActionFieldDef): string | undefined;
|
|
|
80
80
|
* field has no dependency or the depended-on value is empty/unset.
|
|
81
81
|
*/
|
|
82
82
|
export declare function resolveDependsValue(field: ActionFieldDef, formValues?: Record<string, any> | null, rowValues?: Record<string, any> | null): string;
|
|
83
|
+
/**
|
|
84
|
+
* Filters a STATIC enum's `options[]` by each option's `when` gate against the
|
|
85
|
+
* current form values. Pure — no React, no side effects.
|
|
86
|
+
*
|
|
87
|
+
* Rule per option:
|
|
88
|
+
* - No `when` → always included (retrocompat; existing enums untouched).
|
|
89
|
+
* - With `when`: the gating field is `when.field ?? dependsOn`. If neither is
|
|
90
|
+
* present the option is included (nothing to gate on). Otherwise the form's
|
|
91
|
+
* value for that field is compared AS STRING: included when (no `in`, or
|
|
92
|
+
* value ∈ `in`) AND (no `not_in`, or value ∉ `not_in`). Tolerates the
|
|
93
|
+
* snake_case `not_in` the kernel serves alongside camelCase `notIn`.
|
|
94
|
+
*
|
|
95
|
+
* `formValues` is the flat map of the surrounding form/row values the gating
|
|
96
|
+
* field is read from; `dependsOn` is the containing field's declared dependency
|
|
97
|
+
* used as the default gating field.
|
|
98
|
+
*/
|
|
99
|
+
export declare function applyOptionWhen(options: OptionDef[] | undefined, formValues: Record<string, any> | null | undefined, dependsOn?: string): OptionDef[];
|
|
83
100
|
/**
|
|
84
101
|
* Reads a field's enriched options-resolution config, tolerating the camelCase
|
|
85
102
|
* `optionsConfig` (authored SDK shape) and the snake_case `options_config` the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,kBAAkB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -265,6 +265,49 @@ export function resolveDependsValue(field, formValues, rowValues) {
|
|
|
265
265
|
return '';
|
|
266
266
|
return String(raw);
|
|
267
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Filters a STATIC enum's `options[]` by each option's `when` gate against the
|
|
270
|
+
* current form values. Pure — no React, no side effects.
|
|
271
|
+
*
|
|
272
|
+
* Rule per option:
|
|
273
|
+
* - No `when` → always included (retrocompat; existing enums untouched).
|
|
274
|
+
* - With `when`: the gating field is `when.field ?? dependsOn`. If neither is
|
|
275
|
+
* present the option is included (nothing to gate on). Otherwise the form's
|
|
276
|
+
* value for that field is compared AS STRING: included when (no `in`, or
|
|
277
|
+
* value ∈ `in`) AND (no `not_in`, or value ∉ `not_in`). Tolerates the
|
|
278
|
+
* snake_case `not_in` the kernel serves alongside camelCase `notIn`.
|
|
279
|
+
*
|
|
280
|
+
* `formValues` is the flat map of the surrounding form/row values the gating
|
|
281
|
+
* field is read from; `dependsOn` is the containing field's declared dependency
|
|
282
|
+
* used as the default gating field.
|
|
283
|
+
*/
|
|
284
|
+
export function applyOptionWhen(options, formValues, dependsOn) {
|
|
285
|
+
if (!Array.isArray(options))
|
|
286
|
+
return [];
|
|
287
|
+
return options.filter((opt) => {
|
|
288
|
+
const when = opt?.when;
|
|
289
|
+
if (!when)
|
|
290
|
+
return true;
|
|
291
|
+
const gate = (typeof when.field === 'string' && when.field.trim() !== '')
|
|
292
|
+
? when.field.trim()
|
|
293
|
+
: dependsOn;
|
|
294
|
+
if (!gate)
|
|
295
|
+
return true;
|
|
296
|
+
const raw = formValues ? formValues[gate] : undefined;
|
|
297
|
+
const current = raw == null ? '' : String(raw);
|
|
298
|
+
const inList = when.in;
|
|
299
|
+
const notIn = when.notIn ?? when.not_in;
|
|
300
|
+
if (Array.isArray(inList) && inList.length > 0) {
|
|
301
|
+
if (!inList.some((v) => String(v) === current))
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(notIn) && notIn.length > 0) {
|
|
305
|
+
if (notIn.some((v) => String(v) === current))
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
268
311
|
/**
|
|
269
312
|
* Reads a field's enriched options-resolution config, tolerating the camelCase
|
|
270
313
|
* `optionsConfig` (authored SDK shape) and the snake_case `options_config` the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,
|
|
1
|
+
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAKhB,MAAM,uBAAuB,CAAA;AAO9B,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,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,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;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,+BAoFlB"}
|
package/dist/dynamic-form.js
CHANGED
|
@@ -4,7 +4,7 @@ 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, isLineItemsField, evaluateBalance, } from './dynamic-form-schema';
|
|
7
|
+
import { buildZodSchema, resolveWidget, isLineItemsField, evaluateBalance, applyOptionWhen, getDependsOn, } from './dynamic-form-schema';
|
|
8
8
|
import { useOptionsResolver } from './use-options-resolver';
|
|
9
9
|
import { DynamicLineItems } from './dynamic-line-items';
|
|
10
10
|
import { DynamicSelectField } from './dynamic-select-field';
|
|
@@ -73,12 +73,33 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
|
|
|
73
73
|
// line-items grids (and textareas) span the full width so the row table /
|
|
74
74
|
// memo gets room. Mirrors the pro look of the federated journal modal but
|
|
75
75
|
// stays fully declarative — driven only by field shape.
|
|
76
|
-
return (_jsxs("form", { onSubmit: handleSubmit, className: "grid gap-4", children: [_jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: fields.map((field) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
return (_jsxs("form", { onSubmit: handleSubmit, className: "grid gap-4", children: [_jsx("div", { className: "grid gap-4 sm:grid-cols-2", children: fields.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))) }), _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 || balanceBlocked, children: submitLabel })] })] }));
|
|
77
|
+
}
|
|
78
|
+
// One form field row: label + renderer + inline error. Encapsulated as its own
|
|
79
|
+
// component so a STATIC enum select whose options are all gated out by a sibling
|
|
80
|
+
// value (`when`) can hide the ENTIRE row (label included) and run its reset
|
|
81
|
+
// effect with valid hook ordering.
|
|
82
|
+
function FieldRow({ field, value, onChange, values, error, initialValues }) {
|
|
83
|
+
const isStaticSelect = resolveWidget(field) === 'select' && !field.ref && Array.isArray(field.options);
|
|
84
|
+
const effectiveOptions = isStaticSelect
|
|
85
|
+
? applyOptionWhen(field.options, values, getDependsOn(field))
|
|
86
|
+
: undefined;
|
|
87
|
+
// Reset a selection that the current sibling value no longer permits (e.g.
|
|
88
|
+
// the parent switched away from the value that made this option valid).
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!isStaticSelect || !effectiveOptions)
|
|
91
|
+
return;
|
|
92
|
+
if (value && !effectiveOptions.some((o) => String(o.value) === String(value))) {
|
|
93
|
+
onChange('');
|
|
94
|
+
}
|
|
95
|
+
}, [isStaticSelect, effectiveOptions, value, onChange]);
|
|
96
|
+
// No option applies under the current sibling value → hide the whole field.
|
|
97
|
+
if (isStaticSelect && effectiveOptions && effectiveOptions.length === 0)
|
|
98
|
+
return null;
|
|
99
|
+
const fullWidth = isLineItemsField(field) ||
|
|
100
|
+
resolveWidget(field) === 'textarea' ||
|
|
101
|
+
resolveWidget(field) === 'richtext';
|
|
102
|
+
return (_jsxs("div", { className: 'grid gap-2 ' + (fullWidth ? 'sm:col-span-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: value, onChange: onChange, initialValues: initialValues, values: values, effectiveOptions: effectiveOptions }), error && _jsx("span", { className: "text-red-500 text-sm", role: "alert", children: error })] }));
|
|
82
103
|
}
|
|
83
104
|
// seedOptionFromSibling builds a pre-resolved option for an FK field from the
|
|
84
105
|
// resolved sibling the backend served on the initial record (e.g. a line item's
|
|
@@ -105,7 +126,7 @@ function seedOptionFromSibling(field, value, initialValues) {
|
|
|
105
126
|
icon: sib.icon,
|
|
106
127
|
};
|
|
107
128
|
}
|
|
108
|
-
function FieldRenderer({ field, value, onChange, initialValues }) {
|
|
129
|
+
function FieldRenderer({ field, value, onChange, initialValues, effectiveOptions, }) {
|
|
109
130
|
// Repeatable line-items group → render the row grid. Its value is an array
|
|
110
131
|
// of row objects rather than a scalar.
|
|
111
132
|
if (isLineItemsField(field)) {
|
|
@@ -142,7 +163,7 @@ function FieldRenderer({ field, value, onChange, initialValues }) {
|
|
|
142
163
|
case 'color':
|
|
143
164
|
return _jsx(Input, { id: field.key, type: "color", value: value || '#000000', onChange: (e) => onChange(e.target.value) });
|
|
144
165
|
case 'select':
|
|
145
|
-
return (_jsxs(Select, { value: value || '', onValueChange: onChange, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: field.options?.map((opt) => _jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })] }));
|
|
166
|
+
return (_jsxs(Select, { value: value || '', onValueChange: onChange, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: (effectiveOptions ?? field.options)?.map((opt) => _jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })] }));
|
|
146
167
|
case 'switch':
|
|
147
168
|
return _jsx(Switch, { id: field.key, checked: !!value, onCheckedChange: onChange });
|
|
148
169
|
case 'number':
|
package/dist/dynamic-kanban.d.ts
CHANGED
|
@@ -62,6 +62,14 @@ export declare function selectCardColumns(metadata: TableMetadata, maxFields?: n
|
|
|
62
62
|
title: ColumnDefinition | null;
|
|
63
63
|
fields: ColumnDefinition[];
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* The value a card cell should render for a column — same resolution the
|
|
67
|
+
* table's cells apply. An FK column (`<rel>_id`) prefers the backend-resolved
|
|
68
|
+
* sibling object (`card.<rel>`, e.g. `{ name }` / `{ value, label }`) over the
|
|
69
|
+
* raw UUID; a zero-UUID FK counts as unset (renders the em-dash). Pure —
|
|
70
|
+
* exported for unit tests.
|
|
71
|
+
*/
|
|
72
|
+
export declare function cardCellValue(card: any, col: ColumnDefinition): unknown;
|
|
65
73
|
/**
|
|
66
74
|
* Whether a card passes a lane funnel. Picked select/facet `values` match by
|
|
67
75
|
* equality (IN — the card's field value must be one of them); a free-text
|
|
@@ -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;AAyF9B,OAAO,EAEH,qBAAqB,EACrB,qBAAqB,EACxB,MAAM,gBAAgB,CAAA;AAUvB,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAGhB,SAAS,EACT,eAAe,EAClB,MAAM,SAAS,CAAA;AAIhB,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,CAAA;AAMvD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,CAejE;AAQD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,mBAAmB,CAAA;AAE/C,wBAAgB,YAAY,CACxB,OAAO,EAAE,GAAG,EAAE,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EAAE,GACpB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAepB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,eAAe,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACX,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAC3B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAYpB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACpE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAWnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,UAAU,EAAE,OAAO,GACpB,MAAM,CAMR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,SAAS,SAAI,GACd;IAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAkBhE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,GAAG,EACT,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACzE,OAAO,CAUT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACxD,MAAM,CAIR;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,IAAI,EAAE,GAAG,EACT,IAAI,EAAE,gBAAgB,EAAE,EACxB,KAAK,EAAE,MAAM,GACd,OAAO,CAQT;AAwDD,MAAM,WAAW,kBAAkB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACvC;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAQ,EACR,QAAa,EACb,YAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,cAAc,GACjB,EAAE,kBAAkB,qBAsmCpB"}
|
|
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
|
@@ -21,7 +21,7 @@ import { useMetadataCache } from './metadata-cache';
|
|
|
21
21
|
import { ActivityValueRenderer } from './activity-value-renderer';
|
|
22
22
|
import { DynamicIcon } from './dynamic-icon';
|
|
23
23
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
24
|
-
import { isRowActionVisible } from './dynamic-columns';
|
|
24
|
+
import { isRowActionVisible, relationKeyFor } from './dynamic-columns';
|
|
25
25
|
import { useCan, usePermissionsActive, resolveRowActions } from './permissions-context';
|
|
26
26
|
import { useDynamicRowActions } from './dynamic-row-actions';
|
|
27
27
|
import { useStageLayout } from './stage-layout';
|
|
@@ -177,6 +177,27 @@ export function selectCardColumns(metadata, maxFields = 3) {
|
|
|
177
177
|
.slice(0, maxFields);
|
|
178
178
|
return { title, fields };
|
|
179
179
|
}
|
|
180
|
+
/** The all-zeros UUID — a Go zero-value FK serialized as "set" when it isn't. */
|
|
181
|
+
const ZERO_UUID = /^0{8}-0{4}-0{4}-0{4}-0{12}$/;
|
|
182
|
+
/**
|
|
183
|
+
* The value a card cell should render for a column — same resolution the
|
|
184
|
+
* table's cells apply. An FK column (`<rel>_id`) prefers the backend-resolved
|
|
185
|
+
* sibling object (`card.<rel>`, e.g. `{ name }` / `{ value, label }`) over the
|
|
186
|
+
* raw UUID; a zero-UUID FK counts as unset (renders the em-dash). Pure —
|
|
187
|
+
* exported for unit tests.
|
|
188
|
+
*/
|
|
189
|
+
export function cardCellValue(card, col) {
|
|
190
|
+
const raw = card?.[col.key];
|
|
191
|
+
if (typeof raw === 'string' && ZERO_UUID.test(raw))
|
|
192
|
+
return null;
|
|
193
|
+
const relKey = relationKeyFor(col);
|
|
194
|
+
if (relKey !== col.key) {
|
|
195
|
+
const sibling = card?.[relKey];
|
|
196
|
+
if (sibling && typeof sibling === 'object')
|
|
197
|
+
return sibling;
|
|
198
|
+
}
|
|
199
|
+
return raw;
|
|
200
|
+
}
|
|
180
201
|
/**
|
|
181
202
|
* Whether a card passes a lane funnel. Picked select/facet `values` match by
|
|
182
203
|
* equality (IN — the card's field value must be one of them); a free-text
|
|
@@ -1214,14 +1235,14 @@ function KanbanCard({ card, titleCol, fieldCols, actions, locale, timeZone, curr
|
|
|
1214
1235
|
disabled: !draggable,
|
|
1215
1236
|
});
|
|
1216
1237
|
const visibleActions = actions.filter((a) => isRowActionVisible(a, card));
|
|
1217
|
-
return (_jsx(Card, { ref: draggable ? setNodeRef : undefined, ...(draggable ? attributes : {}), ...(draggable ? listeners : {}), className: `w-full min-w-0 border-border/70 shadow-sm ${draggable ? 'cursor-grab active:cursor-grabbing' : 'cursor-default'}`, style: { opacity: isDragging ? 0.4 : 1 }, onClick: () => onClick?.(card), "data-card-id": String(card.id), children: _jsxs(CardContent, { className: "space-y-1.5 p-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("div", { className: "min-w-0 flex-1 break-words text-sm font-medium leading-snug", children: titleCol ? (_jsx(ActivityValueRenderer, { value: card
|
|
1238
|
+
return (_jsx(Card, { ref: draggable ? setNodeRef : undefined, ...(draggable ? attributes : {}), ...(draggable ? listeners : {}), className: `w-full min-w-0 border-border/70 shadow-sm ${draggable ? 'cursor-grab active:cursor-grabbing' : 'cursor-default'}`, style: { opacity: isDragging ? 0.4 : 1 }, onClick: () => onClick?.(card), "data-card-id": String(card.id), children: _jsxs(CardContent, { className: "space-y-1.5 p-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("div", { className: "min-w-0 flex-1 break-words text-sm font-medium leading-snug", children: titleCol ? (_jsx(ActivityValueRenderer, { value: cardCellValue(card, titleCol), col: titleCol, locale: locale, timeZone: timeZone, currency: currency })) : (_jsx("span", { className: "truncate", children: String(card.id) })) }), 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",
|
|
1218
1239
|
// Don't start a drag / card click from the menu button.
|
|
1219
1240
|
onPointerDown: (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) => (_jsxs(DropdownMenuItem, { onClick: (e) => {
|
|
1220
1241
|
e.stopPropagation();
|
|
1221
1242
|
onAction(a.key, card);
|
|
1222
|
-
}, children: [_jsx(DynamicIcon, { name: a.icon || 'Zap', className: "mr-2 h-4 w-4" }), a.label] }, a.key))) })] }))] }), fieldCols.map((col) => (_jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs text-muted-foreground", children: [_jsxs("span", { className: "shrink-0 opacity-70", children: [col.label, ":"] }), _jsx("span", { className: "min-w-0 break-words", children: _jsx(ActivityValueRenderer, { value: card
|
|
1243
|
+
}, children: [_jsx(DynamicIcon, { name: a.icon || 'Zap', className: "mr-2 h-4 w-4" }), a.label] }, a.key))) })] }))] }), fieldCols.map((col) => (_jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs text-muted-foreground", 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)))] }) }));
|
|
1223
1244
|
}
|
|
1224
1245
|
// Static preview rendered inside the DragOverlay (no dnd hooks, no menu).
|
|
1225
1246
|
function CardPreview({ card, titleCol, fieldCols, locale, timeZone, currency, }) {
|
|
1226
|
-
return (_jsx(Card, { className: "w-[284px] cursor-grabbing border-primary/40 shadow-lg", children: _jsxs(CardContent, { className: "space-y-1.5 p-3", children: [_jsx("div", { className: "break-words text-sm font-medium leading-snug", children: titleCol ? (_jsx(ActivityValueRenderer, { value: card
|
|
1247
|
+
return (_jsx(Card, { className: "w-[284px] cursor-grabbing border-primary/40 shadow-lg", children: _jsxs(CardContent, { className: "space-y-1.5 p-3", children: [_jsx("div", { className: "break-words text-sm font-medium leading-snug", children: titleCol ? (_jsx(ActivityValueRenderer, { value: cardCellValue(card, titleCol), col: titleCol, locale: locale, timeZone: timeZone, currency: currency })) : (String(card.id)) }), fieldCols.map((col) => (_jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs text-muted-foreground", 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)))] }) }));
|
|
1227
1248
|
}
|
|
@@ -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;AAgB7C,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;CACnC;AAkBD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAgB,EAAE,UAAU,EAAE,EAAE,qBAAqB,+BAmJ/G"}
|
|
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { Input, Textarea, Switch, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@asteby/metacore-ui/primitives';
|
|
12
12
|
import { useEffect, useRef } from 'react';
|
|
13
13
|
import { Plus, Trash2, Check } from 'lucide-react';
|
|
14
|
-
import { resolveWidget, getItemFields, computeLineItemTotals, evaluateBalance, toNumber, getDependsOn, resolveDependsValue, getOptionsConfig, resolveOptionsSource, } from './dynamic-form-schema';
|
|
14
|
+
import { resolveWidget, getItemFields, computeLineItemTotals, evaluateBalance, toNumber, getDependsOn, resolveDependsValue, getOptionsConfig, resolveOptionsSource, applyOptionWhen, } from './dynamic-form-schema';
|
|
15
15
|
import { DynamicSelectField, DEFAULT_DEPENDS_HINT } from './dynamic-select-field';
|
|
16
16
|
import { useOptionsResolver } from './use-options-resolver';
|
|
17
17
|
const fmtNumber = (n) => n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
@@ -95,6 +95,22 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
95
95
|
const dependsValue = getDependsOn(field)
|
|
96
96
|
? resolveDependsValue(field, formValues, rowValues)
|
|
97
97
|
: undefined;
|
|
98
|
+
// STATIC enum options gated per-cell by a sibling (row) or header value.
|
|
99
|
+
// Row values win over the header when a key exists in both, matching
|
|
100
|
+
// `resolveDependsValue`. Filtered against each option's `when`.
|
|
101
|
+
const isStaticSelect = widget === 'select' && !field.ref && !getOptionsConfig(field)?.source && Array.isArray(field.options);
|
|
102
|
+
const gateValues = isStaticSelect ? { ...(formValues ?? {}), ...(rowValues ?? {}) } : undefined;
|
|
103
|
+
const effectiveOptions = isStaticSelect
|
|
104
|
+
? applyOptionWhen(field.options, gateValues, getDependsOn(field))
|
|
105
|
+
: undefined;
|
|
106
|
+
// Reset a selection the current sibling value no longer permits.
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!isStaticSelect || !effectiveOptions)
|
|
109
|
+
return;
|
|
110
|
+
if (value && !effectiveOptions.some((o) => String(o.value) === String(value))) {
|
|
111
|
+
onChange('');
|
|
112
|
+
}
|
|
113
|
+
}, [isStaticSelect, effectiveOptions, value, onChange]);
|
|
98
114
|
// Async searchable picker per row cell — e.g. the account_id column of a
|
|
99
115
|
// journal entry's debit/credit lines. Same widget as the flat form.
|
|
100
116
|
if (widget === 'dynamic_select') {
|
|
@@ -109,8 +125,13 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
109
125
|
return (_jsx(Textarea, { value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: off, rows: 2 }));
|
|
110
126
|
case 'color':
|
|
111
127
|
return (_jsx(Input, { type: "color", value: value || '#000000', onChange: (e) => onChange(e.target.value), disabled: off }));
|
|
112
|
-
case 'select':
|
|
113
|
-
|
|
128
|
+
case 'select': {
|
|
129
|
+
const opts = effectiveOptions ?? field.options;
|
|
130
|
+
// No option applies under the current sibling value → hide the cell.
|
|
131
|
+
if (effectiveOptions && effectiveOptions.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: off, 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))) })] }));
|
|
134
|
+
}
|
|
114
135
|
case 'switch':
|
|
115
136
|
return _jsx(Switch, { checked: !!value, onCheckedChange: onChange, disabled: off });
|
|
116
137
|
case 'number':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAanF,MAAM,WAAW,iBAAiB;IAC9B,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;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,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;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,cAAsB,GACzB,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAanF,MAAM,WAAW,iBAAiB;IAC9B,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;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,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;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,cAAsB,GACzB,EAAE,iBAAiB,+BA6iCnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -93,6 +93,11 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
93
93
|
return value;
|
|
94
94
|
const prefix = value.substring(0, colonIdx).toLowerCase();
|
|
95
95
|
const rest = value.substring(colonIdx + 1);
|
|
96
|
+
// `eq:` is the wire's explicit equality operator, but internally a
|
|
97
|
+
// select stores the bare value — unwrap it so `f_status=eq:reception`
|
|
98
|
+
// matches the option "reception" (header filter + chip label).
|
|
99
|
+
if (prefix === 'eq')
|
|
100
|
+
return rest;
|
|
96
101
|
const operator = urlAliasToOperator[prefix];
|
|
97
102
|
return operator ? `${operator}:${rest}` : value;
|
|
98
103
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -287,15 +287,44 @@ export interface FieldValidation {
|
|
|
287
287
|
custom?: string;
|
|
288
288
|
}
|
|
289
289
|
export type FieldWidget = 'text' | 'textarea' | 'richtext' | 'color' | 'number' | 'date' | 'select' | 'dynamic_select' | 'switch' | 'upload';
|
|
290
|
+
/**
|
|
291
|
+
* Per-option visibility gate for a STATIC enum (`options[]`). The option is only
|
|
292
|
+
* offered when the value of a sibling field (`field`, defaulting to the
|
|
293
|
+
* containing field's `dependsOn`) passes: value ∈ `in` and value ∉ `notIn`.
|
|
294
|
+
* Comparison is by string. Mirrors the kernel v3 `option.when` block; the SDK
|
|
295
|
+
* tolerates both the snake_case (`not_in`) the kernel serves and camelCase.
|
|
296
|
+
* An option WITHOUT a `when` always applies (retrocompat).
|
|
297
|
+
*/
|
|
298
|
+
export interface OptionWhen {
|
|
299
|
+
/** Sibling field whose value gates this option. Falls back to `dependsOn`. */
|
|
300
|
+
field?: string;
|
|
301
|
+
/** The option applies when the gating field's value is in this list. */
|
|
302
|
+
in?: string[];
|
|
303
|
+
/** The option applies when the gating field's value is NOT in this list. */
|
|
304
|
+
notIn?: string[];
|
|
305
|
+
/** snake_case alias served by the kernel manifest for `notIn`. */
|
|
306
|
+
not_in?: string[];
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* A single static enum option. `value`/`label` are the core pair; `icon`,
|
|
310
|
+
* `color` and `image` drive the option's leading visual where the renderer
|
|
311
|
+
* supports it. `when` gates the option's visibility by a sibling field's value
|
|
312
|
+
* (see {@link OptionWhen}) — used for dependent/cascading STATIC enums.
|
|
313
|
+
*/
|
|
314
|
+
export interface OptionDef {
|
|
315
|
+
value: string;
|
|
316
|
+
label: string;
|
|
317
|
+
icon?: string;
|
|
318
|
+
color?: string;
|
|
319
|
+
image?: string;
|
|
320
|
+
when?: OptionWhen;
|
|
321
|
+
}
|
|
290
322
|
export interface ActionFieldDef {
|
|
291
323
|
key: string;
|
|
292
324
|
label: string;
|
|
293
325
|
type: string;
|
|
294
326
|
required?: boolean;
|
|
295
|
-
options?:
|
|
296
|
-
value: string;
|
|
297
|
-
label: string;
|
|
298
|
-
}[];
|
|
327
|
+
options?: OptionDef[];
|
|
299
328
|
defaultValue?: any;
|
|
300
329
|
placeholder?: string;
|
|
301
330
|
searchEndpoint?: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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": "23.9.
|
|
3
|
+
"version": "23.9.1",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"sonner": ">=1.7",
|
|
39
39
|
"zustand": ">=5",
|
|
40
40
|
"@asteby/metacore-sdk": "^3.2.0",
|
|
41
|
-
"@asteby/metacore-ui": "^2.9.
|
|
41
|
+
"@asteby/metacore-ui": "^2.9.2"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@tanstack/react-router": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"vitest": "^4.0.0",
|
|
69
69
|
"zustand": "^5.0.0",
|
|
70
70
|
"@asteby/metacore-sdk": "3.2.0",
|
|
71
|
-
"@asteby/metacore-ui": "2.9.
|
|
71
|
+
"@asteby/metacore-ui": "2.9.2"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
//
|
|
3
|
+
// Dependent STATIC enum options in DynamicForm:
|
|
4
|
+
// - a static `select` whose options are gated by a sibling field's value
|
|
5
|
+
// shows only the applicable options;
|
|
6
|
+
// - when the sibling value invalidates the current selection, it resets;
|
|
7
|
+
// - when no option applies, the whole field (label included) is hidden.
|
|
8
|
+
//
|
|
9
|
+
// The gating (parent) field here is a plain text field rather than a second
|
|
10
|
+
// Radix `select`. That is deliberate: under happy-dom a Radix Select rendered
|
|
11
|
+
// inside a <form> mounts a hidden native <select> (BubbleSelect) whose value,
|
|
12
|
+
// when set programmatically before its <option> list is present, collapses to
|
|
13
|
+
// '' and dispatches a spurious change event that resets the parent — a
|
|
14
|
+
// test-environment artifact that does not occur in a real browser. Driving the
|
|
15
|
+
// sibling through a text field exercises the exact same gating path
|
|
16
|
+
// (`applyOptionWhen` reads `values[gate]` regardless of the sibling's widget)
|
|
17
|
+
// without that artifact. The pure string-comparison semantics of `when`
|
|
18
|
+
// (in/not_in, field fallback) are covered by dependent-enum-options.test.ts.
|
|
19
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
20
|
+
import { act, cleanup, render, screen } from '@testing-library/react'
|
|
21
|
+
import { useState } from 'react'
|
|
22
|
+
|
|
23
|
+
vi.mock('react-i18next', () => ({
|
|
24
|
+
useTranslation: () => ({ t: (k: string) => k }),
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
import { DynamicForm } from '../dynamic-form'
|
|
28
|
+
import type { ActionFieldDef } from '../types'
|
|
29
|
+
|
|
30
|
+
afterEach(cleanup)
|
|
31
|
+
|
|
32
|
+
const fields: ActionFieldDef[] = [
|
|
33
|
+
{
|
|
34
|
+
key: 'type',
|
|
35
|
+
label: 'Type',
|
|
36
|
+
type: 'text',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: 'provider',
|
|
40
|
+
label: 'Provider',
|
|
41
|
+
type: 'select',
|
|
42
|
+
dependsOn: 'type',
|
|
43
|
+
options: [
|
|
44
|
+
{ value: 'qr', label: 'QR', when: { field: 'type', in: ['whatsapp'] } },
|
|
45
|
+
{ value: 'meta', label: 'Meta', when: { field: 'type', in: ['whatsapp'] } },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
// Harness that lets the test drive `type` and observe the submitted `provider`.
|
|
51
|
+
function Harness({ onSubmit }: { onSubmit: (v: Record<string, any>) => void }) {
|
|
52
|
+
const [initial, setInitial] = useState<Record<string, any>>({ type: 'whatsapp', provider: 'qr' })
|
|
53
|
+
return (
|
|
54
|
+
<div>
|
|
55
|
+
<button type="button" onClick={() => setInitial({ type: 'sms', provider: 'qr' })}>
|
|
56
|
+
to-sms
|
|
57
|
+
</button>
|
|
58
|
+
<DynamicForm fields={fields} initialValues={initial} onSubmit={onSubmit} />
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe('DynamicForm dependent static enum', () => {
|
|
64
|
+
it('hides the gated select and resets its value when no option applies', async () => {
|
|
65
|
+
const onSubmit = vi.fn()
|
|
66
|
+
render(<Harness onSubmit={onSubmit} />)
|
|
67
|
+
|
|
68
|
+
// type=whatsapp → provider field visible.
|
|
69
|
+
expect(screen.getByText('Provider')).toBeTruthy()
|
|
70
|
+
|
|
71
|
+
// Switch type to sms → no provider option applies → field hidden.
|
|
72
|
+
await act(async () => {
|
|
73
|
+
screen.getByText('to-sms').click()
|
|
74
|
+
})
|
|
75
|
+
expect(screen.queryByText('Provider')).toBeNull()
|
|
76
|
+
|
|
77
|
+
// The invalidated selection was reset — submit carries empty provider.
|
|
78
|
+
await act(async () => {
|
|
79
|
+
screen.getByText('Guardar').click()
|
|
80
|
+
})
|
|
81
|
+
expect(onSubmit).toHaveBeenCalled()
|
|
82
|
+
expect(onSubmit.mock.calls[0][0].provider).toBe('')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { applyOptionWhen } from '../dynamic-form-schema'
|
|
3
|
+
import type { OptionDef } from '../types'
|
|
4
|
+
|
|
5
|
+
// A cascading STATIC enum: `provider` options only apply when the sibling
|
|
6
|
+
// `type` field is "whatsapp". Mirrors the link-inbox Device use case, but the
|
|
7
|
+
// helper is fully domain-agnostic.
|
|
8
|
+
const providerOptions: OptionDef[] = [
|
|
9
|
+
{ value: 'qr', label: 'QR', when: { field: 'type', in: ['whatsapp'] } },
|
|
10
|
+
{ value: 'meta', label: 'Meta', when: { field: 'type', in: ['whatsapp'] } },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
describe('applyOptionWhen', () => {
|
|
14
|
+
it('keeps only options whose `when.in` matches the parent value', () => {
|
|
15
|
+
const kept = applyOptionWhen(providerOptions, { type: 'whatsapp' })
|
|
16
|
+
expect(kept.map((o) => o.value)).toEqual(['qr', 'meta'])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('drops all gated options when the parent value is not in `in`', () => {
|
|
20
|
+
expect(applyOptionWhen(providerOptions, { type: 'sms' })).toEqual([])
|
|
21
|
+
expect(applyOptionWhen(providerOptions, { type: '' })).toEqual([])
|
|
22
|
+
expect(applyOptionWhen(providerOptions, {})).toEqual([])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('falls back to `dependsOn` when `when.field` is omitted', () => {
|
|
26
|
+
const opts: OptionDef[] = [
|
|
27
|
+
{ value: 'a', label: 'A', when: { in: ['x'] } },
|
|
28
|
+
{ value: 'b', label: 'B', when: { in: ['y'] } },
|
|
29
|
+
]
|
|
30
|
+
expect(applyOptionWhen(opts, { kind: 'x' }, 'kind').map((o) => o.value)).toEqual(['a'])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('supports `not_in` (snake_case) and `notIn` (camelCase)', () => {
|
|
34
|
+
const opts: OptionDef[] = [
|
|
35
|
+
{ value: 'a', label: 'A', when: { field: 'type', not_in: ['hidden'] } },
|
|
36
|
+
{ value: 'b', label: 'B', when: { field: 'type', notIn: ['hidden'] } },
|
|
37
|
+
]
|
|
38
|
+
expect(applyOptionWhen(opts, { type: 'shown' }).map((o) => o.value)).toEqual(['a', 'b'])
|
|
39
|
+
expect(applyOptionWhen(opts, { type: 'hidden' })).toEqual([])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('compares values as strings', () => {
|
|
43
|
+
const opts: OptionDef[] = [{ value: 'a', label: 'A', when: { field: 'n', in: ['1'] } }]
|
|
44
|
+
expect(applyOptionWhen(opts, { n: 1 }).map((o) => o.value)).toEqual(['a'])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('includes an option with no `when` regardless of parent value (retrocompat)', () => {
|
|
48
|
+
const opts: OptionDef[] = [
|
|
49
|
+
{ value: 'always', label: 'Always' },
|
|
50
|
+
{ value: 'gated', label: 'Gated', when: { field: 'type', in: ['whatsapp'] } },
|
|
51
|
+
]
|
|
52
|
+
expect(applyOptionWhen(opts, { type: 'sms' }).map((o) => o.value)).toEqual(['always'])
|
|
53
|
+
expect(applyOptionWhen(opts, { type: 'whatsapp' }).map((o) => o.value)).toEqual([
|
|
54
|
+
'always',
|
|
55
|
+
'gated',
|
|
56
|
+
])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('includes a gated option when neither `when.field` nor `dependsOn` is present', () => {
|
|
60
|
+
const opts: OptionDef[] = [{ value: 'a', label: 'A', when: { in: ['x'] } }]
|
|
61
|
+
expect(applyOptionWhen(opts, { anything: 'x' }).map((o) => o.value)).toEqual(['a'])
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('returns [] for undefined options', () => {
|
|
65
|
+
expect(applyOptionWhen(undefined, {})).toEqual([])
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// callers (and unit tests) can use the zod schema without pulling in React or
|
|
3
3
|
// metacore-ui primitives.
|
|
4
4
|
import { z, type ZodTypeAny } from 'zod'
|
|
5
|
-
import type { ActionFieldDef, FieldValidation, FieldOptionsConfig } from './types'
|
|
5
|
+
import type { ActionFieldDef, FieldValidation, FieldOptionsConfig, OptionDef } from './types'
|
|
6
6
|
import { resolveValidatorToken } from './use-org-config-bridge'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -287,6 +287,49 @@ export function resolveDependsValue(
|
|
|
287
287
|
return String(raw)
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Filters a STATIC enum's `options[]` by each option's `when` gate against the
|
|
292
|
+
* current form values. Pure — no React, no side effects.
|
|
293
|
+
*
|
|
294
|
+
* Rule per option:
|
|
295
|
+
* - No `when` → always included (retrocompat; existing enums untouched).
|
|
296
|
+
* - With `when`: the gating field is `when.field ?? dependsOn`. If neither is
|
|
297
|
+
* present the option is included (nothing to gate on). Otherwise the form's
|
|
298
|
+
* value for that field is compared AS STRING: included when (no `in`, or
|
|
299
|
+
* value ∈ `in`) AND (no `not_in`, or value ∉ `not_in`). Tolerates the
|
|
300
|
+
* snake_case `not_in` the kernel serves alongside camelCase `notIn`.
|
|
301
|
+
*
|
|
302
|
+
* `formValues` is the flat map of the surrounding form/row values the gating
|
|
303
|
+
* field is read from; `dependsOn` is the containing field's declared dependency
|
|
304
|
+
* used as the default gating field.
|
|
305
|
+
*/
|
|
306
|
+
export function applyOptionWhen(
|
|
307
|
+
options: OptionDef[] | undefined,
|
|
308
|
+
formValues: Record<string, any> | null | undefined,
|
|
309
|
+
dependsOn?: string,
|
|
310
|
+
): OptionDef[] {
|
|
311
|
+
if (!Array.isArray(options)) return []
|
|
312
|
+
return options.filter((opt) => {
|
|
313
|
+
const when = opt?.when
|
|
314
|
+
if (!when) return true
|
|
315
|
+
const gate = (typeof when.field === 'string' && when.field.trim() !== '')
|
|
316
|
+
? when.field.trim()
|
|
317
|
+
: dependsOn
|
|
318
|
+
if (!gate) return true
|
|
319
|
+
const raw = formValues ? formValues[gate] : undefined
|
|
320
|
+
const current = raw == null ? '' : String(raw)
|
|
321
|
+
const inList = when.in
|
|
322
|
+
const notIn = when.notIn ?? when.not_in
|
|
323
|
+
if (Array.isArray(inList) && inList.length > 0) {
|
|
324
|
+
if (!inList.some((v) => String(v) === current)) return false
|
|
325
|
+
}
|
|
326
|
+
if (Array.isArray(notIn) && notIn.length > 0) {
|
|
327
|
+
if (notIn.some((v) => String(v) === current)) return false
|
|
328
|
+
}
|
|
329
|
+
return true
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
290
333
|
/**
|
|
291
334
|
* Reads a field's enriched options-resolution config, tolerating the camelCase
|
|
292
335
|
* `optionsConfig` (authored SDK shape) and the snake_case `options_config` the
|
package/src/dynamic-form.tsx
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
resolveWidget,
|
|
21
21
|
isLineItemsField,
|
|
22
22
|
evaluateBalance,
|
|
23
|
+
applyOptionWhen,
|
|
24
|
+
getDependsOn,
|
|
23
25
|
} from './dynamic-form-schema'
|
|
24
26
|
import { useOptionsResolver, type ResolvedOption } from './use-options-resolver'
|
|
25
27
|
import { DynamicLineItems } from './dynamic-line-items'
|
|
@@ -111,32 +113,17 @@ export function DynamicForm({
|
|
|
111
113
|
return (
|
|
112
114
|
<form onSubmit={handleSubmit} className="grid gap-4">
|
|
113
115
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
114
|
-
{fields.map((field) =>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{field.label}
|
|
126
|
-
{field.required && <span className="text-red-500 ml-1">*</span>}
|
|
127
|
-
</Label>
|
|
128
|
-
<FieldRenderer
|
|
129
|
-
field={field}
|
|
130
|
-
value={values[field.key]}
|
|
131
|
-
onChange={(v: any) => update(field.key, v)}
|
|
132
|
-
initialValues={initialValues}
|
|
133
|
-
/>
|
|
134
|
-
{errors[field.key] && (
|
|
135
|
-
<span className="text-red-500 text-sm" role="alert">{errors[field.key]}</span>
|
|
136
|
-
)}
|
|
137
|
-
</div>
|
|
138
|
-
)
|
|
139
|
-
})}
|
|
116
|
+
{fields.map((field) => (
|
|
117
|
+
<FieldRow
|
|
118
|
+
key={field.key}
|
|
119
|
+
field={field}
|
|
120
|
+
value={values[field.key]}
|
|
121
|
+
onChange={(v: any) => update(field.key, v)}
|
|
122
|
+
values={values}
|
|
123
|
+
error={errors[field.key]}
|
|
124
|
+
initialValues={initialValues}
|
|
125
|
+
/>
|
|
126
|
+
))}
|
|
140
127
|
</div>
|
|
141
128
|
<div className="flex justify-end gap-2 pt-2">
|
|
142
129
|
{onCancel && (
|
|
@@ -158,6 +145,61 @@ interface FieldRendererProps {
|
|
|
158
145
|
onChange: (v: any) => void
|
|
159
146
|
/** The form's initial record — used to seed an FK picker's existing label/image. */
|
|
160
147
|
initialValues?: Record<string, any>
|
|
148
|
+
/**
|
|
149
|
+
* The full flat map of current form values — read by a STATIC enum select to
|
|
150
|
+
* gate its options by a sibling field's value (`applyOptionWhen`).
|
|
151
|
+
*/
|
|
152
|
+
values?: Record<string, any>
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface FieldRowProps extends FieldRendererProps {
|
|
156
|
+
error?: string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// One form field row: label + renderer + inline error. Encapsulated as its own
|
|
160
|
+
// component so a STATIC enum select whose options are all gated out by a sibling
|
|
161
|
+
// value (`when`) can hide the ENTIRE row (label included) and run its reset
|
|
162
|
+
// effect with valid hook ordering.
|
|
163
|
+
function FieldRow({ field, value, onChange, values, error, initialValues }: FieldRowProps) {
|
|
164
|
+
const isStaticSelect =
|
|
165
|
+
resolveWidget(field) === 'select' && !field.ref && Array.isArray(field.options)
|
|
166
|
+
const effectiveOptions = isStaticSelect
|
|
167
|
+
? applyOptionWhen(field.options, values, getDependsOn(field))
|
|
168
|
+
: undefined
|
|
169
|
+
|
|
170
|
+
// Reset a selection that the current sibling value no longer permits (e.g.
|
|
171
|
+
// the parent switched away from the value that made this option valid).
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (!isStaticSelect || !effectiveOptions) return
|
|
174
|
+
if (value && !effectiveOptions.some((o) => String(o.value) === String(value))) {
|
|
175
|
+
onChange('')
|
|
176
|
+
}
|
|
177
|
+
}, [isStaticSelect, effectiveOptions, value, onChange])
|
|
178
|
+
|
|
179
|
+
// No option applies under the current sibling value → hide the whole field.
|
|
180
|
+
if (isStaticSelect && effectiveOptions && effectiveOptions.length === 0) return null
|
|
181
|
+
|
|
182
|
+
const fullWidth =
|
|
183
|
+
isLineItemsField(field) ||
|
|
184
|
+
resolveWidget(field) === 'textarea' ||
|
|
185
|
+
resolveWidget(field) === 'richtext'
|
|
186
|
+
return (
|
|
187
|
+
<div className={'grid gap-2 ' + (fullWidth ? 'sm:col-span-2' : '')}>
|
|
188
|
+
<Label htmlFor={field.key}>
|
|
189
|
+
{field.label}
|
|
190
|
+
{field.required && <span className="text-red-500 ml-1">*</span>}
|
|
191
|
+
</Label>
|
|
192
|
+
<FieldRenderer
|
|
193
|
+
field={field}
|
|
194
|
+
value={value}
|
|
195
|
+
onChange={onChange}
|
|
196
|
+
initialValues={initialValues}
|
|
197
|
+
values={values}
|
|
198
|
+
effectiveOptions={effectiveOptions}
|
|
199
|
+
/>
|
|
200
|
+
{error && <span className="text-red-500 text-sm" role="alert">{error}</span>}
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
161
203
|
}
|
|
162
204
|
|
|
163
205
|
// seedOptionFromSibling builds a pre-resolved option for an FK field from the
|
|
@@ -187,7 +229,13 @@ function seedOptionFromSibling(
|
|
|
187
229
|
}
|
|
188
230
|
}
|
|
189
231
|
|
|
190
|
-
function FieldRenderer({
|
|
232
|
+
function FieldRenderer({
|
|
233
|
+
field,
|
|
234
|
+
value,
|
|
235
|
+
onChange,
|
|
236
|
+
initialValues,
|
|
237
|
+
effectiveOptions,
|
|
238
|
+
}: FieldRendererProps & { effectiveOptions?: import('./types').OptionDef[] }) {
|
|
191
239
|
// Repeatable line-items group → render the row grid. Its value is an array
|
|
192
240
|
// of row objects rather than a scalar.
|
|
193
241
|
if (isLineItemsField(field)) {
|
|
@@ -228,7 +276,7 @@ function FieldRenderer({ field, value, onChange, initialValues }: FieldRendererP
|
|
|
228
276
|
<Select value={value || ''} onValueChange={onChange}>
|
|
229
277
|
<SelectTrigger className="w-full"><SelectValue placeholder={field.placeholder || 'Seleccionar...'} /></SelectTrigger>
|
|
230
278
|
<SelectContent>
|
|
231
|
-
{field.options?.map((opt) => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)}
|
|
279
|
+
{(effectiveOptions ?? field.options)?.map((opt) => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)}
|
|
232
280
|
</SelectContent>
|
|
233
281
|
</Select>
|
|
234
282
|
)
|
package/src/dynamic-kanban.tsx
CHANGED
|
@@ -123,7 +123,7 @@ import { useMetadataCache } from './metadata-cache'
|
|
|
123
123
|
import { ActivityValueRenderer } from './activity-value-renderer'
|
|
124
124
|
import { DynamicIcon } from './dynamic-icon'
|
|
125
125
|
import { isColumnVisibleInTable } from './column-visibility'
|
|
126
|
-
import { isRowActionVisible } from './dynamic-columns'
|
|
126
|
+
import { isRowActionVisible, relationKeyFor } from './dynamic-columns'
|
|
127
127
|
import { useCan, usePermissionsActive, resolveRowActions } from './permissions-context'
|
|
128
128
|
import { useDynamicRowActions } from './dynamic-row-actions'
|
|
129
129
|
import { useStageLayout } from './stage-layout'
|
|
@@ -323,6 +323,27 @@ export function selectCardColumns(
|
|
|
323
323
|
return { title, fields }
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
/** The all-zeros UUID — a Go zero-value FK serialized as "set" when it isn't. */
|
|
327
|
+
const ZERO_UUID = /^0{8}-0{4}-0{4}-0{4}-0{12}$/
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The value a card cell should render for a column — same resolution the
|
|
331
|
+
* table's cells apply. An FK column (`<rel>_id`) prefers the backend-resolved
|
|
332
|
+
* sibling object (`card.<rel>`, e.g. `{ name }` / `{ value, label }`) over the
|
|
333
|
+
* raw UUID; a zero-UUID FK counts as unset (renders the em-dash). Pure —
|
|
334
|
+
* exported for unit tests.
|
|
335
|
+
*/
|
|
336
|
+
export function cardCellValue(card: any, col: ColumnDefinition): unknown {
|
|
337
|
+
const raw = card?.[col.key]
|
|
338
|
+
if (typeof raw === 'string' && ZERO_UUID.test(raw)) return null
|
|
339
|
+
const relKey = relationKeyFor(col)
|
|
340
|
+
if (relKey !== col.key) {
|
|
341
|
+
const sibling = card?.[relKey]
|
|
342
|
+
if (sibling && typeof sibling === 'object') return sibling
|
|
343
|
+
}
|
|
344
|
+
return raw
|
|
345
|
+
}
|
|
346
|
+
|
|
326
347
|
/**
|
|
327
348
|
* Whether a card passes a lane funnel. Picked select/facet `values` match by
|
|
328
349
|
* equality (IN — the card's field value must be one of them); a free-text
|
|
@@ -2342,7 +2363,7 @@ function KanbanCard({
|
|
|
2342
2363
|
<div className="min-w-0 flex-1 break-words text-sm font-medium leading-snug">
|
|
2343
2364
|
{titleCol ? (
|
|
2344
2365
|
<ActivityValueRenderer
|
|
2345
|
-
value={card
|
|
2366
|
+
value={cardCellValue(card, titleCol)}
|
|
2346
2367
|
col={titleCol}
|
|
2347
2368
|
locale={locale}
|
|
2348
2369
|
timeZone={timeZone}
|
|
@@ -2394,7 +2415,7 @@ function KanbanCard({
|
|
|
2394
2415
|
<span className="shrink-0 opacity-70">{col.label}:</span>
|
|
2395
2416
|
<span className="min-w-0 break-words">
|
|
2396
2417
|
<ActivityValueRenderer
|
|
2397
|
-
value={card
|
|
2418
|
+
value={cardCellValue(card, col)}
|
|
2398
2419
|
col={col}
|
|
2399
2420
|
locale={locale}
|
|
2400
2421
|
timeZone={timeZone}
|
|
@@ -2423,7 +2444,7 @@ function CardPreview({
|
|
|
2423
2444
|
<div className="break-words text-sm font-medium leading-snug">
|
|
2424
2445
|
{titleCol ? (
|
|
2425
2446
|
<ActivityValueRenderer
|
|
2426
|
-
value={card
|
|
2447
|
+
value={cardCellValue(card, titleCol)}
|
|
2427
2448
|
col={titleCol}
|
|
2428
2449
|
locale={locale}
|
|
2429
2450
|
timeZone={timeZone}
|
|
@@ -2441,7 +2462,7 @@ function CardPreview({
|
|
|
2441
2462
|
<span className="shrink-0 opacity-70">{col.label}:</span>
|
|
2442
2463
|
<span className="min-w-0 break-words">
|
|
2443
2464
|
<ActivityValueRenderer
|
|
2444
|
-
value={card
|
|
2465
|
+
value={cardCellValue(card, col)}
|
|
2445
2466
|
col={col}
|
|
2446
2467
|
locale={locale}
|
|
2447
2468
|
timeZone={timeZone}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
resolveDependsValue,
|
|
32
32
|
getOptionsConfig,
|
|
33
33
|
resolveOptionsSource,
|
|
34
|
+
applyOptionWhen,
|
|
34
35
|
} from './dynamic-form-schema'
|
|
35
36
|
import { DynamicSelectField, DEFAULT_DEPENDS_HINT } from './dynamic-select-field'
|
|
36
37
|
import { useOptionsResolver, type ResolvedOption } from './use-options-resolver'
|
|
@@ -271,6 +272,25 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
271
272
|
const dependsValue = getDependsOn(field)
|
|
272
273
|
? resolveDependsValue(field, formValues, rowValues)
|
|
273
274
|
: undefined
|
|
275
|
+
|
|
276
|
+
// STATIC enum options gated per-cell by a sibling (row) or header value.
|
|
277
|
+
// Row values win over the header when a key exists in both, matching
|
|
278
|
+
// `resolveDependsValue`. Filtered against each option's `when`.
|
|
279
|
+
const isStaticSelect =
|
|
280
|
+
widget === 'select' && !field.ref && !getOptionsConfig(field)?.source && Array.isArray(field.options)
|
|
281
|
+
const gateValues = isStaticSelect ? { ...(formValues ?? {}), ...(rowValues ?? {}) } : undefined
|
|
282
|
+
const effectiveOptions = isStaticSelect
|
|
283
|
+
? applyOptionWhen(field.options, gateValues, getDependsOn(field))
|
|
284
|
+
: undefined
|
|
285
|
+
|
|
286
|
+
// Reset a selection the current sibling value no longer permits.
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (!isStaticSelect || !effectiveOptions) return
|
|
289
|
+
if (value && !effectiveOptions.some((o) => String(o.value) === String(value))) {
|
|
290
|
+
onChange('')
|
|
291
|
+
}
|
|
292
|
+
}, [isStaticSelect, effectiveOptions, value, onChange])
|
|
293
|
+
|
|
274
294
|
// Async searchable picker per row cell — e.g. the account_id column of a
|
|
275
295
|
// journal entry's debit/credit lines. Same widget as the flat form.
|
|
276
296
|
if (widget === 'dynamic_select') {
|
|
@@ -309,14 +329,17 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
309
329
|
disabled={off}
|
|
310
330
|
/>
|
|
311
331
|
)
|
|
312
|
-
case 'select':
|
|
332
|
+
case 'select': {
|
|
333
|
+
const opts = effectiveOptions ?? field.options
|
|
334
|
+
// No option applies under the current sibling value → hide the cell.
|
|
335
|
+
if (effectiveOptions && effectiveOptions.length === 0) return null
|
|
313
336
|
return (
|
|
314
337
|
<Select value={value || ''} onValueChange={onChange} disabled={off}>
|
|
315
338
|
<SelectTrigger className="w-full">
|
|
316
339
|
<SelectValue placeholder={field.placeholder || 'Seleccionar...'} />
|
|
317
340
|
</SelectTrigger>
|
|
318
341
|
<SelectContent>
|
|
319
|
-
{
|
|
342
|
+
{opts?.map((opt) => (
|
|
320
343
|
<SelectItem key={opt.value} value={opt.value}>
|
|
321
344
|
{opt.label}
|
|
322
345
|
</SelectItem>
|
|
@@ -324,6 +347,7 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
324
347
|
</SelectContent>
|
|
325
348
|
</Select>
|
|
326
349
|
)
|
|
350
|
+
}
|
|
327
351
|
case 'switch':
|
|
328
352
|
return <Switch checked={!!value} onCheckedChange={onChange} disabled={off} />
|
|
329
353
|
case 'number':
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -209,6 +209,10 @@ export function DynamicTable({
|
|
|
209
209
|
if (colonIdx === -1) return value
|
|
210
210
|
const prefix = value.substring(0, colonIdx).toLowerCase()
|
|
211
211
|
const rest = value.substring(colonIdx + 1)
|
|
212
|
+
// `eq:` is the wire's explicit equality operator, but internally a
|
|
213
|
+
// select stores the bare value — unwrap it so `f_status=eq:reception`
|
|
214
|
+
// matches the option "reception" (header filter + chip label).
|
|
215
|
+
if (prefix === 'eq') return rest
|
|
212
216
|
const operator = urlAliasToOperator[prefix]
|
|
213
217
|
return operator ? `${operator}:${rest}` : value
|
|
214
218
|
}
|
package/src/types.ts
CHANGED
|
@@ -335,12 +335,46 @@ export type FieldWidget =
|
|
|
335
335
|
| 'switch'
|
|
336
336
|
| 'upload'
|
|
337
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Per-option visibility gate for a STATIC enum (`options[]`). The option is only
|
|
340
|
+
* offered when the value of a sibling field (`field`, defaulting to the
|
|
341
|
+
* containing field's `dependsOn`) passes: value ∈ `in` and value ∉ `notIn`.
|
|
342
|
+
* Comparison is by string. Mirrors the kernel v3 `option.when` block; the SDK
|
|
343
|
+
* tolerates both the snake_case (`not_in`) the kernel serves and camelCase.
|
|
344
|
+
* An option WITHOUT a `when` always applies (retrocompat).
|
|
345
|
+
*/
|
|
346
|
+
export interface OptionWhen {
|
|
347
|
+
/** Sibling field whose value gates this option. Falls back to `dependsOn`. */
|
|
348
|
+
field?: string
|
|
349
|
+
/** The option applies when the gating field's value is in this list. */
|
|
350
|
+
in?: string[]
|
|
351
|
+
/** The option applies when the gating field's value is NOT in this list. */
|
|
352
|
+
notIn?: string[]
|
|
353
|
+
/** snake_case alias served by the kernel manifest for `notIn`. */
|
|
354
|
+
not_in?: string[]
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* A single static enum option. `value`/`label` are the core pair; `icon`,
|
|
359
|
+
* `color` and `image` drive the option's leading visual where the renderer
|
|
360
|
+
* supports it. `when` gates the option's visibility by a sibling field's value
|
|
361
|
+
* (see {@link OptionWhen}) — used for dependent/cascading STATIC enums.
|
|
362
|
+
*/
|
|
363
|
+
export interface OptionDef {
|
|
364
|
+
value: string
|
|
365
|
+
label: string
|
|
366
|
+
icon?: string
|
|
367
|
+
color?: string
|
|
368
|
+
image?: string
|
|
369
|
+
when?: OptionWhen
|
|
370
|
+
}
|
|
371
|
+
|
|
338
372
|
export interface ActionFieldDef {
|
|
339
373
|
key: string
|
|
340
374
|
label: string
|
|
341
375
|
type: string
|
|
342
376
|
required?: boolean
|
|
343
|
-
options?:
|
|
377
|
+
options?: OptionDef[]
|
|
344
378
|
defaultValue?: any
|
|
345
379
|
placeholder?: string
|
|
346
380
|
searchEndpoint?: string
|