@asteby/metacore-runtime-react 18.23.0 → 18.24.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,23 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 18.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 39c43ea: jsonb line-item `ref` cells render as relation chips (icon/photo + name).
8
+
9
+ In `CollectionCell` (table popover, inline detail view, and the read-only edit
10
+ field), a resolved ref sub-field — e.g. `product_id` inside a transfer's `items`
11
+ — now renders with the same "pro" relation look the FK table columns use: a
12
+ subtle deterministic tint, the resolved record's thumbnail (product photo / logo
13
+ / avatar, resolved via the threaded `getImageUrl`) or a generic entity icon
14
+ fallback, and the resolved name — instead of a truncated uuid.
15
+
16
+ `CollectionCell` gains an optional `getImageUrl` prop, threaded from the columns
17
+ factory and from the record dialog's `ImageUrlContext`. Backend-agnostic: it
18
+ drives off the backend-injected `{ value, label, image }` sibling; an unresolved
19
+ ref still falls back to the scalar value.
20
+
3
21
  ## 18.23.0
4
22
 
5
23
  ### Minor Changes
@@ -57,6 +57,12 @@ export interface CollectionCellProps {
57
57
  * behaviour is unchanged.
58
58
  */
59
59
  itemFields?: ItemField[];
60
+ /**
61
+ * Resolves a stored image path to a displayable URL (same resolver the table
62
+ * columns use). Threaded to the ref-chip thumbnails so resolved line-item
63
+ * relations show a product photo / logo / avatar like the FK columns do.
64
+ */
65
+ getImageUrl?: (path: string) => string;
60
66
  /**
61
67
  * Presentation mode.
62
68
  * - `'badge'` (default): the compact count/preview badge that opens a
@@ -77,5 +83,5 @@ export interface CollectionCellProps {
77
83
  * itemFields schema (localized headers + resolved ref labels) and the
78
84
  * locale-aware generic fallback.
79
85
  */
80
- export declare function CollectionCell({ value, maxInline, locale, t, itemFields, variant, }: CollectionCellProps): React.JSX.Element;
86
+ export declare function CollectionCell({ value, maxInline, locale, t, itemFields, getImageUrl, variant, }: CollectionCellProps): React.JSX.Element;
81
87
  //# sourceMappingURL=collection-cell.d.ts.map
@@ -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;AAoB9B,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;AAmGD;;;;;;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;AAiMD,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;;;;;;;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,OAAiB,GACpB,EAAE,mBAAmB,qBAyHrB"}
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;AA6KD;;;;;;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;AAuMD,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"}
@@ -1,7 +1,37 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { List } from 'lucide-react';
3
- import { Badge, Popover, PopoverContent, PopoverTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, cn, } from '@asteby/metacore-ui';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // Generic, brand-neutral table-cell renderer for jsonb / array / object column
3
+ // values. Kernel-derived dynamic tables surface raw jsonb columns (line items,
4
+ // nested config blobs, scalar arrays) with no per-column metadata; without this
5
+ // they rendered as raw `JSON.stringify(value)` which is unreadable. This renders
6
+ // a compact trigger (count badge / inline pairs) plus a Popover with a clean
7
+ // mini-table — no per-addon config required, safe on any shape.
8
+ import * as React from 'react';
9
+ import { Box, List } from 'lucide-react';
10
+ import { Avatar, AvatarFallback, AvatarImage, Badge, Popover, PopoverContent, PopoverTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, cn, } from '@asteby/metacore-ui';
11
+ import { getInitials, relationChipStyles } from '@asteby/metacore-ui/lib';
4
12
  import { humanizeToken } from './dynamic-columns-helpers';
13
+ /**
14
+ * Tracks the host's dark-mode class on <html> so relation chips pick a tint
15
+ * tuned for the active theme. Replicated from dynamic-columns (kept local to
16
+ * avoid a cross-module import); mirror changes if that one evolves.
17
+ */
18
+ function useIsDarkTheme() {
19
+ const [isDark, setIsDark] = React.useState(() => typeof document !== 'undefined' &&
20
+ document.documentElement.classList.contains('dark'));
21
+ React.useEffect(() => {
22
+ if (typeof document === 'undefined')
23
+ return;
24
+ const sync = () => setIsDark(document.documentElement.classList.contains('dark'));
25
+ sync();
26
+ const observer = new MutationObserver(sync);
27
+ observer.observe(document.documentElement, {
28
+ attributes: true,
29
+ attributeFilter: ['class'],
30
+ });
31
+ return () => observer.disconnect();
32
+ }, []);
33
+ return isDark;
34
+ }
5
35
  const UUID_LIKE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
36
  /**
7
37
  * Resolves the backend-injected resolved sibling key for a ref item-field,
@@ -12,27 +42,59 @@ function siblingKeyFor(key) {
12
42
  return key.endsWith('_id') ? key.slice(0, -3) : `${key}_label`;
13
43
  }
14
44
  /**
15
- * Renders the cell value for one declared item-field of a jsonb row. For a
16
- * `ref` field it prefers the backend-injected resolved sibling (the FK key
17
- * without `_id`, else `<key>_label`): a `{ value, label }` object shows its
18
- * `label`, a bare string shows itself; absent the raw value via
19
- * `formatScalar` (truncated uuid). Non-ref fields render `formatScalar(value)`.
45
+ * Reads the backend-injected resolved sibling for a `ref` item-field (the FK
46
+ * key without `_id`, else `<key>_label`). Returns the normalized `{label,
47
+ * image}` when present, a `{label}` for a bare-string sibling, or null when the
48
+ * ref is unresolved (so the caller falls back to the raw value).
20
49
  */
21
- function renderItemFieldValue(field, row) {
22
- if (field.ref) {
23
- const sibling = row[siblingKeyFor(field.key)];
24
- if (sibling && typeof sibling === 'object' && !Array.isArray(sibling)) {
25
- const label = sibling.label;
26
- if (label !== undefined && label !== null && label !== '') {
27
- return String(label);
28
- }
29
- }
30
- else if (typeof sibling === 'string' && sibling !== '') {
31
- return sibling;
50
+ function resolvedRefFor(field, row) {
51
+ if (!field.ref)
52
+ return null;
53
+ const sibling = row[siblingKeyFor(field.key)];
54
+ if (sibling && typeof sibling === 'object' && !Array.isArray(sibling)) {
55
+ const obj = sibling;
56
+ const label = obj.label;
57
+ if (label !== undefined && label !== null && label !== '') {
58
+ return {
59
+ label: String(label),
60
+ value: obj.value,
61
+ image: typeof obj.image === 'string' && obj.image !== ''
62
+ ? obj.image
63
+ : undefined,
64
+ };
32
65
  }
33
66
  }
67
+ else if (typeof sibling === 'string' && sibling !== '') {
68
+ return { label: sibling };
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Plain-text value for one declared item-field — used for the no-JS popover
74
+ * `title` tooltip (which can't render JSX). Ref fields show the resolved label;
75
+ * everything else uses `formatScalar` (truncated uuid for unresolved ids).
76
+ */
77
+ function itemFieldText(field, row) {
78
+ const ref = resolvedRefFor(field, row);
79
+ if (ref?.label)
80
+ return ref.label;
34
81
  return formatScalar(row[field.key]);
35
82
  }
83
+ /**
84
+ * Visual cell for one declared item-field. A resolved `ref` renders as a
85
+ * relation chip (subtle deterministic tint + product thumbnail or a generic
86
+ * entity icon + name) — the same "pro" look the table FK columns use — so jsonb
87
+ * line items read like first-class relations instead of raw uuids. Non-ref (or
88
+ * unresolved) fields render the plain scalar.
89
+ */
90
+ function ItemFieldCell({ field, row, getImageUrl, }) {
91
+ const isDark = useIsDarkTheme();
92
+ const ref = resolvedRefFor(field, row);
93
+ if (ref?.label) {
94
+ 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(ref.label, { isDark }), title: ref.label, children: [ref.image ? (_jsxs(Avatar, { className: "shrink-0 rounded-sm ring-1 ring-border/40", style: { width: 18, height: 18 }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(ref.image) : ref.image, alt: ref.label, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-sm bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(ref.label) })] })) : (_jsx(Box, { className: "h-3 w-3 shrink-0 opacity-70" })), _jsx("span", { className: "truncate", children: ref.label })] }));
95
+ }
96
+ return _jsx(_Fragment, { children: formatScalar(row[field.key]) });
97
+ }
36
98
  /** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
37
99
  function baseLang(locale) {
38
100
  return (locale || 'en').toLowerCase().split('-')[0];
@@ -194,14 +256,14 @@ function unionKeys(rows) {
194
256
  return seen;
195
257
  }
196
258
  const PANEL_CLASS = 'w-auto max-w-[480px] max-h-[320px] overflow-auto p-0';
197
- function MiniTable({ rows, locale, t, itemFields, }) {
259
+ function MiniTable({ rows, locale, t, itemFields, getImageUrl, }) {
198
260
  // Schema-driven path: a declared `item_fields` schema fixes the column
199
261
  // order + headers (already localized by the backend, used VERBATIM) and
200
262
  // resolves ref columns to the injected sibling label instead of the raw
201
263
  // uuid. Sibling/raw keys not covered by the schema are dropped from the
202
264
  // table (the schema is the source of truth for what to surface).
203
265
  if (itemFields && itemFields.length > 0) {
204
- 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: renderItemFieldValue(field, row) }, field.key))) }, i))) })] }));
266
+ 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))) })] }));
205
267
  }
206
268
  const keys = unionKeys(rows);
207
269
  if (keys.length === 0) {
@@ -229,7 +291,7 @@ function PopoverShell({ label, title, children, icon = true, }) {
229
291
  * itemFields schema (localized headers + resolved ref labels) and the
230
292
  * locale-aware generic fallback.
231
293
  */
232
- export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, variant = 'badge', }) {
294
+ export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, getImageUrl, variant = 'badge', }) {
233
295
  const parsed = parseValue(value);
234
296
  const inline = variant === 'inline';
235
297
  // Empty-ish → muted dash.
@@ -253,7 +315,7 @@ export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, va
253
315
  // Inline mode (detail view): render the mini-table directly, no
254
316
  // badge/popover. The same schema-driven path applies.
255
317
  if (inline) {
256
- return (_jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields }));
318
+ return (_jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields, getImageUrl: getImageUrl }));
257
319
  }
258
320
  const count = rows.length;
259
321
  const label = countLabel(count, locale, t);
@@ -264,7 +326,7 @@ export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, va
264
326
  const title = hasSchema
265
327
  ? rows
266
328
  .map((row) => itemFields
267
- .map((field) => `${field.label}: ${renderItemFieldValue(field, row)}`)
329
+ .map((field) => `${field.label}: ${itemFieldText(field, row)}`)
268
330
  .join(', '))
269
331
  .join(' | ')
270
332
  : rows
@@ -272,7 +334,7 @@ export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, va
272
334
  .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
273
335
  .join(', '))
274
336
  .join(' | ');
275
- return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields }) }));
337
+ return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields, getImageUrl: getImageUrl }) }));
276
338
  }
277
339
  // Array of scalars (or mixed). Inline mode renders the full list; badge
278
340
  // mode previews the first N joined with a "+N" overflow trigger.
@@ -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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"}
@@ -661,6 +661,7 @@ function IconNameViewValue({ name }) {
661
661
  // empty objects keep the "—" marker (CollectionCell renders a muted dash, which
662
662
  // we normalize to the em-dash the detail view uses elsewhere).
663
663
  function StructuredViewValue({ value, field, locale, t, }) {
664
+ const getImageUrl = useContext(ImageUrlContext);
664
665
  const isEmpty = value === null ||
665
666
  value === undefined ||
666
667
  value === '' ||
@@ -671,16 +672,17 @@ function StructuredViewValue({ value, field, locale, t, }) {
671
672
  if (isEmpty) {
672
673
  return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
673
674
  }
674
- return (_jsx("div", { className: "text-sm py-1", children: _jsx(CollectionCell, { value: value, itemFields: field?.itemFields ?? field?.item_fields, variant: "inline", locale: locale, t: t }) }));
675
+ return (_jsx("div", { className: "text-sm py-1", children: _jsx(CollectionCell, { value: value, itemFields: field?.itemFields ?? field?.item_fields, variant: "inline", locale: locale, t: t, getImageUrl: getImageUrl }) }));
675
676
  }
676
677
  export function EditField({ field, value, onChange, record }) {
677
678
  const { t, i18n } = useTranslation();
679
+ const editFieldImageUrl = useContext(ImageUrlContext);
678
680
  // Jsonb line-items columns (e.g. Transfer.items) are action-built documents:
679
681
  // editing the array field-by-field is out of scope. Render them READ-ONLY
680
682
  // with the same inline table the detail view uses — a localized, ref-resolved
681
683
  // mini-table — instead of an input that stringifies to "[object Object]".
682
684
  if (isLineItemsField(field, value)) {
683
- return (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "rounded-md border bg-muted/30 p-2", children: _jsx(CollectionCell, { value: value, itemFields: fieldItemFields(field), variant: "inline", locale: i18n.language, t: t }) }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: t('datatable.readOnly', { defaultValue: 'Solo lectura' }) })] }));
685
+ return (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "rounded-md border bg-muted/30 p-2", children: _jsx(CollectionCell, { value: value, itemFields: fieldItemFields(field), variant: "inline", locale: i18n.language, t: t, getImageUrl: editFieldImageUrl }) }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: t('datatable.readOnly', { defaultValue: 'Solo lectura' }) })] }));
684
686
  }
685
687
  if (field.type === 'boolean') {
686
688
  return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx(Switch, { checked: !!value, onCheckedChange: onChange }), _jsx("span", { className: "text-sm text-muted-foreground", children: value ? 'Sí' : 'No' })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA8DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAqKD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CA8nBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA8DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAqKD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CA+nBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -736,7 +736,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
736
736
  }
737
737
  default: {
738
738
  if (typeof value === 'object' && value !== null) {
739
- return (_jsx(CollectionCell, { value: value, locale: currentLanguage, t: t, itemFields: col.itemFields ?? col.item_fields }));
739
+ return (_jsx(CollectionCell, { value: value, locale: currentLanguage, t: t, itemFields: col.itemFields ?? col.item_fields, getImageUrl: getImageUrl }));
740
740
  }
741
741
  if (col.key === 'description' ||
742
742
  col.key === 'features' ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "18.23.0",
3
+ "version": "18.24.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -6,8 +6,11 @@
6
6
  // mini-table — no per-addon config required, safe on any shape.
7
7
 
8
8
  import * as React from 'react'
9
- import { List } from 'lucide-react'
9
+ import { Box, List } from 'lucide-react'
10
10
  import {
11
+ Avatar,
12
+ AvatarFallback,
13
+ AvatarImage,
11
14
  Badge,
12
15
  Popover,
13
16
  PopoverContent,
@@ -20,8 +23,35 @@ import {
20
23
  TableRow,
21
24
  cn,
22
25
  } from '@asteby/metacore-ui'
26
+ import { getInitials, relationChipStyles } from '@asteby/metacore-ui/lib'
23
27
  import { humanizeToken } from './dynamic-columns-helpers'
24
28
 
29
+ /**
30
+ * Tracks the host's dark-mode class on <html> so relation chips pick a tint
31
+ * tuned for the active theme. Replicated from dynamic-columns (kept local to
32
+ * avoid a cross-module import); mirror changes if that one evolves.
33
+ */
34
+ function useIsDarkTheme(): boolean {
35
+ const [isDark, setIsDark] = React.useState(
36
+ () =>
37
+ typeof document !== 'undefined' &&
38
+ document.documentElement.classList.contains('dark')
39
+ )
40
+ React.useEffect(() => {
41
+ if (typeof document === 'undefined') return
42
+ const sync = () =>
43
+ setIsDark(document.documentElement.classList.contains('dark'))
44
+ sync()
45
+ const observer = new MutationObserver(sync)
46
+ observer.observe(document.documentElement, {
47
+ attributes: true,
48
+ attributeFilter: ['class'],
49
+ })
50
+ return () => observer.disconnect()
51
+ }, [])
52
+ return isDark
53
+ }
54
+
25
55
  const UUID_LIKE_RE =
26
56
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
27
57
 
@@ -56,31 +86,105 @@ function siblingKeyFor(key: string): string {
56
86
  return key.endsWith('_id') ? key.slice(0, -3) : `${key}_label`
57
87
  }
58
88
 
89
+ /** The backend-injected resolved sibling for a ref item-field. */
90
+ interface ResolvedRef {
91
+ label?: string
92
+ value?: unknown
93
+ /** Optional thumbnail (product photo, logo, avatar) resolved by the backend. */
94
+ image?: string
95
+ }
96
+
59
97
  /**
60
- * Renders the cell value for one declared item-field of a jsonb row. For a
61
- * `ref` field it prefers the backend-injected resolved sibling (the FK key
62
- * without `_id`, else `<key>_label`): a `{ value, label }` object shows its
63
- * `label`, a bare string shows itself; absent the raw value via
64
- * `formatScalar` (truncated uuid). Non-ref fields render `formatScalar(value)`.
98
+ * Reads the backend-injected resolved sibling for a `ref` item-field (the FK
99
+ * key without `_id`, else `<key>_label`). Returns the normalized `{label,
100
+ * image}` when present, a `{label}` for a bare-string sibling, or null when the
101
+ * ref is unresolved (so the caller falls back to the raw value).
65
102
  */
66
- function renderItemFieldValue(
103
+ function resolvedRefFor(
67
104
  field: ItemField,
68
105
  row: Record<string, unknown>,
69
- ): string {
70
- if (field.ref) {
71
- const sibling = row[siblingKeyFor(field.key)]
72
- if (sibling && typeof sibling === 'object' && !Array.isArray(sibling)) {
73
- const label = (sibling as Record<string, unknown>).label
74
- if (label !== undefined && label !== null && label !== '') {
75
- return String(label)
106
+ ): ResolvedRef | null {
107
+ if (!field.ref) return null
108
+ const sibling = row[siblingKeyFor(field.key)]
109
+ if (sibling && typeof sibling === 'object' && !Array.isArray(sibling)) {
110
+ const obj = sibling as Record<string, unknown>
111
+ const label = obj.label
112
+ if (label !== undefined && label !== null && label !== '') {
113
+ return {
114
+ label: String(label),
115
+ value: obj.value,
116
+ image:
117
+ typeof obj.image === 'string' && obj.image !== ''
118
+ ? obj.image
119
+ : undefined,
76
120
  }
77
- } else if (typeof sibling === 'string' && sibling !== '') {
78
- return sibling
79
121
  }
122
+ } else if (typeof sibling === 'string' && sibling !== '') {
123
+ return { label: sibling }
80
124
  }
125
+ return null
126
+ }
127
+
128
+ /**
129
+ * Plain-text value for one declared item-field — used for the no-JS popover
130
+ * `title` tooltip (which can't render JSX). Ref fields show the resolved label;
131
+ * everything else uses `formatScalar` (truncated uuid for unresolved ids).
132
+ */
133
+ function itemFieldText(field: ItemField, row: Record<string, unknown>): string {
134
+ const ref = resolvedRefFor(field, row)
135
+ if (ref?.label) return ref.label
81
136
  return formatScalar(row[field.key])
82
137
  }
83
138
 
139
+ /**
140
+ * Visual cell for one declared item-field. A resolved `ref` renders as a
141
+ * relation chip (subtle deterministic tint + product thumbnail or a generic
142
+ * entity icon + name) — the same "pro" look the table FK columns use — so jsonb
143
+ * line items read like first-class relations instead of raw uuids. Non-ref (or
144
+ * unresolved) fields render the plain scalar.
145
+ */
146
+ function ItemFieldCell({
147
+ field,
148
+ row,
149
+ getImageUrl,
150
+ }: {
151
+ field: ItemField
152
+ row: Record<string, unknown>
153
+ getImageUrl?: (path: string) => string
154
+ }): React.ReactElement {
155
+ const isDark = useIsDarkTheme()
156
+ const ref = resolvedRefFor(field, row)
157
+ if (ref?.label) {
158
+ return (
159
+ <span
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>
183
+ )
184
+ }
185
+ return <>{formatScalar(row[field.key])}</>
186
+ }
187
+
84
188
  /** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
85
189
  function baseLang(locale?: string): string {
86
190
  return (locale || 'en').toLowerCase().split('-')[0]
@@ -258,11 +362,13 @@ function MiniTable({
258
362
  locale,
259
363
  t,
260
364
  itemFields,
365
+ getImageUrl,
261
366
  }: {
262
367
  rows: Record<string, unknown>[]
263
368
  locale?: string
264
369
  t?: Translate
265
370
  itemFields?: ItemField[]
371
+ getImageUrl?: (path: string) => string
266
372
  }) {
267
373
  // Schema-driven path: a declared `item_fields` schema fixes the column
268
374
  // order + headers (already localized by the backend, used VERBATIM) and
@@ -292,7 +398,11 @@ function MiniTable({
292
398
  key={field.key}
293
399
  className="text-xs whitespace-nowrap"
294
400
  >
295
- {renderItemFieldValue(field, row)}
401
+ <ItemFieldCell
402
+ field={field}
403
+ row={row}
404
+ getImageUrl={getImageUrl}
405
+ />
296
406
  </TableCell>
297
407
  ))}
298
408
  </TableRow>
@@ -421,6 +531,12 @@ export interface CollectionCellProps {
421
531
  * behaviour is unchanged.
422
532
  */
423
533
  itemFields?: ItemField[]
534
+ /**
535
+ * Resolves a stored image path to a displayable URL (same resolver the table
536
+ * columns use). Threaded to the ref-chip thumbnails so resolved line-item
537
+ * relations show a product photo / logo / avatar like the FK columns do.
538
+ */
539
+ getImageUrl?: (path: string) => string
424
540
  /**
425
541
  * Presentation mode.
426
542
  * - `'badge'` (default): the compact count/preview badge that opens a
@@ -448,6 +564,7 @@ export function CollectionCell({
448
564
  locale,
449
565
  t,
450
566
  itemFields,
567
+ getImageUrl,
451
568
  variant = 'badge',
452
569
  }: CollectionCellProps) {
453
570
  const parsed = parseValue(value)
@@ -491,6 +608,7 @@ export function CollectionCell({
491
608
  locale={locale}
492
609
  t={t}
493
610
  itemFields={itemFields}
611
+ getImageUrl={getImageUrl}
494
612
  />
495
613
  )
496
614
  }
@@ -506,7 +624,7 @@ export function CollectionCell({
506
624
  itemFields!
507
625
  .map(
508
626
  (field) =>
509
- `${field.label}: ${renderItemFieldValue(field, row)}`
627
+ `${field.label}: ${itemFieldText(field, row)}`
510
628
  )
511
629
  .join(', ')
512
630
  )
@@ -528,6 +646,7 @@ export function CollectionCell({
528
646
  locale={locale}
529
647
  t={t}
530
648
  itemFields={itemFields}
649
+ getImageUrl={getImageUrl}
531
650
  />
532
651
  </PopoverShell>
533
652
  )
@@ -1182,6 +1182,7 @@ function StructuredViewValue({
1182
1182
  locale?: string
1183
1183
  t?: (key: string, options?: any) => string
1184
1184
  }) {
1185
+ const getImageUrl = useContext(ImageUrlContext)
1185
1186
  const isEmpty =
1186
1187
  value === null ||
1187
1188
  value === undefined ||
@@ -1201,6 +1202,7 @@ function StructuredViewValue({
1201
1202
  variant="inline"
1202
1203
  locale={locale}
1203
1204
  t={t}
1205
+ getImageUrl={getImageUrl}
1204
1206
  />
1205
1207
  </div>
1206
1208
  )
@@ -1214,6 +1216,7 @@ export function EditField({ field, value, onChange, record }: {
1214
1216
  record?: any
1215
1217
  }) {
1216
1218
  const { t, i18n } = useTranslation()
1219
+ const editFieldImageUrl = useContext(ImageUrlContext)
1217
1220
 
1218
1221
  // Jsonb line-items columns (e.g. Transfer.items) are action-built documents:
1219
1222
  // editing the array field-by-field is out of scope. Render them READ-ONLY
@@ -1229,6 +1232,7 @@ export function EditField({ field, value, onChange, record }: {
1229
1232
  variant="inline"
1230
1233
  locale={i18n.language}
1231
1234
  t={t}
1235
+ getImageUrl={editFieldImageUrl}
1232
1236
  />
1233
1237
  </div>
1234
1238
  <p className="text-[11px] text-muted-foreground">
@@ -1180,6 +1180,7 @@ export function makeDefaultGetDynamicColumns(
1180
1180
  locale={currentLanguage}
1181
1181
  t={t}
1182
1182
  itemFields={col.itemFields ?? col.item_fields}
1183
+ getImageUrl={getImageUrl}
1183
1184
  />
1184
1185
  )
1185
1186
  }