@asteby/metacore-runtime-react 18.19.0 → 18.21.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 +26 -0
- package/dist/collection-cell.d.ts +48 -4
- package/dist/collection-cell.d.ts.map +1 -1
- package/dist/collection-cell.js +161 -18
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/collection-cell.test.tsx +234 -18
- package/src/collection-cell.tsx +279 -20
- package/src/dynamic-columns.tsx +8 -1
- package/src/index.ts +2 -0
- package/src/types.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 18.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 53950ed: CollectionCell renders jsonb line-items from a declared sub-field schema when
|
|
8
|
+
the column carries one (`col.itemFields` / snake `col.item_fields`, kernel v3
|
|
9
|
+
`item_fields`). Headers use the schema's already-localized `label` verbatim (in
|
|
10
|
+
the declared order, no prettify/translate); `ref` columns resolve to the
|
|
11
|
+
backend-injected sibling label — the FK key without `_id` (`product_id` →
|
|
12
|
+
`product`), else `<key>_label` — showing the resolved name instead of the raw
|
|
13
|
+
uuid (`{ value, label }` → `label`, bare string → itself, missing → truncated
|
|
14
|
+
uuid fallback). The badge count noun stays locale-aware. When no schema is
|
|
15
|
+
present the generic dict/prettify behaviour is unchanged. `itemFields` is
|
|
16
|
+
threaded from the dynamic columns factory callsite.
|
|
17
|
+
|
|
18
|
+
## 18.20.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 6ec7baf: CollectionCell is now locale-aware: jsonb/array popover headers and the item
|
|
23
|
+
count noun render in the org's language. Resolution per key: host `t(rawKey)`
|
|
24
|
+
override → built-in es/en dictionary of common data/commerce keys (product_id,
|
|
25
|
+
quantity, price, total, name, sku, …) → snake→Title prettify fallback. Count
|
|
26
|
+
noun localizes (es: ítem/ítems). Locale + translator are threaded from the
|
|
27
|
+
dynamic columns factory; defaults to English when absent.
|
|
28
|
+
|
|
3
29
|
## 18.19.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
2
|
+
/** Host i18n translator (react-i18next `t`), as threaded into the columns factory. */
|
|
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
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Localized key label for a popover column header. Resolution order:
|
|
24
|
+
* (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
|
|
25
|
+
* (b) the built-in generic es/en data/commerce dictionary;
|
|
26
|
+
* (c) snake_case → Title Case prettify (`product_id` → "Product ID").
|
|
27
|
+
* `locale` defaults to 'en' when absent.
|
|
28
|
+
*/
|
|
29
|
+
export declare function prettifyKey(key: string, locale?: string, t?: Translate): string;
|
|
30
|
+
/**
|
|
31
|
+
* Localized, pluralized count noun. Prefers the host i18n `t` (react-i18next
|
|
32
|
+
* count plural via `defaultValue`); otherwise the built-in es/en noun map.
|
|
33
|
+
*/
|
|
34
|
+
export declare function countLabel(count: number, locale?: string, t?: Translate): string;
|
|
4
35
|
/**
|
|
5
36
|
* Render a single scalar (or near-scalar) value for compact display.
|
|
6
37
|
* - uuid-like or very long (32+ char) strings → first 8 chars + "…"
|
|
@@ -13,10 +44,23 @@ export interface CollectionCellProps {
|
|
|
13
44
|
value: unknown;
|
|
14
45
|
/** Max items previewed inline for scalar arrays. */
|
|
15
46
|
maxInline?: number;
|
|
47
|
+
/** Org/UI language tag (e.g. `es`, `en-US`). Defaults to `'en'`. */
|
|
48
|
+
locale?: string;
|
|
49
|
+
/** Host i18n translator; takes precedence over the built-in dictionary. */
|
|
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[];
|
|
16
60
|
}
|
|
17
61
|
/**
|
|
18
62
|
* Generic renderer for jsonb / array / object cell values. Brand-neutral,
|
|
19
|
-
* compact, dark-mode friendly. Never throws on unexpected shapes.
|
|
63
|
+
* compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
|
|
20
64
|
*/
|
|
21
|
-
export declare function CollectionCell({ value, maxInline }: CollectionCellProps): React.JSX.Element;
|
|
65
|
+
export declare function CollectionCell({ value, maxInline, locale, t, itemFields, }: CollectionCellProps): React.JSX.Element;
|
|
22
66
|
//# 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,
|
|
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;CAC3B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,EACD,UAAU,GACb,EAAE,mBAAmB,qBAoGrB"}
|
package/dist/collection-cell.js
CHANGED
|
@@ -3,11 +3,136 @@ 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
|
-
|
|
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
|
+
}
|
|
36
|
+
/** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
|
|
37
|
+
function baseLang(locale) {
|
|
38
|
+
return (locale || 'en').toLowerCase().split('-')[0];
|
|
39
|
+
}
|
|
40
|
+
// Built-in generic data/commerce vocabulary. Localizes the common jsonb keys
|
|
41
|
+
// (line-item rows, config blobs) to the org language out of the box. This is
|
|
42
|
+
// intentionally GENERIC — no domain-specific narrative — and a host i18n bundle
|
|
43
|
+
// can override any key via the `t` resolver (which takes precedence). Keys not
|
|
44
|
+
// found here fall back to snake→Title prettify, so unknown shapes still read.
|
|
45
|
+
const KEY_DICTIONARY = {
|
|
46
|
+
es: {
|
|
47
|
+
product_id: 'Producto',
|
|
48
|
+
product: 'Producto',
|
|
49
|
+
quantity: 'Cantidad',
|
|
50
|
+
qty: 'Cantidad',
|
|
51
|
+
unit_cost: 'Costo unitario',
|
|
52
|
+
cost: 'Costo',
|
|
53
|
+
price: 'Precio',
|
|
54
|
+
total: 'Total',
|
|
55
|
+
subtotal: 'Subtotal',
|
|
56
|
+
amount: 'Importe',
|
|
57
|
+
name: 'Nombre',
|
|
58
|
+
sku: 'SKU',
|
|
59
|
+
code: 'Código',
|
|
60
|
+
date: 'Fecha',
|
|
61
|
+
notes: 'Notas',
|
|
62
|
+
reason: 'Motivo',
|
|
63
|
+
delta: 'Variación',
|
|
64
|
+
warehouse: 'Almacén',
|
|
65
|
+
description: 'Descripción',
|
|
66
|
+
id: 'ID',
|
|
67
|
+
},
|
|
68
|
+
en: {
|
|
69
|
+
product_id: 'Product',
|
|
70
|
+
product: 'Product',
|
|
71
|
+
quantity: 'Quantity',
|
|
72
|
+
qty: 'Quantity',
|
|
73
|
+
unit_cost: 'Unit Cost',
|
|
74
|
+
cost: 'Cost',
|
|
75
|
+
price: 'Price',
|
|
76
|
+
total: 'Total',
|
|
77
|
+
subtotal: 'Subtotal',
|
|
78
|
+
amount: 'Amount',
|
|
79
|
+
name: 'Name',
|
|
80
|
+
sku: 'SKU',
|
|
81
|
+
code: 'Code',
|
|
82
|
+
date: 'Date',
|
|
83
|
+
notes: 'Notes',
|
|
84
|
+
reason: 'Reason',
|
|
85
|
+
delta: 'Delta',
|
|
86
|
+
warehouse: 'Warehouse',
|
|
87
|
+
description: 'Description',
|
|
88
|
+
id: 'ID',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
/** Localized count noun for the array-of-objects badge (`1 ítem` / `2 ítems`). */
|
|
92
|
+
const ITEM_NOUN = {
|
|
93
|
+
es: { one: 'ítem', other: 'ítems' },
|
|
94
|
+
en: { one: 'item', other: 'items' },
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Localized key label for a popover column header. Resolution order:
|
|
98
|
+
* (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
|
|
99
|
+
* (b) the built-in generic es/en data/commerce dictionary;
|
|
100
|
+
* (c) snake_case → Title Case prettify (`product_id` → "Product ID").
|
|
101
|
+
* `locale` defaults to 'en' when absent.
|
|
102
|
+
*/
|
|
103
|
+
export function prettifyKey(key, locale, t) {
|
|
104
|
+
if (t) {
|
|
105
|
+
const translated = t(key);
|
|
106
|
+
if (translated && translated !== key)
|
|
107
|
+
return translated;
|
|
108
|
+
}
|
|
109
|
+
const lang = baseLang(locale);
|
|
110
|
+
const dict = KEY_DICTIONARY[lang] ?? KEY_DICTIONARY.en;
|
|
111
|
+
const hit = dict[key.toLowerCase()];
|
|
112
|
+
if (hit)
|
|
113
|
+
return hit;
|
|
8
114
|
const pretty = humanizeToken(key);
|
|
9
115
|
return pretty || key;
|
|
10
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Localized, pluralized count noun. Prefers the host i18n `t` (react-i18next
|
|
119
|
+
* count plural via `defaultValue`); otherwise the built-in es/en noun map.
|
|
120
|
+
*/
|
|
121
|
+
export function countLabel(count, locale, t) {
|
|
122
|
+
const lang = baseLang(locale);
|
|
123
|
+
const noun = ITEM_NOUN[lang] ?? ITEM_NOUN.en;
|
|
124
|
+
const fallback = `${count} ${count === 1 ? noun.one : noun.other}`;
|
|
125
|
+
if (t) {
|
|
126
|
+
const translated = t('runtime.collectionCell.itemCount', {
|
|
127
|
+
count,
|
|
128
|
+
defaultValue: fallback,
|
|
129
|
+
});
|
|
130
|
+
if (translated && translated !== 'runtime.collectionCell.itemCount') {
|
|
131
|
+
return translated;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return fallback;
|
|
135
|
+
}
|
|
11
136
|
/**
|
|
12
137
|
* Render a single scalar (or near-scalar) value for compact display.
|
|
13
138
|
* - uuid-like or very long (32+ char) strings → first 8 chars + "…"
|
|
@@ -69,18 +194,26 @@ function unionKeys(rows) {
|
|
|
69
194
|
return seen;
|
|
70
195
|
}
|
|
71
196
|
const PANEL_CLASS = 'w-auto max-w-[480px] max-h-[320px] overflow-auto p-0';
|
|
72
|
-
function MiniTable({ rows }) {
|
|
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
|
+
}
|
|
73
206
|
const keys = unionKeys(rows);
|
|
74
207
|
if (keys.length === 0) {
|
|
75
208
|
return _jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "-" });
|
|
76
209
|
}
|
|
77
|
-
return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: keys.map((key) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: prettifyKey(key) }, key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: keys.map((key) => (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: formatScalar(row[key]) }, key))) }, i))) })] }));
|
|
210
|
+
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) => (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: formatScalar(row[key]) }, key))) }, i))) })] }));
|
|
78
211
|
}
|
|
79
212
|
function ScalarList({ values }) {
|
|
80
213
|
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))) }));
|
|
81
214
|
}
|
|
82
|
-
function PairList({ entries }) {
|
|
83
|
-
return (_jsx("ul", { className: "p-3 space-y-1", children: entries.map(([key, v]) => (_jsxs("li", { className: "text-xs", children: [_jsxs("span", { className: "text-muted-foreground", children: [prettifyKey(key), ":"] }), ' ', _jsx("span", { className: "text-foreground", children: formatScalar(v) })] }, key))) }));
|
|
215
|
+
function PairList({ entries, locale, t, }) {
|
|
216
|
+
return (_jsx("ul", { className: "p-3 space-y-1", children: entries.map(([key, v]) => (_jsxs("li", { className: "text-xs", children: [_jsxs("span", { className: "text-muted-foreground", children: [prettifyKey(key, locale, t), ":"] }), ' ', _jsx("span", { className: "text-foreground", children: formatScalar(v) })] }, key))) }));
|
|
84
217
|
}
|
|
85
218
|
/** Compact badge trigger that opens a popover panel. */
|
|
86
219
|
function PopoverShell({ label, title, children, icon = true, }) {
|
|
@@ -88,9 +221,9 @@ function PopoverShell({ label, title, children, icon = true, }) {
|
|
|
88
221
|
}
|
|
89
222
|
/**
|
|
90
223
|
* Generic renderer for jsonb / array / object cell values. Brand-neutral,
|
|
91
|
-
* compact, dark-mode friendly. Never throws on unexpected shapes.
|
|
224
|
+
* compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
|
|
92
225
|
*/
|
|
93
|
-
export function CollectionCell({ value, maxInline = 3 }) {
|
|
226
|
+
export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, }) {
|
|
94
227
|
const parsed = parseValue(value);
|
|
95
228
|
// Empty-ish → muted dash.
|
|
96
229
|
if (parsed === null ||
|
|
@@ -111,13 +244,23 @@ export function CollectionCell({ value, maxInline = 3 }) {
|
|
|
111
244
|
if (allObjects) {
|
|
112
245
|
const rows = parsed;
|
|
113
246
|
const count = rows.length;
|
|
114
|
-
const label = count
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
247
|
+
const label = countLabel(count, locale, t);
|
|
248
|
+
const hasSchema = !!(itemFields && itemFields.length > 0);
|
|
249
|
+
// The no-JS tooltip mirrors the rendered table: schema-driven
|
|
250
|
+
// labels + resolved ref values when a schema is present, else the
|
|
251
|
+
// generic prettify/scalar pairs.
|
|
252
|
+
const title = hasSchema
|
|
253
|
+
? rows
|
|
254
|
+
.map((row) => itemFields
|
|
255
|
+
.map((field) => `${field.label}: ${renderItemFieldValue(field, row)}`)
|
|
256
|
+
.join(', '))
|
|
257
|
+
.join(' | ')
|
|
258
|
+
: rows
|
|
259
|
+
.map((row) => Object.entries(row)
|
|
260
|
+
.map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
|
|
261
|
+
.join(', '))
|
|
262
|
+
.join(' | ');
|
|
263
|
+
return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields }) }));
|
|
121
264
|
}
|
|
122
265
|
// Array of scalars (or mixed): preview first N joined, "+N" overflow.
|
|
123
266
|
const preview = parsed.slice(0, maxInline).map(formatScalar).join(', ');
|
|
@@ -130,12 +273,12 @@ export function CollectionCell({ value, maxInline = 3 }) {
|
|
|
130
273
|
const entries = Object.entries(parsed);
|
|
131
274
|
const inline = entries
|
|
132
275
|
.slice(0, maxInline)
|
|
133
|
-
.map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
|
|
276
|
+
.map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
|
|
134
277
|
.join(', ');
|
|
135
278
|
const overflow = entries.length - maxInline;
|
|
136
279
|
const label = overflow > 0 ? `${inline} +${overflow}` : inline;
|
|
137
280
|
const title = entries
|
|
138
|
-
.map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
|
|
281
|
+
.map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
|
|
139
282
|
.join(', ');
|
|
140
|
-
return (_jsx(PopoverShell, { label: label, title: title, icon: false, children: _jsx(PairList, { entries: entries }) }));
|
|
283
|
+
return (_jsx(PopoverShell, { label: label, title: title, icon: false, children: _jsx(PairList, { entries: entries, locale: locale, t: t }) }));
|
|
141
284
|
}
|
|
@@ -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,
|
|
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"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -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 });
|
|
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/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export * from './dynamic-icon';
|
|
|
22
22
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
23
23
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
24
24
|
export { humanizeToken } from './dynamic-columns-helpers';
|
|
25
|
-
export { CollectionCell, formatScalar, prettifyKey, type CollectionCellProps, } from './collection-cell';
|
|
25
|
+
export { CollectionCell, formatScalar, prettifyKey, countLabel, type CollectionCellProps, type Translate as CollectionCellTranslate, } from './collection-cell';
|
|
26
26
|
export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
27
27
|
export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record';
|
|
28
28
|
export type { DynamicRecordDialogProps, FieldDef, FieldOption, GetImageUrl } from './dialogs/dynamic-record';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,IAAI,uBAAuB,GAC5C,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederation
|
|
|
26
26
|
export * from './dynamic-icon';
|
|
27
27
|
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, } from './dynamic-columns';
|
|
28
28
|
export { humanizeToken } from './dynamic-columns-helpers';
|
|
29
|
-
export { CollectionCell, formatScalar, prettifyKey, } from './collection-cell';
|
|
29
|
+
export { CollectionCell, formatScalar, prettifyKey, countLabel, } from './collection-cell';
|
|
30
30
|
export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
31
31
|
export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record';
|
|
32
32
|
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
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;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
// - plain object → inline key: value pairs
|
|
7
7
|
// - null / empty → "-"
|
|
8
8
|
// - JSON-string value is defensively parsed
|
|
9
|
+
// - locale-aware: count noun + header keys render in the org language; the
|
|
10
|
+
// host `t` overrides; unknown keys fall back to snake→Title prettify.
|
|
9
11
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
10
|
-
import { cleanup, render, screen } from '@testing-library/react'
|
|
12
|
+
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
11
13
|
|
|
12
14
|
// Sin `globals: true` en vitest, RTL no auto-limpia entre tests.
|
|
13
15
|
afterEach(cleanup)
|
|
14
16
|
|
|
15
17
|
import {
|
|
16
18
|
CollectionCell,
|
|
19
|
+
countLabel,
|
|
17
20
|
formatScalar,
|
|
18
21
|
prettifyKey,
|
|
19
22
|
} from '../collection-cell'
|
|
@@ -41,14 +44,50 @@ describe('formatScalar', () => {
|
|
|
41
44
|
})
|
|
42
45
|
|
|
43
46
|
describe('prettifyKey', () => {
|
|
44
|
-
it('
|
|
45
|
-
expect(prettifyKey('product_id')).toBe('
|
|
46
|
-
expect(prettifyKey('quantity')).toBe('
|
|
47
|
+
it('localizes common data/commerce keys to Spanish', () => {
|
|
48
|
+
expect(prettifyKey('product_id', 'es')).toBe('Producto')
|
|
49
|
+
expect(prettifyKey('quantity', 'es')).toBe('Cantidad')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('localizes common keys to English (default locale)', () => {
|
|
53
|
+
expect(prettifyKey('product_id')).toBe('Product')
|
|
54
|
+
expect(prettifyKey('product_id', 'en')).toBe('Product')
|
|
55
|
+
expect(prettifyKey('quantity', 'en')).toBe('Quantity')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('accepts a regional tag and normalizes to base language', () => {
|
|
59
|
+
expect(prettifyKey('quantity', 'es-MX')).toBe('Cantidad')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('prefers a host `t` translation over the built-in dictionary', () => {
|
|
63
|
+
const t = (k: string) => (k === 'quantity' ? 'Piezas' : k)
|
|
64
|
+
expect(prettifyKey('quantity', 'es', t)).toBe('Piezas')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('falls back to snake→Title prettify for unknown keys', () => {
|
|
68
|
+
expect(prettifyKey('shelf_position', 'es')).toBe('Shelf Position')
|
|
69
|
+
expect(prettifyKey('warehouse_bin', 'en')).toBe('Warehouse Bin')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('countLabel', () => {
|
|
74
|
+
it('pluralizes the count noun per locale', () => {
|
|
75
|
+
expect(countLabel(1, 'es')).toBe('1 ítem')
|
|
76
|
+
expect(countLabel(2, 'es')).toBe('2 ítems')
|
|
77
|
+
expect(countLabel(1, 'en')).toBe('1 item')
|
|
78
|
+
expect(countLabel(3, 'en')).toBe('3 items')
|
|
79
|
+
expect(countLabel(1)).toBe('1 item') // default → en
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('prefers a host `t` count plural', () => {
|
|
83
|
+
const t = (_k: string, o?: any) =>
|
|
84
|
+
o?.count === 1 ? '1 renglón' : `${o?.count} renglones`
|
|
85
|
+
expect(countLabel(2, 'es', t)).toBe('2 renglones')
|
|
47
86
|
})
|
|
48
87
|
})
|
|
49
88
|
|
|
50
89
|
describe('CollectionCell', () => {
|
|
51
|
-
it('renders
|
|
90
|
+
it('renders an English count badge by default for an array of objects', () => {
|
|
52
91
|
render(
|
|
53
92
|
<CollectionCell
|
|
54
93
|
value={[
|
|
@@ -57,32 +96,55 @@ describe('CollectionCell', () => {
|
|
|
57
96
|
]}
|
|
58
97
|
/>
|
|
59
98
|
)
|
|
60
|
-
//
|
|
61
|
-
expect(screen.getByText('2
|
|
62
|
-
// The trigger's title carries the
|
|
63
|
-
const badge = screen.getByText('2
|
|
99
|
+
// Default locale → English plural.
|
|
100
|
+
expect(screen.getByText('2 items')).toBeTruthy()
|
|
101
|
+
// The trigger's title carries the localized rows for the no-JS fallback.
|
|
102
|
+
const badge = screen.getByText('2 items').closest('[title]')
|
|
64
103
|
expect(badge).toBeTruthy()
|
|
65
104
|
const title = badge!.getAttribute('title') ?? ''
|
|
66
105
|
expect(title).toContain('Quantity: 2')
|
|
67
106
|
expect(title).toContain('Quantity: 5')
|
|
68
|
-
expect(title).toContain('Product
|
|
107
|
+
expect(title).toContain('Product:') // product_id → "Product" (en)
|
|
69
108
|
})
|
|
70
109
|
|
|
71
|
-
it('renders
|
|
72
|
-
render(
|
|
110
|
+
it('renders Spanish count + headers when locale is es', () => {
|
|
111
|
+
render(
|
|
112
|
+
<CollectionCell
|
|
113
|
+
locale="es"
|
|
114
|
+
value={[
|
|
115
|
+
{ product_id: 'abc', quantity: 2 },
|
|
116
|
+
{ product_id: 'def', quantity: 5 },
|
|
117
|
+
]}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
expect(screen.getByText('2 ítems')).toBeTruthy()
|
|
121
|
+
const title =
|
|
122
|
+
screen.getByText('2 ítems').closest('[title]')!.getAttribute('title') ??
|
|
123
|
+
''
|
|
124
|
+
expect(title).toContain('Producto:')
|
|
125
|
+
expect(title).toContain('Cantidad: 2')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('renders the singular Spanish noun for a single-item array', () => {
|
|
129
|
+
render(<CollectionCell locale="es" value={[{ sku: 'A1' }]} />)
|
|
73
130
|
expect(screen.getByText('1 ítem')).toBeTruthy()
|
|
74
131
|
})
|
|
75
132
|
|
|
133
|
+
it('renders the singular English noun for a single-item array', () => {
|
|
134
|
+
render(<CollectionCell value={[{ sku: 'A1' }]} />)
|
|
135
|
+
expect(screen.getByText('1 item')).toBeTruthy()
|
|
136
|
+
})
|
|
137
|
+
|
|
76
138
|
it('previews the first scalars with overflow for a scalar array', () => {
|
|
77
139
|
render(<CollectionCell value={['a', 'b', 'c', 'd', 'e']} />)
|
|
78
140
|
expect(screen.getByText('a, b, c +2')).toBeTruthy()
|
|
79
141
|
})
|
|
80
142
|
|
|
81
|
-
it('renders inline key: value pairs for a plain object', () => {
|
|
82
|
-
render(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
).toBeTruthy()
|
|
143
|
+
it('renders inline key: value pairs (localized) for a plain object', () => {
|
|
144
|
+
render(
|
|
145
|
+
<CollectionCell locale="es" value={{ price: 10, quantity: 20 }} />
|
|
146
|
+
)
|
|
147
|
+
expect(screen.getByText(/Precio: 10, Cantidad: 20/)).toBeTruthy()
|
|
86
148
|
})
|
|
87
149
|
|
|
88
150
|
it('renders a dash for null / empty values', () => {
|
|
@@ -98,12 +160,13 @@ describe('CollectionCell', () => {
|
|
|
98
160
|
it('parses a JSON-string value into a collection', () => {
|
|
99
161
|
render(
|
|
100
162
|
<CollectionCell
|
|
163
|
+
locale="es"
|
|
101
164
|
value={'[{"product_id":"abc","quantity":1}]'}
|
|
102
165
|
/>
|
|
103
166
|
)
|
|
104
167
|
expect(screen.getByText('1 ítem')).toBeTruthy()
|
|
105
168
|
const badge = screen.getByText('1 ítem').closest('[title]')
|
|
106
|
-
expect(badge!.getAttribute('title')).toContain('
|
|
169
|
+
expect(badge!.getAttribute('title')).toContain('Cantidad: 1')
|
|
107
170
|
})
|
|
108
171
|
|
|
109
172
|
it('truncates an unparseable string instead of crashing', () => {
|
|
@@ -113,3 +176,156 @@ describe('CollectionCell', () => {
|
|
|
113
176
|
expect(container.textContent).toContain('{not valid json')
|
|
114
177
|
})
|
|
115
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
|
+
})
|
package/src/collection-cell.tsx
CHANGED
|
@@ -25,12 +25,173 @@ import { humanizeToken } from './dynamic-columns-helpers'
|
|
|
25
25
|
const UUID_LIKE_RE =
|
|
26
26
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
export
|
|
28
|
+
/** Host i18n translator (react-i18next `t`), as threaded into the columns factory. */
|
|
29
|
+
export type Translate = (key: string, options?: any) => string
|
|
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
|
+
|
|
84
|
+
/** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
|
|
85
|
+
function baseLang(locale?: string): string {
|
|
86
|
+
return (locale || 'en').toLowerCase().split('-')[0]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Built-in generic data/commerce vocabulary. Localizes the common jsonb keys
|
|
90
|
+
// (line-item rows, config blobs) to the org language out of the box. This is
|
|
91
|
+
// intentionally GENERIC — no domain-specific narrative — and a host i18n bundle
|
|
92
|
+
// can override any key via the `t` resolver (which takes precedence). Keys not
|
|
93
|
+
// found here fall back to snake→Title prettify, so unknown shapes still read.
|
|
94
|
+
const KEY_DICTIONARY: Record<string, Record<string, string>> = {
|
|
95
|
+
es: {
|
|
96
|
+
product_id: 'Producto',
|
|
97
|
+
product: 'Producto',
|
|
98
|
+
quantity: 'Cantidad',
|
|
99
|
+
qty: 'Cantidad',
|
|
100
|
+
unit_cost: 'Costo unitario',
|
|
101
|
+
cost: 'Costo',
|
|
102
|
+
price: 'Precio',
|
|
103
|
+
total: 'Total',
|
|
104
|
+
subtotal: 'Subtotal',
|
|
105
|
+
amount: 'Importe',
|
|
106
|
+
name: 'Nombre',
|
|
107
|
+
sku: 'SKU',
|
|
108
|
+
code: 'Código',
|
|
109
|
+
date: 'Fecha',
|
|
110
|
+
notes: 'Notas',
|
|
111
|
+
reason: 'Motivo',
|
|
112
|
+
delta: 'Variación',
|
|
113
|
+
warehouse: 'Almacén',
|
|
114
|
+
description: 'Descripción',
|
|
115
|
+
id: 'ID',
|
|
116
|
+
},
|
|
117
|
+
en: {
|
|
118
|
+
product_id: 'Product',
|
|
119
|
+
product: 'Product',
|
|
120
|
+
quantity: 'Quantity',
|
|
121
|
+
qty: 'Quantity',
|
|
122
|
+
unit_cost: 'Unit Cost',
|
|
123
|
+
cost: 'Cost',
|
|
124
|
+
price: 'Price',
|
|
125
|
+
total: 'Total',
|
|
126
|
+
subtotal: 'Subtotal',
|
|
127
|
+
amount: 'Amount',
|
|
128
|
+
name: 'Name',
|
|
129
|
+
sku: 'SKU',
|
|
130
|
+
code: 'Code',
|
|
131
|
+
date: 'Date',
|
|
132
|
+
notes: 'Notes',
|
|
133
|
+
reason: 'Reason',
|
|
134
|
+
delta: 'Delta',
|
|
135
|
+
warehouse: 'Warehouse',
|
|
136
|
+
description: 'Description',
|
|
137
|
+
id: 'ID',
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Localized count noun for the array-of-objects badge (`1 ítem` / `2 ítems`). */
|
|
142
|
+
const ITEM_NOUN: Record<string, { one: string; other: string }> = {
|
|
143
|
+
es: { one: 'ítem', other: 'ítems' },
|
|
144
|
+
en: { one: 'item', other: 'items' },
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Localized key label for a popover column header. Resolution order:
|
|
149
|
+
* (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
|
|
150
|
+
* (b) the built-in generic es/en data/commerce dictionary;
|
|
151
|
+
* (c) snake_case → Title Case prettify (`product_id` → "Product ID").
|
|
152
|
+
* `locale` defaults to 'en' when absent.
|
|
153
|
+
*/
|
|
154
|
+
export function prettifyKey(
|
|
155
|
+
key: string,
|
|
156
|
+
locale?: string,
|
|
157
|
+
t?: Translate,
|
|
158
|
+
): string {
|
|
159
|
+
if (t) {
|
|
160
|
+
const translated = t(key)
|
|
161
|
+
if (translated && translated !== key) return translated
|
|
162
|
+
}
|
|
163
|
+
const lang = baseLang(locale)
|
|
164
|
+
const dict = KEY_DICTIONARY[lang] ?? KEY_DICTIONARY.en
|
|
165
|
+
const hit = dict[key.toLowerCase()]
|
|
166
|
+
if (hit) return hit
|
|
30
167
|
const pretty = humanizeToken(key)
|
|
31
168
|
return pretty || key
|
|
32
169
|
}
|
|
33
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Localized, pluralized count noun. Prefers the host i18n `t` (react-i18next
|
|
173
|
+
* count plural via `defaultValue`); otherwise the built-in es/en noun map.
|
|
174
|
+
*/
|
|
175
|
+
export function countLabel(
|
|
176
|
+
count: number,
|
|
177
|
+
locale?: string,
|
|
178
|
+
t?: Translate,
|
|
179
|
+
): string {
|
|
180
|
+
const lang = baseLang(locale)
|
|
181
|
+
const noun = ITEM_NOUN[lang] ?? ITEM_NOUN.en
|
|
182
|
+
const fallback = `${count} ${count === 1 ? noun.one : noun.other}`
|
|
183
|
+
if (t) {
|
|
184
|
+
const translated = t('runtime.collectionCell.itemCount', {
|
|
185
|
+
count,
|
|
186
|
+
defaultValue: fallback,
|
|
187
|
+
})
|
|
188
|
+
if (translated && translated !== 'runtime.collectionCell.itemCount') {
|
|
189
|
+
return translated
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return fallback
|
|
193
|
+
}
|
|
194
|
+
|
|
34
195
|
/**
|
|
35
196
|
* Render a single scalar (or near-scalar) value for compact display.
|
|
36
197
|
* - uuid-like or very long (32+ char) strings → first 8 chars + "…"
|
|
@@ -92,7 +253,55 @@ function unionKeys(rows: Record<string, unknown>[]): string[] {
|
|
|
92
253
|
|
|
93
254
|
const PANEL_CLASS = 'w-auto max-w-[480px] max-h-[320px] overflow-auto p-0'
|
|
94
255
|
|
|
95
|
-
function MiniTable({
|
|
256
|
+
function MiniTable({
|
|
257
|
+
rows,
|
|
258
|
+
locale,
|
|
259
|
+
t,
|
|
260
|
+
itemFields,
|
|
261
|
+
}: {
|
|
262
|
+
rows: Record<string, unknown>[]
|
|
263
|
+
locale?: string
|
|
264
|
+
t?: Translate
|
|
265
|
+
itemFields?: ItemField[]
|
|
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
|
+
|
|
96
305
|
const keys = unionKeys(rows)
|
|
97
306
|
if (keys.length === 0) {
|
|
98
307
|
return <div className="p-3 text-xs text-muted-foreground">-</div>
|
|
@@ -103,7 +312,7 @@ function MiniTable({ rows }: { rows: Record<string, unknown>[] }) {
|
|
|
103
312
|
<TableRow>
|
|
104
313
|
{keys.map((key) => (
|
|
105
314
|
<TableHead key={key} className="text-xs whitespace-nowrap">
|
|
106
|
-
{prettifyKey(key)}
|
|
315
|
+
{prettifyKey(key, locale, t)}
|
|
107
316
|
</TableHead>
|
|
108
317
|
))}
|
|
109
318
|
</TableRow>
|
|
@@ -138,13 +347,21 @@ function ScalarList({ values }: { values: unknown[] }) {
|
|
|
138
347
|
)
|
|
139
348
|
}
|
|
140
349
|
|
|
141
|
-
function PairList({
|
|
350
|
+
function PairList({
|
|
351
|
+
entries,
|
|
352
|
+
locale,
|
|
353
|
+
t,
|
|
354
|
+
}: {
|
|
355
|
+
entries: [string, unknown][]
|
|
356
|
+
locale?: string
|
|
357
|
+
t?: Translate
|
|
358
|
+
}) {
|
|
142
359
|
return (
|
|
143
360
|
<ul className="p-3 space-y-1">
|
|
144
361
|
{entries.map(([key, v]) => (
|
|
145
362
|
<li key={key} className="text-xs">
|
|
146
363
|
<span className="text-muted-foreground">
|
|
147
|
-
{prettifyKey(key)}:
|
|
364
|
+
{prettifyKey(key, locale, t)}:
|
|
148
365
|
</span>{' '}
|
|
149
366
|
<span className="text-foreground">{formatScalar(v)}</span>
|
|
150
367
|
</li>
|
|
@@ -191,13 +408,32 @@ export interface CollectionCellProps {
|
|
|
191
408
|
value: unknown
|
|
192
409
|
/** Max items previewed inline for scalar arrays. */
|
|
193
410
|
maxInline?: number
|
|
411
|
+
/** Org/UI language tag (e.g. `es`, `en-US`). Defaults to `'en'`. */
|
|
412
|
+
locale?: string
|
|
413
|
+
/** Host i18n translator; takes precedence over the built-in dictionary. */
|
|
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[]
|
|
194
424
|
}
|
|
195
425
|
|
|
196
426
|
/**
|
|
197
427
|
* Generic renderer for jsonb / array / object cell values. Brand-neutral,
|
|
198
|
-
* compact, dark-mode friendly. Never throws on unexpected shapes.
|
|
428
|
+
* compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
|
|
199
429
|
*/
|
|
200
|
-
export function CollectionCell({
|
|
430
|
+
export function CollectionCell({
|
|
431
|
+
value,
|
|
432
|
+
maxInline = 3,
|
|
433
|
+
locale,
|
|
434
|
+
t,
|
|
435
|
+
itemFields,
|
|
436
|
+
}: CollectionCellProps) {
|
|
201
437
|
const parsed = parseValue(value)
|
|
202
438
|
|
|
203
439
|
// Empty-ish → muted dash.
|
|
@@ -230,17 +466,40 @@ export function CollectionCell({ value, maxInline = 3 }: CollectionCellProps) {
|
|
|
230
466
|
if (allObjects) {
|
|
231
467
|
const rows = parsed as Record<string, unknown>[]
|
|
232
468
|
const count = rows.length
|
|
233
|
-
const label = count
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
469
|
+
const label = countLabel(count, locale, t)
|
|
470
|
+
const hasSchema = !!(itemFields && itemFields.length > 0)
|
|
471
|
+
// The no-JS tooltip mirrors the rendered table: schema-driven
|
|
472
|
+
// labels + resolved ref values when a schema is present, else the
|
|
473
|
+
// generic prettify/scalar pairs.
|
|
474
|
+
const title = hasSchema
|
|
475
|
+
? rows
|
|
476
|
+
.map((row) =>
|
|
477
|
+
itemFields!
|
|
478
|
+
.map(
|
|
479
|
+
(field) =>
|
|
480
|
+
`${field.label}: ${renderItemFieldValue(field, row)}`
|
|
481
|
+
)
|
|
482
|
+
.join(', ')
|
|
483
|
+
)
|
|
484
|
+
.join(' | ')
|
|
485
|
+
: rows
|
|
486
|
+
.map((row) =>
|
|
487
|
+
Object.entries(row)
|
|
488
|
+
.map(
|
|
489
|
+
([k, v]) =>
|
|
490
|
+
`${prettifyKey(k, locale, t)}: ${formatScalar(v)}`
|
|
491
|
+
)
|
|
492
|
+
.join(', ')
|
|
493
|
+
)
|
|
494
|
+
.join(' | ')
|
|
241
495
|
return (
|
|
242
496
|
<PopoverShell label={label} title={title}>
|
|
243
|
-
<MiniTable
|
|
497
|
+
<MiniTable
|
|
498
|
+
rows={rows}
|
|
499
|
+
locale={locale}
|
|
500
|
+
t={t}
|
|
501
|
+
itemFields={itemFields}
|
|
502
|
+
/>
|
|
244
503
|
</PopoverShell>
|
|
245
504
|
)
|
|
246
505
|
}
|
|
@@ -262,16 +521,16 @@ export function CollectionCell({ value, maxInline = 3 }: CollectionCellProps) {
|
|
|
262
521
|
const entries = Object.entries(parsed)
|
|
263
522
|
const inline = entries
|
|
264
523
|
.slice(0, maxInline)
|
|
265
|
-
.map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
|
|
524
|
+
.map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
|
|
266
525
|
.join(', ')
|
|
267
526
|
const overflow = entries.length - maxInline
|
|
268
527
|
const label = overflow > 0 ? `${inline} +${overflow}` : inline
|
|
269
528
|
const title = entries
|
|
270
|
-
.map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
|
|
529
|
+
.map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
|
|
271
530
|
.join(', ')
|
|
272
531
|
return (
|
|
273
532
|
<PopoverShell label={label} title={title} icon={false}>
|
|
274
|
-
<PairList entries={entries} />
|
|
533
|
+
<PairList entries={entries} locale={locale} t={t} />
|
|
275
534
|
</PopoverShell>
|
|
276
535
|
)
|
|
277
536
|
}
|
package/src/dynamic-columns.tsx
CHANGED
|
@@ -1174,7 +1174,14 @@ export function makeDefaultGetDynamicColumns(
|
|
|
1174
1174
|
|
|
1175
1175
|
default: {
|
|
1176
1176
|
if (typeof value === 'object' && value !== null) {
|
|
1177
|
-
return
|
|
1177
|
+
return (
|
|
1178
|
+
<CollectionCell
|
|
1179
|
+
value={value}
|
|
1180
|
+
locale={currentLanguage}
|
|
1181
|
+
t={t}
|
|
1182
|
+
itemFields={col.itemFields ?? col.item_fields}
|
|
1183
|
+
/>
|
|
1184
|
+
)
|
|
1178
1185
|
}
|
|
1179
1186
|
if (
|
|
1180
1187
|
col.key === 'description' ||
|
package/src/index.ts
CHANGED
|
@@ -105,7 +105,9 @@ export {
|
|
|
105
105
|
CollectionCell,
|
|
106
106
|
formatScalar,
|
|
107
107
|
prettifyKey,
|
|
108
|
+
countLabel,
|
|
108
109
|
type CollectionCellProps,
|
|
110
|
+
type Translate as CollectionCellTranslate,
|
|
109
111
|
} from './collection-cell'
|
|
110
112
|
export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid'
|
|
111
113
|
export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record'
|
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 {
|