@asteby/metacore-runtime-react 13.10.2 → 14.0.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 +36 -0
- package/dist/dynamic-columns.d.ts +19 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +70 -2
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +33 -16
- 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 +16 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/relation-option-cells.test.ts +53 -0
- package/src/dynamic-columns.tsx +83 -2
- package/src/dynamic-table.tsx +30 -16
- package/src/index.ts +2 -0
- package/src/types.ts +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 14.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6299af7: Pro dynamic-table cells + relation/option multi-select filters
|
|
8
|
+
|
|
9
|
+
`DynamicTable` now renders resolved FK relations and option/type columns, and
|
|
10
|
+
filters them server-side — generically, for every declarative addon.
|
|
11
|
+
|
|
12
|
+
**Cells (`dynamic-columns.tsx`)**
|
|
13
|
+
- `relation` renderer: a column carrying a `ref` (belongs_to FK) or
|
|
14
|
+
`cellStyle: 'relation'` renders the backend-resolved sibling
|
|
15
|
+
`row[<key without _id>] = { value, label }` as a clean truncated chip
|
|
16
|
+
(e.g. `category_id` → `row.category.label`). Falls back to the raw id, then
|
|
17
|
+
to an empty marker. Mirrors how `created_by` ships as a `{ name, avatar }`
|
|
18
|
+
sibling for the `creator` renderer.
|
|
19
|
+
- option/type badge: a `select`-style column shipping inline localized
|
|
20
|
+
`options: [{ value, label, color, icon }]` renders the matched option's label
|
|
21
|
+
as a colored `OptionBadge` (e.g. `product_type: "storable"` → the
|
|
22
|
+
"Almacenable" badge), reusing the same badge path as `badge`/`status`.
|
|
23
|
+
|
|
24
|
+
**Filters (`dynamic-table.tsx` + `FilterableColumnHeader`)**
|
|
25
|
+
- New `dynamic_select` filter type: a `filterable` `ref` column loads its
|
|
26
|
+
options from `searchEndpoint = /options/<ref>` (prefetched + cached into
|
|
27
|
+
`filterOptionsMap`) and renders the same multi-value checkbox combobox as
|
|
28
|
+
`select`. The backend's explicit `column.filterType` wins; otherwise it is
|
|
29
|
+
inferred from the column shape.
|
|
30
|
+
- `select` and `dynamic_select` filters support MULTIPLE selected values
|
|
31
|
+
(already Set-based in the header; the gate/active-count/loading states were
|
|
32
|
+
generalized to cover `dynamic_select`).
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- Updated dependencies [6299af7]
|
|
37
|
+
- @asteby/metacore-ui@2.2.0
|
|
38
|
+
|
|
3
39
|
## 13.10.2
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ColumnDefinition } from './types';
|
|
1
2
|
import type { GetDynamicColumns } from './dynamic-columns-shim';
|
|
2
3
|
/** Host-supplied helpers consumed by avatar/image cell renderers. */
|
|
3
4
|
export interface DynamicColumnsHelpers {
|
|
@@ -27,6 +28,24 @@ export interface DynamicColumnsHelpers {
|
|
|
27
28
|
* - row with no `status` field → all actions shown.
|
|
28
29
|
*/
|
|
29
30
|
export declare const isActionAllowedForRowState: (action: any, row: any) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the relation sibling object a backend serves alongside an FK column.
|
|
33
|
+
* For a column keyed `category_id` the data row also carries
|
|
34
|
+
* `row.category = { value, label }` (the FK key with the trailing `_id`
|
|
35
|
+
* stripped) — mirroring how `created_by` ships as a `{ name, avatar, email }`
|
|
36
|
+
* sibling consumed by the `creator` renderer. Returns the relation key so the
|
|
37
|
+
* cell can read `row[relationKeyFor(col)]`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const relationKeyFor: (col: Pick<ColumnDefinition, "key">) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Reads the resolved relation/option label a backend serves for an FK or
|
|
42
|
+
* option column, falling back to the raw value. Pure so the cell renderers and
|
|
43
|
+
* tests share one resolution path:
|
|
44
|
+
* - relation: prefer the sibling `{ value, label }` object's label.
|
|
45
|
+
* - option: prefer the matched `options[].label` (value compared as string).
|
|
46
|
+
* - else: the raw value coerced to string ('' when nullish).
|
|
47
|
+
*/
|
|
48
|
+
export declare const resolveRelationLabel: (col: ColumnDefinition, row: any) => string;
|
|
30
49
|
/**
|
|
31
50
|
* Builds the canonical column factory used by `<DynamicTable>` when the host
|
|
32
51
|
* does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAyCA,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;AAgGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAmHD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAStE,CAAA;AA0DD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAgmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -4,8 +4,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// badge (static + endpoint-loaded options), avatar/search, creator/user,
|
|
5
5
|
// phone, date, boolean, relation-badge-list, media-gallery, image, plus the
|
|
6
6
|
// declarative pro renderers url/link, email, currency, number, percent/
|
|
7
|
-
// progress, status, tags, color, code/truncate-text,
|
|
8
|
-
//
|
|
7
|
+
// progress, status, tags, color, code/truncate-text, relation (resolved FK
|
|
8
|
+
// chip), option/select badges, and a generic text fallback. The renderer
|
|
9
|
+
// resolves `cellStyle ?? type` for each column.
|
|
9
10
|
//
|
|
10
11
|
// The implementation was previously duplicated across multiple host apps
|
|
11
12
|
// (~550 LOC each, drifting). It now lives here so a single fix propagates
|
|
@@ -179,6 +180,49 @@ const BadgeWithEndpointOptions = ({ endpoint, value }) => {
|
|
|
179
180
|
return _jsx(OptionBadge, { option: option, fallback: String(value) });
|
|
180
181
|
return _jsx(Badge, { variant: "outline", children: String(value) });
|
|
181
182
|
};
|
|
183
|
+
/**
|
|
184
|
+
* Resolves the relation sibling object a backend serves alongside an FK column.
|
|
185
|
+
* For a column keyed `category_id` the data row also carries
|
|
186
|
+
* `row.category = { value, label }` (the FK key with the trailing `_id`
|
|
187
|
+
* stripped) — mirroring how `created_by` ships as a `{ name, avatar, email }`
|
|
188
|
+
* sibling consumed by the `creator` renderer. Returns the relation key so the
|
|
189
|
+
* cell can read `row[relationKeyFor(col)]`.
|
|
190
|
+
*/
|
|
191
|
+
export const relationKeyFor = (col) => {
|
|
192
|
+
const k = col.key;
|
|
193
|
+
return k.endsWith('_id') ? k.slice(0, -3) : k;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Reads the resolved relation/option label a backend serves for an FK or
|
|
197
|
+
* option column, falling back to the raw value. Pure so the cell renderers and
|
|
198
|
+
* tests share one resolution path:
|
|
199
|
+
* - relation: prefer the sibling `{ value, label }` object's label.
|
|
200
|
+
* - option: prefer the matched `options[].label` (value compared as string).
|
|
201
|
+
* - else: the raw value coerced to string ('' when nullish).
|
|
202
|
+
*/
|
|
203
|
+
export const resolveRelationLabel = (col, row) => {
|
|
204
|
+
const sibling = getNestedValue(row, relationKeyFor(col));
|
|
205
|
+
const label = sibling && typeof sibling === 'object'
|
|
206
|
+
? sibling.label ?? sibling.name
|
|
207
|
+
: undefined;
|
|
208
|
+
if (label !== undefined && label !== null && label !== '')
|
|
209
|
+
return String(label);
|
|
210
|
+
const raw = getNestedValue(row, col.key);
|
|
211
|
+
return raw !== undefined && raw !== null ? String(raw) : '';
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Renders a resolved FK relation as a clean, truncated chip. Reads the
|
|
215
|
+
* backend-resolved sibling `{ value, label }` (see `relationKeyFor`) and shows
|
|
216
|
+
* its `label`. Falls back to the raw id when no sibling was resolved, and to an
|
|
217
|
+
* empty marker when there is no value at all. Domain-agnostic: works for every
|
|
218
|
+
* `belongs_to` column (category, supplier, warehouse, …) without per-addon code.
|
|
219
|
+
*/
|
|
220
|
+
const RelationCell = ({ col, row }) => {
|
|
221
|
+
const display = resolveRelationLabel(col, row);
|
|
222
|
+
if (!display)
|
|
223
|
+
return _jsx(EmptyCell, {});
|
|
224
|
+
return (_jsx("span", { className: "inline-flex max-w-[220px] items-center truncate rounded-md bg-muted px-2 py-0.5 text-sm font-medium text-foreground/80", title: display, children: _jsx("span", { className: "truncate", children: display }) }));
|
|
225
|
+
};
|
|
182
226
|
/**
|
|
183
227
|
* Generic avatar-style cell: round/rounded photo (or initials fallback) +
|
|
184
228
|
* primary name + optional subtitle. Backs the `avatar`/`search` columns as
|
|
@@ -277,6 +321,30 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
|
|
|
277
321
|
const styles = generateBadgeStyles(statusColorFor(sv), { isDark });
|
|
278
322
|
return (_jsx(Badge, { variant: "outline", className: "border-0 capitalize", style: styles, children: sv }));
|
|
279
323
|
}
|
|
324
|
+
// Resolved FK relation chip. Triggers on an explicit
|
|
325
|
+
// `cellStyle: 'relation'` or on any column carrying a `ref`
|
|
326
|
+
// (a belongs_to FK) that isn't being rendered as an
|
|
327
|
+
// option/badge. Reads the backend-resolved
|
|
328
|
+
// `row[<key w/o _id>] = { value, label }` sibling.
|
|
329
|
+
if (renderAs === 'relation' ||
|
|
330
|
+
(col.ref && !col.options?.length && renderAs !== 'badge' && renderAs !== 'status')) {
|
|
331
|
+
return _jsx(RelationCell, { col: col, row: row.original });
|
|
332
|
+
}
|
|
333
|
+
// Option/type column: a `select`-style column ships its
|
|
334
|
+
// localized `options: [{value,label,color,icon}]` inline and
|
|
335
|
+
// the cell value is the raw option value (e.g. "storable").
|
|
336
|
+
// Render the matched option's label as a colored badge —
|
|
337
|
+
// same OptionBadge the `badge`/`status` cells use.
|
|
338
|
+
if ((renderAs === 'select' || renderAs === 'option' || col.type === 'select') &&
|
|
339
|
+
col.options &&
|
|
340
|
+
col.options.length > 0) {
|
|
341
|
+
if (!value && value !== 0)
|
|
342
|
+
return _jsx(EmptyCell, {});
|
|
343
|
+
const option = col.options.find((o) => o.value === String(value));
|
|
344
|
+
if (option)
|
|
345
|
+
return _jsx(OptionBadge, { option: option, fallback: String(value) });
|
|
346
|
+
return _jsx(Badge, { variant: "outline", children: String(value) });
|
|
347
|
+
}
|
|
280
348
|
switch (renderAs) {
|
|
281
349
|
case 'date': {
|
|
282
350
|
if (!value)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CA+xBnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -229,8 +229,15 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
229
229
|
if (!meta)
|
|
230
230
|
return;
|
|
231
231
|
const columnEndpoints = meta.columns.filter(c => c.useOptions && c.searchEndpoint).map(c => c.searchEndpoint);
|
|
232
|
-
const filterEndpoints = (meta.filters || []).filter(f => f.searchEndpoint && (f.type === 'select' || f.type === 'boolean')).map(f => f.searchEndpoint);
|
|
233
|
-
|
|
232
|
+
const filterEndpoints = (meta.filters || []).filter(f => f.searchEndpoint && (f.type === 'select' || f.type === 'dynamic_select' || f.type === 'boolean')).map(f => f.searchEndpoint);
|
|
233
|
+
// Relation (`ref`/`dynamic_select`) columns flagged `filterable`
|
|
234
|
+
// also need their options preloaded so the per-column multi-select
|
|
235
|
+
// combobox has something to show. Mirrors the explicit-filter path
|
|
236
|
+
// above for columns that drive their filter off the column def.
|
|
237
|
+
const columnFilterEndpoints = meta.columns
|
|
238
|
+
.filter(c => c.filterable && c.searchEndpoint)
|
|
239
|
+
.map(c => c.searchEndpoint);
|
|
240
|
+
const allEndpoints = [...columnEndpoints, ...filterEndpoints, ...columnFilterEndpoints];
|
|
234
241
|
if (allEndpoints.length > 0) {
|
|
235
242
|
prefetchOptions(allEndpoints).then(fetchedMap => {
|
|
236
243
|
const colMap = new Map();
|
|
@@ -238,16 +245,18 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
238
245
|
colMap.set(ep, fetchedMap.get(ep)); });
|
|
239
246
|
setOptionsMap(colMap);
|
|
240
247
|
const fMap = new Map();
|
|
241
|
-
|
|
242
|
-
if (fetchedMap.has(ep))
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
}
|
|
248
|
+
const projectFilterOptions = (ep) => {
|
|
249
|
+
if (!fetchedMap.has(ep) || fMap.has(ep))
|
|
250
|
+
return;
|
|
251
|
+
fMap.set(ep, (fetchedMap.get(ep) || []).map((item) => ({
|
|
252
|
+
label: item.label || item.name || '',
|
|
253
|
+
value: String(item.value ?? item.id ?? ''),
|
|
254
|
+
icon: item.icon,
|
|
255
|
+
color: item.color || item.class,
|
|
256
|
+
})));
|
|
257
|
+
};
|
|
258
|
+
filterEndpoints.forEach(projectFilterOptions);
|
|
259
|
+
columnFilterEndpoints.forEach(projectFilterOptions);
|
|
251
260
|
setFilterOptionsMap(fMap);
|
|
252
261
|
});
|
|
253
262
|
}
|
|
@@ -490,14 +499,22 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
490
499
|
continue;
|
|
491
500
|
const hasStaticOptions = (c.options?.length ?? 0) > 0;
|
|
492
501
|
const hasEndpoint = !!c.searchEndpoint;
|
|
493
|
-
|
|
494
|
-
//
|
|
502
|
+
const isRelation = !!c.ref || c.filterType === 'dynamic_select';
|
|
503
|
+
// Pick the filter UI. The backend's explicit `filterType` wins; when
|
|
504
|
+
// absent we infer it from the column shape:
|
|
505
|
+
// - ref/dynamic_select column → relation multi-select
|
|
506
|
+
// (options stream from searchEndpoint = /options/<ref>)
|
|
507
|
+
// - inline options or searchEndpoint → static multi-select
|
|
495
508
|
// - boolean → boolean toggle (renders as select under the hood)
|
|
496
509
|
// - number / number_range / numeric → number range
|
|
497
510
|
// - date → date range picker (start/end calendar)
|
|
498
511
|
// - everything else (text, email, phone, tags…) → text contains
|
|
499
|
-
let filterType
|
|
500
|
-
if (
|
|
512
|
+
let filterType;
|
|
513
|
+
if (c.filterType)
|
|
514
|
+
filterType = c.filterType;
|
|
515
|
+
else if (isRelation && hasEndpoint)
|
|
516
|
+
filterType = 'dynamic_select';
|
|
517
|
+
else if (hasStaticOptions || hasEndpoint)
|
|
501
518
|
filterType = 'select';
|
|
502
519
|
else if (c.type === 'boolean')
|
|
503
520
|
filterType = 'boolean';
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { ADDON_MANIFEST_CHANGED_TYPE, wireHotSwapInvalidation, useManifestHotSwa
|
|
|
16
16
|
export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederationContainer, shortenHash, type HotSwapReloadStrategy, type HotSwapReloadConfig, type HotSwapReloadAction, type HotSwapReloadDeps, type UseHotSwapReloadResult, } from './hotswap-reload-policy';
|
|
17
17
|
export * from './dynamic-icon';
|
|
18
18
|
export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
|
|
19
|
-
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
19
|
+
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, type DynamicColumnsHelpers, } from './dynamic-columns';
|
|
20
20
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
21
21
|
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
22
22
|
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export * from './metadata-cache';
|
|
|
20
20
|
export { ADDON_MANIFEST_CHANGED_TYPE, wireHotSwapInvalidation, useManifestHotSwapSubscriber, } from './manifest-hotswap-subscriber';
|
|
21
21
|
export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederationContainer, shortenHash, } from './hotswap-reload-policy';
|
|
22
22
|
export * from './dynamic-icon';
|
|
23
|
-
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, } from './dynamic-columns';
|
|
23
|
+
export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, } from './dynamic-columns';
|
|
24
24
|
export { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
25
25
|
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
26
26
|
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
package/dist/types.d.ts
CHANGED
|
@@ -51,7 +51,13 @@ export interface RelationMeta {
|
|
|
51
51
|
export interface FilterDefinition {
|
|
52
52
|
key: string;
|
|
53
53
|
label: string;
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* `dynamic_select` resolves its options server-side from a relation
|
|
56
|
+
* (`searchEndpoint = /options/<ref>`) and renders the same multi-value
|
|
57
|
+
* combobox as `select`. The host loads + caches the options before they
|
|
58
|
+
* surface in the dropdown.
|
|
59
|
+
*/
|
|
60
|
+
type: 'select' | 'dynamic_select' | 'boolean' | 'date_range' | 'number_range' | 'text';
|
|
55
61
|
column: string;
|
|
56
62
|
options?: {
|
|
57
63
|
value: string | boolean;
|
|
@@ -75,9 +81,17 @@ export type ColumnVisibility = 'all' | 'table' | 'modal' | 'list' | (string & {}
|
|
|
75
81
|
export interface ColumnDefinition {
|
|
76
82
|
key: string;
|
|
77
83
|
label: string;
|
|
78
|
-
type: 'text' | 'number' | 'date' | 'select' | 'search' | 'relation-badge-list' | 'avatar' | 'boolean' | 'phone' | 'media-gallery' | 'image' | 'url' | 'link' | 'email' | 'currency' | 'percent' | 'progress' | 'badge' | 'status' | 'tags' | 'color' | 'code' | 'truncate-text' | 'creator' | 'user';
|
|
84
|
+
type: 'text' | 'number' | 'date' | 'select' | 'search' | 'relation-badge-list' | 'avatar' | 'boolean' | 'phone' | 'media-gallery' | 'image' | 'url' | 'link' | 'email' | 'currency' | 'percent' | 'progress' | 'badge' | 'status' | 'tags' | 'color' | 'code' | 'truncate-text' | 'creator' | 'user' | 'relation';
|
|
79
85
|
sortable: boolean;
|
|
80
86
|
filterable: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Explicit filter UI the backend wants for this column when `filterable`.
|
|
89
|
+
* When absent the SDK infers it from the column shape (options/endpoint →
|
|
90
|
+
* `select`, boolean/number/date → their range pickers, else `text`). A
|
|
91
|
+
* `ref` (belongs_to FK) column is served as `dynamic_select` so its options
|
|
92
|
+
* stream from `searchEndpoint = /options/<ref>` into a multi-value combobox.
|
|
93
|
+
*/
|
|
94
|
+
filterType?: 'select' | 'dynamic_select' | 'boolean' | 'date_range' | 'number_range' | 'text';
|
|
81
95
|
hidden?: boolean;
|
|
82
96
|
/**
|
|
83
97
|
* Scopes where this column is rendered. When `'modal'` (or `'list'`) the
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAA;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,EAAE,aAAa,GAAG,cAAc,CAAA;IACpC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;
|
|
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": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"date-fns": ">=3",
|
|
35
35
|
"react-day-picker": ">=8",
|
|
36
36
|
"@asteby/metacore-sdk": "^3.1.0",
|
|
37
|
-
"@asteby/metacore-ui": "^2.
|
|
37
|
+
"@asteby/metacore-ui": "^2.2.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@tanstack/react-router": {
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^6.0.0",
|
|
62
62
|
"vitest": "^4.0.0",
|
|
63
63
|
"zustand": "^5.0.0",
|
|
64
|
-
"@asteby/metacore-
|
|
65
|
-
"@asteby/metacore-
|
|
64
|
+
"@asteby/metacore-sdk": "3.1.0",
|
|
65
|
+
"@asteby/metacore-ui": "2.2.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Pure-logic coverage for the relation/option cell resolution path used by
|
|
2
|
+
// `defaultGetDynamicColumns`. The renderers themselves are JSX (covered in the
|
|
3
|
+
// host's render tests); here we lock the value-resolution contract that drives
|
|
4
|
+
// them so a backend shape change is caught without a DOM.
|
|
5
|
+
import { describe, it, expect } from 'vitest'
|
|
6
|
+
import { relationKeyFor, resolveRelationLabel } from '../dynamic-columns'
|
|
7
|
+
import type { ColumnDefinition } from '../types'
|
|
8
|
+
|
|
9
|
+
const col = (over: Partial<ColumnDefinition>): ColumnDefinition => ({
|
|
10
|
+
key: 'category_id',
|
|
11
|
+
label: 'Categoría',
|
|
12
|
+
type: 'text',
|
|
13
|
+
sortable: true,
|
|
14
|
+
filterable: true,
|
|
15
|
+
...over,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('relationKeyFor', () => {
|
|
19
|
+
it('strips the trailing _id (FK key → relation sibling key)', () => {
|
|
20
|
+
expect(relationKeyFor({ key: 'category_id' })).toBe('category')
|
|
21
|
+
expect(relationKeyFor({ key: 'supplier_id' })).toBe('supplier')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('leaves keys without _id untouched', () => {
|
|
25
|
+
expect(relationKeyFor({ key: 'category' })).toBe('category')
|
|
26
|
+
expect(relationKeyFor({ key: 'parent_uid' })).toBe('parent_uid')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('resolveRelationLabel', () => {
|
|
31
|
+
it('prefers the backend-resolved sibling label', () => {
|
|
32
|
+
const row = {
|
|
33
|
+
category_id: 'uuid-1',
|
|
34
|
+
category: { value: 'uuid-1', label: 'Llantas' },
|
|
35
|
+
}
|
|
36
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), row)).toBe('Llantas')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('accepts a sibling that uses { name } instead of { label }', () => {
|
|
40
|
+
const row = { category_id: 'uuid-2', category: { value: 'uuid-2', name: 'Frenos' } }
|
|
41
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), row)).toBe('Frenos')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('falls back to the raw id when no sibling was resolved', () => {
|
|
45
|
+
const row = { category_id: 'uuid-3' }
|
|
46
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), row)).toBe('uuid-3')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns empty string when there is no value at all', () => {
|
|
50
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), {})).toBe('')
|
|
51
|
+
expect(resolveRelationLabel(col({ ref: 'categories' }), { category_id: null })).toBe('')
|
|
52
|
+
})
|
|
53
|
+
})
|
package/src/dynamic-columns.tsx
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// badge (static + endpoint-loaded options), avatar/search, creator/user,
|
|
4
4
|
// phone, date, boolean, relation-badge-list, media-gallery, image, plus the
|
|
5
5
|
// declarative pro renderers url/link, email, currency, number, percent/
|
|
6
|
-
// progress, status, tags, color, code/truncate-text,
|
|
7
|
-
//
|
|
6
|
+
// progress, status, tags, color, code/truncate-text, relation (resolved FK
|
|
7
|
+
// chip), option/select badges, and a generic text fallback. The renderer
|
|
8
|
+
// resolves `cellStyle ?? type` for each column.
|
|
8
9
|
//
|
|
9
10
|
// The implementation was previously duplicated across multiple host apps
|
|
10
11
|
// (~550 LOC each, drifting). It now lives here so a single fix propagates
|
|
@@ -288,6 +289,58 @@ const BadgeWithEndpointOptions: React.FC<{ endpoint: string; value: any }> = ({
|
|
|
288
289
|
return <Badge variant="outline">{String(value)}</Badge>
|
|
289
290
|
}
|
|
290
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Resolves the relation sibling object a backend serves alongside an FK column.
|
|
294
|
+
* For a column keyed `category_id` the data row also carries
|
|
295
|
+
* `row.category = { value, label }` (the FK key with the trailing `_id`
|
|
296
|
+
* stripped) — mirroring how `created_by` ships as a `{ name, avatar, email }`
|
|
297
|
+
* sibling consumed by the `creator` renderer. Returns the relation key so the
|
|
298
|
+
* cell can read `row[relationKeyFor(col)]`.
|
|
299
|
+
*/
|
|
300
|
+
export const relationKeyFor = (col: Pick<ColumnDefinition, 'key'>): string => {
|
|
301
|
+
const k = col.key
|
|
302
|
+
return k.endsWith('_id') ? k.slice(0, -3) : k
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Reads the resolved relation/option label a backend serves for an FK or
|
|
307
|
+
* option column, falling back to the raw value. Pure so the cell renderers and
|
|
308
|
+
* tests share one resolution path:
|
|
309
|
+
* - relation: prefer the sibling `{ value, label }` object's label.
|
|
310
|
+
* - option: prefer the matched `options[].label` (value compared as string).
|
|
311
|
+
* - else: the raw value coerced to string ('' when nullish).
|
|
312
|
+
*/
|
|
313
|
+
export const resolveRelationLabel = (col: ColumnDefinition, row: any): string => {
|
|
314
|
+
const sibling = getNestedValue(row, relationKeyFor(col))
|
|
315
|
+
const label =
|
|
316
|
+
sibling && typeof sibling === 'object'
|
|
317
|
+
? sibling.label ?? sibling.name
|
|
318
|
+
: undefined
|
|
319
|
+
if (label !== undefined && label !== null && label !== '') return String(label)
|
|
320
|
+
const raw = getNestedValue(row, col.key)
|
|
321
|
+
return raw !== undefined && raw !== null ? String(raw) : ''
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Renders a resolved FK relation as a clean, truncated chip. Reads the
|
|
326
|
+
* backend-resolved sibling `{ value, label }` (see `relationKeyFor`) and shows
|
|
327
|
+
* its `label`. Falls back to the raw id when no sibling was resolved, and to an
|
|
328
|
+
* empty marker when there is no value at all. Domain-agnostic: works for every
|
|
329
|
+
* `belongs_to` column (category, supplier, warehouse, …) without per-addon code.
|
|
330
|
+
*/
|
|
331
|
+
const RelationCell: React.FC<{ col: ColumnDefinition; row: any }> = ({ col, row }) => {
|
|
332
|
+
const display = resolveRelationLabel(col, row)
|
|
333
|
+
if (!display) return <EmptyCell />
|
|
334
|
+
return (
|
|
335
|
+
<span
|
|
336
|
+
className="inline-flex max-w-[220px] items-center truncate rounded-md bg-muted px-2 py-0.5 text-sm font-medium text-foreground/80"
|
|
337
|
+
title={display}
|
|
338
|
+
>
|
|
339
|
+
<span className="truncate">{display}</span>
|
|
340
|
+
</span>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
291
344
|
/**
|
|
292
345
|
* Generic avatar-style cell: round/rounded photo (or initials fallback) +
|
|
293
346
|
* primary name + optional subtitle. Backs the `avatar`/`search` columns as
|
|
@@ -453,6 +506,34 @@ export function makeDefaultGetDynamicColumns(
|
|
|
453
506
|
)
|
|
454
507
|
}
|
|
455
508
|
|
|
509
|
+
// Resolved FK relation chip. Triggers on an explicit
|
|
510
|
+
// `cellStyle: 'relation'` or on any column carrying a `ref`
|
|
511
|
+
// (a belongs_to FK) that isn't being rendered as an
|
|
512
|
+
// option/badge. Reads the backend-resolved
|
|
513
|
+
// `row[<key w/o _id>] = { value, label }` sibling.
|
|
514
|
+
if (
|
|
515
|
+
renderAs === 'relation' ||
|
|
516
|
+
(col.ref && !col.options?.length && renderAs !== 'badge' && renderAs !== 'status')
|
|
517
|
+
) {
|
|
518
|
+
return <RelationCell col={col} row={row.original} />
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Option/type column: a `select`-style column ships its
|
|
522
|
+
// localized `options: [{value,label,color,icon}]` inline and
|
|
523
|
+
// the cell value is the raw option value (e.g. "storable").
|
|
524
|
+
// Render the matched option's label as a colored badge —
|
|
525
|
+
// same OptionBadge the `badge`/`status` cells use.
|
|
526
|
+
if (
|
|
527
|
+
(renderAs === 'select' || renderAs === 'option' || col.type === 'select') &&
|
|
528
|
+
col.options &&
|
|
529
|
+
col.options.length > 0
|
|
530
|
+
) {
|
|
531
|
+
if (!value && value !== 0) return <EmptyCell />
|
|
532
|
+
const option = col.options.find((o) => o.value === String(value))
|
|
533
|
+
if (option) return <OptionBadge option={option} fallback={String(value)} />
|
|
534
|
+
return <Badge variant="outline">{String(value)}</Badge>
|
|
535
|
+
}
|
|
536
|
+
|
|
456
537
|
switch (renderAs) {
|
|
457
538
|
case 'date': {
|
|
458
539
|
if (!value) return <span className="text-muted-foreground">-</span>
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -300,24 +300,32 @@ export function DynamicTable({
|
|
|
300
300
|
}
|
|
301
301
|
if (!meta) return
|
|
302
302
|
const columnEndpoints = meta.columns.filter(c => c.useOptions && c.searchEndpoint).map(c => c.searchEndpoint!)
|
|
303
|
-
const filterEndpoints = (meta.filters || []).filter(f => f.searchEndpoint && (f.type === 'select' || f.type === 'boolean')).map(f => f.searchEndpoint!)
|
|
304
|
-
|
|
303
|
+
const filterEndpoints = (meta.filters || []).filter(f => f.searchEndpoint && (f.type === 'select' || f.type === 'dynamic_select' || f.type === 'boolean')).map(f => f.searchEndpoint!)
|
|
304
|
+
// Relation (`ref`/`dynamic_select`) columns flagged `filterable`
|
|
305
|
+
// also need their options preloaded so the per-column multi-select
|
|
306
|
+
// combobox has something to show. Mirrors the explicit-filter path
|
|
307
|
+
// above for columns that drive their filter off the column def.
|
|
308
|
+
const columnFilterEndpoints = meta.columns
|
|
309
|
+
.filter(c => c.filterable && c.searchEndpoint)
|
|
310
|
+
.map(c => c.searchEndpoint!)
|
|
311
|
+
const allEndpoints = [...columnEndpoints, ...filterEndpoints, ...columnFilterEndpoints]
|
|
305
312
|
if (allEndpoints.length > 0) {
|
|
306
313
|
prefetchOptions(allEndpoints).then(fetchedMap => {
|
|
307
314
|
const colMap = new Map<string, any[]>()
|
|
308
315
|
columnEndpoints.forEach(ep => { if (fetchedMap.has(ep)) colMap.set(ep, fetchedMap.get(ep)!) })
|
|
309
316
|
setOptionsMap(colMap)
|
|
310
317
|
const fMap = new Map<string, DynamicFilterOption[]>()
|
|
311
|
-
|
|
312
|
-
if (fetchedMap.has(ep))
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
const projectFilterOptions = (ep: string) => {
|
|
319
|
+
if (!fetchedMap.has(ep) || fMap.has(ep)) return
|
|
320
|
+
fMap.set(ep, (fetchedMap.get(ep) || []).map((item: any) => ({
|
|
321
|
+
label: item.label || item.name || '',
|
|
322
|
+
value: String(item.value ?? item.id ?? ''),
|
|
323
|
+
icon: item.icon,
|
|
324
|
+
color: item.color || item.class,
|
|
325
|
+
})))
|
|
326
|
+
}
|
|
327
|
+
filterEndpoints.forEach(projectFilterOptions)
|
|
328
|
+
columnFilterEndpoints.forEach(projectFilterOptions)
|
|
321
329
|
setFilterOptionsMap(fMap)
|
|
322
330
|
})
|
|
323
331
|
}
|
|
@@ -531,14 +539,20 @@ export function DynamicTable({
|
|
|
531
539
|
if (!c.filterable || map.has(c.key)) continue
|
|
532
540
|
const hasStaticOptions = (c.options?.length ?? 0) > 0
|
|
533
541
|
const hasEndpoint = !!c.searchEndpoint
|
|
534
|
-
|
|
535
|
-
//
|
|
542
|
+
const isRelation = !!c.ref || c.filterType === 'dynamic_select'
|
|
543
|
+
// Pick the filter UI. The backend's explicit `filterType` wins; when
|
|
544
|
+
// absent we infer it from the column shape:
|
|
545
|
+
// - ref/dynamic_select column → relation multi-select
|
|
546
|
+
// (options stream from searchEndpoint = /options/<ref>)
|
|
547
|
+
// - inline options or searchEndpoint → static multi-select
|
|
536
548
|
// - boolean → boolean toggle (renders as select under the hood)
|
|
537
549
|
// - number / number_range / numeric → number range
|
|
538
550
|
// - date → date range picker (start/end calendar)
|
|
539
551
|
// - everything else (text, email, phone, tags…) → text contains
|
|
540
|
-
let filterType: ColumnFilterConfig['filterType']
|
|
541
|
-
if (
|
|
552
|
+
let filterType: ColumnFilterConfig['filterType']
|
|
553
|
+
if (c.filterType) filterType = c.filterType
|
|
554
|
+
else if (isRelation && hasEndpoint) filterType = 'dynamic_select'
|
|
555
|
+
else if (hasStaticOptions || hasEndpoint) filterType = 'select'
|
|
542
556
|
else if (c.type === 'boolean') filterType = 'boolean'
|
|
543
557
|
else if (c.type === 'number') filterType = 'number_range'
|
|
544
558
|
else if (c.type === 'date') filterType = 'date_range'
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -56,7 +56,13 @@ export interface RelationMeta {
|
|
|
56
56
|
export interface FilterDefinition {
|
|
57
57
|
key: string
|
|
58
58
|
label: string
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* `dynamic_select` resolves its options server-side from a relation
|
|
61
|
+
* (`searchEndpoint = /options/<ref>`) and renders the same multi-value
|
|
62
|
+
* combobox as `select`. The host loads + caches the options before they
|
|
63
|
+
* surface in the dropdown.
|
|
64
|
+
*/
|
|
65
|
+
type: 'select' | 'dynamic_select' | 'boolean' | 'date_range' | 'number_range' | 'text'
|
|
60
66
|
column: string
|
|
61
67
|
options?: { value: string | boolean; label: string; icon?: string; color?: string }[]
|
|
62
68
|
searchEndpoint?: string
|
|
@@ -104,8 +110,21 @@ export interface ColumnDefinition {
|
|
|
104
110
|
| 'truncate-text'
|
|
105
111
|
| 'creator'
|
|
106
112
|
| 'user'
|
|
113
|
+
// Resolved FK relation chip. The data row carries a sibling
|
|
114
|
+
// `{ value, label }` object keyed by the column key with the trailing
|
|
115
|
+
// `_id` stripped (e.g. `category_id` → `row.category`). Also triggered
|
|
116
|
+
// implicitly whenever the column carries a `ref` (belongs_to FK).
|
|
117
|
+
| 'relation'
|
|
107
118
|
sortable: boolean
|
|
108
119
|
filterable: boolean
|
|
120
|
+
/**
|
|
121
|
+
* Explicit filter UI the backend wants for this column when `filterable`.
|
|
122
|
+
* When absent the SDK infers it from the column shape (options/endpoint →
|
|
123
|
+
* `select`, boolean/number/date → their range pickers, else `text`). A
|
|
124
|
+
* `ref` (belongs_to FK) column is served as `dynamic_select` so its options
|
|
125
|
+
* stream from `searchEndpoint = /options/<ref>` into a multi-value combobox.
|
|
126
|
+
*/
|
|
127
|
+
filterType?: 'select' | 'dynamic_select' | 'boolean' | 'date_range' | 'number_range' | 'text'
|
|
109
128
|
hidden?: boolean
|
|
110
129
|
/**
|
|
111
130
|
* Scopes where this column is rendered. When `'modal'` (or `'list'`) the
|