@asteby/metacore-runtime-react 33.1.0 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 34.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [5c51b7f]
|
|
8
|
+
- @asteby/metacore-ui@2.16.0
|
|
9
|
+
|
|
10
|
+
## 33.1.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 921a74c: Crear una categoría (u otro registro) desde el "+" de un modal ya no
|
|
15
|
+
valida el formulario de atrás. Cada diálogo tiene su propio `form` id, y
|
|
16
|
+
el toast de validación lista los mensajes del validator.
|
|
17
|
+
|
|
3
18
|
## 33.1.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
|
@@ -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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"}
|
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
// flows through <ApiProvider> from runtime-react. Host-specific runtime values —
|
|
11
11
|
// the image-url resolver and the org IANA timezone — are passed as props so the
|
|
12
12
|
// SDK stays transport- and host-agnostic.
|
|
13
|
-
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { createContext, useCallback, useContext, useEffect, useId, useRef, useState } from 'react';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
/** Model key of the open create/edit dialog — used to hide "+" on self-FK pickers
|
|
16
16
|
* (e.g. Customer.parent_id) so they don't nest another "Crear Cliente" modal. */
|
|
@@ -284,9 +284,19 @@ export function stripHiddenFieldValues(values, fields, mode) {
|
|
|
284
284
|
}
|
|
285
285
|
return out;
|
|
286
286
|
}
|
|
287
|
+
function toastValidationFailed(t, lang, localized) {
|
|
288
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
|
|
289
|
+
description: Object.values(localized).filter(Boolean).join('\n'),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
287
292
|
export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, lockedFields, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl = identityImageUrl, timeZone, currency, onChange, }) {
|
|
288
293
|
const api = useApi();
|
|
289
294
|
const { t, i18n } = useTranslation();
|
|
295
|
+
// Unique per dialog instance. The footer submit lives OUTSIDE <form>, so
|
|
296
|
+
// it binds via `form={id}`. A hardcoded id made nested create (product +
|
|
297
|
+
// "Crear categoría") submit the PARENT form — toast "Revisa los campos
|
|
298
|
+
// marcados" with no marks on the inner modal.
|
|
299
|
+
const formId = useId();
|
|
290
300
|
const [modalMeta, setModalMeta] = useState(schema ? schema : null);
|
|
291
301
|
const [relations, setRelations] = useState([]);
|
|
292
302
|
const [record, setRecord] = useState(null);
|
|
@@ -493,8 +503,9 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
493
503
|
const labels = {};
|
|
494
504
|
for (const f of modalMeta?.fields ?? [])
|
|
495
505
|
labels[f.key] = labelForKey(f.key);
|
|
496
|
-
|
|
497
|
-
|
|
506
|
+
const localized = localizeFieldErrorMap(map, t, { labels, language: lang });
|
|
507
|
+
setFieldErrors(localized);
|
|
508
|
+
toastValidationFailed(t, lang, localized);
|
|
498
509
|
return;
|
|
499
510
|
}
|
|
500
511
|
toastServerError(err, { t, language: lang, fallback: t('dynamic.save_error', { defaultValue: 'No se pudo guardar' }) });
|
|
@@ -515,8 +526,9 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
515
526
|
const labels = {};
|
|
516
527
|
for (const f of visible)
|
|
517
528
|
labels[f.key] = t(f.label, { defaultValue: f.label });
|
|
518
|
-
|
|
519
|
-
|
|
529
|
+
const localized = localizeFieldErrorMap(bag, t, { labels, language: lang });
|
|
530
|
+
setFieldErrors(localized);
|
|
531
|
+
toastValidationFailed(t, lang, localized);
|
|
520
532
|
return;
|
|
521
533
|
}
|
|
522
534
|
}
|
|
@@ -642,15 +654,16 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
642
654
|
const labels = {};
|
|
643
655
|
for (const f of step?.fields ?? [])
|
|
644
656
|
labels[f.key] = t(f.label, { defaultValue: f.label });
|
|
645
|
-
|
|
646
|
-
|
|
657
|
+
const localized = localizeFieldErrorMap(bag, t, { labels, language: lang });
|
|
658
|
+
setFieldErrors(localized);
|
|
659
|
+
toastValidationFailed(t, lang, localized);
|
|
647
660
|
return;
|
|
648
661
|
}
|
|
649
662
|
setFieldErrors({});
|
|
650
663
|
setStepIndex(Math.min(clampedStep + 1, groups.length - 1));
|
|
651
664
|
};
|
|
652
665
|
const goBackStep = () => setStepIndex(Math.max(clampedStep - 1, 0));
|
|
653
|
-
return (_jsx(RecordDialogModelContext.Provider, { value: model, children: _jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90dvh] flex flex-col p-0 gap-0 overflow-hidden", style: { maxHeight: '90dvh' }, children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsx(ImageUrlContext.Provider, { value: getImageUrl, children: _jsx(TimeZoneContext.Provider, { value: timeZone, children: _jsxs(CurrencyContext.Provider, { value: currency, children: [_jsxs("form", { id:
|
|
666
|
+
return (_jsx(RecordDialogModelContext.Provider, { value: model, children: _jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90dvh] flex flex-col p-0 gap-0 overflow-hidden", style: { maxHeight: '90dvh' }, children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsx(ImageUrlContext.Provider, { value: getImageUrl, children: _jsx(TimeZoneContext.Provider, { value: timeZone, children: _jsxs(CurrencyContext.Provider, { value: currency, children: [_jsxs("form", { id: formId, onSubmit: handleSubmit, className: "grid gap-y-4", children: [isSteps && (_jsx(WizardProgress, { groups: groups, stepIndex: clampedStep })), (isSteps ? [groups[clampedStep]] : groups).map(group => (_jsx(FieldSection, { group: group, children: _jsx("div", { className: "grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2", children: renderFields(group.fields) }) }, group.key))), record?.external_url && (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2", children: _jsx("div", { className: "sm:col-span-2 min-w-0", children: _jsxs("a", { href: record.external_url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Ver en ", record.external_provider ?? 'proveedor externo'] }) }) }))] }), !isCreate && record && relations.length > 0 && (_jsx("div", { className: "mt-6", children: _jsx(DynamicRelations, { record: record, relations: relations, lineSubtable: true, embedOnly: true, canCreate: mode === 'edit', canEdit: mode === 'edit', canDelete: mode === 'edit', onChange: handleChildChange }) }))] }) }) }) })) : null }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0 sm:justify-between", children: [isView && onOpenFullPage ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "text-muted-foreground", onClick: () => { onOpenChange(false); onOpenFullPage(); }, children: [_jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }), "Ver p\u00E1gina completa"] })) : _jsx("span", {}), _jsxs("div", { className: "flex items-center gap-2", children: [isSteps && clampedStep > 0 ? (_jsx(Button, { variant: "outline", onClick: goBackStep, disabled: saving || deleting, children: "Anterior" })) : (_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || deleting, children: config.cancelLabel })), isView && onDelete && (_jsxs(Button, { variant: "destructive", onClick: handleDelete, disabled: deleting || loading, children: [deleting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), deleting ? 'Eliminando...' : 'Eliminar'] })), isView && onEdit && (_jsx(Button, { onClick: onEdit, disabled: deleting || loading, children: "Editar" })), isEditable && isSteps && !isLastStep && (_jsx(Button, { type: "button", onClick: goNextStep, disabled: saving || loading, children: "Siguiente" })), isEditable && (!isSteps || isLastStep) && (_jsxs(Button, { type: "submit", form: formId, disabled: saving || loading, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? config.submittingLabel : config.submitLabel] }))] })] })] }) }) }));
|
|
654
667
|
}
|
|
655
668
|
function LoadingSkeleton() {
|
|
656
669
|
return (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3.5 w-24" }), _jsx(Skeleton, { className: "h-9 w-full" })] }, i))) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "34.0.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"sonner": ">=1.7",
|
|
39
39
|
"zustand": ">=5",
|
|
40
40
|
"@asteby/metacore-sdk": "^3.6.0",
|
|
41
|
-
"@asteby/metacore-ui": "^2.
|
|
41
|
+
"@asteby/metacore-ui": "^2.16.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@tanstack/react-router": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"vitest": "^4.0.0",
|
|
69
69
|
"zustand": "^5.0.0",
|
|
70
70
|
"@asteby/metacore-sdk": "3.6.0",
|
|
71
|
-
"@asteby/metacore-ui": "2.
|
|
71
|
+
"@asteby/metacore-ui": "2.16.0"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
//
|
|
3
|
+
// Nested create (product modal + "Crear categoría" from the "+" picker) used
|
|
4
|
+
// to share id="dynamic-record-form". The inner footer's submit lives outside
|
|
5
|
+
// <form> and binds via form={id}, so HTML sent the click to the PARENT form
|
|
6
|
+
// — toast "Revisa los campos marcados" with no marks on the category fields.
|
|
7
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
8
|
+
import { cleanup, render } 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
|
+
function makeI18n() {
|
|
18
|
+
const inst = i18next.createInstance()
|
|
19
|
+
void inst.init({
|
|
20
|
+
lng: 'es',
|
|
21
|
+
fallbackLng: 'es',
|
|
22
|
+
react: { useSuspense: false },
|
|
23
|
+
resources: { es: { translation: {} } },
|
|
24
|
+
})
|
|
25
|
+
return inst
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const api = {
|
|
29
|
+
get: vi.fn(async () => ({ data: { data: {} } })),
|
|
30
|
+
post: async () => ({ data: { success: true } }),
|
|
31
|
+
put: async () => ({ data: { success: true } }),
|
|
32
|
+
delete: async () => ({ data: { success: true } }),
|
|
33
|
+
} as never
|
|
34
|
+
|
|
35
|
+
function schema(title: string) {
|
|
36
|
+
return {
|
|
37
|
+
title,
|
|
38
|
+
fields: [{ key: 'name', label: 'Nombre', type: 'text', required: true }],
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('DynamicRecordDialog nested create — unique form ids', () => {
|
|
43
|
+
it('gives each open dialog its own form id so Crear does not submit the parent', () => {
|
|
44
|
+
render(
|
|
45
|
+
<I18nextProvider i18n={makeI18n()}>
|
|
46
|
+
<ApiProvider client={api}>
|
|
47
|
+
<DynamicRecordDialog
|
|
48
|
+
open
|
|
49
|
+
onOpenChange={() => {}}
|
|
50
|
+
mode="create"
|
|
51
|
+
model="Product"
|
|
52
|
+
schema={schema('Crear Producto') as never}
|
|
53
|
+
/>
|
|
54
|
+
<DynamicRecordDialog
|
|
55
|
+
open
|
|
56
|
+
onOpenChange={() => {}}
|
|
57
|
+
mode="create"
|
|
58
|
+
model="Category"
|
|
59
|
+
schema={schema('Crear Categoría') as never}
|
|
60
|
+
/>
|
|
61
|
+
</ApiProvider>
|
|
62
|
+
</I18nextProvider>,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const forms = [...document.querySelectorAll('form')]
|
|
66
|
+
expect(forms).toHaveLength(2)
|
|
67
|
+
const ids = forms.map(f => f.id)
|
|
68
|
+
expect(ids[0]).toBeTruthy()
|
|
69
|
+
expect(ids[1]).toBeTruthy()
|
|
70
|
+
expect(ids[0]).not.toBe(ids[1])
|
|
71
|
+
expect(ids).not.toContain('dynamic-record-form')
|
|
72
|
+
|
|
73
|
+
const submits = [...document.querySelectorAll('button[type="submit"]')]
|
|
74
|
+
expect(submits).toHaveLength(2)
|
|
75
|
+
const bound = submits.map(b => b.getAttribute('form'))
|
|
76
|
+
expect(new Set(bound)).toEqual(new Set(ids))
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// flows through <ApiProvider> from runtime-react. Host-specific runtime values —
|
|
10
10
|
// the image-url resolver and the org IANA timezone — are passed as props so the
|
|
11
11
|
// SDK stays transport- and host-agnostic.
|
|
12
|
-
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
|
12
|
+
import { createContext, useCallback, useContext, useEffect, useId, useRef, useState } from 'react'
|
|
13
13
|
import { useTranslation } from 'react-i18next'
|
|
14
14
|
import type { ModelSchema } from './types'
|
|
15
15
|
|
|
@@ -53,7 +53,7 @@ import { es } from 'date-fns/locale'
|
|
|
53
53
|
import { ExternalLink, Loader2, CalendarIcon, ChevronDown, Check, Upload, X as XIcon, ScanLine } from 'lucide-react'
|
|
54
54
|
import { BarcodeScanner } from '../barcode-scanner'
|
|
55
55
|
import { useApi } from '../api-context'
|
|
56
|
-
import { toastServerError, extractFieldErrors, localizeFieldErrorMap } from '../server-error'
|
|
56
|
+
import { toastServerError, extractFieldErrors, localizeFieldErrorMap, type Translate } from '../server-error'
|
|
57
57
|
import { validateValues, bagHasErrors } from '../validator'
|
|
58
58
|
import { validationCatalog } from '../validation-catalog'
|
|
59
59
|
import { DynamicSelectField, OptionLead, OptionThumb } from '../dynamic-select-field'
|
|
@@ -543,6 +543,12 @@ export function stripHiddenFieldValues(
|
|
|
543
543
|
return out
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
function toastValidationFailed(t: Translate, lang: string, localized: Record<string, string>) {
|
|
547
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
|
|
548
|
+
description: Object.values(localized).filter(Boolean).join('\n'),
|
|
549
|
+
})
|
|
550
|
+
}
|
|
551
|
+
|
|
546
552
|
export function DynamicRecordDialog({
|
|
547
553
|
open,
|
|
548
554
|
onOpenChange,
|
|
@@ -567,6 +573,11 @@ export function DynamicRecordDialog({
|
|
|
567
573
|
}: DynamicRecordDialogProps) {
|
|
568
574
|
const api = useApi()
|
|
569
575
|
const { t, i18n } = useTranslation()
|
|
576
|
+
// Unique per dialog instance. The footer submit lives OUTSIDE <form>, so
|
|
577
|
+
// it binds via `form={id}`. A hardcoded id made nested create (product +
|
|
578
|
+
// "Crear categoría") submit the PARENT form — toast "Revisa los campos
|
|
579
|
+
// marcados" with no marks on the inner modal.
|
|
580
|
+
const formId = useId()
|
|
570
581
|
const [modalMeta, setModalMeta] = useState<ModalMetadata | null>(
|
|
571
582
|
schema ? (schema as ModalMetadata) : null,
|
|
572
583
|
)
|
|
@@ -781,8 +792,9 @@ export function DynamicRecordDialog({
|
|
|
781
792
|
if (map) {
|
|
782
793
|
const labels: Record<string, string> = {}
|
|
783
794
|
for (const f of modalMeta?.fields ?? []) labels[f.key] = labelForKey(f.key)
|
|
784
|
-
|
|
785
|
-
|
|
795
|
+
const localized = localizeFieldErrorMap(map, t, { labels, language: lang })
|
|
796
|
+
setFieldErrors(localized)
|
|
797
|
+
toastValidationFailed(t, lang, localized)
|
|
786
798
|
return
|
|
787
799
|
}
|
|
788
800
|
toastServerError(err, { t, language: lang, fallback: t('dynamic.save_error', { defaultValue: 'No se pudo guardar' }) })
|
|
@@ -803,8 +815,9 @@ export function DynamicRecordDialog({
|
|
|
803
815
|
if (bagHasErrors(bag)) {
|
|
804
816
|
const labels: Record<string, string> = {}
|
|
805
817
|
for (const f of visible) labels[f.key] = t(f.label, { defaultValue: f.label })
|
|
806
|
-
|
|
807
|
-
|
|
818
|
+
const localized = localizeFieldErrorMap(bag, t, { labels, language: lang })
|
|
819
|
+
setFieldErrors(localized)
|
|
820
|
+
toastValidationFailed(t, lang, localized)
|
|
808
821
|
return
|
|
809
822
|
}
|
|
810
823
|
}
|
|
@@ -950,8 +963,9 @@ export function DynamicRecordDialog({
|
|
|
950
963
|
if (bagHasErrors(bag)) {
|
|
951
964
|
const labels: Record<string, string> = {}
|
|
952
965
|
for (const f of step?.fields ?? []) labels[f.key] = t(f.label, { defaultValue: f.label })
|
|
953
|
-
|
|
954
|
-
|
|
966
|
+
const localized = localizeFieldErrorMap(bag, t, { labels, language: lang })
|
|
967
|
+
setFieldErrors(localized)
|
|
968
|
+
toastValidationFailed(t, lang, localized)
|
|
955
969
|
return
|
|
956
970
|
}
|
|
957
971
|
setFieldErrors({})
|
|
@@ -984,7 +998,7 @@ export function DynamicRecordDialog({
|
|
|
984
998
|
cell `min-w-0` so a long select/input value can't
|
|
985
999
|
blow the two columns past the dialog width. */}
|
|
986
1000
|
<form
|
|
987
|
-
id=
|
|
1001
|
+
id={formId}
|
|
988
1002
|
onSubmit={handleSubmit}
|
|
989
1003
|
className="grid gap-y-4"
|
|
990
1004
|
>
|
|
@@ -1094,7 +1108,7 @@ export function DynamicRecordDialog({
|
|
|
1094
1108
|
{isEditable && (!isSteps || isLastStep) && (
|
|
1095
1109
|
<Button
|
|
1096
1110
|
type="submit"
|
|
1097
|
-
form=
|
|
1111
|
+
form={formId}
|
|
1098
1112
|
disabled={saving || loading}
|
|
1099
1113
|
>
|
|
1100
1114
|
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|