@asteby/metacore-runtime-react 18.24.0 → 18.25.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 +21 -0
- package/dist/collection-cell.d.ts.map +1 -1
- package/dist/collection-cell.js +50 -7
- package/package.json +3 -3
- package/src/collection-cell.tsx +102 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 18.25.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- adb1c52: `CollectionCell` renders resolved relation references as "pro" chips in EVERY
|
|
8
|
+
path — including the schema-less generic one.
|
|
9
|
+
|
|
10
|
+
Previously only the declared-`item_fields` path resolved a jsonb line-item ref
|
|
11
|
+
(e.g. `product_id`) to a relation chip. The generic path (used by the full-page
|
|
12
|
+
record detail and any jsonb without a declared schema) dumped the
|
|
13
|
+
backend-injected resolved sibling object as raw `"{…}"` AND showed the raw uuid
|
|
14
|
+
in a duplicate column. Now the generic path:
|
|
15
|
+
- detects the backend-injected `{ value, label, image }` ref siblings,
|
|
16
|
+
- renders them as the same relation chip (subtle tint + thumbnail or entity icon
|
|
17
|
+
- name) the FK table columns use, and
|
|
18
|
+
- hides the raw `<key>_id` twin column,
|
|
19
|
+
|
|
20
|
+
so an unconfigured jsonb line-items blob reads as first-class relations
|
|
21
|
+
(foto/nombre) instead of uuid soup. The shared chip is extracted as `RefChip`
|
|
22
|
+
and reused by the schema (`ItemFieldCell`) and generic paths.
|
|
23
|
+
|
|
3
24
|
## 18.24.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;
|
|
1
|
+
{"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAoND;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYnD;AAiOD,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,CAAC,CAAC,EAAE,SAAS,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,EACD,UAAU,EACV,WAAW,EACX,OAAiB,GACpB,EAAE,mBAAmB,qBA2HrB"}
|
package/dist/collection-cell.js
CHANGED
|
@@ -80,18 +80,44 @@ function itemFieldText(field, row) {
|
|
|
80
80
|
return ref.label;
|
|
81
81
|
return formatScalar(row[field.key]);
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* A backend-resolved relation reference shaped `{ value, label, image? }` — the
|
|
85
|
+
* sibling the backend injects for a ref (FK column or jsonb item-field), the
|
|
86
|
+
* SAME shape the relation table columns use. Lets a jsonb line item read as a
|
|
87
|
+
* first-class relation, not a raw uuid.
|
|
88
|
+
*/
|
|
89
|
+
function resolvedRefObject(v) {
|
|
90
|
+
if (!isPlainObject(v))
|
|
91
|
+
return null;
|
|
92
|
+
const label = v.label;
|
|
93
|
+
if (label === undefined || label === null || label === '')
|
|
94
|
+
return null;
|
|
95
|
+
return {
|
|
96
|
+
label: String(label),
|
|
97
|
+
value: v.value,
|
|
98
|
+
image: typeof v.image === 'string' && v.image !== '' ? v.image : undefined,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The "pro" relation chip — subtle deterministic tint + the related record's
|
|
103
|
+
* thumbnail (product photo / logo / avatar) or a generic entity icon + the
|
|
104
|
+
* resolved name. The exact look the FK table columns use, so resolved relations
|
|
105
|
+
* read consistently everywhere (table popover, detail view, edit, line items).
|
|
106
|
+
*/
|
|
107
|
+
function RefChip({ label, image, getImageUrl, }) {
|
|
108
|
+
const isDark = useIsDarkTheme();
|
|
109
|
+
return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", style: relationChipStyles(label, { isDark }), title: label, children: [image ? (_jsxs(Avatar, { className: "shrink-0 rounded-sm ring-1 ring-border/40", style: { width: 18, height: 18 }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(image) : image, alt: label, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-sm bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(label) })] })) : (_jsx(Box, { className: "h-3 w-3 shrink-0 opacity-70" })), _jsx("span", { className: "truncate", children: label })] }));
|
|
110
|
+
}
|
|
83
111
|
/**
|
|
84
112
|
* Visual cell for one declared item-field. A resolved `ref` renders as a
|
|
85
|
-
* relation chip (
|
|
86
|
-
*
|
|
87
|
-
* line items read like first-class relations instead of raw uuids. Non-ref (or
|
|
113
|
+
* relation chip (foto/ícono + nombre) — the "pro" FK-column look — so jsonb line
|
|
114
|
+
* items read like first-class relations instead of raw uuids. Non-ref (or
|
|
88
115
|
* unresolved) fields render the plain scalar.
|
|
89
116
|
*/
|
|
90
117
|
function ItemFieldCell({ field, row, getImageUrl, }) {
|
|
91
|
-
const isDark = useIsDarkTheme();
|
|
92
118
|
const ref = resolvedRefFor(field, row);
|
|
93
119
|
if (ref?.label) {
|
|
94
|
-
return (
|
|
120
|
+
return (_jsx(RefChip, { label: ref.label, image: ref.image, getImageUrl: getImageUrl }));
|
|
95
121
|
}
|
|
96
122
|
return _jsx(_Fragment, { children: formatScalar(row[field.key]) });
|
|
97
123
|
}
|
|
@@ -265,11 +291,28 @@ function MiniTable({ rows, locale, t, itemFields, getImageUrl, }) {
|
|
|
265
291
|
if (itemFields && itemFields.length > 0) {
|
|
266
292
|
return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: itemFields.map((field) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: field.label }, field.key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: itemFields.map((field) => (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: _jsx(ItemFieldCell, { field: field, row: row, getImageUrl: getImageUrl }) }, field.key))) }, i))) })] }));
|
|
267
293
|
}
|
|
268
|
-
|
|
294
|
+
// Generic (no-schema) path — still relation-AWARE. The backend injects a
|
|
295
|
+
// resolved-ref sibling object (`{value,label,image}`) next to each FK id
|
|
296
|
+
// inside the items (e.g. `product` next to `product_id`). Without a declared
|
|
297
|
+
// schema we'd otherwise dump that object as "{…}" AND the raw uuid in a
|
|
298
|
+
// duplicate column. Instead: detect those sibling objects, render them as
|
|
299
|
+
// relation chips, and HIDE their raw `<key>_id` twin — so even an
|
|
300
|
+
// unconfigured jsonb blob reads "pro" (foto/nombre, no uuid soup).
|
|
301
|
+
const allKeys = unionKeys(rows);
|
|
302
|
+
const refKeys = new Set(allKeys.filter((k) => rows.some((r) => resolvedRefObject(r[k]) !== null)));
|
|
303
|
+
const hiddenTwins = new Set();
|
|
304
|
+
for (const rk of refKeys)
|
|
305
|
+
hiddenTwins.add(`${rk}_id`);
|
|
306
|
+
const keys = allKeys.filter((k) => !hiddenTwins.has(k));
|
|
269
307
|
if (keys.length === 0) {
|
|
270
308
|
return _jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "-" });
|
|
271
309
|
}
|
|
272
|
-
return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: keys.map((key) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: prettifyKey(key, locale, t) }, key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: keys.map((key) =>
|
|
310
|
+
return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: keys.map((key) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: prettifyKey(key, locale, t) }, key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: keys.map((key) => {
|
|
311
|
+
const ref = refKeys.has(key)
|
|
312
|
+
? resolvedRefObject(row[key])
|
|
313
|
+
: null;
|
|
314
|
+
return (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: ref ? (_jsx(RefChip, { label: ref.label, image: ref.image, getImageUrl: getImageUrl })) : (formatScalar(row[key])) }, key));
|
|
315
|
+
}) }, i))) })] }));
|
|
273
316
|
}
|
|
274
317
|
function ScalarList({ values }) {
|
|
275
318
|
return (_jsx("ul", { className: "p-3 space-y-1", children: values.map((v, i) => (_jsx("li", { className: "text-xs text-foreground", children: formatScalar(v) }, i))) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.25.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"react-i18next": ">=13",
|
|
35
35
|
"sonner": ">=1.7",
|
|
36
36
|
"zustand": ">=5",
|
|
37
|
-
"@asteby/metacore-
|
|
38
|
-
"@asteby/metacore-
|
|
37
|
+
"@asteby/metacore-ui": "^2.5.2",
|
|
38
|
+
"@asteby/metacore-sdk": "^3.2.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@tanstack/react-router": {
|
package/src/collection-cell.tsx
CHANGED
|
@@ -136,11 +136,74 @@ function itemFieldText(field: ItemField, row: Record<string, unknown>): string {
|
|
|
136
136
|
return formatScalar(row[field.key])
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* A backend-resolved relation reference shaped `{ value, label, image? }` — the
|
|
141
|
+
* sibling the backend injects for a ref (FK column or jsonb item-field), the
|
|
142
|
+
* SAME shape the relation table columns use. Lets a jsonb line item read as a
|
|
143
|
+
* first-class relation, not a raw uuid.
|
|
144
|
+
*/
|
|
145
|
+
function resolvedRefObject(
|
|
146
|
+
v: unknown,
|
|
147
|
+
): (ResolvedRef & { label: string }) | null {
|
|
148
|
+
if (!isPlainObject(v)) return null
|
|
149
|
+
const label = v.label
|
|
150
|
+
if (label === undefined || label === null || label === '') return null
|
|
151
|
+
return {
|
|
152
|
+
label: String(label),
|
|
153
|
+
value: v.value,
|
|
154
|
+
image:
|
|
155
|
+
typeof v.image === 'string' && v.image !== '' ? v.image : undefined,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The "pro" relation chip — subtle deterministic tint + the related record's
|
|
161
|
+
* thumbnail (product photo / logo / avatar) or a generic entity icon + the
|
|
162
|
+
* resolved name. The exact look the FK table columns use, so resolved relations
|
|
163
|
+
* read consistently everywhere (table popover, detail view, edit, line items).
|
|
164
|
+
*/
|
|
165
|
+
function RefChip({
|
|
166
|
+
label,
|
|
167
|
+
image,
|
|
168
|
+
getImageUrl,
|
|
169
|
+
}: {
|
|
170
|
+
label: string
|
|
171
|
+
image?: string
|
|
172
|
+
getImageUrl?: (path: string) => string
|
|
173
|
+
}): React.ReactElement {
|
|
174
|
+
const isDark = useIsDarkTheme()
|
|
175
|
+
return (
|
|
176
|
+
<span
|
|
177
|
+
className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium"
|
|
178
|
+
style={relationChipStyles(label, { isDark })}
|
|
179
|
+
title={label}
|
|
180
|
+
>
|
|
181
|
+
{image ? (
|
|
182
|
+
<Avatar
|
|
183
|
+
className="shrink-0 rounded-sm ring-1 ring-border/40"
|
|
184
|
+
style={{ width: 18, height: 18 }}
|
|
185
|
+
>
|
|
186
|
+
<AvatarImage
|
|
187
|
+
src={getImageUrl ? getImageUrl(image) : image}
|
|
188
|
+
alt={label}
|
|
189
|
+
className="object-cover"
|
|
190
|
+
/>
|
|
191
|
+
<AvatarFallback className="rounded-sm bg-primary/10 text-[8px] font-bold text-primary">
|
|
192
|
+
{getInitials(label)}
|
|
193
|
+
</AvatarFallback>
|
|
194
|
+
</Avatar>
|
|
195
|
+
) : (
|
|
196
|
+
<Box className="h-3 w-3 shrink-0 opacity-70" />
|
|
197
|
+
)}
|
|
198
|
+
<span className="truncate">{label}</span>
|
|
199
|
+
</span>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
139
203
|
/**
|
|
140
204
|
* Visual cell for one declared item-field. A resolved `ref` renders as a
|
|
141
|
-
* relation chip (
|
|
142
|
-
*
|
|
143
|
-
* line items read like first-class relations instead of raw uuids. Non-ref (or
|
|
205
|
+
* relation chip (foto/ícono + nombre) — the "pro" FK-column look — so jsonb line
|
|
206
|
+
* items read like first-class relations instead of raw uuids. Non-ref (or
|
|
144
207
|
* unresolved) fields render the plain scalar.
|
|
145
208
|
*/
|
|
146
209
|
function ItemFieldCell({
|
|
@@ -152,34 +215,10 @@ function ItemFieldCell({
|
|
|
152
215
|
row: Record<string, unknown>
|
|
153
216
|
getImageUrl?: (path: string) => string
|
|
154
217
|
}): React.ReactElement {
|
|
155
|
-
const isDark = useIsDarkTheme()
|
|
156
218
|
const ref = resolvedRefFor(field, row)
|
|
157
219
|
if (ref?.label) {
|
|
158
220
|
return (
|
|
159
|
-
<
|
|
160
|
-
className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium"
|
|
161
|
-
style={relationChipStyles(ref.label, { isDark })}
|
|
162
|
-
title={ref.label}
|
|
163
|
-
>
|
|
164
|
-
{ref.image ? (
|
|
165
|
-
<Avatar
|
|
166
|
-
className="shrink-0 rounded-sm ring-1 ring-border/40"
|
|
167
|
-
style={{ width: 18, height: 18 }}
|
|
168
|
-
>
|
|
169
|
-
<AvatarImage
|
|
170
|
-
src={getImageUrl ? getImageUrl(ref.image) : ref.image}
|
|
171
|
-
alt={ref.label}
|
|
172
|
-
className="object-cover"
|
|
173
|
-
/>
|
|
174
|
-
<AvatarFallback className="rounded-sm bg-primary/10 text-[8px] font-bold text-primary">
|
|
175
|
-
{getInitials(ref.label)}
|
|
176
|
-
</AvatarFallback>
|
|
177
|
-
</Avatar>
|
|
178
|
-
) : (
|
|
179
|
-
<Box className="h-3 w-3 shrink-0 opacity-70" />
|
|
180
|
-
)}
|
|
181
|
-
<span className="truncate">{ref.label}</span>
|
|
182
|
-
</span>
|
|
221
|
+
<RefChip label={ref.label} image={ref.image} getImageUrl={getImageUrl} />
|
|
183
222
|
)
|
|
184
223
|
}
|
|
185
224
|
return <>{formatScalar(row[field.key])}</>
|
|
@@ -412,7 +451,20 @@ function MiniTable({
|
|
|
412
451
|
)
|
|
413
452
|
}
|
|
414
453
|
|
|
415
|
-
|
|
454
|
+
// Generic (no-schema) path — still relation-AWARE. The backend injects a
|
|
455
|
+
// resolved-ref sibling object (`{value,label,image}`) next to each FK id
|
|
456
|
+
// inside the items (e.g. `product` next to `product_id`). Without a declared
|
|
457
|
+
// schema we'd otherwise dump that object as "{…}" AND the raw uuid in a
|
|
458
|
+
// duplicate column. Instead: detect those sibling objects, render them as
|
|
459
|
+
// relation chips, and HIDE their raw `<key>_id` twin — so even an
|
|
460
|
+
// unconfigured jsonb blob reads "pro" (foto/nombre, no uuid soup).
|
|
461
|
+
const allKeys = unionKeys(rows)
|
|
462
|
+
const refKeys = new Set(
|
|
463
|
+
allKeys.filter((k) => rows.some((r) => resolvedRefObject(r[k]) !== null))
|
|
464
|
+
)
|
|
465
|
+
const hiddenTwins = new Set<string>()
|
|
466
|
+
for (const rk of refKeys) hiddenTwins.add(`${rk}_id`)
|
|
467
|
+
const keys = allKeys.filter((k) => !hiddenTwins.has(k))
|
|
416
468
|
if (keys.length === 0) {
|
|
417
469
|
return <div className="p-3 text-xs text-muted-foreground">-</div>
|
|
418
470
|
}
|
|
@@ -430,14 +482,27 @@ function MiniTable({
|
|
|
430
482
|
<TableBody>
|
|
431
483
|
{rows.map((row, i) => (
|
|
432
484
|
<TableRow key={i}>
|
|
433
|
-
{keys.map((key) =>
|
|
434
|
-
|
|
435
|
-
key
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
485
|
+
{keys.map((key) => {
|
|
486
|
+
const ref = refKeys.has(key)
|
|
487
|
+
? resolvedRefObject(row[key])
|
|
488
|
+
: null
|
|
489
|
+
return (
|
|
490
|
+
<TableCell
|
|
491
|
+
key={key}
|
|
492
|
+
className="text-xs whitespace-nowrap"
|
|
493
|
+
>
|
|
494
|
+
{ref ? (
|
|
495
|
+
<RefChip
|
|
496
|
+
label={ref.label}
|
|
497
|
+
image={ref.image}
|
|
498
|
+
getImageUrl={getImageUrl}
|
|
499
|
+
/>
|
|
500
|
+
) : (
|
|
501
|
+
formatScalar(row[key])
|
|
502
|
+
)}
|
|
503
|
+
</TableCell>
|
|
504
|
+
)
|
|
505
|
+
})}
|
|
441
506
|
</TableRow>
|
|
442
507
|
))}
|
|
443
508
|
</TableBody>
|