@asteby/metacore-runtime-react 46.2.1 → 46.2.3
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.js +1 -1
- package/dist/dynamic-date-field.d.ts +3 -1
- package/dist/dynamic-date-field.d.ts.map +1 -1
- package/dist/dynamic-date-field.js +3 -2
- package/dist/dynamic-relation.js +8 -1
- package/package.json +1 -1
- package/src/action-modal-dispatcher.tsx +1 -1
- package/src/dynamic-date-field.tsx +8 -2
- package/src/dynamic-relation.tsx +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 46.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ab15b46: El campo de fecha de un diálogo de acción marca aria-invalid cuando el servidor rechaza ese campo.
|
|
8
|
+
|
|
9
|
+
## 46.2.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 97f77eb: Una relación muchos-a-muchos distingue «sin permiso» y un error de carga de una lista vacía, igual que la subtabla de uno-a-muchos.
|
|
14
|
+
|
|
3
15
|
## 46.2.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -996,7 +996,7 @@ formValues, record, fieldErrors) {
|
|
|
996
996
|
case 'date':
|
|
997
997
|
// Modern shadcn Calendar in a Popover (portaled, never clipped by the
|
|
998
998
|
// modal) instead of the native, dated, easily-cut <input type=date>.
|
|
999
|
-
return _jsx(DynamicDateField, { field: field, value: value, onChange: onChange });
|
|
999
|
+
return _jsx(DynamicDateField, { field: field, value: value, onChange: onChange, invalid: invalid });
|
|
1000
1000
|
default:
|
|
1001
1001
|
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 });
|
|
1002
1002
|
}
|
|
@@ -3,7 +3,9 @@ export interface DynamicDateFieldProps {
|
|
|
3
3
|
field: ActionFieldDef;
|
|
4
4
|
value: any;
|
|
5
5
|
onChange: (v: any) => void;
|
|
6
|
+
/** Server or client validation failed for this field (PIT-046). */
|
|
7
|
+
invalid?: boolean;
|
|
6
8
|
}
|
|
7
|
-
export declare function DynamicDateField({ field, value, onChange }: DynamicDateFieldProps): import("react").JSX.Element;
|
|
9
|
+
export declare function DynamicDateField({ field, value, onChange, invalid }: DynamicDateFieldProps): import("react").JSX.Element;
|
|
8
10
|
export default DynamicDateField;
|
|
9
11
|
//# sourceMappingURL=dynamic-date-field.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-date-field.d.ts","sourceRoot":"","sources":["../src/dynamic-date-field.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG7C,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-date-field.d.ts","sourceRoot":"","sources":["../src/dynamic-date-field.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG7C,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;CACpB;AAsBD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,qBAAqB,+BAsC1F;AAED,eAAe,gBAAgB,CAAA"}
|
|
@@ -35,10 +35,11 @@ function label(d) {
|
|
|
35
35
|
return toISO(d);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
export function DynamicDateField({ field, value, onChange }) {
|
|
38
|
+
export function DynamicDateField({ field, value, onChange, invalid }) {
|
|
39
39
|
const [open, setOpen] = useState(false);
|
|
40
40
|
const selected = toDate(value);
|
|
41
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", id: field.key, "data-empty": !selected, className:
|
|
41
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", id: field.key, "data-empty": !selected, "aria-invalid": invalid || undefined, className: 'w-full justify-start text-start font-normal data-[empty=true]:text-muted-foreground' +
|
|
42
|
+
(invalid ? ' border-destructive ring-1 ring-destructive/30' : ''), children: [_jsx(CalendarIcon, { className: "mr-2 size-4 shrink-0 opacity-50" }), _jsx("span", { className: "truncate", children: selected ? label(selected) : (field.placeholder || 'Elegí una fecha') })] }) }), _jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: _jsx(Calendar, { mode: "single", captionLayout: "dropdown", selected: selected, defaultMonth: selected, onSelect: (d) => {
|
|
42
43
|
onChange(d ? toISO(d) : '');
|
|
43
44
|
setOpen(false);
|
|
44
45
|
} }) })] }));
|
package/dist/dynamic-relation.js
CHANGED
|
@@ -341,6 +341,9 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
341
341
|
const [pivotRows, setPivotRows] = useState([]);
|
|
342
342
|
const [loading, setLoading] = useState(true);
|
|
343
343
|
const [syncing, setSyncing] = useState(false);
|
|
344
|
+
// Same distinction as the 1:N sub-table (PIT-045): a 403 is not "no rows",
|
|
345
|
+
// and an empty pivot is not "sin permiso".
|
|
346
|
+
const [fetchErrorStatus, setFetchErrorStatus] = useState(null);
|
|
344
347
|
// Canonical path: SDK options resolver. Only fires when no legacy
|
|
345
348
|
// override is set. The hook is a no-op when `useResolver` is false.
|
|
346
349
|
const resolved = useOptionsResolver({
|
|
@@ -366,6 +369,7 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
366
369
|
tasks.push(api.get(legacyTargetPath));
|
|
367
370
|
const results = await Promise.all(tasks);
|
|
368
371
|
const pivotRes = results[0];
|
|
372
|
+
setFetchErrorStatus(null);
|
|
369
373
|
if (pivotRes.data.success)
|
|
370
374
|
setPivotRows(pivotRes.data.data || []);
|
|
371
375
|
let cursor = 1;
|
|
@@ -384,6 +388,7 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
384
388
|
}
|
|
385
389
|
catch (err) {
|
|
386
390
|
console.error('DynamicRelation m2m fetch error', err);
|
|
391
|
+
setFetchErrorStatus(err?.response?.status ?? 0);
|
|
387
392
|
}
|
|
388
393
|
finally {
|
|
389
394
|
setLoading(false);
|
|
@@ -449,5 +454,7 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
449
454
|
}
|
|
450
455
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
451
456
|
}, [api, canCreate, canDelete, fetchPivotAndMeta, useResolver, resolved, foreignKey, filtersKey, onChange, parentId, pivotIndex, pivotPath, refKey, selectedIds, syncing]);
|
|
452
|
-
return (_jsxs("div", { className: className, "data-relation-kind": kind, "data-relation-through": through, "data-relation-references": references, children: [labels.title && (_jsx("div", { className: "pb-3", children: _jsx("h3", { className: "text-sm font-medium", children: labels.title }) })), (loading || (useResolver && resolved.loading)) ? (_jsx(Skeleton, { className: "h-10 w-full" })) :
|
|
457
|
+
return (_jsxs("div", { className: className, "data-relation-kind": kind, "data-relation-through": through, "data-relation-references": references, children: [labels.title && (_jsx("div", { className: "pb-3", children: _jsx("h3", { className: "text-sm font-medium", children: labels.title }) })), (loading || (useResolver && resolved.loading)) ? (_jsx(Skeleton, { className: "h-10 w-full" })) : fetchErrorStatus !== null ? (_jsx("div", { className: "text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30", children: fetchErrorStatus === 403
|
|
458
|
+
? (labels.forbiddenState ?? 'Sin permiso para ver estos registros.')
|
|
459
|
+
: (labels.errorState ?? 'No se pudieron cargar los registros relacionados.') })) : options.length === 0 ? (_jsx("div", { className: "text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30", children: labels.emptyState })) : (_jsx(MultiSelect, { options: options, selected: selectedIds, onChange: handleChange, placeholder: labels.selectPlaceholder, searchPlaceholder: labels.selectSearchPlaceholder, emptyMessage: labels.selectEmpty }))] }));
|
|
453
460
|
}
|
package/package.json
CHANGED
|
@@ -1501,7 +1501,7 @@ function renderField(
|
|
|
1501
1501
|
case 'date':
|
|
1502
1502
|
// Modern shadcn Calendar in a Popover (portaled, never clipped by the
|
|
1503
1503
|
// modal) instead of the native, dated, easily-cut <input type=date>.
|
|
1504
|
-
return <DynamicDateField field={field} value={value} onChange={onChange} />
|
|
1504
|
+
return <DynamicDateField field={field} value={value} onChange={onChange} invalid={invalid} />
|
|
1505
1505
|
default:
|
|
1506
1506
|
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} />
|
|
1507
1507
|
}
|
|
@@ -27,6 +27,8 @@ export interface DynamicDateFieldProps {
|
|
|
27
27
|
field: ActionFieldDef
|
|
28
28
|
value: any
|
|
29
29
|
onChange: (v: any) => void
|
|
30
|
+
/** Server or client validation failed for this field (PIT-046). */
|
|
31
|
+
invalid?: boolean
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const toDate = parseCalendarDate
|
|
@@ -49,7 +51,7 @@ function label(d: Date | undefined): string {
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
export function DynamicDateField({ field, value, onChange }: DynamicDateFieldProps) {
|
|
54
|
+
export function DynamicDateField({ field, value, onChange, invalid }: DynamicDateFieldProps) {
|
|
53
55
|
const [open, setOpen] = useState(false)
|
|
54
56
|
const selected = toDate(value)
|
|
55
57
|
|
|
@@ -61,7 +63,11 @@ export function DynamicDateField({ field, value, onChange }: DynamicDateFieldPro
|
|
|
61
63
|
variant="outline"
|
|
62
64
|
id={field.key}
|
|
63
65
|
data-empty={!selected}
|
|
64
|
-
|
|
66
|
+
aria-invalid={invalid || undefined}
|
|
67
|
+
className={
|
|
68
|
+
'w-full justify-start text-start font-normal data-[empty=true]:text-muted-foreground' +
|
|
69
|
+
(invalid ? ' border-destructive ring-1 ring-destructive/30' : '')
|
|
70
|
+
}
|
|
65
71
|
>
|
|
66
72
|
<CalendarIcon className="mr-2 size-4 shrink-0 opacity-50" />
|
|
67
73
|
<span className="truncate">
|
package/src/dynamic-relation.tsx
CHANGED
|
@@ -718,6 +718,9 @@ function ManyToManyRelation({
|
|
|
718
718
|
const [pivotRows, setPivotRows] = useState<any[]>([])
|
|
719
719
|
const [loading, setLoading] = useState(true)
|
|
720
720
|
const [syncing, setSyncing] = useState(false)
|
|
721
|
+
// Same distinction as the 1:N sub-table (PIT-045): a 403 is not "no rows",
|
|
722
|
+
// and an empty pivot is not "sin permiso".
|
|
723
|
+
const [fetchErrorStatus, setFetchErrorStatus] = useState<number | null>(null)
|
|
721
724
|
|
|
722
725
|
// Canonical path: SDK options resolver. Only fires when no legacy
|
|
723
726
|
// override is set. The hook is a no-op when `useResolver` is false.
|
|
@@ -744,6 +747,7 @@ function ManyToManyRelation({
|
|
|
744
747
|
if (!useResolver) tasks.push(api.get(legacyTargetPath))
|
|
745
748
|
const results = await Promise.all(tasks)
|
|
746
749
|
const pivotRes = results[0] as { data: ApiResponse<any[]> }
|
|
750
|
+
setFetchErrorStatus(null)
|
|
747
751
|
if (pivotRes.data.success) setPivotRows(pivotRes.data.data || [])
|
|
748
752
|
let cursor = 1
|
|
749
753
|
if (!targetMeta) {
|
|
@@ -759,6 +763,7 @@ function ManyToManyRelation({
|
|
|
759
763
|
}
|
|
760
764
|
} catch (err) {
|
|
761
765
|
console.error('DynamicRelation m2m fetch error', err)
|
|
766
|
+
setFetchErrorStatus((err as { response?: { status?: number } })?.response?.status ?? 0)
|
|
762
767
|
} finally {
|
|
763
768
|
setLoading(false)
|
|
764
769
|
}
|
|
@@ -840,6 +845,12 @@ function ManyToManyRelation({
|
|
|
840
845
|
|
|
841
846
|
{(loading || (useResolver && resolved.loading)) ? (
|
|
842
847
|
<Skeleton className="h-10 w-full" />
|
|
848
|
+
) : fetchErrorStatus !== null ? (
|
|
849
|
+
<div className="text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30">
|
|
850
|
+
{fetchErrorStatus === 403
|
|
851
|
+
? (labels.forbiddenState ?? 'Sin permiso para ver estos registros.')
|
|
852
|
+
: (labels.errorState ?? 'No se pudieron cargar los registros relacionados.')}
|
|
853
|
+
</div>
|
|
843
854
|
) : options.length === 0 ? (
|
|
844
855
|
<div className="text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30">
|
|
845
856
|
{labels.emptyState}
|