@asteby/metacore-runtime-react 17.0.2 → 17.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 17.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - a745f5c: Relation/option thumbnails: resolved FK relation chips and option badges now
8
+ render a small thumbnail when the backend stamps an `image` on the sibling
9
+ `{ value, label }` object or the option (brand logo, product photo, customer
10
+ avatar), with a graceful initials fallback when the image is missing or fails to
11
+ load. Applies to the table `relation`/`select`/`status`/`badge` cells; the
12
+ searchable picker (`DynamicSelectField`) and the detail-view picker already
13
+ rendered option images. Adds a pure `resolveRelationImage` helper (+ tests).
14
+
15
+ ## 17.0.3
16
+
17
+ ### Patch Changes
18
+
19
+ - b5c8f5f: Pro datetime columns: `datetime`/`timestamp`/`timestamptz` columns now use the
20
+ date cell renderer instead of falling through to the raw-ISO fallback. Datetime
21
+ variants show day + time with a full-precision tooltip on hover (the 7Leguas
22
+ pattern); plain `date` columns stay day-only. Null and the Go zero-time render
23
+ an em-dash. Date-typed columns (including the timestamp variants) now infer the
24
+ `date_range` filter. Adds a pure `formatDateCell` helper (+ tests).
25
+ - Updated dependencies [b5c8f5f]
26
+ - @asteby/metacore-ui@2.4.2
27
+
3
28
  ## 17.0.2
4
29
 
5
30
  ### Patch Changes
@@ -1,3 +1,4 @@
1
+ import { type Locale } from 'date-fns';
1
2
  import type { ColumnDefinition } from './types';
2
3
  import type { GetDynamicColumns } from './dynamic-columns-shim';
3
4
  /** Host-supplied helpers consumed by avatar/image cell renderers. */
@@ -37,6 +38,20 @@ export declare const isActionAllowedForRowState: (action: any, row: any) => bool
37
38
  * cell can read `row[relationKeyFor(col)]`.
38
39
  */
39
40
  export declare const relationKeyFor: (col: Pick<ColumnDefinition, "key">) => string;
41
+ /** Cell renderers (`cellStyle`/`type`) that resolve to the date renderer. */
42
+ export declare const DATE_CELL_TYPES: readonly ["date", "datetime", "timestamp", "timestamptz"];
43
+ /**
44
+ * Pure formatter behind the date/datetime cell. Returns the display string and
45
+ * an optional full-precision `title` (tooltip), or `null` when the value is
46
+ * empty/invalid/the Go zero-time so the cell renders an em-dash.
47
+ * - `date`: day only (`PPP`), no tooltip.
48
+ * - `datetime`/`timestamp(tz)`: day + time (`Pp`) with a full-precision
49
+ * tooltip (`PPpp`) — the 7Leguas pattern.
50
+ */
51
+ export declare function formatDateCell(value: unknown, renderAs: string | undefined, locale: Locale): {
52
+ display: string;
53
+ title?: string;
54
+ } | null;
40
55
  /**
41
56
  * Reads the resolved relation/option label a backend serves for an FK or
42
57
  * option column, falling back to the raw value. Pure so the cell renderers and
@@ -46,6 +61,14 @@ export declare const relationKeyFor: (col: Pick<ColumnDefinition, "key">) => str
46
61
  * - else: the raw value coerced to string ('' when nullish).
47
62
  */
48
63
  export declare const resolveRelationLabel: (col: ColumnDefinition, row: any) => string;
64
+ /**
65
+ * Reads the thumbnail URL a backend serves on a resolved FK sibling, when
66
+ * present. The backend stamps `image` onto the `{ value, label }` relation
67
+ * object when the referenced model carries an image column (brand logo,
68
+ * product photo, customer avatar). Returns '' when there is no sibling image —
69
+ * the chip then renders text-only, exactly as before.
70
+ */
71
+ export declare const resolveRelationImage: (col: ColumnDefinition, row: any) => string;
49
72
  /**
50
73
  * Builds the canonical column factory used by `<DynamicTable>` when the host
51
74
  * 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":"AAgDA,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;AA0HD;;;;;;;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,MAWtE,CAAA;AAiED;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAsmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAgC9C,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;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;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,GACf;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAY5C;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;AA0ED;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAsmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -169,7 +169,16 @@ const renderRelationBadges = (items, col) => {
169
169
  return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1", children: [iconValue && (_jsx(DynamicIcon, { name: iconValue, className: "h-3 w-3" })), _jsx("span", { children: label })] }, `${col.key}-${idx}`));
170
170
  }) }));
171
171
  };
172
- const OptionBadge = ({ option }) => {
172
+ /**
173
+ * Tiny square thumbnail for a resolved relation/option that carries an `image`
174
+ * (brand logo, product photo, customer/user avatar). Uses the same Avatar
175
+ * primitive as the `avatar`/`creator` cells so a broken/loading image
176
+ * gracefully falls back to the record's initials. Sized small (the box is an
177
+ * inline style so an addon-arbitrary Tailwind class never gets dropped by a
178
+ * consuming app's class scan). Rendered inline alongside a label — never alone.
179
+ */
180
+ const RelationThumbnail = ({ src, alt, getImageUrl, size = 18 }) => (_jsxs(Avatar, { className: "shrink-0 rounded-sm ring-1 ring-border/40", style: { width: size, height: size }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(src) : src, alt: alt, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-sm bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(alt) })] }));
181
+ const OptionBadge = ({ option, getImageUrl }) => {
173
182
  const isDark = useIsDarkTheme();
174
183
  // Explicit backend color wins; otherwise derive a stable, cohesive color
175
184
  // from the option's value (fallback label) so "dead" gray badges come
@@ -177,14 +186,14 @@ const OptionBadge = ({ option }) => {
177
186
  // tailwind safelist — addon-arbitrary classes aren't in the host scan.
178
187
  const colorSource = option.color || optionColor(option.value || option.label);
179
188
  const colorStyles = generateBadgeStyles(colorSource, { isDark });
180
- return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" }), _jsx("span", { children: option.label })] }));
189
+ return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.image ? (_jsx(RelationThumbnail, { src: option.image, alt: option.label, getImageUrl: getImageUrl, size: 16 })) : (option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" })), _jsx("span", { children: option.label })] }));
181
190
  };
182
- const BadgeWithEndpointOptions = ({ endpoint, value }) => {
191
+ const BadgeWithEndpointOptions = ({ endpoint, value, getImageUrl }) => {
183
192
  const { optionsMap } = React.useContext(OptionsContext);
184
193
  const options = optionsMap.get(endpoint) || [];
185
194
  const option = options.find((opt) => opt.value === value);
186
195
  if (option)
187
- return _jsx(OptionBadge, { option: option, fallback: String(value) });
196
+ return _jsx(OptionBadge, { option: option, fallback: String(value), getImageUrl: getImageUrl });
188
197
  // No declared option matched → humanize the raw token as a safety net so a
189
198
  // cell never shows `in_progress` verbatim (option.label still wins above).
190
199
  return _jsx(Badge, { variant: "outline", children: humanizeToken(value) });
@@ -201,6 +210,31 @@ export const relationKeyFor = (col) => {
201
210
  const k = col.key;
202
211
  return k.endsWith('_id') ? k.slice(0, -3) : k;
203
212
  };
213
+ /** Cell renderers (`cellStyle`/`type`) that resolve to the date renderer. */
214
+ export const DATE_CELL_TYPES = ['date', 'datetime', 'timestamp', 'timestamptz'];
215
+ /**
216
+ * Pure formatter behind the date/datetime cell. Returns the display string and
217
+ * an optional full-precision `title` (tooltip), or `null` when the value is
218
+ * empty/invalid/the Go zero-time so the cell renders an em-dash.
219
+ * - `date`: day only (`PPP`), no tooltip.
220
+ * - `datetime`/`timestamp(tz)`: day + time (`Pp`) with a full-precision
221
+ * tooltip (`PPpp`) — the 7Leguas pattern.
222
+ */
223
+ export function formatDateCell(value, renderAs, locale) {
224
+ if (value === null || value === undefined || value === '')
225
+ return null;
226
+ const date = new Date(value);
227
+ if (isNaN(date.getTime()) || date.getFullYear() <= 1)
228
+ return null;
229
+ const withTime = renderAs !== 'date';
230
+ if (withTime) {
231
+ return {
232
+ display: format(date, 'Pp', { locale }),
233
+ title: format(date, 'PPpp', { locale }),
234
+ };
235
+ }
236
+ return { display: format(date, 'PPP', { locale }) };
237
+ }
204
238
  /**
205
239
  * Reads the resolved relation/option label a backend serves for an FK or
206
240
  * option column, falling back to the raw value. Pure so the cell renderers and
@@ -222,24 +256,42 @@ export const resolveRelationLabel = (col, row) => {
222
256
  return '';
223
257
  return String(raw);
224
258
  };
259
+ /**
260
+ * Reads the thumbnail URL a backend serves on a resolved FK sibling, when
261
+ * present. The backend stamps `image` onto the `{ value, label }` relation
262
+ * object when the referenced model carries an image column (brand logo,
263
+ * product photo, customer avatar). Returns '' when there is no sibling image —
264
+ * the chip then renders text-only, exactly as before.
265
+ */
266
+ export const resolveRelationImage = (col, row) => {
267
+ const sibling = getNestedValue(row, relationKeyFor(col));
268
+ if (sibling && typeof sibling === 'object') {
269
+ const img = sibling.image ?? sibling.avatar ?? sibling.photo;
270
+ if (img !== undefined && img !== null && img !== '')
271
+ return String(img);
272
+ }
273
+ return '';
274
+ };
225
275
  /**
226
276
  * Renders a resolved FK relation as a clean, truncated chip. Reads the
227
- * backend-resolved sibling `{ value, label }` (see `relationKeyFor`) and shows
228
- * its `label`. Falls back to the raw id when no sibling was resolved, and to an
229
- * empty marker when there is no value at all. Domain-agnostic: works for every
230
- * `belongs_to` column (category, supplier, warehouse, …) without per-addon code.
277
+ * backend-resolved sibling `{ value, label[, image] }` (see `relationKeyFor`)
278
+ * and shows its `label`, prefixed with a small thumbnail when the sibling
279
+ * carries an `image`. Falls back to the raw id when no sibling was resolved, and
280
+ * to an empty marker when there is no value at all. Domain-agnostic: works for
281
+ * every `belongs_to` column (category, supplier, brand, …) without per-addon code.
231
282
  */
232
- const RelationCell = ({ col, row }) => {
283
+ const RelationCell = ({ col, row, getImageUrl }) => {
233
284
  const isDark = useIsDarkTheme();
234
285
  const display = resolveRelationLabel(col, row);
235
286
  if (!display)
236
287
  return _jsx(EmptyCell, {});
288
+ const image = resolveRelationImage(col, row);
237
289
  // Deterministic, SUBTLE color keyed on the resolved label — lighter than
238
290
  // enum badges (soft tint, no heavy fill) so category/brand chips read as
239
291
  // alive yet stay visually distinct from option/status badges. Inline style
240
292
  // (hex-derived) bypasses the host tailwind safelist.
241
293
  const chipStyles = relationChipStyles(display, { isDark });
242
- return (_jsx("span", { className: "inline-flex max-w-[220px] items-center truncate rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: display, children: _jsx("span", { className: "truncate", children: display }) }));
294
+ return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: display, children: [image && (_jsx(RelationThumbnail, { src: image, alt: display, getImageUrl: getImageUrl, size: 18 })), _jsx("span", { className: "truncate", children: display })] }));
243
295
  };
244
296
  /**
245
297
  * Generic avatar-style cell: round/rounded photo (or initials fallback) +
@@ -309,7 +361,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
309
361
  if (renderAs === 'badge' && col.useOptions && col.searchEndpoint) {
310
362
  if (!value)
311
363
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
312
- return _jsx(BadgeWithEndpointOptions, { endpoint: col.searchEndpoint, value: value });
364
+ return _jsx(BadgeWithEndpointOptions, { endpoint: col.searchEndpoint, value: value, getImageUrl: getImageUrl });
313
365
  }
314
366
  // Static badge options — map value → label/icon/color
315
367
  if (renderAs === 'badge' && col.options && col.options.length > 0) {
@@ -317,7 +369,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
317
369
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
318
370
  const option = col.options.find((o) => o.value === String(value));
319
371
  if (option)
320
- return _jsx(OptionBadge, { option: option, fallback: String(value) });
372
+ return _jsx(OptionBadge, { option: option, fallback: String(value), getImageUrl: getImageUrl });
321
373
  return _jsx(Badge, { variant: "outline", children: humanizeToken(value) });
322
374
  }
323
375
  if (renderAs === 'relation-badge-list') {
@@ -337,7 +389,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
337
389
  const sv = String(value);
338
390
  const option = col.options?.find((o) => o.value === sv);
339
391
  if (option)
340
- return _jsx(OptionBadge, { option: option, fallback: sv });
392
+ return _jsx(OptionBadge, { option: option, fallback: sv, getImageUrl: getImageUrl });
341
393
  const isDark = typeof document !== 'undefined' &&
342
394
  document.documentElement.classList.contains('dark');
343
395
  const styles = generateBadgeStyles(statusColorFor(sv), { isDark });
@@ -352,7 +404,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
352
404
  // `row[<key w/o _id>] = { value, label }` sibling.
353
405
  if (renderAs === 'relation' ||
354
406
  (col.ref && !col.options?.length && renderAs !== 'badge' && renderAs !== 'status')) {
355
- return _jsx(RelationCell, { col: col, row: row.original });
407
+ return _jsx(RelationCell, { col: col, row: row.original, getImageUrl: getImageUrl });
356
408
  }
357
409
  // Option/type column: a `select`-style column ships its
358
410
  // localized `options: [{value,label,color,icon}]` inline and
@@ -366,23 +418,18 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
366
418
  return _jsx(EmptyCell, {});
367
419
  const option = col.options.find((o) => o.value === String(value));
368
420
  if (option)
369
- return _jsx(OptionBadge, { option: option, fallback: String(value) });
421
+ return _jsx(OptionBadge, { option: option, fallback: String(value), getImageUrl: getImageUrl });
370
422
  return _jsx(Badge, { variant: "outline", children: humanizeToken(value) });
371
423
  }
372
424
  switch (renderAs) {
373
- case 'date': {
374
- if (!value)
425
+ case 'date':
426
+ case 'datetime':
427
+ case 'timestamp':
428
+ case 'timestamptz': {
429
+ const formatted = formatDateCell(value, renderAs, dateLocale);
430
+ if (!formatted)
375
431
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
376
- try {
377
- const date = new Date(value);
378
- if (isNaN(date.getTime()) || date.getFullYear() <= 1) {
379
- return _jsx("span", { className: "text-muted-foreground", children: "-" });
380
- }
381
- return (_jsxs("div", { className: "flex items-center gap-1.5 text-muted-foreground", children: [_jsx(icons.Calendar, { className: "h-3.5 w-3.5 opacity-70" }), _jsx("span", { className: "text-sm font-medium", children: format(date, 'PPP', { locale: dateLocale }) })] }));
382
- }
383
- catch {
384
- return _jsx("span", { children: String(value) });
385
- }
432
+ return (_jsxs("div", { className: "flex items-center gap-1.5 text-muted-foreground", title: formatted.title, children: [_jsx(icons.Calendar, { className: "h-3.5 w-3.5 opacity-70" }), _jsx("span", { className: "text-sm font-medium", children: formatted.display })] }));
386
433
  }
387
434
  case 'search':
388
435
  case 'avatar':
@@ -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,+BA+xBnB"}
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,+BAgyBnB"}
@@ -24,7 +24,7 @@ import { toast } from 'sonner';
24
24
  import { Progress } from './dialogs/_primitives';
25
25
  import { useMetadataCache } from './metadata-cache';
26
26
  import { useApi, useCurrentBranch } from './api-context';
27
- import { defaultGetDynamicColumns } from './dynamic-columns';
27
+ import { defaultGetDynamicColumns, DATE_CELL_TYPES } from './dynamic-columns';
28
28
  import { OptionsContext } from './options-context';
29
29
  import { ActionModalDispatcher } from './action-modal-dispatcher';
30
30
  import { getSearchableColumnKeys } from './column-visibility';
@@ -520,7 +520,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
520
520
  filterType = 'boolean';
521
521
  else if (c.type === 'number')
522
522
  filterType = 'number_range';
523
- else if (c.type === 'date')
523
+ else if (DATE_CELL_TYPES.includes(c.type))
524
524
  filterType = 'date_range';
525
525
  else
526
526
  filterType = 'text';
package/dist/types.d.ts CHANGED
@@ -81,7 +81,7 @@ export type ColumnVisibility = 'all' | 'table' | 'modal' | 'list' | (string & {}
81
81
  export interface ColumnDefinition {
82
82
  key: string;
83
83
  label: string;
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';
84
+ type: 'text' | 'number' | 'date' | 'datetime' | 'timestamp' | 'timestamptz' | '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';
85
85
  sortable: boolean;
86
86
  filterable: boolean;
87
87
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "17.0.2",
3
+ "version": "17.0.4",
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.2.0",
37
- "@asteby/metacore-ui": "^2.4.1"
37
+ "@asteby/metacore-ui": "^2.4.2"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@tanstack/react-router": {
@@ -62,7 +62,7 @@
62
62
  "vitest": "^4.0.0",
63
63
  "zustand": "^5.0.0",
64
64
  "@asteby/metacore-sdk": "3.2.0",
65
- "@asteby/metacore-ui": "2.4.1"
65
+ "@asteby/metacore-ui": "2.4.2"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsc -p tsconfig.json",
@@ -0,0 +1,52 @@
1
+ // Pure-logic coverage for the date/datetime cell formatter behind
2
+ // `defaultGetDynamicColumns`. Locks the contract the JSX cell relies on:
3
+ // `date` → day only (no tooltip), `datetime`/`timestamp(tz)` → day + time with
4
+ // a full-precision tooltip, and null/invalid/Go-zero-time → `null` (em-dash).
5
+ import { describe, it, expect } from 'vitest'
6
+ import { enUS } from 'date-fns/locale'
7
+ import { formatDateCell } from '../dynamic-columns'
8
+
9
+ describe('formatDateCell', () => {
10
+ const iso = '2026-06-06T17:47:33.201Z'
11
+
12
+ it('renders day only with no tooltip for `date`', () => {
13
+ const out = formatDateCell(iso, 'date', enUS)
14
+ expect(out).not.toBeNull()
15
+ expect(out!.title).toBeUndefined()
16
+ // `PPP` is the long day form — no time component.
17
+ expect(out!.display).not.toMatch(/\d{1,2}:\d{2}/)
18
+ expect(out!.display).toMatch(/2026/)
19
+ })
20
+
21
+ it('renders day + time and a full-precision tooltip for `datetime`', () => {
22
+ const out = formatDateCell(iso, 'datetime', enUS)
23
+ expect(out).not.toBeNull()
24
+ expect(out!.display).toMatch(/\d{1,2}:\d{2}/)
25
+ expect(out!.title).toBeDefined()
26
+ expect(out!.title).toMatch(/\d{1,2}:\d{2}/)
27
+ })
28
+
29
+ it('treats timestamp/timestamptz like datetime (day + time + tooltip)', () => {
30
+ for (const t of ['timestamp', 'timestamptz']) {
31
+ const out = formatDateCell(iso, t, enUS)
32
+ expect(out).not.toBeNull()
33
+ expect(out!.display).toMatch(/\d{1,2}:\d{2}/)
34
+ expect(out!.title).toBeDefined()
35
+ }
36
+ })
37
+
38
+ it('returns null for null/undefined/empty (empty cell)', () => {
39
+ expect(formatDateCell(null, 'datetime', enUS)).toBeNull()
40
+ expect(formatDateCell(undefined, 'datetime', enUS)).toBeNull()
41
+ expect(formatDateCell('', 'datetime', enUS)).toBeNull()
42
+ })
43
+
44
+ it('returns null for the Go zero-time (0001-01-01T00:00:00Z)', () => {
45
+ expect(formatDateCell('0001-01-01T00:00:00Z', 'datetime', enUS)).toBeNull()
46
+ expect(formatDateCell('0001-01-01T00:00:00Z', 'date', enUS)).toBeNull()
47
+ })
48
+
49
+ it('returns null for an unparseable value', () => {
50
+ expect(formatDateCell('not-a-date', 'datetime', enUS)).toBeNull()
51
+ })
52
+ })
@@ -3,7 +3,7 @@
3
3
  // host's render tests); here we lock the value-resolution contract that drives
4
4
  // them so a backend shape change is caught without a DOM.
5
5
  import { describe, it, expect } from 'vitest'
6
- import { relationKeyFor, resolveRelationLabel } from '../dynamic-columns'
6
+ import { relationKeyFor, resolveRelationImage, resolveRelationLabel } from '../dynamic-columns'
7
7
  import type { ColumnDefinition } from '../types'
8
8
 
9
9
  const col = (over: Partial<ColumnDefinition>): ColumnDefinition => ({
@@ -64,3 +64,44 @@ describe('resolveRelationLabel', () => {
64
64
  expect(resolveRelationLabel(col({ ref: 'categories' }), row)).toBe('Sin categoría')
65
65
  })
66
66
  })
67
+
68
+ describe('resolveRelationImage', () => {
69
+ const brandCol = (over: Partial<ColumnDefinition> = {}): ColumnDefinition =>
70
+ col({ key: 'brand_id', label: 'Marca', ref: 'brands', ...over })
71
+
72
+ it('reads the thumbnail the backend stamps on the resolved sibling', () => {
73
+ const row = {
74
+ brand_id: 'uuid-1',
75
+ brand: { value: 'uuid-1', label: 'Michelin', image: 'https://cdn/x/m.png' },
76
+ }
77
+ expect(resolveRelationImage(brandCol(), row)).toBe('https://cdn/x/m.png')
78
+ })
79
+
80
+ it('also accepts avatar/photo aliases on the sibling', () => {
81
+ expect(
82
+ resolveRelationImage(brandCol(), {
83
+ brand: { value: 'u', label: 'X', avatar: 'a.png' },
84
+ }),
85
+ ).toBe('a.png')
86
+ expect(
87
+ resolveRelationImage(brandCol(), {
88
+ brand: { value: 'u', label: 'X', photo: 'p.png' },
89
+ }),
90
+ ).toBe('p.png')
91
+ })
92
+
93
+ it('returns empty string when the sibling carries no image (text-only chip)', () => {
94
+ const row = { brand_id: 'uuid-2', brand: { value: 'uuid-2', label: 'Genérica' } }
95
+ expect(resolveRelationImage(brandCol(), row)).toBe('')
96
+ })
97
+
98
+ it('returns empty string when there is no sibling at all', () => {
99
+ expect(resolveRelationImage(brandCol(), { brand_id: 'uuid-3' })).toBe('')
100
+ expect(resolveRelationImage(brandCol(), {})).toBe('')
101
+ })
102
+
103
+ it('ignores an empty-string image (no broken thumbnail)', () => {
104
+ const row = { brand: { value: 'u', label: 'X', image: '' } }
105
+ expect(resolveRelationImage(brandCol(), row)).toBe('')
106
+ })
107
+ })
@@ -14,7 +14,7 @@
14
14
 
15
15
  import * as React from 'react'
16
16
  import { ColumnDef } from '@tanstack/react-table'
17
- import { format } from 'date-fns'
17
+ import { format, type Locale } from 'date-fns'
18
18
  import { es, enUS } from 'date-fns/locale'
19
19
  import * as icons from 'lucide-react'
20
20
  import { MoreHorizontal } from 'lucide-react'
@@ -272,12 +272,42 @@ const renderRelationBadges = (items: any, col: ColumnDefinition) => {
272
272
  )
273
273
  }
274
274
 
275
+ /**
276
+ * Tiny square thumbnail for a resolved relation/option that carries an `image`
277
+ * (brand logo, product photo, customer/user avatar). Uses the same Avatar
278
+ * primitive as the `avatar`/`creator` cells so a broken/loading image
279
+ * gracefully falls back to the record's initials. Sized small (the box is an
280
+ * inline style so an addon-arbitrary Tailwind class never gets dropped by a
281
+ * consuming app's class scan). Rendered inline alongside a label — never alone.
282
+ */
283
+ const RelationThumbnail: React.FC<{
284
+ src: string
285
+ alt: string
286
+ getImageUrl?: (path: string) => string
287
+ size?: number
288
+ }> = ({ src, alt, getImageUrl, size = 18 }) => (
289
+ <Avatar
290
+ className="shrink-0 rounded-sm ring-1 ring-border/40"
291
+ style={{ width: size, height: size }}
292
+ >
293
+ <AvatarImage
294
+ src={getImageUrl ? getImageUrl(src) : src}
295
+ alt={alt}
296
+ className="object-cover"
297
+ />
298
+ <AvatarFallback className="rounded-sm bg-primary/10 text-[8px] font-bold text-primary">
299
+ {getInitials(alt)}
300
+ </AvatarFallback>
301
+ </Avatar>
302
+ )
303
+
275
304
  interface OptionBadgeProps {
276
- option: { value: string; label: string; icon?: string; color?: string }
305
+ option: { value: string; label: string; icon?: string; color?: string; image?: string }
277
306
  fallback: string
307
+ getImageUrl?: (path: string) => string
278
308
  }
279
309
 
280
- const OptionBadge: React.FC<OptionBadgeProps> = ({ option }) => {
310
+ const OptionBadge: React.FC<OptionBadgeProps> = ({ option, getImageUrl }) => {
281
311
  const isDark = useIsDarkTheme()
282
312
  // Explicit backend color wins; otherwise derive a stable, cohesive color
283
313
  // from the option's value (fallback label) so "dead" gray badges come
@@ -287,17 +317,30 @@ const OptionBadge: React.FC<OptionBadgeProps> = ({ option }) => {
287
317
  const colorStyles = generateBadgeStyles(colorSource, { isDark })
288
318
  return (
289
319
  <Badge variant="outline" className="flex items-center gap-1 border-0" style={colorStyles}>
290
- {option.icon && <DynamicIcon name={option.icon} className="h-3.5 w-3.5" />}
320
+ {option.image ? (
321
+ <RelationThumbnail
322
+ src={option.image}
323
+ alt={option.label}
324
+ getImageUrl={getImageUrl}
325
+ size={16}
326
+ />
327
+ ) : (
328
+ option.icon && <DynamicIcon name={option.icon} className="h-3.5 w-3.5" />
329
+ )}
291
330
  <span>{option.label}</span>
292
331
  </Badge>
293
332
  )
294
333
  }
295
334
 
296
- const BadgeWithEndpointOptions: React.FC<{ endpoint: string; value: any }> = ({ endpoint, value }) => {
335
+ const BadgeWithEndpointOptions: React.FC<{
336
+ endpoint: string
337
+ value: any
338
+ getImageUrl?: (path: string) => string
339
+ }> = ({ endpoint, value, getImageUrl }) => {
297
340
  const { optionsMap } = React.useContext(OptionsContext)
298
341
  const options = optionsMap.get(endpoint) || []
299
342
  const option = options.find((opt: any) => opt.value === value)
300
- if (option) return <OptionBadge option={option} fallback={String(value)} />
343
+ if (option) return <OptionBadge option={option} fallback={String(value)} getImageUrl={getImageUrl} />
301
344
  // No declared option matched → humanize the raw token as a safety net so a
302
345
  // cell never shows `in_progress` verbatim (option.label still wins above).
303
346
  return <Badge variant="outline">{humanizeToken(value)}</Badge>
@@ -316,6 +359,35 @@ export const relationKeyFor = (col: Pick<ColumnDefinition, 'key'>): string => {
316
359
  return k.endsWith('_id') ? k.slice(0, -3) : k
317
360
  }
318
361
 
362
+ /** Cell renderers (`cellStyle`/`type`) that resolve to the date renderer. */
363
+ export const DATE_CELL_TYPES = ['date', 'datetime', 'timestamp', 'timestamptz'] as const
364
+
365
+ /**
366
+ * Pure formatter behind the date/datetime cell. Returns the display string and
367
+ * an optional full-precision `title` (tooltip), or `null` when the value is
368
+ * empty/invalid/the Go zero-time so the cell renders an em-dash.
369
+ * - `date`: day only (`PPP`), no tooltip.
370
+ * - `datetime`/`timestamp(tz)`: day + time (`Pp`) with a full-precision
371
+ * tooltip (`PPpp`) — the 7Leguas pattern.
372
+ */
373
+ export function formatDateCell(
374
+ value: unknown,
375
+ renderAs: string | undefined,
376
+ locale: Locale,
377
+ ): { display: string; title?: string } | null {
378
+ if (value === null || value === undefined || value === '') return null
379
+ const date = new Date(value as any)
380
+ if (isNaN(date.getTime()) || date.getFullYear() <= 1) return null
381
+ const withTime = renderAs !== 'date'
382
+ if (withTime) {
383
+ return {
384
+ display: format(date, 'Pp', { locale }),
385
+ title: format(date, 'PPpp', { locale }),
386
+ }
387
+ }
388
+ return { display: format(date, 'PPP', { locale }) }
389
+ }
390
+
319
391
  /**
320
392
  * Reads the resolved relation/option label a backend serves for an FK or
321
393
  * option column, falling back to the raw value. Pure so the cell renderers and
@@ -337,17 +409,39 @@ export const resolveRelationLabel = (col: ColumnDefinition, row: any): string =>
337
409
  return String(raw)
338
410
  }
339
411
 
412
+ /**
413
+ * Reads the thumbnail URL a backend serves on a resolved FK sibling, when
414
+ * present. The backend stamps `image` onto the `{ value, label }` relation
415
+ * object when the referenced model carries an image column (brand logo,
416
+ * product photo, customer avatar). Returns '' when there is no sibling image —
417
+ * the chip then renders text-only, exactly as before.
418
+ */
419
+ export const resolveRelationImage = (col: ColumnDefinition, row: any): string => {
420
+ const sibling = getNestedValue(row, relationKeyFor(col))
421
+ if (sibling && typeof sibling === 'object') {
422
+ const img = sibling.image ?? sibling.avatar ?? sibling.photo
423
+ if (img !== undefined && img !== null && img !== '') return String(img)
424
+ }
425
+ return ''
426
+ }
427
+
340
428
  /**
341
429
  * Renders a resolved FK relation as a clean, truncated chip. Reads the
342
- * backend-resolved sibling `{ value, label }` (see `relationKeyFor`) and shows
343
- * its `label`. Falls back to the raw id when no sibling was resolved, and to an
344
- * empty marker when there is no value at all. Domain-agnostic: works for every
345
- * `belongs_to` column (category, supplier, warehouse, …) without per-addon code.
430
+ * backend-resolved sibling `{ value, label[, image] }` (see `relationKeyFor`)
431
+ * and shows its `label`, prefixed with a small thumbnail when the sibling
432
+ * carries an `image`. Falls back to the raw id when no sibling was resolved, and
433
+ * to an empty marker when there is no value at all. Domain-agnostic: works for
434
+ * every `belongs_to` column (category, supplier, brand, …) without per-addon code.
346
435
  */
347
- const RelationCell: React.FC<{ col: ColumnDefinition; row: any }> = ({ col, row }) => {
436
+ const RelationCell: React.FC<{
437
+ col: ColumnDefinition
438
+ row: any
439
+ getImageUrl?: (path: string) => string
440
+ }> = ({ col, row, getImageUrl }) => {
348
441
  const isDark = useIsDarkTheme()
349
442
  const display = resolveRelationLabel(col, row)
350
443
  if (!display) return <EmptyCell />
444
+ const image = resolveRelationImage(col, row)
351
445
  // Deterministic, SUBTLE color keyed on the resolved label — lighter than
352
446
  // enum badges (soft tint, no heavy fill) so category/brand chips read as
353
447
  // alive yet stay visually distinct from option/status badges. Inline style
@@ -355,10 +449,13 @@ const RelationCell: React.FC<{ col: ColumnDefinition; row: any }> = ({ col, row
355
449
  const chipStyles = relationChipStyles(display, { isDark })
356
450
  return (
357
451
  <span
358
- className="inline-flex max-w-[220px] items-center truncate rounded-md px-2 py-0.5 text-sm font-medium"
452
+ className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium"
359
453
  style={chipStyles}
360
454
  title={display}
361
455
  >
456
+ {image && (
457
+ <RelationThumbnail src={image} alt={display} getImageUrl={getImageUrl} size={18} />
458
+ )}
362
459
  <span className="truncate">{display}</span>
363
460
  </span>
364
461
  )
@@ -494,14 +591,14 @@ export function makeDefaultGetDynamicColumns(
494
591
  // Endpoint-loaded badge options (preloaded into OptionsContext)
495
592
  if (renderAs === 'badge' && col.useOptions && col.searchEndpoint) {
496
593
  if (!value) return <span className="text-muted-foreground">-</span>
497
- return <BadgeWithEndpointOptions endpoint={col.searchEndpoint} value={value} />
594
+ return <BadgeWithEndpointOptions endpoint={col.searchEndpoint} value={value} getImageUrl={getImageUrl} />
498
595
  }
499
596
 
500
597
  // Static badge options — map value → label/icon/color
501
598
  if (renderAs === 'badge' && col.options && col.options.length > 0) {
502
599
  if (!value && value !== 0) return <span className="text-muted-foreground">-</span>
503
600
  const option = col.options.find((o) => o.value === String(value))
504
- if (option) return <OptionBadge option={option} fallback={String(value)} />
601
+ if (option) return <OptionBadge option={option} fallback={String(value)} getImageUrl={getImageUrl} />
505
602
  return <Badge variant="outline">{humanizeToken(value)}</Badge>
506
603
  }
507
604
 
@@ -521,7 +618,7 @@ export function makeDefaultGetDynamicColumns(
521
618
  if (!value && value !== 0) return <EmptyCell />
522
619
  const sv = String(value)
523
620
  const option = col.options?.find((o) => o.value === sv)
524
- if (option) return <OptionBadge option={option} fallback={sv} />
621
+ if (option) return <OptionBadge option={option} fallback={sv} getImageUrl={getImageUrl} />
525
622
  const isDark =
526
623
  typeof document !== 'undefined' &&
527
624
  document.documentElement.classList.contains('dark')
@@ -544,7 +641,7 @@ export function makeDefaultGetDynamicColumns(
544
641
  renderAs === 'relation' ||
545
642
  (col.ref && !col.options?.length && renderAs !== 'badge' && renderAs !== 'status')
546
643
  ) {
547
- return <RelationCell col={col} row={row.original} />
644
+ return <RelationCell col={col} row={row.original} getImageUrl={getImageUrl} />
548
645
  }
549
646
 
550
647
  // Option/type column: a `select`-style column ships its
@@ -559,29 +656,29 @@ export function makeDefaultGetDynamicColumns(
559
656
  ) {
560
657
  if (!value && value !== 0) return <EmptyCell />
561
658
  const option = col.options.find((o) => o.value === String(value))
562
- if (option) return <OptionBadge option={option} fallback={String(value)} />
659
+ if (option) return <OptionBadge option={option} fallback={String(value)} getImageUrl={getImageUrl} />
563
660
  return <Badge variant="outline">{humanizeToken(value)}</Badge>
564
661
  }
565
662
 
566
663
  switch (renderAs) {
567
- case 'date': {
568
- if (!value) return <span className="text-muted-foreground">-</span>
569
- try {
570
- const date = new Date(value)
571
- if (isNaN(date.getTime()) || date.getFullYear() <= 1) {
572
- return <span className="text-muted-foreground">-</span>
573
- }
574
- return (
575
- <div className="flex items-center gap-1.5 text-muted-foreground">
576
- <icons.Calendar className="h-3.5 w-3.5 opacity-70" />
577
- <span className="text-sm font-medium">
578
- {format(date, 'PPP', { locale: dateLocale })}
579
- </span>
580
- </div>
581
- )
582
- } catch {
583
- return <span>{String(value)}</span>
584
- }
664
+ case 'date':
665
+ case 'datetime':
666
+ case 'timestamp':
667
+ case 'timestamptz': {
668
+ const formatted = formatDateCell(value, renderAs, dateLocale)
669
+ if (!formatted)
670
+ return <span className="text-muted-foreground">-</span>
671
+ return (
672
+ <div
673
+ className="flex items-center gap-1.5 text-muted-foreground"
674
+ title={formatted.title}
675
+ >
676
+ <icons.Calendar className="h-3.5 w-3.5 opacity-70" />
677
+ <span className="text-sm font-medium">
678
+ {formatted.display}
679
+ </span>
680
+ </div>
681
+ )
585
682
  }
586
683
 
587
684
  case 'search':
@@ -65,7 +65,7 @@ import { Progress } from './dialogs/_primitives'
65
65
  import { useMetadataCache } from './metadata-cache'
66
66
  import { useApi, useCurrentBranch } from './api-context'
67
67
  import type { ColumnFilterConfig, GetDynamicColumns } from './dynamic-columns-shim'
68
- import { defaultGetDynamicColumns } from './dynamic-columns'
68
+ import { defaultGetDynamicColumns, DATE_CELL_TYPES } from './dynamic-columns'
69
69
  import { OptionsContext } from './options-context'
70
70
  import { ActionModalDispatcher } from './action-modal-dispatcher'
71
71
  import type { TableMetadata, ApiResponse, ActionMetadata } from './types'
@@ -555,7 +555,8 @@ export function DynamicTable({
555
555
  else if (hasStaticOptions || hasEndpoint) filterType = 'select'
556
556
  else if (c.type === 'boolean') filterType = 'boolean'
557
557
  else if (c.type === 'number') filterType = 'number_range'
558
- else if (c.type === 'date') filterType = 'date_range'
558
+ else if ((DATE_CELL_TYPES as readonly string[]).includes(c.type))
559
+ filterType = 'date_range'
559
560
  else filterType = 'text'
560
561
 
561
562
  const options = hasStaticOptions
package/src/types.ts CHANGED
@@ -87,6 +87,11 @@ export interface ColumnDefinition {
87
87
  | 'text'
88
88
  | 'number'
89
89
  | 'date'
90
+ // Timestamp variants. They share the `date` cell renderer but append
91
+ // the time + a full-precision tooltip (see formatDateCell).
92
+ | 'datetime'
93
+ | 'timestamp'
94
+ | 'timestamptz'
90
95
  | 'select'
91
96
  | 'search'
92
97
  | 'relation-badge-list'