@asteby/metacore-runtime-react 18.20.0 → 18.22.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,36 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 18.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 24cced0: The read-only record detail view now renders jsonb line-items with the same pro
8
+ rendering as the table instead of raw `JSON.stringify`. `CollectionCell` gains a
9
+ `variant?: 'badge' | 'inline'` prop (default `'badge'` = unchanged behaviour);
10
+ `'inline'` renders the mini-table / pair-list / scalar-list directly, with no
11
+ badge or popover, for the full-width detail dialog. The detail view's
12
+ `StructuredViewValue` delegates to `<CollectionCell variant="inline" …>`,
13
+ threading the field's `item_fields` schema plus locale + translator: an
14
+ `item_fields` schema drives localized headers + resolved ref labels (the
15
+ injected `{ value, label }` sibling — product name instead of the raw uuid),
16
+ and without a schema it falls back to a localized mini-table / pair list. The
17
+ "—" empty marker is preserved.
18
+
19
+ ## 18.21.0
20
+
21
+ ### Minor Changes
22
+
23
+ - 53950ed: CollectionCell renders jsonb line-items from a declared sub-field schema when
24
+ the column carries one (`col.itemFields` / snake `col.item_fields`, kernel v3
25
+ `item_fields`). Headers use the schema's already-localized `label` verbatim (in
26
+ the declared order, no prettify/translate); `ref` columns resolve to the
27
+ backend-injected sibling label — the FK key without `_id` (`product_id` →
28
+ `product`), else `<key>_label` — showing the resolved name instead of the raw
29
+ uuid (`{ value, label }` → `label`, bare string → itself, missing → truncated
30
+ uuid fallback). The badge count noun stays locale-aware. When no schema is
31
+ present the generic dict/prettify behaviour is unchanged. `itemFields` is
32
+ threaded from the dynamic columns factory callsite.
33
+
3
34
  ## 18.20.0
4
35
 
5
36
  ### Minor Changes
@@ -1,6 +1,24 @@
1
1
  import * as React from 'react';
2
2
  /** Host i18n translator (react-i18next `t`), as threaded into the columns factory. */
3
3
  export type Translate = (key: string, options?: any) => string;
4
+ /**
5
+ * Declared schema for one column of a jsonb line-items array. Mirrors the
6
+ * kernel v3 `item_fields` entry the backend serves on the column metadata
7
+ * (`col.itemFields` / snake `col.item_fields`). When present it drives the
8
+ * popover mini-table: headers come from `label` (already LOCALIZED by the
9
+ * backend — never re-translated here) and `ref` columns resolve to the
10
+ * backend-injected sibling label instead of the raw uuid.
11
+ */
12
+ export interface ItemField {
13
+ /** jsonb key this column maps to (e.g. `product_id`, `quantity`). */
14
+ key: string;
15
+ /** Header text — ALREADY localized by the backend. Used verbatim. */
16
+ label: string;
17
+ /** Declarative cell type hint (informational; not branched on today). */
18
+ type?: string;
19
+ /** FK target model. When set, the cell renders the resolved sibling label. */
20
+ ref?: string;
21
+ }
4
22
  /**
5
23
  * Localized key label for a popover column header. Resolution order:
6
24
  * (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
@@ -30,10 +48,34 @@ export interface CollectionCellProps {
30
48
  locale?: string;
31
49
  /** Host i18n translator; takes precedence over the built-in dictionary. */
32
50
  t?: Translate;
51
+ /**
52
+ * Declared schema for the jsonb line-items columns (kernel v3 `item_fields`,
53
+ * read from `col.itemFields ?? col.item_fields` at the callsite). When
54
+ * present AND the value is an array of objects, the popover mini-table uses
55
+ * these (already-localized) headers in order and resolves `ref` columns to
56
+ * the backend-injected sibling label. Absent → the generic dict/prettify
57
+ * behaviour is unchanged.
58
+ */
59
+ itemFields?: ItemField[];
60
+ /**
61
+ * Presentation mode.
62
+ * - `'badge'` (default): the compact count/preview badge that opens a
63
+ * popover with the mini-table / pair-list. Used in dense table cells.
64
+ * - `'inline'`: render the mini-table / pair-list DIRECTLY, with no badge
65
+ * or popover. Used by the read-only record detail view, which has full
66
+ * width and shows one field per row. All schema/locale logic is shared.
67
+ */
68
+ variant?: 'badge' | 'inline';
33
69
  }
34
70
  /**
35
71
  * Generic renderer for jsonb / array / object cell values. Brand-neutral,
36
72
  * compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
73
+ *
74
+ * `variant` selects the surface: the default `'badge'` shows a compact trigger
75
+ * + popover (dense table cells); `'inline'` renders the mini-table / pair-list
76
+ * directly for the full-width record detail view. Both paths share the
77
+ * itemFields schema (localized headers + resolved ref labels) and the
78
+ * locale-aware generic fallback.
37
79
  */
38
- export declare function CollectionCell({ value, maxInline, locale, t, }: CollectionCellProps): React.JSX.Element;
80
+ export declare function CollectionCell({ value, maxInline, locale, t, itemFields, variant, }: CollectionCellProps): React.JSX.Element;
39
81
  //# 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;AAiE9D;;;;;;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;AAyJD,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;CAChB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,GACJ,EAAE,mBAAmB,qBAgFrB"}
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"}
@@ -3,6 +3,36 @@ import { List } from 'lucide-react';
3
3
  import { Badge, Popover, PopoverContent, PopoverTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, cn, } from '@asteby/metacore-ui';
4
4
  import { humanizeToken } from './dynamic-columns-helpers';
5
5
  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
+ /**
7
+ * Resolves the backend-injected resolved sibling key for a ref item-field,
8
+ * mirroring `relationKeyFor` in dynamic-columns: the raw key with a trailing
9
+ * `_id` stripped (`product_id` → `product`), else `<key>_label`.
10
+ */
11
+ function siblingKeyFor(key) {
12
+ return key.endsWith('_id') ? key.slice(0, -3) : `${key}_label`;
13
+ }
14
+ /**
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)`.
20
+ */
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;
32
+ }
33
+ }
34
+ return formatScalar(row[field.key]);
35
+ }
6
36
  /** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
7
37
  function baseLang(locale) {
8
38
  return (locale || 'en').toLowerCase().split('-')[0];
@@ -164,7 +194,15 @@ function unionKeys(rows) {
164
194
  return seen;
165
195
  }
166
196
  const PANEL_CLASS = 'w-auto max-w-[480px] max-h-[320px] overflow-auto p-0';
167
- function MiniTable({ rows, locale, t, }) {
197
+ function MiniTable({ rows, locale, t, itemFields, }) {
198
+ // Schema-driven path: a declared `item_fields` schema fixes the column
199
+ // order + headers (already localized by the backend, used VERBATIM) and
200
+ // resolves ref columns to the injected sibling label instead of the raw
201
+ // uuid. Sibling/raw keys not covered by the schema are dropped from the
202
+ // table (the schema is the source of truth for what to surface).
203
+ 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))) })] }));
205
+ }
168
206
  const keys = unionKeys(rows);
169
207
  if (keys.length === 0) {
170
208
  return _jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "-" });
@@ -184,9 +222,16 @@ function PopoverShell({ label, title, children, icon = true, }) {
184
222
  /**
185
223
  * Generic renderer for jsonb / array / object cell values. Brand-neutral,
186
224
  * compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
225
+ *
226
+ * `variant` selects the surface: the default `'badge'` shows a compact trigger
227
+ * + popover (dense table cells); `'inline'` renders the mini-table / pair-list
228
+ * directly for the full-width record detail view. Both paths share the
229
+ * itemFields schema (localized headers + resolved ref labels) and the
230
+ * locale-aware generic fallback.
187
231
  */
188
- export function CollectionCell({ value, maxInline = 3, locale, t, }) {
232
+ export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, variant = 'badge', }) {
189
233
  const parsed = parseValue(value);
234
+ const inline = variant === 'inline';
190
235
  // Empty-ish → muted dash.
191
236
  if (parsed === null ||
192
237
  parsed === undefined ||
@@ -205,16 +250,35 @@ export function CollectionCell({ value, maxInline = 3, locale, t, }) {
205
250
  const allObjects = parsed.every((item) => isPlainObject(item));
206
251
  if (allObjects) {
207
252
  const rows = parsed;
253
+ // Inline mode (detail view): render the mini-table directly, no
254
+ // badge/popover. The same schema-driven path applies.
255
+ if (inline) {
256
+ return (_jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields }));
257
+ }
208
258
  const count = rows.length;
209
259
  const label = countLabel(count, locale, t);
210
- const title = rows
211
- .map((row) => Object.entries(row)
212
- .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
213
- .join(', '))
214
- .join(' | ');
215
- return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows, locale: locale, t: t }) }));
260
+ const hasSchema = !!(itemFields && itemFields.length > 0);
261
+ // The no-JS tooltip mirrors the rendered table: schema-driven
262
+ // labels + resolved ref values when a schema is present, else the
263
+ // generic prettify/scalar pairs.
264
+ const title = hasSchema
265
+ ? rows
266
+ .map((row) => itemFields
267
+ .map((field) => `${field.label}: ${renderItemFieldValue(field, row)}`)
268
+ .join(', '))
269
+ .join(' | ')
270
+ : rows
271
+ .map((row) => Object.entries(row)
272
+ .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
273
+ .join(', '))
274
+ .join(' | ');
275
+ return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields }) }));
276
+ }
277
+ // Array of scalars (or mixed). Inline mode renders the full list; badge
278
+ // mode previews the first N joined with a "+N" overflow trigger.
279
+ if (inline) {
280
+ return _jsx(ScalarList, { values: parsed });
216
281
  }
217
- // Array of scalars (or mixed): preview first N joined, "+N" overflow.
218
282
  const preview = parsed.slice(0, maxInline).map(formatScalar).join(', ');
219
283
  const overflow = parsed.length - maxInline;
220
284
  const label = overflow > 0 ? `${preview} +${overflow}` : preview;
@@ -223,12 +287,16 @@ export function CollectionCell({ value, maxInline = 3, locale, t, }) {
223
287
  }
224
288
  // PLAIN OBJECT -----------------------------------------------------------
225
289
  const entries = Object.entries(parsed);
226
- const inline = entries
290
+ // Inline mode renders the full key→value pair list directly.
291
+ if (inline) {
292
+ return _jsx(PairList, { entries: entries, locale: locale, t: t });
293
+ }
294
+ const previewPairs = entries
227
295
  .slice(0, maxInline)
228
296
  .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
229
297
  .join(', ');
230
298
  const overflow = entries.length - maxInline;
231
- const label = overflow > 0 ? `${inline} +${overflow}` : inline;
299
+ const label = overflow > 0 ? `${previewPairs} +${overflow}` : previewPairs;
232
300
  const title = entries
233
301
  .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
234
302
  .join(', ');
@@ -1,4 +1,5 @@
1
1
  import type { ModelSchema } from './types';
2
+ import { type ItemField } from '../collection-cell';
2
3
  import { type GetImageUrl } from '../image-url-context';
3
4
  export type { GetImageUrl };
4
5
  export interface FieldOption {
@@ -58,6 +59,17 @@ export interface FieldDef {
58
59
  * over the org fallback.
59
60
  */
60
61
  styleConfig?: Record<string, any>;
62
+ /**
63
+ * Declared schema for a jsonb line-items field (kernel v3 `item_fields`).
64
+ * The backend serves this on modal/detail fields the same way it does on
65
+ * table columns. When present the read-only detail view renders the
66
+ * `CollectionCell` mini-table with these (already-localized) headers in
67
+ * order and resolves `ref` columns to the backend-injected sibling label.
68
+ * Tolerates the snake_case `item_fields` the kernel serves.
69
+ */
70
+ itemFields?: ItemField[];
71
+ /** snake_case alias served by the kernel for `itemFields`. */
72
+ item_fields?: ItemField[];
61
73
  }
62
74
  export interface DynamicRecordDialogProps {
63
75
  open: boolean;
@@ -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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"}
@@ -27,6 +27,7 @@ import { isNilUuid, normalizeNilUuid } from '../nil-uuid';
27
27
  import { DynamicIcon, isLucideIconName } from '../dynamic-icon';
28
28
  import { humanizeToken } from '../dynamic-columns-helpers';
29
29
  import { formatDateCell } from '../dynamic-columns';
30
+ import { CollectionCell } from '../collection-cell';
30
31
  import { ImageUrlContext, identityImageUrl } from '../image-url-context';
31
32
  import { TimeZoneContext, CurrencyContext } from '../org-runtime-context';
32
33
  // localizedModelName resolves the (possibly addon-i18n) model name: prefer the
@@ -496,7 +497,7 @@ function RelationViewValue({ field, value, record }) {
496
497
  return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx(OptionLead, { option: lead, size: 24 }), _jsx("span", { className: "text-sm", children: label ?? '—' })] }));
497
498
  }
498
499
  export function ViewValue({ field, value: rawValue, record, getImageUrl: getImageUrlProp, timeZone: timeZoneProp, currency: currencyProp, }) {
499
- const { i18n } = useTranslation();
500
+ const { t, i18n } = useTranslation();
500
501
  const ctxImageUrl = useContext(ImageUrlContext);
501
502
  const ctxTimeZone = useContext(TimeZoneContext);
502
503
  const ctxCurrency = useContext(CurrencyContext);
@@ -592,7 +593,7 @@ export function ViewValue({ field, value: rawValue, record, getImageUrl: getImag
592
593
  // to surface — render readable key/value pairs instead of falling through to
593
594
  // String(value) ("[object Object]").
594
595
  if (value !== null && typeof value === 'object') {
595
- return _jsx(StructuredViewValue, { value: value });
596
+ return (_jsx(StructuredViewValue, { value: value, field: field, locale: i18n.language, t: t }));
596
597
  }
597
598
  const display = formatDisplayValue(value, field);
598
599
  if (field.type === 'textarea') {
@@ -606,25 +607,26 @@ export function ViewValue({ field, value: rawValue, record, getImageUrl: getImag
606
607
  function IconNameViewValue({ name }) {
607
608
  return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx("div", { className: "h-8 w-8 flex items-center justify-center rounded bg-muted", children: _jsx(DynamicIcon, { name: name, className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm text-muted-foreground", children: name })] }));
608
609
  }
609
- // StructuredViewValue renders a jsonb object/array that has no resolvable label:
610
- // plain objects become a key→value list (keys humanized), primitive arrays a
611
- // comma-joined line, and anything deeper a pretty-printed JSON block. Empty
612
- // structures render the same "—" marker as null scalars.
613
- function StructuredViewValue({ value }) {
614
- if (Array.isArray(value)) {
615
- if (value.length === 0) {
616
- return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
617
- }
618
- if (value.every(v => v === null || typeof v !== 'object')) {
619
- return _jsx("p", { className: "text-sm py-1", children: value.map(v => String(v ?? '—')).join(', ') });
620
- }
621
- return (_jsx("pre", { className: "text-xs whitespace-pre-wrap rounded-md bg-muted/40 p-3 overflow-x-auto", children: JSON.stringify(value, null, 2) }));
622
- }
623
- const entries = Object.entries(value).filter(([, v]) => v !== null && v !== undefined && v !== '');
624
- if (entries.length === 0) {
610
+ // StructuredViewValue renders a jsonb object/array that has no resolvable label.
611
+ // It delegates to the shared `CollectionCell` in `'inline'` mode so the detail
612
+ // view gets the SAME pro rendering as the table: a declared `item_fields` schema
613
+ // drives localized headers + resolved ref labels (the injected `{value,label}`
614
+ // sibling) for line-items; without a schema it falls back to a localized
615
+ // key→value pair list / mini-table — never raw `JSON.stringify`. Empty arrays /
616
+ // empty objects keep the "—" marker (CollectionCell renders a muted dash, which
617
+ // we normalize to the em-dash the detail view uses elsewhere).
618
+ function StructuredViewValue({ value, field, locale, t, }) {
619
+ const isEmpty = value === null ||
620
+ value === undefined ||
621
+ value === '' ||
622
+ (Array.isArray(value) && value.length === 0) ||
623
+ (typeof value === 'object' &&
624
+ !Array.isArray(value) &&
625
+ Object.keys(value).length === 0);
626
+ if (isEmpty) {
625
627
  return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
626
628
  }
627
- return (_jsx("dl", { className: "text-sm py-1 space-y-0.5", children: entries.map(([k, v]) => (_jsxs("div", { className: "flex gap-2", children: [_jsxs("dt", { className: "text-muted-foreground shrink-0", children: [humanizeToken(k), ":"] }), _jsx("dd", { className: "break-words", children: typeof v === 'object' ? JSON.stringify(v) : String(v) })] }, k))) }));
629
+ 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 }) }));
628
630
  }
629
631
  function EditField({ field, value, onChange }) {
630
632
  if (field.type === 'boolean') {
@@ -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,CA6nBnB;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,CA8nBnB;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 }));
739
+ return (_jsx(CollectionCell, { value: value, locale: currentLanguage, t: t, itemFields: col.itemFields ?? col.item_fields }));
740
740
  }
741
741
  if (col.key === 'description' ||
742
742
  col.key === 'features' ||
package/dist/types.d.ts CHANGED
@@ -137,6 +137,32 @@ export interface ColumnDefinition {
137
137
  * reference resolved through the OrgConfigProvider.
138
138
  */
139
139
  validation?: FieldValidation;
140
+ /**
141
+ * Declared schema for a jsonb line-items column (kernel v3 `item_fields`).
142
+ * Each entry describes one sub-field of the array's row objects: a `key`
143
+ * (the jsonb key), an already-LOCALIZED `label` (backend-translated), an
144
+ * optional `type` hint and an optional `ref` (FK target). When present the
145
+ * `CollectionCell` renders the popover mini-table with these headers in
146
+ * order and resolves `ref` columns to the backend-injected sibling label
147
+ * (the FK key without `_id`, else `<key>_label`) instead of the raw uuid.
148
+ * Tolerates the snake_case `item_fields` the kernel serves.
149
+ */
150
+ itemFields?: ColumnItemField[];
151
+ /** snake_case alias served by the kernel for `itemFields`. */
152
+ item_fields?: ColumnItemField[];
153
+ }
154
+ /**
155
+ * One declared sub-field of a jsonb line-items column (see
156
+ * `ColumnDefinition.itemFields`). `label` is already localized by the backend
157
+ * and consumed verbatim; a non-empty `ref` flags the column for resolved-label
158
+ * rendering against the injected sibling. Structurally compatible with the
159
+ * `ItemField` consumed by `collection-cell`.
160
+ */
161
+ export interface ColumnItemField {
162
+ key: string;
163
+ label: string;
164
+ type?: string;
165
+ ref?: string;
140
166
  }
141
167
  export interface ActionCondition {
142
168
  field: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "18.20.0",
3
+ "version": "18.22.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -64,8 +64,8 @@
64
64
  "typescript": "^6.0.0",
65
65
  "vitest": "^4.0.0",
66
66
  "zustand": "^5.0.0",
67
- "@asteby/metacore-sdk": "3.2.0",
68
- "@asteby/metacore-ui": "2.5.2"
67
+ "@asteby/metacore-ui": "2.5.2",
68
+ "@asteby/metacore-sdk": "3.2.0"
69
69
  },
70
70
  "scripts": {
71
71
  "build": "tsc -p tsconfig.json",
@@ -9,7 +9,7 @@
9
9
  // - locale-aware: count noun + header keys render in the org language; the
10
10
  // host `t` overrides; unknown keys fall back to snake→Title prettify.
11
11
  import { afterEach, describe, expect, it } from 'vitest'
12
- import { cleanup, render, screen } from '@testing-library/react'
12
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react'
13
13
 
14
14
  // Sin `globals: true` en vitest, RTL no auto-limpia entre tests.
15
15
  afterEach(cleanup)
@@ -176,3 +176,250 @@ describe('CollectionCell', () => {
176
176
  expect(container.textContent).toContain('{not valid json')
177
177
  })
178
178
  })
179
+
180
+ describe('CollectionCell with itemFields schema', () => {
181
+ const itemFields = [
182
+ { key: 'product_id', label: 'Producto', ref: 'Product' },
183
+ { key: 'quantity', label: 'Cantidad' },
184
+ ]
185
+
186
+ // The popover mini-table mounts lazily — open it by clicking the count
187
+ // badge (Radix opens on pointerDown + click under happy-dom).
188
+ const openPopover = (badgeText: string) => {
189
+ const badge = screen.getByText(badgeText)
190
+ fireEvent.pointerDown(badge)
191
+ fireEvent.click(badge)
192
+ return badge
193
+ }
194
+
195
+ it('uses the schema labels verbatim as headers (no prettify/dict)', () => {
196
+ render(
197
+ <CollectionCell
198
+ locale="es"
199
+ itemFields={itemFields}
200
+ value={[
201
+ {
202
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
203
+ product: { value: '550e8400-e29b-41d4-a716-446655440000', label: 'Llanta 195/65' },
204
+ quantity: 2,
205
+ },
206
+ ]}
207
+ />
208
+ )
209
+ openPopover('1 ítem')
210
+ // Headers come from the schema `label` verbatim.
211
+ expect(screen.getByRole('columnheader', { name: 'Producto' })).toBeTruthy()
212
+ expect(screen.getByRole('columnheader', { name: 'Cantidad' })).toBeTruthy()
213
+ })
214
+
215
+ it('resolves a ref field to the injected sibling label, not the uuid', () => {
216
+ render(
217
+ <CollectionCell
218
+ itemFields={itemFields}
219
+ value={[
220
+ {
221
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
222
+ product: { value: '550e8400-e29b-41d4-a716-446655440000', label: 'Llanta 195/65' },
223
+ quantity: 2,
224
+ },
225
+ ]}
226
+ />
227
+ )
228
+ openPopover('1 item')
229
+ expect(screen.getByRole('cell', { name: 'Llanta 195/65' })).toBeTruthy()
230
+ // The raw uuid (truncated form) must NOT appear in any cell.
231
+ expect(screen.queryByText('550e8400…')).toBeNull()
232
+ })
233
+
234
+ it('resolves a ref field from a `<key>_label` sibling when key has no _id suffix', () => {
235
+ render(
236
+ <CollectionCell
237
+ itemFields={[{ key: 'product', label: 'Producto', ref: 'Product' }]}
238
+ value={[
239
+ {
240
+ product: '550e8400-e29b-41d4-a716-446655440000',
241
+ product_label: { value: '550e8400-e29b-41d4-a716-446655440000', label: 'Balanceo' },
242
+ },
243
+ ]}
244
+ />
245
+ )
246
+ openPopover('1 item')
247
+ expect(screen.getByRole('cell', { name: 'Balanceo' })).toBeTruthy()
248
+ })
249
+
250
+ it('accepts a bare string sibling for a ref field', () => {
251
+ render(
252
+ <CollectionCell
253
+ itemFields={[{ key: 'product_id', label: 'Producto', ref: 'Product' }]}
254
+ value={[{ product_id: 'x', product: 'Aceite 5W30' }]}
255
+ />
256
+ )
257
+ openPopover('1 item')
258
+ expect(screen.getByRole('cell', { name: 'Aceite 5W30' })).toBeTruthy()
259
+ })
260
+
261
+ it('falls back to a truncated uuid when the ref sibling is missing', () => {
262
+ render(
263
+ <CollectionCell
264
+ itemFields={[{ key: 'product_id', label: 'Producto', ref: 'Product' }]}
265
+ value={[{ product_id: '550e8400-e29b-41d4-a716-446655440000' }]}
266
+ />
267
+ )
268
+ openPopover('1 item')
269
+ expect(screen.getByRole('cell', { name: '550e8400…' })).toBeTruthy()
270
+ })
271
+
272
+ it('keeps the locale-aware count noun on the badge', () => {
273
+ render(
274
+ <CollectionCell
275
+ locale="es"
276
+ itemFields={itemFields}
277
+ value={[
278
+ { product_id: 'a', product: { value: 'a', label: 'A' }, quantity: 1 },
279
+ { product_id: 'b', product: { value: 'b', label: 'B' }, quantity: 2 },
280
+ ]}
281
+ />
282
+ )
283
+ expect(screen.getByText('2 ítems')).toBeTruthy()
284
+ })
285
+
286
+ it('renders non-ref fields via formatScalar under the schema header', () => {
287
+ render(
288
+ <CollectionCell
289
+ itemFields={itemFields}
290
+ value={[{ product_id: 'a', product: { value: 'a', label: 'A' }, quantity: 7 }]}
291
+ />
292
+ )
293
+ openPopover('1 item')
294
+ expect(screen.getByRole('cell', { name: '7' })).toBeTruthy()
295
+ })
296
+
297
+ it('mirrors the schema labels + resolved ref values in the badge title', () => {
298
+ render(
299
+ <CollectionCell
300
+ itemFields={itemFields}
301
+ value={[
302
+ {
303
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
304
+ product: { value: 'x', label: 'Llanta 195/65' },
305
+ quantity: 2,
306
+ },
307
+ ]}
308
+ />
309
+ )
310
+ const title =
311
+ screen.getByText('1 item').closest('[title]')!.getAttribute('title') ?? ''
312
+ expect(title).toContain('Producto: Llanta 195/65')
313
+ expect(title).toContain('Cantidad: 2')
314
+ expect(title).not.toContain('550e8400')
315
+ })
316
+
317
+ it('is unchanged (generic prettify) when no itemFields are provided', () => {
318
+ render(
319
+ <CollectionCell
320
+ locale="es"
321
+ value={[{ product_id: 'abc', quantity: 2 }]}
322
+ />
323
+ )
324
+ // Generic dict path: the badge title carries the prettified headers and
325
+ // the raw (unresolved) values, exactly as before.
326
+ const title =
327
+ screen.getByText('1 ítem').closest('[title]')!.getAttribute('title') ?? ''
328
+ expect(title).toContain('Producto:')
329
+ expect(title).toContain('Cantidad: 2')
330
+ })
331
+ })
332
+
333
+ describe('CollectionCell variant="inline" (detail view)', () => {
334
+ const itemFields = [
335
+ { key: 'product_id', label: 'Producto', ref: 'Product' },
336
+ { key: 'quantity', label: 'Cantidad' },
337
+ ]
338
+
339
+ it('renders the mini-table DIRECTLY with no popover trigger / badge', () => {
340
+ render(
341
+ <CollectionCell
342
+ variant="inline"
343
+ value={[{ product_id: 'a', quantity: 2 }]}
344
+ />
345
+ )
346
+ // The table is in the DOM immediately — no click needed.
347
+ expect(screen.getByRole('table')).toBeTruthy()
348
+ // No count-badge trigger ("1 item") is rendered in inline mode.
349
+ expect(screen.queryByText('1 item')).toBeNull()
350
+ })
351
+
352
+ it('uses localized schema headers + resolved ref labels (no raw uuid/JSON)', () => {
353
+ const { container } = render(
354
+ <CollectionCell
355
+ variant="inline"
356
+ locale="es"
357
+ itemFields={itemFields}
358
+ value={[
359
+ {
360
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
361
+ product: { value: 'x', label: 'Test' },
362
+ quantity: 10,
363
+ },
364
+ ]}
365
+ />
366
+ )
367
+ // "Producto | Cantidad / Test | 10"
368
+ expect(screen.getByRole('columnheader', { name: 'Producto' })).toBeTruthy()
369
+ expect(screen.getByRole('columnheader', { name: 'Cantidad' })).toBeTruthy()
370
+ expect(screen.getByRole('cell', { name: 'Test' })).toBeTruthy()
371
+ expect(screen.getByRole('cell', { name: '10' })).toBeTruthy()
372
+ // The raw uuid must not leak, and there is no JSON.stringify <pre> block.
373
+ expect(screen.queryByText('550e8400…')).toBeNull()
374
+ expect(container.querySelector('pre')).toBeNull()
375
+ expect(container.textContent).not.toContain('550e8400-e29b')
376
+ })
377
+
378
+ it('falls back to a generic localized mini-table when no itemFields (no raw JSON)', () => {
379
+ const { container } = render(
380
+ <CollectionCell
381
+ variant="inline"
382
+ locale="es"
383
+ value={[{ product_id: 'abc', quantity: 2 }]}
384
+ />
385
+ )
386
+ // Generic dict path still drives the header; no JSON dump.
387
+ expect(screen.getByRole('columnheader', { name: 'Producto' })).toBeTruthy()
388
+ expect(screen.getByRole('cell', { name: '2' })).toBeTruthy()
389
+ expect(container.querySelector('pre')).toBeNull()
390
+ })
391
+
392
+ it('renders a plain object as an inline localized pair list (no popover)', () => {
393
+ render(
394
+ <CollectionCell
395
+ variant="inline"
396
+ locale="es"
397
+ value={{ price: 10, quantity: 20 }}
398
+ />
399
+ )
400
+ // Pair list is rendered directly; the badge "+N" preview is not used.
401
+ expect(screen.getByText('Precio:')).toBeTruthy()
402
+ expect(screen.getByText('Cantidad:')).toBeTruthy()
403
+ expect(screen.queryByRole('table')).toBeNull()
404
+ })
405
+
406
+ it('renders a scalar array as an inline list directly', () => {
407
+ render(
408
+ <CollectionCell variant="inline" value={['a', 'b', 'c', 'd', 'e']} />
409
+ )
410
+ // Full list, no "+2" overflow badge.
411
+ expect(screen.getByText('e')).toBeTruthy()
412
+ expect(screen.queryByText('a, b, c +2')).toBeNull()
413
+ })
414
+
415
+ it('keeps the muted dash for empty / null values', () => {
416
+ const { container: empty } = render(
417
+ <CollectionCell variant="inline" value={[]} />
418
+ )
419
+ expect(empty.textContent).toBe('-')
420
+ const { container: nul } = render(
421
+ <CollectionCell variant="inline" value={null} />
422
+ )
423
+ expect(nul.textContent).toBe('-')
424
+ })
425
+ })
@@ -0,0 +1,99 @@
1
+ // @vitest-environment happy-dom
2
+ //
3
+ // Detail-view wiring: the read-only "Información detallada del registro" dialog
4
+ // renders jsonb line-items through `ViewValue` → `StructuredViewValue` →
5
+ // `CollectionCell variant="inline"`. This locks in the regression fix: the
6
+ // detail view no longer dumps raw `JSON.stringify`, and an `item_fields` schema
7
+ // drives localized headers + resolved ref labels (the injected `{value,label}`
8
+ // sibling) — "Producto | Cantidad / Test | 10".
9
+ import { afterEach, describe, expect, it, vi } from 'vitest'
10
+ import { cleanup, render, screen } from '@testing-library/react'
11
+
12
+ // Identity translator + a Spanish locale, matching the host dialog.
13
+ vi.mock('react-i18next', () => ({
14
+ useTranslation: () => ({ t: (k: string) => k, i18n: { language: 'es' } }),
15
+ }))
16
+
17
+ import { ViewValue } from '../dialogs/dynamic-record'
18
+
19
+ afterEach(cleanup)
20
+
21
+ describe('detail-view jsonb line-items (ViewValue → inline CollectionCell)', () => {
22
+ const itemFields = [
23
+ { key: 'product_id', label: 'Producto', ref: 'Product' },
24
+ { key: 'quantity', label: 'Cantidad' },
25
+ ]
26
+
27
+ it('renders the schema mini-table with resolved ref labels, not raw JSON', () => {
28
+ const { container } = render(
29
+ <ViewValue
30
+ field={{ key: 'items', label: 'Items', type: 'json', itemFields }}
31
+ value={[
32
+ {
33
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
34
+ product: { value: 'x', label: 'Test' },
35
+ quantity: 10,
36
+ },
37
+ ]}
38
+ record={{}}
39
+ />
40
+ )
41
+ // Localized headers from the schema (verbatim) + resolved ref value.
42
+ expect(screen.getByRole('columnheader', { name: 'Producto' })).toBeTruthy()
43
+ expect(screen.getByRole('columnheader', { name: 'Cantidad' })).toBeTruthy()
44
+ expect(screen.getByRole('cell', { name: 'Test' })).toBeTruthy()
45
+ expect(screen.getByRole('cell', { name: '10' })).toBeTruthy()
46
+ // No raw JSON dump, no leaked uuid.
47
+ expect(container.querySelector('pre')).toBeNull()
48
+ expect(container.textContent).not.toContain('550e8400-e29b')
49
+ })
50
+
51
+ it('tolerates the snake_case item_fields alias', () => {
52
+ render(
53
+ <ViewValue
54
+ field={{ key: 'items', label: 'Items', type: 'json', item_fields: itemFields }}
55
+ value={[
56
+ { product_id: 'a', product: { value: 'a', label: 'Aceite' }, quantity: 3 },
57
+ ]}
58
+ record={{}}
59
+ />
60
+ )
61
+ expect(screen.getByRole('cell', { name: 'Aceite' })).toBeTruthy()
62
+ })
63
+
64
+ it('falls back to a generic localized mini-table when no schema (no raw JSON)', () => {
65
+ const { container } = render(
66
+ <ViewValue
67
+ field={{ key: 'items', label: 'Items', type: 'json' }}
68
+ value={[{ product_id: 'abc', quantity: 2 }]}
69
+ record={{}}
70
+ />
71
+ )
72
+ // product_id → "Producto" via the built-in es dictionary; no <pre>.
73
+ expect(screen.getByRole('columnheader', { name: 'Producto' })).toBeTruthy()
74
+ expect(container.querySelector('pre')).toBeNull()
75
+ })
76
+
77
+ it('renders a plain jsonb object as a localized pair list (not [object Object])', () => {
78
+ const { container } = render(
79
+ <ViewValue
80
+ field={{ key: 'fiscal_data', label: 'Fiscal', type: 'json' }}
81
+ value={{ price: 10, quantity: 20 }}
82
+ record={{}}
83
+ />
84
+ )
85
+ expect(screen.getByText('Precio:')).toBeTruthy()
86
+ expect(container.textContent).not.toContain('[object Object]')
87
+ })
88
+
89
+ it('keeps the "—" empty marker for an empty array', () => {
90
+ const { container } = render(
91
+ <ViewValue
92
+ field={{ key: 'items', label: 'Items', type: 'json' }}
93
+ value={[]}
94
+ record={{}}
95
+ />
96
+ )
97
+ expect(container.textContent).toContain('—')
98
+ })
99
+ })
@@ -28,6 +28,59 @@ const UUID_LIKE_RE =
28
28
  /** Host i18n translator (react-i18next `t`), as threaded into the columns factory. */
29
29
  export type Translate = (key: string, options?: any) => string
30
30
 
31
+ /**
32
+ * Declared schema for one column of a jsonb line-items array. Mirrors the
33
+ * kernel v3 `item_fields` entry the backend serves on the column metadata
34
+ * (`col.itemFields` / snake `col.item_fields`). When present it drives the
35
+ * popover mini-table: headers come from `label` (already LOCALIZED by the
36
+ * backend — never re-translated here) and `ref` columns resolve to the
37
+ * backend-injected sibling label instead of the raw uuid.
38
+ */
39
+ export interface ItemField {
40
+ /** jsonb key this column maps to (e.g. `product_id`, `quantity`). */
41
+ key: string
42
+ /** Header text — ALREADY localized by the backend. Used verbatim. */
43
+ label: string
44
+ /** Declarative cell type hint (informational; not branched on today). */
45
+ type?: string
46
+ /** FK target model. When set, the cell renders the resolved sibling label. */
47
+ ref?: string
48
+ }
49
+
50
+ /**
51
+ * Resolves the backend-injected resolved sibling key for a ref item-field,
52
+ * mirroring `relationKeyFor` in dynamic-columns: the raw key with a trailing
53
+ * `_id` stripped (`product_id` → `product`), else `<key>_label`.
54
+ */
55
+ function siblingKeyFor(key: string): string {
56
+ return key.endsWith('_id') ? key.slice(0, -3) : `${key}_label`
57
+ }
58
+
59
+ /**
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)`.
65
+ */
66
+ function renderItemFieldValue(
67
+ field: ItemField,
68
+ 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)
76
+ }
77
+ } else if (typeof sibling === 'string' && sibling !== '') {
78
+ return sibling
79
+ }
80
+ }
81
+ return formatScalar(row[field.key])
82
+ }
83
+
31
84
  /** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
32
85
  function baseLang(locale?: string): string {
33
86
  return (locale || 'en').toLowerCase().split('-')[0]
@@ -204,11 +257,51 @@ function MiniTable({
204
257
  rows,
205
258
  locale,
206
259
  t,
260
+ itemFields,
207
261
  }: {
208
262
  rows: Record<string, unknown>[]
209
263
  locale?: string
210
264
  t?: Translate
265
+ itemFields?: ItemField[]
211
266
  }) {
267
+ // Schema-driven path: a declared `item_fields` schema fixes the column
268
+ // order + headers (already localized by the backend, used VERBATIM) and
269
+ // resolves ref columns to the injected sibling label instead of the raw
270
+ // uuid. Sibling/raw keys not covered by the schema are dropped from the
271
+ // table (the schema is the source of truth for what to surface).
272
+ if (itemFields && itemFields.length > 0) {
273
+ return (
274
+ <Table>
275
+ <TableHeader>
276
+ <TableRow>
277
+ {itemFields.map((field) => (
278
+ <TableHead
279
+ key={field.key}
280
+ className="text-xs whitespace-nowrap"
281
+ >
282
+ {field.label}
283
+ </TableHead>
284
+ ))}
285
+ </TableRow>
286
+ </TableHeader>
287
+ <TableBody>
288
+ {rows.map((row, i) => (
289
+ <TableRow key={i}>
290
+ {itemFields.map((field) => (
291
+ <TableCell
292
+ key={field.key}
293
+ className="text-xs whitespace-nowrap"
294
+ >
295
+ {renderItemFieldValue(field, row)}
296
+ </TableCell>
297
+ ))}
298
+ </TableRow>
299
+ ))}
300
+ </TableBody>
301
+ </Table>
302
+ )
303
+ }
304
+
212
305
  const keys = unionKeys(rows)
213
306
  if (keys.length === 0) {
214
307
  return <div className="p-3 text-xs text-muted-foreground">-</div>
@@ -319,19 +412,46 @@ export interface CollectionCellProps {
319
412
  locale?: string
320
413
  /** Host i18n translator; takes precedence over the built-in dictionary. */
321
414
  t?: Translate
415
+ /**
416
+ * Declared schema for the jsonb line-items columns (kernel v3 `item_fields`,
417
+ * read from `col.itemFields ?? col.item_fields` at the callsite). When
418
+ * present AND the value is an array of objects, the popover mini-table uses
419
+ * these (already-localized) headers in order and resolves `ref` columns to
420
+ * the backend-injected sibling label. Absent → the generic dict/prettify
421
+ * behaviour is unchanged.
422
+ */
423
+ itemFields?: ItemField[]
424
+ /**
425
+ * Presentation mode.
426
+ * - `'badge'` (default): the compact count/preview badge that opens a
427
+ * popover with the mini-table / pair-list. Used in dense table cells.
428
+ * - `'inline'`: render the mini-table / pair-list DIRECTLY, with no badge
429
+ * or popover. Used by the read-only record detail view, which has full
430
+ * width and shows one field per row. All schema/locale logic is shared.
431
+ */
432
+ variant?: 'badge' | 'inline'
322
433
  }
323
434
 
324
435
  /**
325
436
  * Generic renderer for jsonb / array / object cell values. Brand-neutral,
326
437
  * compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
438
+ *
439
+ * `variant` selects the surface: the default `'badge'` shows a compact trigger
440
+ * + popover (dense table cells); `'inline'` renders the mini-table / pair-list
441
+ * directly for the full-width record detail view. Both paths share the
442
+ * itemFields schema (localized headers + resolved ref labels) and the
443
+ * locale-aware generic fallback.
327
444
  */
328
445
  export function CollectionCell({
329
446
  value,
330
447
  maxInline = 3,
331
448
  locale,
332
449
  t,
450
+ itemFields,
451
+ variant = 'badge',
333
452
  }: CollectionCellProps) {
334
453
  const parsed = parseValue(value)
454
+ const inline = variant === 'inline'
335
455
 
336
456
  // Empty-ish → muted dash.
337
457
  if (
@@ -362,26 +482,62 @@ export function CollectionCell({
362
482
  const allObjects = parsed.every((item) => isPlainObject(item))
363
483
  if (allObjects) {
364
484
  const rows = parsed as Record<string, unknown>[]
485
+ // Inline mode (detail view): render the mini-table directly, no
486
+ // badge/popover. The same schema-driven path applies.
487
+ if (inline) {
488
+ return (
489
+ <MiniTable
490
+ rows={rows}
491
+ locale={locale}
492
+ t={t}
493
+ itemFields={itemFields}
494
+ />
495
+ )
496
+ }
365
497
  const count = rows.length
366
498
  const label = countLabel(count, locale, t)
367
- const title = rows
368
- .map((row) =>
369
- Object.entries(row)
370
- .map(
371
- ([k, v]) =>
372
- `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`
373
- )
374
- .join(', ')
375
- )
376
- .join(' | ')
499
+ const hasSchema = !!(itemFields && itemFields.length > 0)
500
+ // The no-JS tooltip mirrors the rendered table: schema-driven
501
+ // labels + resolved ref values when a schema is present, else the
502
+ // generic prettify/scalar pairs.
503
+ const title = hasSchema
504
+ ? rows
505
+ .map((row) =>
506
+ itemFields!
507
+ .map(
508
+ (field) =>
509
+ `${field.label}: ${renderItemFieldValue(field, row)}`
510
+ )
511
+ .join(', ')
512
+ )
513
+ .join(' | ')
514
+ : rows
515
+ .map((row) =>
516
+ Object.entries(row)
517
+ .map(
518
+ ([k, v]) =>
519
+ `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`
520
+ )
521
+ .join(', ')
522
+ )
523
+ .join(' | ')
377
524
  return (
378
525
  <PopoverShell label={label} title={title}>
379
- <MiniTable rows={rows} locale={locale} t={t} />
526
+ <MiniTable
527
+ rows={rows}
528
+ locale={locale}
529
+ t={t}
530
+ itemFields={itemFields}
531
+ />
380
532
  </PopoverShell>
381
533
  )
382
534
  }
383
535
 
384
- // Array of scalars (or mixed): preview first N joined, "+N" overflow.
536
+ // Array of scalars (or mixed). Inline mode renders the full list; badge
537
+ // mode previews the first N joined with a "+N" overflow trigger.
538
+ if (inline) {
539
+ return <ScalarList values={parsed} />
540
+ }
385
541
  const preview = parsed.slice(0, maxInline).map(formatScalar).join(', ')
386
542
  const overflow = parsed.length - maxInline
387
543
  const label =
@@ -396,12 +552,16 @@ export function CollectionCell({
396
552
 
397
553
  // PLAIN OBJECT -----------------------------------------------------------
398
554
  const entries = Object.entries(parsed)
399
- const inline = entries
555
+ // Inline mode renders the full key→value pair list directly.
556
+ if (inline) {
557
+ return <PairList entries={entries} locale={locale} t={t} />
558
+ }
559
+ const previewPairs = entries
400
560
  .slice(0, maxInline)
401
561
  .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
402
562
  .join(', ')
403
563
  const overflow = entries.length - maxInline
404
- const label = overflow > 0 ? `${inline} +${overflow}` : inline
564
+ const label = overflow > 0 ? `${previewPairs} +${overflow}` : previewPairs
405
565
  const title = entries
406
566
  .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
407
567
  .join(', ')
@@ -56,6 +56,7 @@ import { isNilUuid, normalizeNilUuid } from '../nil-uuid'
56
56
  import { DynamicIcon, isLucideIconName } from '../dynamic-icon'
57
57
  import { humanizeToken } from '../dynamic-columns-helpers'
58
58
  import { formatDateCell } from '../dynamic-columns'
59
+ import { CollectionCell, type ItemField } from '../collection-cell'
59
60
  import type { ActionFieldDef, RelationMeta } from '../types'
60
61
  import { ImageUrlContext, identityImageUrl, type GetImageUrl } from '../image-url-context'
61
62
  import { TimeZoneContext, CurrencyContext } from '../org-runtime-context'
@@ -122,6 +123,17 @@ export interface FieldDef {
122
123
  * over the org fallback.
123
124
  */
124
125
  styleConfig?: Record<string, any>
126
+ /**
127
+ * Declared schema for a jsonb line-items field (kernel v3 `item_fields`).
128
+ * The backend serves this on modal/detail fields the same way it does on
129
+ * table columns. When present the read-only detail view renders the
130
+ * `CollectionCell` mini-table with these (already-localized) headers in
131
+ * order and resolves `ref` columns to the backend-injected sibling label.
132
+ * Tolerates the snake_case `item_fields` the kernel serves.
133
+ */
134
+ itemFields?: ItemField[]
135
+ /** snake_case alias served by the kernel for `itemFields`. */
136
+ item_fields?: ItemField[]
125
137
  }
126
138
 
127
139
  // Permissive shape: the wire payload may omit some fields (e.g. `title` is
@@ -910,7 +922,7 @@ export function ViewValue({
910
922
  /** Optional override; when omitted falls back to the nearest provider. */
911
923
  currency?: string
912
924
  }) {
913
- const { i18n } = useTranslation()
925
+ const { t, i18n } = useTranslation()
914
926
  const ctxImageUrl = useContext(ImageUrlContext)
915
927
  const ctxTimeZone = useContext(TimeZoneContext)
916
928
  const ctxCurrency = useContext(CurrencyContext)
@@ -1069,7 +1081,14 @@ export function ViewValue({
1069
1081
  // to surface — render readable key/value pairs instead of falling through to
1070
1082
  // String(value) ("[object Object]").
1071
1083
  if (value !== null && typeof value === 'object') {
1072
- return <StructuredViewValue value={value} />
1084
+ return (
1085
+ <StructuredViewValue
1086
+ value={value}
1087
+ field={field}
1088
+ locale={i18n.language}
1089
+ t={t}
1090
+ />
1091
+ )
1073
1092
  }
1074
1093
 
1075
1094
  const display = formatDisplayValue(value, field)
@@ -1099,41 +1118,46 @@ function IconNameViewValue({ name }: { name: string }) {
1099
1118
  )
1100
1119
  }
1101
1120
 
1102
- // StructuredViewValue renders a jsonb object/array that has no resolvable label:
1103
- // plain objects become a key→value list (keys humanized), primitive arrays a
1104
- // comma-joined line, and anything deeper a pretty-printed JSON block. Empty
1105
- // structures render the same "—" marker as null scalars.
1106
- function StructuredViewValue({ value }: { value: any }) {
1107
- if (Array.isArray(value)) {
1108
- if (value.length === 0) {
1109
- return <p className="text-sm py-1 text-muted-foreground">—</p>
1110
- }
1111
- if (value.every(v => v === null || typeof v !== 'object')) {
1112
- return <p className="text-sm py-1">{value.map(v => String(v ?? '—')).join(', ')}</p>
1113
- }
1114
- return (
1115
- <pre className="text-xs whitespace-pre-wrap rounded-md bg-muted/40 p-3 overflow-x-auto">
1116
- {JSON.stringify(value, null, 2)}
1117
- </pre>
1118
- )
1119
- }
1120
- const entries = Object.entries(value).filter(
1121
- ([, v]) => v !== null && v !== undefined && v !== '',
1122
- )
1123
- if (entries.length === 0) {
1121
+ // StructuredViewValue renders a jsonb object/array that has no resolvable label.
1122
+ // It delegates to the shared `CollectionCell` in `'inline'` mode so the detail
1123
+ // view gets the SAME pro rendering as the table: a declared `item_fields` schema
1124
+ // drives localized headers + resolved ref labels (the injected `{value,label}`
1125
+ // sibling) for line-items; without a schema it falls back to a localized
1126
+ // key→value pair list / mini-table — never raw `JSON.stringify`. Empty arrays /
1127
+ // empty objects keep the "—" marker (CollectionCell renders a muted dash, which
1128
+ // we normalize to the em-dash the detail view uses elsewhere).
1129
+ function StructuredViewValue({
1130
+ value,
1131
+ field,
1132
+ locale,
1133
+ t,
1134
+ }: {
1135
+ value: any
1136
+ field?: FieldDef
1137
+ locale?: string
1138
+ t?: (key: string, options?: any) => string
1139
+ }) {
1140
+ const isEmpty =
1141
+ value === null ||
1142
+ value === undefined ||
1143
+ value === '' ||
1144
+ (Array.isArray(value) && value.length === 0) ||
1145
+ (typeof value === 'object' &&
1146
+ !Array.isArray(value) &&
1147
+ Object.keys(value).length === 0)
1148
+ if (isEmpty) {
1124
1149
  return <p className="text-sm py-1 text-muted-foreground">—</p>
1125
1150
  }
1126
1151
  return (
1127
- <dl className="text-sm py-1 space-y-0.5">
1128
- {entries.map(([k, v]) => (
1129
- <div key={k} className="flex gap-2">
1130
- <dt className="text-muted-foreground shrink-0">{humanizeToken(k)}:</dt>
1131
- <dd className="break-words">
1132
- {typeof v === 'object' ? JSON.stringify(v) : String(v)}
1133
- </dd>
1134
- </div>
1135
- ))}
1136
- </dl>
1152
+ <div className="text-sm py-1">
1153
+ <CollectionCell
1154
+ value={value}
1155
+ itemFields={field?.itemFields ?? field?.item_fields}
1156
+ variant="inline"
1157
+ locale={locale}
1158
+ t={t}
1159
+ />
1160
+ </div>
1137
1161
  )
1138
1162
  }
1139
1163
 
@@ -1179,6 +1179,7 @@ export function makeDefaultGetDynamicColumns(
1179
1179
  value={value}
1180
1180
  locale={currentLanguage}
1181
1181
  t={t}
1182
+ itemFields={col.itemFields ?? col.item_fields}
1182
1183
  />
1183
1184
  )
1184
1185
  }
package/src/types.ts CHANGED
@@ -170,6 +170,33 @@ export interface ColumnDefinition {
170
170
  * reference resolved through the OrgConfigProvider.
171
171
  */
172
172
  validation?: FieldValidation
173
+ /**
174
+ * Declared schema for a jsonb line-items column (kernel v3 `item_fields`).
175
+ * Each entry describes one sub-field of the array's row objects: a `key`
176
+ * (the jsonb key), an already-LOCALIZED `label` (backend-translated), an
177
+ * optional `type` hint and an optional `ref` (FK target). When present the
178
+ * `CollectionCell` renders the popover mini-table with these headers in
179
+ * order and resolves `ref` columns to the backend-injected sibling label
180
+ * (the FK key without `_id`, else `<key>_label`) instead of the raw uuid.
181
+ * Tolerates the snake_case `item_fields` the kernel serves.
182
+ */
183
+ itemFields?: ColumnItemField[]
184
+ /** snake_case alias served by the kernel for `itemFields`. */
185
+ item_fields?: ColumnItemField[]
186
+ }
187
+
188
+ /**
189
+ * One declared sub-field of a jsonb line-items column (see
190
+ * `ColumnDefinition.itemFields`). `label` is already localized by the backend
191
+ * and consumed verbatim; a non-empty `ref` flags the column for resolved-label
192
+ * rendering against the injected sibling. Structurally compatible with the
193
+ * `ItemField` consumed by `collection-cell`.
194
+ */
195
+ export interface ColumnItemField {
196
+ key: string
197
+ label: string
198
+ type?: string
199
+ ref?: string
173
200
  }
174
201
 
175
202
  export interface ActionCondition {