@asteby/metacore-runtime-react 37.0.4 → 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 +6 -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.map +1 -1
- package/dist/dialogs/dynamic-record.js +8 -5
- 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/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__/field-validation-ui.test.ts +57 -0
- package/src/action-modal-dispatcher.tsx +60 -19
- package/src/dialogs/dynamic-record.tsx +8 -5
- package/src/dynamic-form.tsx +12 -4
- package/src/dynamic-line-items.tsx +58 -9
- package/src/field-validation-ui.ts +140 -0
- package/src/index.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 37.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5f2a732: Action modal + line-items: preserve numeric zero (valueAsNumber), paint cell errors (`lines.0.*`), and toast a Label: message description for every field failure.
|
|
8
|
+
|
|
3
9
|
## 37.0.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AAmEA,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,SAAS,CAAA;AAE9E,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAExB,MAAM,sBAAsB,CAAA;AAE7B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAA;AAiBhD,MAAM,WAAW,WAAW;IACxB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,WAAW,CAM1D;AAsBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAStE;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAkB3F;AASD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY1D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASjE;AAUD,oEAAoE;AACpE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAgBnF;AAED,wBAAgB,qBAAqB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,GACZ,EAAE,gBAAgB,sCAoElB"}
|
|
@@ -16,6 +16,7 @@ import { Loader2 } from 'lucide-react';
|
|
|
16
16
|
import { toast } from 'sonner';
|
|
17
17
|
import { toastServerError, toastServerSuccess, extractFieldErrors, localizeFieldErrorMap } from './server-error';
|
|
18
18
|
import { validateValues, bagHasErrors } from './validator';
|
|
19
|
+
import { clearFieldErrorTree, formatFieldErrorsDescription, labelsForValidationFields, labelForValidationPath, lineItemErrorsFor, } from './field-validation-ui';
|
|
19
20
|
import { validationCatalog } from './validation-catalog';
|
|
20
21
|
import { useApi } from './api-context';
|
|
21
22
|
import { DynamicIcon } from './dynamic-icon';
|
|
@@ -432,33 +433,43 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
432
433
|
}, [open, action.fields, record]);
|
|
433
434
|
const updateField = (key, value) => {
|
|
434
435
|
setFormData((prev) => ({ ...prev, [key]: value }));
|
|
435
|
-
setFieldErrors(prev =>
|
|
436
|
-
if (!prev[key])
|
|
437
|
-
return prev;
|
|
438
|
-
const next = { ...prev };
|
|
439
|
-
delete next[key];
|
|
440
|
-
return next;
|
|
441
|
-
});
|
|
436
|
+
setFieldErrors((prev) => clearFieldErrorTree(prev, key));
|
|
442
437
|
};
|
|
443
438
|
const lang = i18n.language;
|
|
444
439
|
const handleActionError = (err) => {
|
|
440
|
+
const labels = labelsForValidationFields(action.fields, t);
|
|
445
441
|
const localized = localizeActionFieldErrors(err, action.fields, t, lang);
|
|
446
442
|
if (localized) {
|
|
447
|
-
|
|
448
|
-
|
|
443
|
+
// Enrich labels for dotted line-item paths before toasting.
|
|
444
|
+
const withPathLabels = {};
|
|
445
|
+
for (const [path, msg] of Object.entries(localized)) {
|
|
446
|
+
withPathLabels[path] = msg;
|
|
447
|
+
if (!labels[path])
|
|
448
|
+
labels[path] = labelForValidationPath(path, action.fields, t);
|
|
449
|
+
}
|
|
450
|
+
setFieldErrors(withPathLabels);
|
|
451
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
|
|
452
|
+
description: formatFieldErrorsDescription(withPathLabels, action.fields, t),
|
|
453
|
+
});
|
|
449
454
|
return;
|
|
450
455
|
}
|
|
451
|
-
toastServerError(err, { t, language: lang });
|
|
456
|
+
toastServerError(err, { t, language: lang, labels });
|
|
452
457
|
};
|
|
453
458
|
const execute = async () => {
|
|
454
459
|
if (action.fields) {
|
|
455
460
|
const bag = validateValues(action.fields, formData);
|
|
456
461
|
if (bagHasErrors(bag)) {
|
|
457
|
-
const labels =
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
+
const labels = labelsForValidationFields(action.fields, (k, o) => t(k, o));
|
|
463
|
+
// Exact dotted keys (`lines.0.qty`) need path-aware labels.
|
|
464
|
+
for (const path of Object.keys(bag)) {
|
|
465
|
+
if (!labels[path])
|
|
466
|
+
labels[path] = labelForValidationPath(path, action.fields, t);
|
|
467
|
+
}
|
|
468
|
+
const next = localizeFieldErrorMap(bag, t, { labels, language: lang });
|
|
469
|
+
setFieldErrors(next);
|
|
470
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
|
|
471
|
+
description: formatFieldErrorsDescription(next, action.fields, t),
|
|
472
|
+
});
|
|
462
473
|
return;
|
|
463
474
|
}
|
|
464
475
|
}
|
|
@@ -502,7 +513,7 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
502
513
|
const fullWidth = isLineItemsField(field) ||
|
|
503
514
|
resolveWidget(field) === 'textarea' ||
|
|
504
515
|
resolveWidget(field) === 'richtext';
|
|
505
|
-
return (_jsxs(FieldCell, { fullWidth: fullWidth, children: [_jsx(FieldLabel, { htmlFor: field.key, required: field.required, children: tl(field.label) }), renderField(field, formData[field.key], (v) => updateField(field.key, v), formData, record), fieldErrors[field.key] && (_jsx("p", { className: "text-destructive text-xs mt-1", children: fieldErrors[field.key] }))] }, field.key));
|
|
516
|
+
return (_jsxs(FieldCell, { fullWidth: fullWidth, children: [_jsx(FieldLabel, { htmlFor: field.key, required: field.required, children: tl(field.label) }), renderField(field, formData[field.key], (v) => updateField(field.key, v), formData, record, fieldErrors), fieldErrors[field.key] && (_jsx("p", { className: "text-destructive text-xs mt-1", children: fieldErrors[field.key] }))] }, field.key));
|
|
506
517
|
}), embedRelations && relations.length > 0 && (_jsx(FieldCell, { fullWidth: true, children: _jsx(DynamicRelations, { record: record, relations: relations, lineSubtable: true, embedOnly: true }) }))] }) }), _jsxs(DialogFooter, { className: "shrink-0", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: executing, children: t('common.cancel') }), _jsxs(Button, { onClick: execute, disabled: executing, style: action.color ? { backgroundColor: action.color, color: 'white' } : undefined, children: [executing ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(DynamicIcon, { name: action.icon, className: "mr-2 h-4 w-4" }), tl(action.label)] })] })] }) }));
|
|
507
518
|
}
|
|
508
519
|
// buildFieldDefaults seeds formData for a set of action fields, honoring the
|
|
@@ -648,24 +659,28 @@ function renderField(field, value, onChange,
|
|
|
648
659
|
// header picker) resolve a `dependsOn` reference against sibling header
|
|
649
660
|
// fields. Omitted by callers that have no surrounding form (the field is
|
|
650
661
|
// then treated as having no resolvable dependency).
|
|
651
|
-
formValues, record) {
|
|
662
|
+
formValues, record, fieldErrors) {
|
|
652
663
|
// Repeatable line-items group → row grid (value is an array of row objects).
|
|
653
664
|
// The header form values flow in so a cell can depend on a header field.
|
|
654
665
|
if (isLineItemsField(field)) {
|
|
655
|
-
return _jsx(DynamicLineItems, { field: applyPrefillLock(field), value: value, onChange: onChange, formValues: formValues });
|
|
666
|
+
return (_jsx(DynamicLineItems, { field: applyPrefillLock(field), value: value, onChange: onChange, formValues: formValues, errors: lineItemErrorsFor(field.key, fieldErrors) }));
|
|
656
667
|
}
|
|
657
668
|
// Resolve the widget the same way DynamicForm does (explicit widget wins,
|
|
658
669
|
// else inferred from type) so action modals and the standalone form stay in
|
|
659
670
|
// lockstep — previously this switch keyed off `field.type` and silently
|
|
660
671
|
// dropped `dynamic_select` to a plain text input.
|
|
661
672
|
const widget = resolveWidget(field);
|
|
673
|
+
const invalid = !!(fieldErrors && fieldErrors[field.key]);
|
|
674
|
+
const invalidCls = invalid
|
|
675
|
+
? 'border-destructive ring-1 ring-destructive/30 focus-visible:ring-destructive'
|
|
676
|
+
: '';
|
|
662
677
|
if (widget === 'dynamic_select') {
|
|
663
678
|
// A header-level dynamic_select may itself depend on another header
|
|
664
679
|
// field; resolve its filter_value from the form context.
|
|
665
680
|
const dependsValue = getDependsOn(field)
|
|
666
681
|
? resolveDependsValue(field, formValues)
|
|
667
682
|
: undefined;
|
|
668
|
-
return (_jsx(DynamicSelectField, { field: field, value: value, onChange: onChange, dependsValue: dependsValue, seedOption: seedOptionFromRecord(field, value, record) }));
|
|
683
|
+
return (_jsx(DynamicSelectField, { field: field, value: value, onChange: onChange, dependsValue: dependsValue, seedOption: seedOptionFromRecord(field, value, record), invalid: invalid }));
|
|
669
684
|
}
|
|
670
685
|
// File upload → themed picker that POSTs the file to the host upload
|
|
671
686
|
// endpoint and stores the returned url/path. Kept in sync with DynamicForm.
|
|
@@ -674,18 +689,21 @@ formValues, record) {
|
|
|
674
689
|
}
|
|
675
690
|
switch (widget) {
|
|
676
691
|
case 'textarea':
|
|
677
|
-
return _jsx(Textarea, { id: field.key, value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
|
|
692
|
+
return _jsx(Textarea, { id: field.key, value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, "aria-invalid": invalid || undefined, className: invalidCls || undefined });
|
|
678
693
|
case 'select':
|
|
679
|
-
return (_jsxs(Select, { value: value || '', onValueChange: onChange, children: [_jsx(SelectTrigger, { className:
|
|
694
|
+
return (_jsxs(Select, { value: value || '', onValueChange: onChange, children: [_jsx(SelectTrigger, { className: 'w-full' + (invalidCls ? ` ${invalidCls}` : ''), "aria-invalid": invalid || undefined, children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: field.options?.map((opt) => _jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })] }));
|
|
680
695
|
case 'switch':
|
|
681
696
|
return _jsx(Switch, { id: field.key, checked: !!value, onCheckedChange: onChange });
|
|
682
697
|
case 'number':
|
|
683
|
-
return _jsx(Input, { id: field.key, type: "number", value: value ?? '', onChange: (e) =>
|
|
698
|
+
return _jsx(Input, { id: field.key, type: "number", value: value ?? '', onChange: (e) => {
|
|
699
|
+
const n = e.target.valueAsNumber;
|
|
700
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n);
|
|
701
|
+
}, placeholder: field.placeholder, "aria-invalid": invalid || undefined, className: invalidCls || undefined });
|
|
684
702
|
case 'date':
|
|
685
703
|
// Modern shadcn Calendar in a Popover (portaled, never clipped by the
|
|
686
704
|
// modal) instead of the native, dated, easily-cut <input type=date>.
|
|
687
705
|
return _jsx(DynamicDateField, { field: field, value: value, onChange: onChange });
|
|
688
706
|
default:
|
|
689
|
-
return _jsx(Input, { id: field.key, type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
|
|
707
|
+
return _jsx(Input, { id: field.key, type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, "aria-invalid": invalid || undefined, className: invalidCls || undefined });
|
|
690
708
|
}
|
|
691
709
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
|
|
@@ -556,11 +556,14 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
|
|
|
556
556
|
labels[f.key] = f.label;
|
|
557
557
|
const next = localizeFieldErrorMap(bag, t, { labels });
|
|
558
558
|
setFieldErrors(next);
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
559
|
+
const description = Object.entries(next)
|
|
560
|
+
.map(([k, msg]) => {
|
|
561
|
+
const label = labels[k] || labelForKey(k);
|
|
562
|
+
return msg.toLowerCase().startsWith(String(label).toLowerCase())
|
|
563
|
+
? msg
|
|
564
|
+
: `${label}: ${msg}`;
|
|
565
|
+
})
|
|
566
|
+
.join(' · ');
|
|
564
567
|
toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }), description ? { description } : undefined);
|
|
565
568
|
return;
|
|
566
569
|
}
|
package/dist/dynamic-form.js
CHANGED
|
@@ -238,7 +238,10 @@ function FieldRenderer({ field, value, onChange, initialValues, effectiveOptions
|
|
|
238
238
|
case 'switch':
|
|
239
239
|
return _jsx(Switch, { id: field.key, checked: !!value, onCheckedChange: onChange });
|
|
240
240
|
case 'number':
|
|
241
|
-
return _jsx(Input, { id: field.key, type: "number", value: value ?? '', onChange: (e) =>
|
|
241
|
+
return _jsx(Input, { id: field.key, type: "number", value: value ?? '', onChange: (e) => {
|
|
242
|
+
const n = e.target.valueAsNumber;
|
|
243
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n);
|
|
244
|
+
}, placeholder: field.placeholder });
|
|
242
245
|
case 'date':
|
|
243
246
|
return _jsx(DynamicDateField, { field: field, value: value, onChange: onChange });
|
|
244
247
|
default:
|
|
@@ -263,7 +266,14 @@ function ScannableInput({ field, value, onChange, type, }) {
|
|
|
263
266
|
// degrada con un mensaje ("usá un lector físico / escribí a mano") cuando no
|
|
264
267
|
// hay soporte, así que mostrar el botón es seguro y consistente.
|
|
265
268
|
const scanEnabled = !!(field.scan ?? field.scannable);
|
|
266
|
-
const input = (_jsx(Input, { id: field.key, type: type, value: value ?? '', onChange: (e) =>
|
|
269
|
+
const input = (_jsx(Input, { id: field.key, type: type, value: value ?? '', onChange: (e) => {
|
|
270
|
+
if (type !== 'number') {
|
|
271
|
+
onChange(e.target.value);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const n = e.target.valueAsNumber;
|
|
275
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n);
|
|
276
|
+
}, placeholder: field.placeholder }));
|
|
267
277
|
if (!scanEnabled)
|
|
268
278
|
return input;
|
|
269
279
|
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsx("div", { className: "min-w-0 flex-1", children: input }), _jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: () => setScanOpen(true), title: "Escanear con la c\u00E1mara", "aria-label": "Escanear c\u00F3digo de barras con la c\u00E1mara", children: _jsx(ScanLine, { className: "size-4" }) }), _jsx(BarcodeScanner, { open: scanOpen, onClose: () => setScanOpen(false), onDetected: (code) => onChange(code),
|
|
@@ -10,6 +10,11 @@ export interface DynamicLineItemsProps {
|
|
|
10
10
|
* (e.g. `source_warehouse_id`), not just a sibling cell on the same row.
|
|
11
11
|
*/
|
|
12
12
|
formValues?: Record<string, any>;
|
|
13
|
+
/**
|
|
14
|
+
* Localized validation messages keyed as `rowIndex.columnKey` (e.g.
|
|
15
|
+
* `0.unit_price`). Painted as destructive borders + under-cell text.
|
|
16
|
+
*/
|
|
17
|
+
errors?: Record<string, string>;
|
|
13
18
|
}
|
|
14
|
-
export declare function DynamicLineItems({ field, value, onChange, disabled, formValues }: DynamicLineItemsProps): import("react").JSX.Element;
|
|
19
|
+
export declare function DynamicLineItems({ field, value, onChange, disabled, formValues, errors }: DynamicLineItemsProps): import("react").JSX.Element;
|
|
15
20
|
//# sourceMappingURL=dynamic-line-items.d.ts.map
|
|
@@ -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;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;
|
|
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;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAkBD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAgB,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,qBAAqB,+BA4PvH"}
|
|
@@ -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 }) {
|
|
@@ -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":"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"}
|
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,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
|
+
})
|
|
@@ -40,6 +40,13 @@ import { toast } from 'sonner'
|
|
|
40
40
|
import { toastServerError, toastServerSuccess, extractFieldErrors, localizeFieldErrorMap } from './server-error'
|
|
41
41
|
import type { Translate } from './server-error'
|
|
42
42
|
import { validateValues, bagHasErrors } from './validator'
|
|
43
|
+
import {
|
|
44
|
+
clearFieldErrorTree,
|
|
45
|
+
formatFieldErrorsDescription,
|
|
46
|
+
labelsForValidationFields,
|
|
47
|
+
labelForValidationPath,
|
|
48
|
+
lineItemErrorsFor,
|
|
49
|
+
} from './field-validation-ui'
|
|
43
50
|
import { validationCatalog } from './validation-catalog'
|
|
44
51
|
import { useApi } from './api-context'
|
|
45
52
|
import { DynamicIcon } from './dynamic-icon'
|
|
@@ -621,33 +628,43 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
621
628
|
|
|
622
629
|
const updateField = (key: string, value: any) => {
|
|
623
630
|
setFormData((prev: Record<string, any>) => ({ ...prev, [key]: value }))
|
|
624
|
-
setFieldErrors(prev =>
|
|
625
|
-
if (!prev[key]) return prev
|
|
626
|
-
const next = { ...prev }
|
|
627
|
-
delete next[key]
|
|
628
|
-
return next
|
|
629
|
-
})
|
|
631
|
+
setFieldErrors((prev) => clearFieldErrorTree(prev, key))
|
|
630
632
|
}
|
|
631
633
|
|
|
632
634
|
const lang = i18n.language
|
|
633
635
|
const handleActionError = (err: unknown) => {
|
|
636
|
+
const labels = labelsForValidationFields(action.fields, t)
|
|
634
637
|
const localized = localizeActionFieldErrors(err, action.fields, t, lang)
|
|
635
638
|
if (localized) {
|
|
636
|
-
|
|
637
|
-
|
|
639
|
+
// Enrich labels for dotted line-item paths before toasting.
|
|
640
|
+
const withPathLabels: Record<string, string> = {}
|
|
641
|
+
for (const [path, msg] of Object.entries(localized)) {
|
|
642
|
+
withPathLabels[path] = msg
|
|
643
|
+
if (!labels[path]) labels[path] = labelForValidationPath(path, action.fields, t)
|
|
644
|
+
}
|
|
645
|
+
setFieldErrors(withPathLabels)
|
|
646
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
|
|
647
|
+
description: formatFieldErrorsDescription(withPathLabels, action.fields, t),
|
|
648
|
+
})
|
|
638
649
|
return
|
|
639
650
|
}
|
|
640
|
-
toastServerError(err, { t, language: lang })
|
|
651
|
+
toastServerError(err, { t, language: lang, labels })
|
|
641
652
|
}
|
|
642
653
|
|
|
643
654
|
const execute = async () => {
|
|
644
655
|
if (action.fields) {
|
|
645
656
|
const bag = validateValues(action.fields, formData)
|
|
646
657
|
if (bagHasErrors(bag)) {
|
|
647
|
-
const labels
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
658
|
+
const labels = labelsForValidationFields(action.fields, (k, o) => t(k, o))
|
|
659
|
+
// Exact dotted keys (`lines.0.qty`) need path-aware labels.
|
|
660
|
+
for (const path of Object.keys(bag)) {
|
|
661
|
+
if (!labels[path]) labels[path] = labelForValidationPath(path, action.fields, t)
|
|
662
|
+
}
|
|
663
|
+
const next = localizeFieldErrorMap(bag, t, { labels, language: lang })
|
|
664
|
+
setFieldErrors(next)
|
|
665
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
|
|
666
|
+
description: formatFieldErrorsDescription(next, action.fields, t),
|
|
667
|
+
})
|
|
651
668
|
return
|
|
652
669
|
}
|
|
653
670
|
}
|
|
@@ -727,7 +744,14 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
727
744
|
<FieldLabel htmlFor={field.key} required={field.required}>
|
|
728
745
|
{tl(field.label)}
|
|
729
746
|
</FieldLabel>
|
|
730
|
-
{renderField(
|
|
747
|
+
{renderField(
|
|
748
|
+
field,
|
|
749
|
+
formData[field.key],
|
|
750
|
+
(v: any) => updateField(field.key, v),
|
|
751
|
+
formData,
|
|
752
|
+
record,
|
|
753
|
+
fieldErrors,
|
|
754
|
+
)}
|
|
731
755
|
{fieldErrors[field.key] && (
|
|
732
756
|
<p className="text-destructive text-xs mt-1">{fieldErrors[field.key]}</p>
|
|
733
757
|
)}
|
|
@@ -1011,17 +1035,30 @@ function renderField(
|
|
|
1011
1035
|
// then treated as having no resolvable dependency).
|
|
1012
1036
|
formValues?: Record<string, any>,
|
|
1013
1037
|
record?: Record<string, any>,
|
|
1038
|
+
fieldErrors?: Record<string, string>,
|
|
1014
1039
|
) {
|
|
1015
1040
|
// Repeatable line-items group → row grid (value is an array of row objects).
|
|
1016
1041
|
// The header form values flow in so a cell can depend on a header field.
|
|
1017
1042
|
if (isLineItemsField(field)) {
|
|
1018
|
-
return
|
|
1043
|
+
return (
|
|
1044
|
+
<DynamicLineItems
|
|
1045
|
+
field={applyPrefillLock(field)}
|
|
1046
|
+
value={value}
|
|
1047
|
+
onChange={onChange}
|
|
1048
|
+
formValues={formValues}
|
|
1049
|
+
errors={lineItemErrorsFor(field.key, fieldErrors)}
|
|
1050
|
+
/>
|
|
1051
|
+
)
|
|
1019
1052
|
}
|
|
1020
1053
|
// Resolve the widget the same way DynamicForm does (explicit widget wins,
|
|
1021
1054
|
// else inferred from type) so action modals and the standalone form stay in
|
|
1022
1055
|
// lockstep — previously this switch keyed off `field.type` and silently
|
|
1023
1056
|
// dropped `dynamic_select` to a plain text input.
|
|
1024
1057
|
const widget = resolveWidget(field)
|
|
1058
|
+
const invalid = !!(fieldErrors && fieldErrors[field.key])
|
|
1059
|
+
const invalidCls = invalid
|
|
1060
|
+
? 'border-destructive ring-1 ring-destructive/30 focus-visible:ring-destructive'
|
|
1061
|
+
: ''
|
|
1025
1062
|
if (widget === 'dynamic_select') {
|
|
1026
1063
|
// A header-level dynamic_select may itself depend on another header
|
|
1027
1064
|
// field; resolve its filter_value from the form context.
|
|
@@ -1035,6 +1072,7 @@ function renderField(
|
|
|
1035
1072
|
onChange={onChange}
|
|
1036
1073
|
dependsValue={dependsValue}
|
|
1037
1074
|
seedOption={seedOptionFromRecord(field, value, record)}
|
|
1075
|
+
invalid={invalid}
|
|
1038
1076
|
/>
|
|
1039
1077
|
)
|
|
1040
1078
|
}
|
|
@@ -1045,11 +1083,11 @@ function renderField(
|
|
|
1045
1083
|
}
|
|
1046
1084
|
switch (widget) {
|
|
1047
1085
|
case 'textarea':
|
|
1048
|
-
return <Textarea id={field.key} value={value || ''} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)} placeholder={field.placeholder} />
|
|
1086
|
+
return <Textarea id={field.key} value={value || ''} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)} placeholder={field.placeholder} aria-invalid={invalid || undefined} className={invalidCls || undefined} />
|
|
1049
1087
|
case 'select':
|
|
1050
1088
|
return (
|
|
1051
1089
|
<Select value={value || ''} onValueChange={onChange}>
|
|
1052
|
-
<SelectTrigger className=
|
|
1090
|
+
<SelectTrigger className={'w-full' + (invalidCls ? ` ${invalidCls}` : '')} aria-invalid={invalid || undefined}><SelectValue placeholder={field.placeholder || 'Seleccionar...'} /></SelectTrigger>
|
|
1053
1091
|
<SelectContent>
|
|
1054
1092
|
{field.options?.map((opt) => <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>)}
|
|
1055
1093
|
</SelectContent>
|
|
@@ -1058,12 +1096,15 @@ function renderField(
|
|
|
1058
1096
|
case 'switch':
|
|
1059
1097
|
return <Switch id={field.key} checked={!!value} onCheckedChange={onChange} />
|
|
1060
1098
|
case 'number':
|
|
1061
|
-
return <Input id={field.key} type="number" value={value ?? ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
1099
|
+
return <Input id={field.key} type="number" value={value ?? ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
1100
|
+
const n = e.target.valueAsNumber
|
|
1101
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n)
|
|
1102
|
+
}} placeholder={field.placeholder} aria-invalid={invalid || undefined} className={invalidCls || undefined} />
|
|
1062
1103
|
case 'date':
|
|
1063
1104
|
// Modern shadcn Calendar in a Popover (portaled, never clipped by the
|
|
1064
1105
|
// modal) instead of the native, dated, easily-cut <input type=date>.
|
|
1065
1106
|
return <DynamicDateField field={field} value={value} onChange={onChange} />
|
|
1066
1107
|
default:
|
|
1067
|
-
return <Input id={field.key} type={field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text'} value={value || ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)} placeholder={field.placeholder} />
|
|
1108
|
+
return <Input id={field.key} type={field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text'} value={value || ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)} placeholder={field.placeholder} aria-invalid={invalid || undefined} className={invalidCls || undefined} />
|
|
1068
1109
|
}
|
|
1069
1110
|
}
|
|
@@ -864,11 +864,14 @@ export function DynamicRecordDialog({
|
|
|
864
864
|
for (const f of visible) labels[f.key] = f.label
|
|
865
865
|
const next = localizeFieldErrorMap(bag, t, { labels })
|
|
866
866
|
setFieldErrors(next)
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
867
|
+
const description = Object.entries(next)
|
|
868
|
+
.map(([k, msg]) => {
|
|
869
|
+
const label = labels[k] || labelForKey(k)
|
|
870
|
+
return msg.toLowerCase().startsWith(String(label).toLowerCase())
|
|
871
|
+
? msg
|
|
872
|
+
: `${label}: ${msg}`
|
|
873
|
+
})
|
|
874
|
+
.join(' · ')
|
|
872
875
|
toast.error(
|
|
873
876
|
t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }),
|
|
874
877
|
description ? { description } : undefined,
|
package/src/dynamic-form.tsx
CHANGED
|
@@ -420,7 +420,10 @@ function FieldRenderer({
|
|
|
420
420
|
case 'switch':
|
|
421
421
|
return <Switch id={field.key} checked={!!value} onCheckedChange={onChange} />
|
|
422
422
|
case 'number':
|
|
423
|
-
return <Input id={field.key} type="number" value={value ?? ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
423
|
+
return <Input id={field.key} type="number" value={value ?? ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
424
|
+
const n = e.target.valueAsNumber
|
|
425
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n)
|
|
426
|
+
}} placeholder={field.placeholder} />
|
|
424
427
|
case 'date':
|
|
425
428
|
return <DynamicDateField field={field} value={value} onChange={onChange} />
|
|
426
429
|
default:
|
|
@@ -463,9 +466,14 @@ function ScannableInput({
|
|
|
463
466
|
id={field.key}
|
|
464
467
|
type={type}
|
|
465
468
|
value={value ?? ''}
|
|
466
|
-
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
470
|
+
if (type !== 'number') {
|
|
471
|
+
onChange(e.target.value)
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
const n = e.target.valueAsNumber
|
|
475
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n)
|
|
476
|
+
}}
|
|
469
477
|
placeholder={field.placeholder}
|
|
470
478
|
/>
|
|
471
479
|
)
|
|
@@ -47,6 +47,11 @@ export interface DynamicLineItemsProps {
|
|
|
47
47
|
* (e.g. `source_warehouse_id`), not just a sibling cell on the same row.
|
|
48
48
|
*/
|
|
49
49
|
formValues?: Record<string, any>
|
|
50
|
+
/**
|
|
51
|
+
* Localized validation messages keyed as `rowIndex.columnKey` (e.g.
|
|
52
|
+
* `0.unit_price`). Painted as destructive borders + under-cell text.
|
|
53
|
+
*/
|
|
54
|
+
errors?: Record<string, string>
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
const fmtNumber = (n: number): string =>
|
|
@@ -65,9 +70,10 @@ function emptyRow(itemFields: ActionFieldDef[]): Record<string, any> {
|
|
|
65
70
|
return row
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
export function DynamicLineItems({ field, value, onChange, disabled = false, formValues }: DynamicLineItemsProps) {
|
|
73
|
+
export function DynamicLineItems({ field, value, onChange, disabled = false, formValues, errors }: DynamicLineItemsProps) {
|
|
69
74
|
const itemFields = getItemFields(field)
|
|
70
75
|
const rows: any[] = Array.isArray(value) ? value : []
|
|
76
|
+
const errMap = errors ?? {}
|
|
71
77
|
|
|
72
78
|
// `lock_rows` fixes the row set: no add-row button, no per-row delete. Rows
|
|
73
79
|
// stay editable cell-by-cell. Snake_case is what the kernel serves; tolerate
|
|
@@ -149,7 +155,9 @@ export function DynamicLineItems({ field, value, onChange, disabled = false, for
|
|
|
149
155
|
)}
|
|
150
156
|
</div>
|
|
151
157
|
<div className="grid gap-2.5">
|
|
152
|
-
{itemFields.map((col) =>
|
|
158
|
+
{itemFields.map((col) => {
|
|
159
|
+
const cellErr = errMap[`${idx}.${col.key}`]
|
|
160
|
+
return (
|
|
153
161
|
<div key={col.key} className="grid gap-1">
|
|
154
162
|
<span className="text-xs font-medium">
|
|
155
163
|
{col.label}
|
|
@@ -164,9 +172,14 @@ export function DynamicLineItems({ field, value, onChange, disabled = false, for
|
|
|
164
172
|
disabled={disabled}
|
|
165
173
|
formValues={formValues}
|
|
166
174
|
rowValues={row}
|
|
175
|
+
invalid={!!cellErr}
|
|
167
176
|
/>
|
|
177
|
+
{cellErr && (
|
|
178
|
+
<p className="text-destructive text-xs">{cellErr}</p>
|
|
179
|
+
)}
|
|
168
180
|
</div>
|
|
169
|
-
|
|
181
|
+
)
|
|
182
|
+
})}
|
|
170
183
|
</div>
|
|
171
184
|
</div>
|
|
172
185
|
))}
|
|
@@ -219,7 +232,9 @@ export function DynamicLineItems({ field, value, onChange, disabled = false, for
|
|
|
219
232
|
)}
|
|
220
233
|
{rows.map((row, idx) => (
|
|
221
234
|
<tr key={idx} className="border-t align-top">
|
|
222
|
-
{itemFields.map((col) =>
|
|
235
|
+
{itemFields.map((col) => {
|
|
236
|
+
const cellErr = errMap[`${idx}.${col.key}`]
|
|
237
|
+
return (
|
|
223
238
|
<td key={col.key} className="px-2 py-1.5">
|
|
224
239
|
<CellRenderer
|
|
225
240
|
field={col}
|
|
@@ -228,9 +243,14 @@ export function DynamicLineItems({ field, value, onChange, disabled = false, for
|
|
|
228
243
|
disabled={disabled}
|
|
229
244
|
formValues={formValues}
|
|
230
245
|
rowValues={row}
|
|
246
|
+
invalid={!!cellErr}
|
|
231
247
|
/>
|
|
248
|
+
{cellErr && (
|
|
249
|
+
<p className="text-destructive mt-1 text-xs">{cellErr}</p>
|
|
250
|
+
)}
|
|
232
251
|
</td>
|
|
233
|
-
|
|
252
|
+
)
|
|
253
|
+
})}
|
|
234
254
|
{!lockRows && (
|
|
235
255
|
<td className="px-2 py-1.5 text-center">
|
|
236
256
|
<Button
|
|
@@ -342,14 +362,19 @@ interface CellRendererProps {
|
|
|
342
362
|
formValues?: Record<string, any>
|
|
343
363
|
/** This row's values — for resolving a cell's `dependsOn` to a sibling cell. */
|
|
344
364
|
rowValues?: Record<string, any>
|
|
365
|
+
/** Paint the control as invalid (destructive border). */
|
|
366
|
+
invalid?: boolean
|
|
345
367
|
}
|
|
346
368
|
|
|
347
369
|
// Per-cell widget. Mirrors the flat FieldRenderer in dynamic-form.tsx but
|
|
348
370
|
// without the per-field Label (the column header is the label) and sized for a
|
|
349
371
|
// table cell. Nested line-items inside a row are not supported (a row column is
|
|
350
372
|
// a scalar widget).
|
|
351
|
-
function CellRenderer({ field, value, onChange, disabled, formValues, rowValues }: CellRendererProps) {
|
|
373
|
+
function CellRenderer({ field, value, onChange, disabled, formValues, rowValues, invalid }: CellRendererProps) {
|
|
352
374
|
const widget = resolveWidget(field)
|
|
375
|
+
const invalidCls = invalid
|
|
376
|
+
? 'border-destructive ring-1 ring-destructive/30 focus-visible:ring-destructive'
|
|
377
|
+
: ''
|
|
353
378
|
// Per-field read-only: a column locked by a PrefillSpec.lock (e.g. the
|
|
354
379
|
// "ordered" / "already received" progress columns of a receive-goods modal)
|
|
355
380
|
// renders disabled so it shows context without being editable. Tolerates the
|
|
@@ -384,7 +409,16 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
384
409
|
// Async searchable picker per row cell — e.g. the account_id column of a
|
|
385
410
|
// journal entry's debit/credit lines. Same widget as the flat form.
|
|
386
411
|
if (widget === 'dynamic_select') {
|
|
387
|
-
return
|
|
412
|
+
return (
|
|
413
|
+
<DynamicSelectField
|
|
414
|
+
field={field}
|
|
415
|
+
value={value}
|
|
416
|
+
onChange={onChange}
|
|
417
|
+
dependsValue={dependsValue}
|
|
418
|
+
readonly={ro}
|
|
419
|
+
invalid={invalid}
|
|
420
|
+
/>
|
|
421
|
+
)
|
|
388
422
|
}
|
|
389
423
|
if (widget === 'select' && (field.ref || getOptionsConfig(field)?.source)) {
|
|
390
424
|
return (
|
|
@@ -408,6 +442,8 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
408
442
|
placeholder={field.placeholder}
|
|
409
443
|
disabled={off}
|
|
410
444
|
rows={2}
|
|
445
|
+
aria-invalid={invalid || undefined}
|
|
446
|
+
className={invalidCls || undefined}
|
|
411
447
|
/>
|
|
412
448
|
)
|
|
413
449
|
case 'color':
|
|
@@ -417,6 +453,8 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
417
453
|
value={value || '#000000'}
|
|
418
454
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
419
455
|
disabled={off}
|
|
456
|
+
aria-invalid={invalid || undefined}
|
|
457
|
+
className={invalidCls || undefined}
|
|
420
458
|
/>
|
|
421
459
|
)
|
|
422
460
|
case 'select': {
|
|
@@ -425,7 +463,7 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
425
463
|
if (effectiveOptions && effectiveOptions.length === 0) return null
|
|
426
464
|
return (
|
|
427
465
|
<Select value={value || ''} onValueChange={onChange} disabled={off}>
|
|
428
|
-
<SelectTrigger className=
|
|
466
|
+
<SelectTrigger className={'w-full' + (invalidCls ? ` ${invalidCls}` : '')} aria-invalid={invalid || undefined}>
|
|
429
467
|
<SelectValue placeholder={field.placeholder || 'Seleccionar...'} />
|
|
430
468
|
</SelectTrigger>
|
|
431
469
|
<SelectContent>
|
|
@@ -445,9 +483,16 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
445
483
|
<Input
|
|
446
484
|
type="number"
|
|
447
485
|
value={value ?? ''}
|
|
448
|
-
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
486
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
487
|
+
const n = e.target.valueAsNumber
|
|
488
|
+
// `0 || ''` would wipe a legitimate zero (unit_price=0)
|
|
489
|
+
// and then required-validation fires with nothing marked.
|
|
490
|
+
onChange(e.target.value === '' || !Number.isFinite(n) ? '' : n)
|
|
491
|
+
}}
|
|
449
492
|
placeholder={field.placeholder}
|
|
450
493
|
disabled={off}
|
|
494
|
+
aria-invalid={invalid || undefined}
|
|
495
|
+
className={invalidCls || undefined}
|
|
451
496
|
/>
|
|
452
497
|
)
|
|
453
498
|
case 'date':
|
|
@@ -457,6 +502,8 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
457
502
|
value={value || ''}
|
|
458
503
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
459
504
|
disabled={off}
|
|
505
|
+
aria-invalid={invalid || undefined}
|
|
506
|
+
className={invalidCls || undefined}
|
|
460
507
|
/>
|
|
461
508
|
)
|
|
462
509
|
default:
|
|
@@ -467,6 +514,8 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
467
514
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
468
515
|
placeholder={field.placeholder}
|
|
469
516
|
disabled={off}
|
|
517
|
+
aria-invalid={invalid || undefined}
|
|
518
|
+
className={invalidCls || undefined}
|
|
470
519
|
/>
|
|
471
520
|
)
|
|
472
521
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Shared helpers for painting + toasting client/server field validation so
|
|
2
|
+
// action modals and DynamicRecordDialog stay in lockstep.
|
|
3
|
+
import type { ActionFieldDef } from './types'
|
|
4
|
+
import type { Translate } from './server-error'
|
|
5
|
+
|
|
6
|
+
function itemFieldsOf(field: ActionFieldDef): ActionFieldDef[] {
|
|
7
|
+
const raw = field.itemFields ?? (field as { item_fields?: ActionFieldDef[] }).item_fields
|
|
8
|
+
return Array.isArray(raw) ? raw : []
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function translateLabel(raw: string | undefined, t?: Translate): string {
|
|
12
|
+
if (!raw) return ''
|
|
13
|
+
if (!t) return raw
|
|
14
|
+
return t(raw, { defaultValue: raw })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function humanizeToken(key: string): string {
|
|
18
|
+
return key.replace(/[._-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a validation path (`customer_id`, `lines.0.unit_price`) to a
|
|
23
|
+
* human label using the action/modal field schema. Nested line-item paths
|
|
24
|
+
* become "Renglones → Precio unitario (fila 1)".
|
|
25
|
+
*/
|
|
26
|
+
export function labelForValidationPath(
|
|
27
|
+
path: string,
|
|
28
|
+
fields: readonly ActionFieldDef[] | undefined,
|
|
29
|
+
t?: Translate,
|
|
30
|
+
): string {
|
|
31
|
+
if (!path) return ''
|
|
32
|
+
const parts = path.split('.')
|
|
33
|
+
if (!fields?.length) return humanizeToken(path)
|
|
34
|
+
|
|
35
|
+
let cursor: readonly ActionFieldDef[] | undefined = fields
|
|
36
|
+
const bits: string[] = []
|
|
37
|
+
let i = 0
|
|
38
|
+
while (i < parts.length && cursor) {
|
|
39
|
+
const seg = parts[i]!
|
|
40
|
+
if (/^\d+$/.test(seg)) {
|
|
41
|
+
const row = Number(seg) + 1
|
|
42
|
+
const nextKey = parts[i + 1]
|
|
43
|
+
if (nextKey && cursor) {
|
|
44
|
+
const col = cursor.find((f) => f.key === nextKey)
|
|
45
|
+
const colLabel = translateLabel(col?.label, t) || humanizeToken(nextKey)
|
|
46
|
+
bits.push(`${colLabel} (fila ${row})`)
|
|
47
|
+
i += 2
|
|
48
|
+
cursor = undefined
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
bits.push(`fila ${row}`)
|
|
52
|
+
i += 1
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
const field = cursor.find((f) => f.key === seg)
|
|
56
|
+
if (!field) {
|
|
57
|
+
bits.push(humanizeToken(parts.slice(i).join('.')))
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
bits.push(translateLabel(field.label, t) || humanizeToken(seg))
|
|
61
|
+
const nested = itemFieldsOf(field)
|
|
62
|
+
cursor = nested.length ? nested : undefined
|
|
63
|
+
i += 1
|
|
64
|
+
if (!cursor && i < parts.length) {
|
|
65
|
+
bits.push(humanizeToken(parts.slice(i).join('.')))
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return bits.join(' → ')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Build `{path: label}` covering scalar fields and `parent.N.child` templates
|
|
73
|
+
* used by localizeFieldErrorMap (exact keys win when present). */
|
|
74
|
+
export function labelsForValidationFields(
|
|
75
|
+
fields: readonly ActionFieldDef[] | undefined,
|
|
76
|
+
t?: Translate,
|
|
77
|
+
): Record<string, string> {
|
|
78
|
+
const out: Record<string, string> = {}
|
|
79
|
+
if (!fields) return out
|
|
80
|
+
for (const f of fields) {
|
|
81
|
+
if (!f.key) continue
|
|
82
|
+
out[f.key] = translateLabel(f.label, t) || humanizeToken(f.key)
|
|
83
|
+
for (const child of itemFieldsOf(f)) {
|
|
84
|
+
if (!child.key) continue
|
|
85
|
+
out[`${f.key}.${child.key}`] = translateLabel(child.label, t) || humanizeToken(child.key)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Toast description: one "Label: message" line per error. Re-resolves labels
|
|
93
|
+
* for dotted line-item paths so operators see e.g.
|
|
94
|
+
* "Precio unitario (fila 1): es obligatorio" instead of a bare headline.
|
|
95
|
+
*/
|
|
96
|
+
export function formatFieldErrorsDescription(
|
|
97
|
+
errors: Record<string, string>,
|
|
98
|
+
fields?: readonly ActionFieldDef[],
|
|
99
|
+
t?: Translate,
|
|
100
|
+
): string | undefined {
|
|
101
|
+
const entries = Object.entries(errors)
|
|
102
|
+
if (!entries.length) return undefined
|
|
103
|
+
return entries
|
|
104
|
+
.map(([path, msg]) => {
|
|
105
|
+
const label = labelForValidationPath(path, fields, t)
|
|
106
|
+
// localizeFieldIssue already prefixes "{{label}}: …" — avoid
|
|
107
|
+
// "Label: Label: msg" when the message already starts with the label.
|
|
108
|
+
if (label && msg.toLowerCase().startsWith(label.toLowerCase())) return msg
|
|
109
|
+
return label ? `${label}: ${msg}` : msg
|
|
110
|
+
})
|
|
111
|
+
.join(' · ')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Drop `key` and every `key.*` / `key.N.*` entry after the operator edits that field. */
|
|
115
|
+
export function clearFieldErrorTree(
|
|
116
|
+
prev: Record<string, string>,
|
|
117
|
+
key: string,
|
|
118
|
+
): Record<string, string> {
|
|
119
|
+
if (!prev[key] && !Object.keys(prev).some((k) => k.startsWith(`${key}.`))) return prev
|
|
120
|
+
const next: Record<string, string> = {}
|
|
121
|
+
for (const [k, v] of Object.entries(prev)) {
|
|
122
|
+
if (k === key || k.startsWith(`${key}.`)) continue
|
|
123
|
+
next[k] = v
|
|
124
|
+
}
|
|
125
|
+
return next
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Slice `parent.0.child` errors into `{ "0.child": msg }` for DynamicLineItems. */
|
|
129
|
+
export function lineItemErrorsFor(
|
|
130
|
+
parentKey: string,
|
|
131
|
+
errors: Record<string, string> | undefined,
|
|
132
|
+
): Record<string, string> {
|
|
133
|
+
if (!errors) return {}
|
|
134
|
+
const prefix = `${parentKey}.`
|
|
135
|
+
const out: Record<string, string> = {}
|
|
136
|
+
for (const [k, v] of Object.entries(errors)) {
|
|
137
|
+
if (k.startsWith(prefix)) out[k.slice(prefix.length)] = v
|
|
138
|
+
}
|
|
139
|
+
return out
|
|
140
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,13 @@ export {
|
|
|
40
40
|
type ValidationSpec,
|
|
41
41
|
} from './validator'
|
|
42
42
|
export { VALIDATION_CATALOGS, validationCatalog, validationMessageKey } from './validation-catalog'
|
|
43
|
+
export {
|
|
44
|
+
labelForValidationPath,
|
|
45
|
+
labelsForValidationFields,
|
|
46
|
+
formatFieldErrorsDescription,
|
|
47
|
+
clearFieldErrorTree,
|
|
48
|
+
lineItemErrorsFor,
|
|
49
|
+
} from './field-validation-ui'
|
|
43
50
|
export * from './dynamic-table'
|
|
44
51
|
export {
|
|
45
52
|
DynamicKanban,
|