@asteby/metacore-runtime-react 37.0.3 → 37.0.5
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 +12 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +41 -23
- package/dist/dialogs/dynamic-record.d.ts +10 -2
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +70 -36
- package/dist/dynamic-form.js +12 -2
- package/dist/dynamic-line-items.d.ts +6 -1
- package/dist/dynamic-line-items.d.ts.map +1 -1
- package/dist/dynamic-line-items.js +26 -11
- package/dist/dynamic-select-field.d.ts +3 -1
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +3 -2
- package/dist/field-validation-ui.d.ts +22 -0
- package/dist/field-validation-ui.d.ts.map +1 -0
- package/dist/field-validation-ui.js +123 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/__tests__/dynamic-record-field-validation.test.tsx +110 -0
- package/src/__tests__/field-validation-ui.test.ts +57 -0
- package/src/action-modal-dispatcher.tsx +60 -19
- package/src/dialogs/dynamic-record.tsx +89 -32
- package/src/dynamic-form.tsx +12 -4
- package/src/dynamic-line-items.tsx +58 -9
- package/src/dynamic-select-field.tsx +8 -1
- package/src/field-validation-ui.ts +140 -0
- package/src/index.ts +7 -0
|
@@ -26,9 +26,10 @@ function emptyRow(itemFields) {
|
|
|
26
26
|
}
|
|
27
27
|
return row;
|
|
28
28
|
}
|
|
29
|
-
export function DynamicLineItems({ field, value, onChange, disabled = false, formValues }) {
|
|
29
|
+
export function DynamicLineItems({ field, value, onChange, disabled = false, formValues, errors }) {
|
|
30
30
|
const itemFields = getItemFields(field);
|
|
31
31
|
const rows = Array.isArray(value) ? value : [];
|
|
32
|
+
const errMap = errors ?? {};
|
|
32
33
|
// `lock_rows` fixes the row set: no add-row button, no per-row delete. Rows
|
|
33
34
|
// stay editable cell-by-cell. Snake_case is what the kernel serves; tolerate
|
|
34
35
|
// the camelCase alias too. Derived once.
|
|
@@ -64,8 +65,14 @@ export function DynamicLineItems({ field, value, onChange, disabled = false, for
|
|
|
64
65
|
updateCell(idx, key, cellValue);
|
|
65
66
|
};
|
|
66
67
|
const totalCols = itemFields.filter((c) => c.total);
|
|
67
|
-
return (_jsxs("div", { className: "grid gap-2", "data-widget": "line_items", children: [_jsxs("div", { className: "grid gap-2 sm:hidden", children: [rows.length === 0 && (_jsx("div", { className: "text-muted-foreground rounded-md border px-3 py-4 text-center text-sm", children: "Sin renglones" })), rows.map((row, idx) => (_jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("span", { className: "text-muted-foreground text-xs font-medium", children: ["Rengl\u00F3n ", idx + 1] }), !lockRows && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "size-7", onClick: () => removeRow(idx), disabled: disabled, "aria-label": "Eliminar rengl\u00F3n", children: _jsx(Trash2, { className: "h-4 w-4 text-red-500" }) }))] }), _jsx("div", { className: "grid gap-2.5", children: itemFields.map((col) =>
|
|
68
|
-
|
|
68
|
+
return (_jsxs("div", { className: "grid gap-2", "data-widget": "line_items", children: [_jsxs("div", { className: "grid gap-2 sm:hidden", children: [rows.length === 0 && (_jsx("div", { className: "text-muted-foreground rounded-md border px-3 py-4 text-center text-sm", children: "Sin renglones" })), rows.map((row, idx) => (_jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("span", { className: "text-muted-foreground text-xs font-medium", children: ["Rengl\u00F3n ", idx + 1] }), !lockRows && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "size-7", onClick: () => removeRow(idx), disabled: disabled, "aria-label": "Eliminar rengl\u00F3n", children: _jsx(Trash2, { className: "h-4 w-4 text-red-500" }) }))] }), _jsx("div", { className: "grid gap-2.5", children: itemFields.map((col) => {
|
|
69
|
+
const cellErr = errMap[`${idx}.${col.key}`];
|
|
70
|
+
return (_jsxs("div", { className: "grid gap-1", children: [_jsxs("span", { className: "text-xs font-medium", children: [col.label, col.required && (_jsx("span", { className: "ml-1 text-red-500", children: "*" }))] }), _jsx(CellRenderer, { field: col, value: row?.[col.key], onChange: (v) => handleCell(idx, col.key, v), disabled: disabled, formValues: formValues, rowValues: row, invalid: !!cellErr }), cellErr && (_jsx("p", { className: "text-destructive text-xs", children: cellErr }))] }, col.key));
|
|
71
|
+
}) })] }, idx))), hasTotals && rows.length > 0 && (_jsx("div", { className: "bg-muted/30 grid gap-1.5 rounded-md border p-3", children: totalCols.map((col) => (_jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: col.label }), _jsx("span", { className: "font-semibold tabular-nums", children: fmtNumber(totals[col.key] ?? 0) })] }, col.key))) }))] }), _jsx("div", { className: "hidden overflow-x-auto rounded-md border sm:block", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50", children: _jsxs("tr", { children: [itemFields.map((col) => (_jsxs("th", { className: 'px-3 py-2 font-medium ' +
|
|
72
|
+
(isNumericCol(col) ? 'text-right' : 'text-left'), children: [col.label, col.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }, col.key))), !lockRows && _jsx("th", { className: "w-12 px-3 py-2", "aria-label": "acciones" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: itemFields.length + (lockRows ? 0 : 1), className: "px-3 py-4 text-center text-muted-foreground", children: "Sin renglones" }) })), rows.map((row, idx) => (_jsxs("tr", { className: "border-t align-top", children: [itemFields.map((col) => {
|
|
73
|
+
const cellErr = errMap[`${idx}.${col.key}`];
|
|
74
|
+
return (_jsxs("td", { className: "px-2 py-1.5", children: [_jsx(CellRenderer, { field: col, value: row?.[col.key], onChange: (v) => handleCell(idx, col.key, v), disabled: disabled, formValues: formValues, rowValues: row, invalid: !!cellErr }), cellErr && (_jsx("p", { className: "text-destructive mt-1 text-xs", children: cellErr }))] }, col.key));
|
|
75
|
+
}), !lockRows && (_jsx("td", { className: "px-2 py-1.5 text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeRow(idx), disabled: disabled, "aria-label": "Eliminar rengl\u00F3n", children: _jsx(Trash2, { className: "h-4 w-4 text-red-500" }) }) }))] }, idx)))] }), hasTotals && rows.length > 0 && (_jsx("tfoot", { className: "border-t bg-muted/30", children: _jsxs("tr", { children: [itemFields.map((col, ci) => {
|
|
69
76
|
if (ci === 0) {
|
|
70
77
|
return (_jsx("td", { className: "px-3 py-2 text-left font-medium text-muted-foreground", children: "Totales" }, col.key));
|
|
71
78
|
}
|
|
@@ -86,8 +93,11 @@ function BalanceBadge({ state, }) {
|
|
|
86
93
|
// without the per-field Label (the column header is the label) and sized for a
|
|
87
94
|
// table cell. Nested line-items inside a row are not supported (a row column is
|
|
88
95
|
// a scalar widget).
|
|
89
|
-
function CellRenderer({ field, value, onChange, disabled, formValues, rowValues }) {
|
|
96
|
+
function CellRenderer({ field, value, onChange, disabled, formValues, rowValues, invalid }) {
|
|
90
97
|
const widget = resolveWidget(field);
|
|
98
|
+
const invalidCls = invalid
|
|
99
|
+
? 'border-destructive ring-1 ring-destructive/30 focus-visible:ring-destructive'
|
|
100
|
+
: '';
|
|
91
101
|
// Per-field read-only: a column locked by a PrefillSpec.lock (e.g. the
|
|
92
102
|
// "ordered" / "already received" progress columns of a receive-goods modal)
|
|
93
103
|
// renders disabled so it shows context without being editable. Tolerates the
|
|
@@ -119,7 +129,7 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
119
129
|
// Async searchable picker per row cell — e.g. the account_id column of a
|
|
120
130
|
// journal entry's debit/credit lines. Same widget as the flat form.
|
|
121
131
|
if (widget === 'dynamic_select') {
|
|
122
|
-
return _jsx(DynamicSelectField, { field: field, value: value, onChange: onChange, dependsValue: dependsValue, readonly: ro });
|
|
132
|
+
return (_jsx(DynamicSelectField, { field: field, value: value, onChange: onChange, dependsValue: dependsValue, readonly: ro, invalid: invalid }));
|
|
123
133
|
}
|
|
124
134
|
if (widget === 'select' && (field.ref || getOptionsConfig(field)?.source)) {
|
|
125
135
|
return (_jsx(RefCell, { field: field, value: value, onChange: onChange, disabled: disabled, formValues: formValues, rowValues: rowValues }));
|
|
@@ -127,24 +137,29 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
127
137
|
switch (widget) {
|
|
128
138
|
case 'textarea':
|
|
129
139
|
case 'richtext':
|
|
130
|
-
return (_jsx(Textarea, { value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: off, rows: 2 }));
|
|
140
|
+
return (_jsx(Textarea, { value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: off, rows: 2, "aria-invalid": invalid || undefined, className: invalidCls || undefined }));
|
|
131
141
|
case 'color':
|
|
132
|
-
return (_jsx(Input, { type: "color", value: value || '#000000', onChange: (e) => onChange(e.target.value), disabled: off }));
|
|
142
|
+
return (_jsx(Input, { type: "color", value: value || '#000000', onChange: (e) => onChange(e.target.value), disabled: off, "aria-invalid": invalid || undefined, className: invalidCls || undefined }));
|
|
133
143
|
case 'select': {
|
|
134
144
|
const opts = effectiveOptions ?? field.options;
|
|
135
145
|
// No option applies under the current sibling value → hide the cell.
|
|
136
146
|
if (effectiveOptions && effectiveOptions.length === 0)
|
|
137
147
|
return null;
|
|
138
|
-
return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: off, children: [_jsx(SelectTrigger, { className:
|
|
148
|
+
return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: off, children: [_jsx(SelectTrigger, { className: 'w-full' + (invalidCls ? ` ${invalidCls}` : ''), "aria-invalid": invalid || undefined, children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: opts?.map((opt) => (_jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
139
149
|
}
|
|
140
150
|
case 'switch':
|
|
141
151
|
return _jsx(Switch, { checked: !!value, onCheckedChange: onChange, disabled: off });
|
|
142
152
|
case 'number':
|
|
143
|
-
return (_jsx(Input, { type: "number", value: value ?? '', onChange: (e) =>
|
|
153
|
+
return (_jsx(Input, { type: "number", value: value ?? '', onChange: (e) => {
|
|
154
|
+
const n = e.target.valueAsNumber;
|
|
155
|
+
// `0 || ''` would wipe a legitimate zero (unit_price=0)
|
|
156
|
+
// and then required-validation fires with nothing marked.
|
|
157
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n);
|
|
158
|
+
}, placeholder: field.placeholder, disabled: off, "aria-invalid": invalid || undefined, className: invalidCls || undefined }));
|
|
144
159
|
case 'date':
|
|
145
|
-
return (_jsx(Input, { type: "date", value: value || '', onChange: (e) => onChange(e.target.value), disabled: off }));
|
|
160
|
+
return (_jsx(Input, { type: "date", value: value || '', onChange: (e) => onChange(e.target.value), disabled: off, "aria-invalid": invalid || undefined, className: invalidCls || undefined }));
|
|
146
161
|
default:
|
|
147
|
-
return (_jsx(Input, { type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: off }));
|
|
162
|
+
return (_jsx(Input, { type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, disabled: off, "aria-invalid": invalid || undefined, className: invalidCls || undefined }));
|
|
148
163
|
}
|
|
149
164
|
}
|
|
150
165
|
function RefCell({ field, value, onChange, disabled, formValues, rowValues }) {
|
|
@@ -88,7 +88,9 @@ export interface DynamicSelectFieldProps {
|
|
|
88
88
|
* host's create modal renders them locked. Pairs with `createDefaults`.
|
|
89
89
|
*/
|
|
90
90
|
createLockedFields?: string[];
|
|
91
|
+
/** Paint trigger with destructive border when validation failed. */
|
|
92
|
+
invalid?: boolean;
|
|
91
93
|
}
|
|
92
|
-
export declare function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly, staticOptions, descriptionAsBadge, hideCreate, createDefaults, createLockedFields, }: DynamicSelectFieldProps): import("react").JSX.Element;
|
|
94
|
+
export declare function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly, staticOptions, descriptionAsBadge, hideCreate, createDefaults, createLockedFields, invalid, }: DynamicSelectFieldProps): import("react").JSX.Element;
|
|
93
95
|
export default DynamicSelectField;
|
|
94
96
|
//# sourceMappingURL=dynamic-select-field.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AA4CA,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;;;;;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;
|
|
1
|
+
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AA4CA,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;;;;;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,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,EAChB,aAAoB,EACpB,kBAA0B,EAC1B,UAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,OAAe,GAClB,EAAE,uBAAuB,+BA0TzB;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -88,7 +88,7 @@ export function OptionLead({ option, size = 20, }) {
|
|
|
88
88
|
function useDebounced(value, ms) {
|
|
89
89
|
return useDebouncedValue(value, ms);
|
|
90
90
|
}
|
|
91
|
-
export function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly = false, staticOptions = null, descriptionAsBadge = false, hideCreate = false, createDefaults, createLockedFields, }) {
|
|
91
|
+
export function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly = false, staticOptions = null, descriptionAsBadge = false, hideCreate = false, createDefaults, createLockedFields, invalid = false, }) {
|
|
92
92
|
const { t } = useTranslation();
|
|
93
93
|
const ph = (fallback) => field.placeholder ? t(field.placeholder, { defaultValue: field.placeholder }) : fallback;
|
|
94
94
|
const [open, setOpen] = useState(false);
|
|
@@ -221,7 +221,8 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
|
|
|
221
221
|
// (the long empty-state placeholder) and overflows the column, pushing the
|
|
222
222
|
// "+" off-screen — it only "fit" once a short value was selected.
|
|
223
223
|
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsxs(Popover, { open: open && !blockedByDependency, onOpenChange: (o) => { if (!blockedByDependency)
|
|
224
|
-
setOpen(o); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, disabled: blockedByDependency,
|
|
224
|
+
setOpen(o); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, disabled: blockedByDependency, "aria-invalid": invalid || undefined, className: 'min-w-0 flex-1 justify-between font-normal' +
|
|
225
|
+
(invalid ? ' border-destructive ring-1 ring-destructive/30' : ''), "data-empty": !value, "data-depends-blocked": blockedByDependency ? '' : undefined, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [value && selectedOption ? (_jsx(OptionLead, { option: selectedOption, size: 20 })) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: blockedByDependency
|
|
225
226
|
? (dependsHint || DEFAULT_DEPENDS_HINT)
|
|
226
227
|
: selectedLabel || ph('Buscar…') }), descriptionAsBadge && selectedOption?.description ? (_jsx(Badge, { variant: "secondary", className: "shrink-0 font-normal tabular-nums", children: selectedOption.description })) : null] }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
|
|
227
228
|
// Match the trigger width without an arbitrary Tailwind class
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ActionFieldDef } from './types';
|
|
2
|
+
import type { Translate } from './server-error';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a validation path (`customer_id`, `lines.0.unit_price`) to a
|
|
5
|
+
* human label using the action/modal field schema. Nested line-item paths
|
|
6
|
+
* become "Renglones → Precio unitario (fila 1)".
|
|
7
|
+
*/
|
|
8
|
+
export declare function labelForValidationPath(path: string, fields: readonly ActionFieldDef[] | undefined, t?: Translate): string;
|
|
9
|
+
/** Build `{path: label}` covering scalar fields and `parent.N.child` templates
|
|
10
|
+
* used by localizeFieldErrorMap (exact keys win when present). */
|
|
11
|
+
export declare function labelsForValidationFields(fields: readonly ActionFieldDef[] | undefined, t?: Translate): Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Toast description: one "Label: message" line per error. Re-resolves labels
|
|
14
|
+
* for dotted line-item paths so operators see e.g.
|
|
15
|
+
* "Precio unitario (fila 1): es obligatorio" instead of a bare headline.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatFieldErrorsDescription(errors: Record<string, string>, fields?: readonly ActionFieldDef[], t?: Translate): string | undefined;
|
|
18
|
+
/** Drop `key` and every `key.*` / `key.N.*` entry after the operator edits that field. */
|
|
19
|
+
export declare function clearFieldErrorTree(prev: Record<string, string>, key: string): Record<string, string>;
|
|
20
|
+
/** Slice `parent.0.child` errors into `{ "0.child": msg }` for DynamicLineItems. */
|
|
21
|
+
export declare function lineItemErrorsFor(parentKey: string, errors: Record<string, string> | undefined): Record<string, string>;
|
|
22
|
+
//# sourceMappingURL=field-validation-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-validation-ui.d.ts","sourceRoot":"","sources":["../src/field-validation-ui.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAiB/C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,SAAS,EAC7C,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAwCR;AAED;mEACmE;AACnE,wBAAgB,yBAAyB,CACrC,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,SAAS,EAC7C,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYxB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,EAClC,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,GAAG,SAAS,CAYpB;AAED,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAC3C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
function itemFieldsOf(field) {
|
|
2
|
+
const raw = field.itemFields ?? field.item_fields;
|
|
3
|
+
return Array.isArray(raw) ? raw : [];
|
|
4
|
+
}
|
|
5
|
+
function translateLabel(raw, t) {
|
|
6
|
+
if (!raw)
|
|
7
|
+
return '';
|
|
8
|
+
if (!t)
|
|
9
|
+
return raw;
|
|
10
|
+
return t(raw, { defaultValue: raw });
|
|
11
|
+
}
|
|
12
|
+
function humanizeToken(key) {
|
|
13
|
+
return key.replace(/[._-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a validation path (`customer_id`, `lines.0.unit_price`) to a
|
|
17
|
+
* human label using the action/modal field schema. Nested line-item paths
|
|
18
|
+
* become "Renglones → Precio unitario (fila 1)".
|
|
19
|
+
*/
|
|
20
|
+
export function labelForValidationPath(path, fields, t) {
|
|
21
|
+
if (!path)
|
|
22
|
+
return '';
|
|
23
|
+
const parts = path.split('.');
|
|
24
|
+
if (!fields?.length)
|
|
25
|
+
return humanizeToken(path);
|
|
26
|
+
let cursor = fields;
|
|
27
|
+
const bits = [];
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < parts.length && cursor) {
|
|
30
|
+
const seg = parts[i];
|
|
31
|
+
if (/^\d+$/.test(seg)) {
|
|
32
|
+
const row = Number(seg) + 1;
|
|
33
|
+
const nextKey = parts[i + 1];
|
|
34
|
+
if (nextKey && cursor) {
|
|
35
|
+
const col = cursor.find((f) => f.key === nextKey);
|
|
36
|
+
const colLabel = translateLabel(col?.label, t) || humanizeToken(nextKey);
|
|
37
|
+
bits.push(`${colLabel} (fila ${row})`);
|
|
38
|
+
i += 2;
|
|
39
|
+
cursor = undefined;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
bits.push(`fila ${row}`);
|
|
43
|
+
i += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const field = cursor.find((f) => f.key === seg);
|
|
47
|
+
if (!field) {
|
|
48
|
+
bits.push(humanizeToken(parts.slice(i).join('.')));
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
bits.push(translateLabel(field.label, t) || humanizeToken(seg));
|
|
52
|
+
const nested = itemFieldsOf(field);
|
|
53
|
+
cursor = nested.length ? nested : undefined;
|
|
54
|
+
i += 1;
|
|
55
|
+
if (!cursor && i < parts.length) {
|
|
56
|
+
bits.push(humanizeToken(parts.slice(i).join('.')));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return bits.join(' → ');
|
|
61
|
+
}
|
|
62
|
+
/** Build `{path: label}` covering scalar fields and `parent.N.child` templates
|
|
63
|
+
* used by localizeFieldErrorMap (exact keys win when present). */
|
|
64
|
+
export function labelsForValidationFields(fields, t) {
|
|
65
|
+
const out = {};
|
|
66
|
+
if (!fields)
|
|
67
|
+
return out;
|
|
68
|
+
for (const f of fields) {
|
|
69
|
+
if (!f.key)
|
|
70
|
+
continue;
|
|
71
|
+
out[f.key] = translateLabel(f.label, t) || humanizeToken(f.key);
|
|
72
|
+
for (const child of itemFieldsOf(f)) {
|
|
73
|
+
if (!child.key)
|
|
74
|
+
continue;
|
|
75
|
+
out[`${f.key}.${child.key}`] = translateLabel(child.label, t) || humanizeToken(child.key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Toast description: one "Label: message" line per error. Re-resolves labels
|
|
82
|
+
* for dotted line-item paths so operators see e.g.
|
|
83
|
+
* "Precio unitario (fila 1): es obligatorio" instead of a bare headline.
|
|
84
|
+
*/
|
|
85
|
+
export function formatFieldErrorsDescription(errors, fields, t) {
|
|
86
|
+
const entries = Object.entries(errors);
|
|
87
|
+
if (!entries.length)
|
|
88
|
+
return undefined;
|
|
89
|
+
return entries
|
|
90
|
+
.map(([path, msg]) => {
|
|
91
|
+
const label = labelForValidationPath(path, fields, t);
|
|
92
|
+
// localizeFieldIssue already prefixes "{{label}}: …" — avoid
|
|
93
|
+
// "Label: Label: msg" when the message already starts with the label.
|
|
94
|
+
if (label && msg.toLowerCase().startsWith(label.toLowerCase()))
|
|
95
|
+
return msg;
|
|
96
|
+
return label ? `${label}: ${msg}` : msg;
|
|
97
|
+
})
|
|
98
|
+
.join(' · ');
|
|
99
|
+
}
|
|
100
|
+
/** Drop `key` and every `key.*` / `key.N.*` entry after the operator edits that field. */
|
|
101
|
+
export function clearFieldErrorTree(prev, key) {
|
|
102
|
+
if (!prev[key] && !Object.keys(prev).some((k) => k.startsWith(`${key}.`)))
|
|
103
|
+
return prev;
|
|
104
|
+
const next = {};
|
|
105
|
+
for (const [k, v] of Object.entries(prev)) {
|
|
106
|
+
if (k === key || k.startsWith(`${key}.`))
|
|
107
|
+
continue;
|
|
108
|
+
next[k] = v;
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
/** Slice `parent.0.child` errors into `{ "0.child": msg }` for DynamicLineItems. */
|
|
113
|
+
export function lineItemErrorsFor(parentKey, errors) {
|
|
114
|
+
if (!errors)
|
|
115
|
+
return {};
|
|
116
|
+
const prefix = `${parentKey}.`;
|
|
117
|
+
const out = {};
|
|
118
|
+
for (const [k, v] of Object.entries(errors)) {
|
|
119
|
+
if (k.startsWith(prefix))
|
|
120
|
+
out[k.slice(prefix.length)] = v;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './options-context';
|
|
|
4
4
|
export { extractServerError, extractFieldErrors, localizeFieldIssue, localizeFieldErrorMap, toastServerError, toastServerSuccess, type ExtractedError, type Translate, type FieldIssue, } from './server-error';
|
|
5
5
|
export { parseRuleString, fieldValidationOf, checkValue, validateValues, bagHasErrors, type ValidationSpec, } from './validator';
|
|
6
6
|
export { VALIDATION_CATALOGS, validationCatalog, validationMessageKey } from './validation-catalog';
|
|
7
|
+
export { labelForValidationPath, labelsForValidationFields, formatFieldErrorsDescription, clearFieldErrorTree, lineItemErrorsFor, } from './field-validation-ui';
|
|
7
8
|
export * from './dynamic-table';
|
|
8
9
|
export { DynamicKanban, type DynamicKanbanProps, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
9
10
|
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, type StageAutomation, type StageAutomationAction, type StageAutomationActionType, type NewStageAutomation, type UseStageAutomationsResult, type StageAutomationsButtonProps, } from './stage-automations';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,WAAW,CAAA;AAClB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,UAAU,GAClB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,KAAK,cAAc,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AACnG,OAAO,EACH,sBAAsB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,GACpB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,GACnC,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,GAC9B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,oBAAoB,GAC5B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC/B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,gBAAgB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC/B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC7E,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,cAAc,EACd,KAAK,mBAAmB,GAC3B,MAAM,oBAAoB,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACtB,MAAM,eAAe,CAAA;AACtB,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,GACzB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACzF,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,GAC9B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACjF,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EACH,OAAO,EACP,QAAQ,EACR,cAAc,EACd,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,cAAc,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,IAAI,uBAAuB,GAC5C,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,0BAA0B,EAC1B,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,+BAA+B,GAClC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './options-context';
|
|
|
9
9
|
export { extractServerError, extractFieldErrors, localizeFieldIssue, localizeFieldErrorMap, toastServerError, toastServerSuccess, } from './server-error';
|
|
10
10
|
export { parseRuleString, fieldValidationOf, checkValue, validateValues, bagHasErrors, } from './validator';
|
|
11
11
|
export { VALIDATION_CATALOGS, validationCatalog, validationMessageKey } from './validation-catalog';
|
|
12
|
+
export { labelForValidationPath, labelsForValidationFields, formatFieldErrorsDescription, clearFieldErrorTree, lineItemErrorsFor, } from './field-validation-ui';
|
|
12
13
|
export * from './dynamic-table';
|
|
13
14
|
export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
14
15
|
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, } from './stage-automations';
|
package/package.json
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
//
|
|
3
|
+
// Client-side validateValues must paint invalid fields (border + message) so a
|
|
4
|
+
// toast "Revisa los campos marcados" is never orphaned when the field exists
|
|
5
|
+
// in the schema. ensureFields merges host-injected keys (e.g. branch_id) into
|
|
6
|
+
// the loaded modal before seed/render.
|
|
7
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
8
|
+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
9
|
+
import { I18nextProvider } from 'react-i18next'
|
|
10
|
+
import i18next from 'i18next'
|
|
11
|
+
|
|
12
|
+
import { DynamicRecordDialog } from '../dialogs/dynamic-record'
|
|
13
|
+
import { ApiProvider } from '../api-context'
|
|
14
|
+
|
|
15
|
+
afterEach(cleanup)
|
|
16
|
+
|
|
17
|
+
vi.mock('sonner', () => ({
|
|
18
|
+
toast: {
|
|
19
|
+
error: vi.fn(),
|
|
20
|
+
success: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
function makeI18n() {
|
|
25
|
+
const inst = i18next.createInstance()
|
|
26
|
+
void inst.init({
|
|
27
|
+
lng: 'es',
|
|
28
|
+
fallbackLng: 'es',
|
|
29
|
+
react: { useSuspense: false },
|
|
30
|
+
resources: { es: { translation: {} } },
|
|
31
|
+
})
|
|
32
|
+
return inst
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const api = {
|
|
36
|
+
get: vi.fn(async (url: string) => {
|
|
37
|
+
if (String(url).includes('/metadata/modal/')) {
|
|
38
|
+
return {
|
|
39
|
+
data: {
|
|
40
|
+
data: {
|
|
41
|
+
title: 'Nuevo pedido',
|
|
42
|
+
fields: [{ key: 'name', label: 'Nombre', type: 'text', required: true }],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { data: { data: {} } }
|
|
48
|
+
}),
|
|
49
|
+
post: vi.fn(async () => ({ data: { success: true } })),
|
|
50
|
+
put: async () => ({ data: { success: true } }),
|
|
51
|
+
delete: async () => ({ data: { success: true } }),
|
|
52
|
+
} as never
|
|
53
|
+
|
|
54
|
+
describe('DynamicRecordDialog field validation UX', () => {
|
|
55
|
+
it('marks required empty fields invalid on submit', async () => {
|
|
56
|
+
render(
|
|
57
|
+
<I18nextProvider i18n={makeI18n()}>
|
|
58
|
+
<ApiProvider client={api}>
|
|
59
|
+
<DynamicRecordDialog
|
|
60
|
+
open
|
|
61
|
+
onOpenChange={() => {}}
|
|
62
|
+
mode="create"
|
|
63
|
+
model="SalesOrder"
|
|
64
|
+
schema={{
|
|
65
|
+
title: 'Nuevo pedido',
|
|
66
|
+
fields: [{ key: 'name', label: 'Nombre', type: 'text', required: true }],
|
|
67
|
+
} as never}
|
|
68
|
+
/>
|
|
69
|
+
</ApiProvider>
|
|
70
|
+
</I18nextProvider>,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
await waitFor(() => expect(screen.getByText('Nombre')).toBeTruthy())
|
|
74
|
+
fireEvent.click(screen.getByRole('button', { name: 'Crear' }))
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
const input = document.querySelector('input[aria-invalid="true"]')
|
|
78
|
+
expect(input).toBeTruthy()
|
|
79
|
+
expect(input!.className).toMatch(/border-destructive/)
|
|
80
|
+
expect(screen.getByText(/obligatorio|required/i)).toBeTruthy()
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('merges ensureFields into fetched modal schema', async () => {
|
|
85
|
+
render(
|
|
86
|
+
<I18nextProvider i18n={makeI18n()}>
|
|
87
|
+
<ApiProvider client={api}>
|
|
88
|
+
<DynamicRecordDialog
|
|
89
|
+
open
|
|
90
|
+
onOpenChange={() => {}}
|
|
91
|
+
mode="create"
|
|
92
|
+
model="SalesOrder"
|
|
93
|
+
ensureFields={[
|
|
94
|
+
{
|
|
95
|
+
key: 'branch_id',
|
|
96
|
+
label: 'Sucursal',
|
|
97
|
+
type: 'dynamic_select',
|
|
98
|
+
ref: 'Branch',
|
|
99
|
+
required: true,
|
|
100
|
+
},
|
|
101
|
+
]}
|
|
102
|
+
/>
|
|
103
|
+
</ApiProvider>
|
|
104
|
+
</I18nextProvider>,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
await waitFor(() => expect(screen.getByText('Sucursal')).toBeTruthy())
|
|
108
|
+
expect(screen.getByText('Nombre')).toBeTruthy()
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
clearFieldErrorTree,
|
|
4
|
+
formatFieldErrorsDescription,
|
|
5
|
+
labelForValidationPath,
|
|
6
|
+
lineItemErrorsFor,
|
|
7
|
+
} from '../field-validation-ui'
|
|
8
|
+
import type { ActionFieldDef } from '../types'
|
|
9
|
+
|
|
10
|
+
const fields: ActionFieldDef[] = [
|
|
11
|
+
{ key: 'customer_id', label: 'Cliente', type: 'dynamic_select', required: true },
|
|
12
|
+
{
|
|
13
|
+
key: 'lines',
|
|
14
|
+
label: 'Renglones',
|
|
15
|
+
type: 'array',
|
|
16
|
+
required: true,
|
|
17
|
+
itemFields: [
|
|
18
|
+
{ key: 'product_id', label: 'Producto', type: 'dynamic_select', required: true },
|
|
19
|
+
{ key: 'unit_price', label: 'Precio unitario', type: 'number', required: true },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
describe('field-validation-ui', () => {
|
|
25
|
+
it('labels dotted line-item paths with fila N', () => {
|
|
26
|
+
expect(labelForValidationPath('lines.0.unit_price', fields)).toBe(
|
|
27
|
+
'Renglones → Precio unitario (fila 1)',
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('formats toast description with field labels', () => {
|
|
32
|
+
const desc = formatFieldErrorsDescription(
|
|
33
|
+
{ 'lines.0.unit_price': 'es obligatorio' },
|
|
34
|
+
fields,
|
|
35
|
+
)
|
|
36
|
+
expect(desc).toContain('Precio unitario (fila 1)')
|
|
37
|
+
expect(desc).toContain('es obligatorio')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('slices parent errors for DynamicLineItems', () => {
|
|
41
|
+
expect(
|
|
42
|
+
lineItemErrorsFor('lines', {
|
|
43
|
+
'lines.0.unit_price': 'required',
|
|
44
|
+
customer_id: 'required',
|
|
45
|
+
}),
|
|
46
|
+
).toEqual({ '0.unit_price': 'required' })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('clears dotted children when parent key edits', () => {
|
|
50
|
+
expect(
|
|
51
|
+
clearFieldErrorTree(
|
|
52
|
+
{ 'lines.0.unit_price': 'x', customer_id: 'y' },
|
|
53
|
+
'lines',
|
|
54
|
+
),
|
|
55
|
+
).toEqual({ customer_id: 'y' })
|
|
56
|
+
})
|
|
57
|
+
})
|