@asteby/metacore-runtime-react 13.8.5 → 13.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 13.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 27b37f3: feat(dynamic-columns): declarative pro cell renderers for dynamic tables
8
+
9
+ Adds a library of declarative cell renderers so columns are rendered
10
+ beautifully out of the box instead of raw text. Driven by `col.cellStyle`
11
+ (or `col.type`), resolved via the existing `renderAs = col.cellStyle ?? col.type`.
12
+ Config is read from `col.styleConfig`, tolerating both snake_case (kernel) and
13
+ camelCase (compiled models).
14
+
15
+ New cellStyles:
16
+ - `url` / `link` — clickable link with an `ExternalLink` icon. `styleConfig`:
17
+ `{ label_field?, url_field?, icon?, new_tab? }`. Shows `label_field` text or
18
+ the URL hostname; opens in a new tab for external URLs (or `new_tab`);
19
+ prefixes `https://` when the scheme is missing.
20
+ - `email` — `mailto:` link with a `Mail` icon.
21
+ - `currency` — `Intl.NumberFormat` currency formatting, right-aligned.
22
+ Currency from `styleConfig.currency` (default `USD`), decimals from
23
+ `styleConfig.decimals` (default 2). No hardcoded MXN.
24
+ - `number` — thousands-separated number, right-aligned.
25
+ - `percent` / `progress` — progress bar (shadcn `Progress`) + `NN%` label.
26
+ - `badge` (generic) — pills a plain value even without `options`/`searchEndpoint`.
27
+ - `status` — badge with semantic color by value (active/paid→green,
28
+ pending/draft→amber, cancelled/failed→red, else grey); explicit
29
+ `options` colors win.
30
+ - `tags` — array / comma-separated string → row of small badges.
31
+ - `color` — color swatch + hex code.
32
+ - `code` / `truncate-text` — monospaced, truncated (`styleConfig.max_length`)
33
+ with a hover copy button.
34
+ - `creator` / `user` — avatar + name + subtitle, generalising the existing
35
+ `avatar`/`search` renderer (name from `styleConfig.name_field`, photo from a
36
+ sibling `.avatar`/`.photo` or `base_path + value`, initials fallback).
37
+
38
+ Also improves the existing `boolean` cell (green `Check` / muted `Minus` icon)
39
+ without breaking the `avatar`/`search`, `date`, `phone`, `image`,
40
+ `media-gallery`, `badge+options` and `relation-badge-list` renderers.
41
+
3
42
  ## 13.8.5
4
43
 
5
44
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAsCA,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;AAOD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAmHD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAsXnB;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":"AA0CA,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;AAuJD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAokBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Default `getDynamicColumns` factory used by hosts that don't need a custom
3
3
  // renderer. Supports every cell type produced by kernel/dynamic metadata:
4
- // badge (static + endpoint-loaded options), avatar, phone, date, boolean,
5
- // relation-badge-list, media-gallery, image, plus a generic text fallback.
4
+ // badge (static + endpoint-loaded options), avatar/search, creator/user,
5
+ // phone, date, boolean, relation-badge-list, media-gallery, image, plus the
6
+ // declarative pro renderers url/link, email, currency, number, percent/
7
+ // progress, status, tags, color, code/truncate-text, and a generic text
8
+ // fallback. The renderer resolves `cellStyle ?? type` for each column.
6
9
  //
7
10
  // The implementation was previously duplicated across multiple host apps
8
11
  // (~550 LOC each, drifting). It now lives here so a single fix propagates
@@ -16,11 +19,63 @@ import { MoreHorizontal } from 'lucide-react';
16
19
  import { Avatar, AvatarFallback, AvatarImage, Badge, Button, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@asteby/metacore-ui';
17
20
  import { DataTableColumnHeader, FilterableColumnHeader, } from '@asteby/metacore-ui/data-table';
18
21
  import { generateBadgeStyles, getInitials } from '@asteby/metacore-ui/lib';
22
+ import { Progress } from './dialogs/_primitives';
19
23
  import { OptionsContext } from './options-context';
20
24
  import { DynamicIcon } from './dynamic-icon';
21
25
  import { isColumnVisibleInTable } from './column-visibility';
22
26
  const defaultGetImageUrl = (path) => path;
23
27
  const getNestedValue = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
28
+ /**
29
+ * Reads a styleConfig key tolerating both snake_case (emitted by the kernel)
30
+ * and camelCase (sometimes produced by compiled models). Returns the first
31
+ * defined match, e.g. `cfg('label_field', 'labelField')`.
32
+ */
33
+ const styleCfg = (col, ...keys) => {
34
+ const cfg = col.styleConfig;
35
+ if (!cfg)
36
+ return undefined;
37
+ for (const k of keys) {
38
+ if (cfg[k] !== undefined && cfg[k] !== null)
39
+ return cfg[k];
40
+ }
41
+ return undefined;
42
+ };
43
+ const EmptyCell = () => _jsx("span", { className: "text-muted-foreground", children: "-" });
44
+ /** Resolves the active org currency, defaulting to USD when no override. */
45
+ const resolveCurrency = (col) => styleCfg(col, 'currency') || 'USD';
46
+ const formatNumber = (value, opts, locale) => new Intl.NumberFormat(locale || undefined, opts).format(value);
47
+ /**
48
+ * Semantic status → badge color. Used by the `status` cell when no explicit
49
+ * `options` color is declared. Generic, value-driven mapping.
50
+ */
51
+ const statusColorFor = (value) => {
52
+ const v = value.toLowerCase();
53
+ if (['active', 'enabled', 'paid', 'completed', 'done', 'success', 'approved', 'open']
54
+ .includes(v))
55
+ return '#22c55e';
56
+ if (['pending', 'draft', 'processing', 'in_progress', 'review', 'waiting'].includes(v))
57
+ return '#eab308';
58
+ if (['inactive', 'disabled', 'cancelled', 'canceled', 'failed', 'rejected', 'error', 'closed']
59
+ .includes(v))
60
+ return '#ef4444';
61
+ return '#6b7280';
62
+ };
63
+ /** Copyable monospaced text cell (code/IDs/hashes). */
64
+ const CodeCell = ({ text, maxLength }) => {
65
+ const [copied, setCopied] = React.useState(false);
66
+ const display = maxLength && text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
67
+ const onCopy = () => {
68
+ try {
69
+ navigator.clipboard?.writeText(text);
70
+ setCopied(true);
71
+ setTimeout(() => setCopied(false), 1200);
72
+ }
73
+ catch {
74
+ /* clipboard unavailable */
75
+ }
76
+ };
77
+ return (_jsxs("div", { className: "group flex items-center gap-1.5", children: [_jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/80", title: text, children: display }), _jsx("button", { type: "button", onClick: onCopy, className: "opacity-0 transition-opacity group-hover:opacity-100 text-muted-foreground hover:text-foreground", "aria-label": "Copiar", title: "Copiar", children: copied ? (_jsx(icons.Check, { className: "h-3.5 w-3.5 text-green-500" })) : (_jsx(icons.Copy, { className: "h-3.5 w-3.5" })) })] }));
78
+ };
24
79
  /**
25
80
  * State-machine gate for per-row actions.
26
81
  *
@@ -124,6 +179,13 @@ const BadgeWithEndpointOptions = ({ endpoint, value }) => {
124
179
  return _jsx(OptionBadge, { option: option, fallback: String(value) });
125
180
  return _jsx(Badge, { variant: "outline", children: String(value) });
126
181
  };
182
+ /**
183
+ * Generic avatar-style cell: round/rounded photo (or initials fallback) +
184
+ * primary name + optional subtitle. Backs the `avatar`/`search` columns as
185
+ * well as the `creator`/`user` cellStyles. Paths are parameterised so the same
186
+ * JSX serves every variant.
187
+ */
188
+ const AvatarCell = ({ name, desc, avatarSrc, getImageUrl }) => (_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg ring-1 ring-border/50", children: [_jsx(AvatarImage, { src: avatarSrc ? getImageUrl(avatarSrc) : '', alt: name, className: "object-cover" }), _jsx(AvatarFallback, { className: "text-[10px] font-bold bg-primary/5 text-primary rounded-lg", children: getInitials(name) })] }), _jsxs("div", { className: "flex flex-col min-w-0 overflow-hidden", children: [_jsx("span", { className: "font-medium text-sm truncate leading-none mb-0.5 text-foreground/90", children: name }), desc && (_jsx("span", { className: "text-[11px] text-muted-foreground truncate leading-none", children: desc }))] })] }));
127
189
  /**
128
190
  * Builds the canonical column factory used by `<DynamicTable>` when the host
129
191
  * does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
@@ -196,7 +258,26 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
196
258
  if (renderAs === 'relation-badge-list') {
197
259
  return renderRelationBadges(value, col);
198
260
  }
199
- switch (col.type) {
261
+ // Generic badge (no options/endpoint) — still pill it.
262
+ if (renderAs === 'badge') {
263
+ if (!value && value !== 0)
264
+ return _jsx(EmptyCell, {});
265
+ return _jsx(Badge, { variant: "outline", children: String(value) });
266
+ }
267
+ // Status — semantic color by value, options color wins.
268
+ if (renderAs === 'status') {
269
+ if (!value && value !== 0)
270
+ return _jsx(EmptyCell, {});
271
+ const sv = String(value);
272
+ const option = col.options?.find((o) => o.value === sv);
273
+ if (option)
274
+ return _jsx(OptionBadge, { option: option, fallback: sv });
275
+ const isDark = typeof document !== 'undefined' &&
276
+ document.documentElement.classList.contains('dark');
277
+ const styles = generateBadgeStyles(statusColorFor(sv), { isDark });
278
+ return (_jsx(Badge, { variant: "outline", className: "border-0 capitalize", style: styles, children: sv }));
279
+ }
280
+ switch (renderAs) {
200
281
  case 'date': {
201
282
  if (!value)
202
283
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
@@ -212,36 +293,143 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
212
293
  }
213
294
  }
214
295
  case 'search':
215
- case 'avatar': {
216
- const namePath = col.tooltip || col.key;
296
+ case 'avatar':
297
+ case 'creator':
298
+ case 'user': {
299
+ // `creator`/`user` resolve the name from an explicit
300
+ // styleConfig.name_field first, then the legacy
301
+ // tooltip/displayField hints, then the column key.
302
+ const namePath = styleCfg(col, 'name_field', 'nameField') ||
303
+ col.tooltip ||
304
+ col.displayField ||
305
+ col.key;
217
306
  const name = getNestedValue(row.original, namePath) || 'N/A';
218
307
  const desc = getNestedValue(row.original, col.description || '');
308
+ const basePath = styleCfg(col, 'base_path', 'basePath') ?? col.basePath ?? '';
219
309
  let avatarSrc;
220
310
  if (col.key.includes('.')) {
311
+ // Look for a sibling `.avatar` or `.photo` field.
221
312
  const parentPath = col.key.split('.').slice(0, -1).join('.');
222
- const avatarPath = `${parentPath}.avatar`;
223
- const possibleAvatar = getNestedValue(row.original, avatarPath);
224
- if (possibleAvatar)
225
- avatarSrc = String(possibleAvatar);
226
- }
227
- else if (value &&
228
- (String(value).startsWith('http') || String(value).startsWith('https'))) {
229
- avatarSrc = String(value);
313
+ const sibling = getNestedValue(row.original, `${parentPath}.avatar`) ||
314
+ getNestedValue(row.original, `${parentPath}.photo`);
315
+ if (sibling)
316
+ avatarSrc = String(sibling);
230
317
  }
231
- else if (value) {
232
- avatarSrc = `${apiBaseUrl}${col.basePath || ''}${value}`;
318
+ if (!avatarSrc && value) {
319
+ if (String(value).startsWith('http')) {
320
+ avatarSrc = String(value);
321
+ }
322
+ else {
323
+ avatarSrc = `${apiBaseUrl}${basePath}${value}`;
324
+ }
233
325
  }
234
- return (_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg ring-1 ring-border/50", children: [_jsx(AvatarImage, { src: getImageUrl(avatarSrc || ''), alt: String(name), className: "object-cover" }), _jsx(AvatarFallback, { className: "text-[10px] font-bold bg-primary/5 text-primary rounded-lg", children: getInitials(String(name)) })] }), _jsxs("div", { className: "flex flex-col min-w-0 overflow-hidden", children: [_jsx("span", { className: "font-medium text-sm truncate leading-none mb-0.5 text-foreground/90", children: String(name) }), desc && (_jsx("span", { className: "text-[11px] text-muted-foreground truncate leading-none", children: String(desc) }))] })] }));
326
+ return (_jsx(AvatarCell, { name: String(name), desc: desc ? String(desc) : undefined, avatarSrc: avatarSrc, getImageUrl: getImageUrl }));
235
327
  }
236
328
  case 'relation-badge-list':
237
329
  return renderRelationBadges(value, col);
330
+ case 'url':
331
+ case 'link': {
332
+ const labelField = styleCfg(col, 'label_field', 'labelField');
333
+ const urlField = styleCfg(col, 'url_field', 'urlField');
334
+ const rawUrl = urlField
335
+ ? getNestedValue(row.original, urlField)
336
+ : value;
337
+ if (!rawUrl)
338
+ return _jsx(EmptyCell, {});
339
+ const urlStr = String(rawUrl);
340
+ const href = /^https?:\/\//i.test(urlStr) ? urlStr : `https://${urlStr}`;
341
+ let label;
342
+ if (labelField) {
343
+ label = String(getNestedValue(row.original, labelField) ?? href);
344
+ }
345
+ else {
346
+ try {
347
+ label = new URL(href).hostname;
348
+ }
349
+ catch {
350
+ label = urlStr;
351
+ }
352
+ }
353
+ const isExternal = !/^https?:\/\/(localhost|127\.)/i.test(href);
354
+ const newTab = styleCfg(col, 'new_tab', 'newTab') === true || isExternal;
355
+ const iconName = styleCfg(col, 'icon') || 'ExternalLink';
356
+ return (_jsxs("a", { href: href, ...(newTab
357
+ ? { target: '_blank', rel: 'noopener noreferrer' }
358
+ : {}), className: "inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: [_jsx(DynamicIcon, { name: iconName, className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "truncate max-w-[260px]", children: label })] }));
359
+ }
360
+ case 'email': {
361
+ if (!value)
362
+ return _jsx(EmptyCell, {});
363
+ const email = String(value);
364
+ return (_jsxs("a", { href: `mailto:${email}`, className: "inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: [_jsx(icons.Mail, { className: "h-3.5 w-3.5 shrink-0 opacity-70" }), _jsx("span", { className: "truncate max-w-[260px]", children: email })] }));
365
+ }
366
+ case 'currency': {
367
+ const num = typeof value === 'number' ? value : Number(value);
368
+ if (value === null || value === undefined || isNaN(num))
369
+ return (_jsx("div", { className: "text-right", children: _jsx(EmptyCell, {}) }));
370
+ const decimals = styleCfg(col, 'decimals') ?? 2;
371
+ return (_jsx("span", { className: "block text-right font-medium tabular-nums", children: formatNumber(num, {
372
+ style: 'currency',
373
+ currency: resolveCurrency(col),
374
+ minimumFractionDigits: decimals,
375
+ maximumFractionDigits: decimals,
376
+ }, currentLanguage) }));
377
+ }
378
+ case 'number': {
379
+ const num = typeof value === 'number' ? value : Number(value);
380
+ if (value === null || value === undefined || isNaN(num))
381
+ return (_jsx("div", { className: "text-right", children: _jsx(EmptyCell, {}) }));
382
+ const decimals = styleCfg(col, 'decimals');
383
+ return (_jsx("span", { className: "block text-right font-medium tabular-nums", children: formatNumber(num, decimals !== undefined
384
+ ? {
385
+ minimumFractionDigits: decimals,
386
+ maximumFractionDigits: decimals,
387
+ }
388
+ : {}, currentLanguage) }));
389
+ }
390
+ case 'percent':
391
+ case 'progress': {
392
+ const num = typeof value === 'number' ? value : Number(value);
393
+ if (value === null || value === undefined || isNaN(num))
394
+ return _jsx(EmptyCell, {});
395
+ const pct = Math.max(0, Math.min(100, num));
396
+ return (_jsxs("div", { className: "flex items-center gap-2 min-w-[120px]", children: [_jsx(Progress, { value: pct, className: "flex-1" }), _jsxs("span", { className: "text-xs font-medium tabular-nums text-muted-foreground w-9 text-right", children: [Math.round(pct), "%"] })] }));
397
+ }
398
+ case 'tags': {
399
+ const list = Array.isArray(value)
400
+ ? value.map(String)
401
+ : value
402
+ ? String(value)
403
+ .split(',')
404
+ .map((s) => s.trim())
405
+ .filter(Boolean)
406
+ : [];
407
+ if (list.length === 0)
408
+ return _jsx(EmptyCell, {});
409
+ return (_jsx("div", { className: "flex flex-wrap gap-1", children: list.map((tag, i) => (_jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-[10px]", children: tag }, `${col.key}-${i}`))) }));
410
+ }
411
+ case 'color': {
412
+ if (!value)
413
+ return _jsx(EmptyCell, {});
414
+ const hex = String(value);
415
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-4 w-4 rounded border border-border/60 shrink-0", style: { background: hex } }), _jsx("code", { className: "font-mono text-xs text-muted-foreground", children: hex })] }));
416
+ }
417
+ case 'code':
418
+ case 'truncate-text': {
419
+ if (value === null || value === undefined || value === '')
420
+ return _jsx(EmptyCell, {});
421
+ const maxLength = styleCfg(col, 'max_length', 'maxLength');
422
+ return _jsx(CodeCell, { text: String(value), maxLength: maxLength });
423
+ }
238
424
  case 'phone': {
239
425
  if (!value)
240
426
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
241
427
  return _jsx("span", { className: "font-medium text-sm", children: String(value) });
242
428
  }
243
- case 'boolean':
244
- return value ? _jsx(Badge, { children: "S\u00ED" }) : _jsx(Badge, { variant: "secondary", children: "No" });
429
+ case 'boolean': {
430
+ const showText = styleCfg(col, 'show_text', 'showText') !== false;
431
+ return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [value ? (_jsx(icons.Check, { className: "h-4 w-4 text-green-500" })) : (_jsx(icons.Minus, { className: "h-4 w-4 text-muted-foreground" })), showText && (_jsx("span", { className: "text-sm text-muted-foreground", children: value ? 'Sí' : 'No' }))] }));
432
+ }
245
433
  case 'media-gallery': {
246
434
  if (!value || (Array.isArray(value) && value.length === 0)) {
247
435
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
package/dist/types.d.ts CHANGED
@@ -75,7 +75,7 @@ export type ColumnVisibility = 'all' | 'table' | 'modal' | 'list' | (string & {}
75
75
  export interface ColumnDefinition {
76
76
  key: string;
77
77
  label: string;
78
- type: 'text' | 'number' | 'date' | 'select' | 'search' | 'relation-badge-list' | 'avatar' | 'boolean' | 'phone' | 'media-gallery' | 'image';
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';
79
79
  sortable: boolean;
80
80
  filterable: boolean;
81
81
  hidden?: boolean;
@@ -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": "13.8.5",
3
+ "version": "13.9.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,10 @@
1
1
  // Default `getDynamicColumns` factory used by hosts that don't need a custom
2
2
  // renderer. Supports every cell type produced by kernel/dynamic metadata:
3
- // badge (static + endpoint-loaded options), avatar, phone, date, boolean,
4
- // relation-badge-list, media-gallery, image, plus a generic text fallback.
3
+ // badge (static + endpoint-loaded options), avatar/search, creator/user,
4
+ // phone, date, boolean, relation-badge-list, media-gallery, image, plus the
5
+ // declarative pro renderers url/link, email, currency, number, percent/
6
+ // progress, status, tags, color, code/truncate-text, and a generic text
7
+ // fallback. The renderer resolves `cellStyle ?? type` for each column.
5
8
  //
6
9
  // The implementation was previously duplicated across multiple host apps
7
10
  // (~550 LOC each, drifting). It now lives here so a single fix propagates
@@ -32,6 +35,7 @@ import {
32
35
  type ColumnFilterMeta,
33
36
  } from '@asteby/metacore-ui/data-table'
34
37
  import { generateBadgeStyles, getInitials } from '@asteby/metacore-ui/lib'
38
+ import { Progress } from './dialogs/_primitives'
35
39
  import { OptionsContext } from './options-context'
36
40
  import { DynamicIcon } from './dynamic-icon'
37
41
  import type { TableMetadata, ColumnDefinition } from './types'
@@ -62,6 +66,95 @@ const defaultGetImageUrl = (path: string) => path
62
66
  const getNestedValue = (obj: any, path: string) =>
63
67
  path.split('.').reduce((acc, part) => acc && acc[part], obj)
64
68
 
69
+ /**
70
+ * Reads a styleConfig key tolerating both snake_case (emitted by the kernel)
71
+ * and camelCase (sometimes produced by compiled models). Returns the first
72
+ * defined match, e.g. `cfg('label_field', 'labelField')`.
73
+ */
74
+ const styleCfg = (
75
+ col: ColumnDefinition,
76
+ ...keys: string[]
77
+ ): any => {
78
+ const cfg = col.styleConfig
79
+ if (!cfg) return undefined
80
+ for (const k of keys) {
81
+ if (cfg[k] !== undefined && cfg[k] !== null) return cfg[k]
82
+ }
83
+ return undefined
84
+ }
85
+
86
+ const EmptyCell = () => <span className="text-muted-foreground">-</span>
87
+
88
+ /** Resolves the active org currency, defaulting to USD when no override. */
89
+ const resolveCurrency = (col: ColumnDefinition): string =>
90
+ styleCfg(col, 'currency') || 'USD'
91
+
92
+ const formatNumber = (
93
+ value: number,
94
+ opts: Intl.NumberFormatOptions,
95
+ locale?: string,
96
+ ) => new Intl.NumberFormat(locale || undefined, opts).format(value)
97
+
98
+ /**
99
+ * Semantic status → badge color. Used by the `status` cell when no explicit
100
+ * `options` color is declared. Generic, value-driven mapping.
101
+ */
102
+ const statusColorFor = (value: string): string => {
103
+ const v = value.toLowerCase()
104
+ if (
105
+ ['active', 'enabled', 'paid', 'completed', 'done', 'success', 'approved', 'open']
106
+ .includes(v)
107
+ )
108
+ return '#22c55e'
109
+ if (['pending', 'draft', 'processing', 'in_progress', 'review', 'waiting'].includes(v))
110
+ return '#eab308'
111
+ if (
112
+ ['inactive', 'disabled', 'cancelled', 'canceled', 'failed', 'rejected', 'error', 'closed']
113
+ .includes(v)
114
+ )
115
+ return '#ef4444'
116
+ return '#6b7280'
117
+ }
118
+
119
+ /** Copyable monospaced text cell (code/IDs/hashes). */
120
+ const CodeCell: React.FC<{ text: string; maxLength?: number }> = ({ text, maxLength }) => {
121
+ const [copied, setCopied] = React.useState(false)
122
+ const display =
123
+ maxLength && text.length > maxLength ? `${text.slice(0, maxLength)}…` : text
124
+ const onCopy = () => {
125
+ try {
126
+ navigator.clipboard?.writeText(text)
127
+ setCopied(true)
128
+ setTimeout(() => setCopied(false), 1200)
129
+ } catch {
130
+ /* clipboard unavailable */
131
+ }
132
+ }
133
+ return (
134
+ <div className="group flex items-center gap-1.5">
135
+ <code
136
+ className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/80"
137
+ title={text}
138
+ >
139
+ {display}
140
+ </code>
141
+ <button
142
+ type="button"
143
+ onClick={onCopy}
144
+ className="opacity-0 transition-opacity group-hover:opacity-100 text-muted-foreground hover:text-foreground"
145
+ aria-label="Copiar"
146
+ title="Copiar"
147
+ >
148
+ {copied ? (
149
+ <icons.Check className="h-3.5 w-3.5 text-green-500" />
150
+ ) : (
151
+ <icons.Copy className="h-3.5 w-3.5" />
152
+ )}
153
+ </button>
154
+ </div>
155
+ )
156
+ }
157
+
65
158
  /**
66
159
  * State-machine gate for per-row actions.
67
160
  *
@@ -195,6 +288,42 @@ const BadgeWithEndpointOptions: React.FC<{ endpoint: string; value: any }> = ({
195
288
  return <Badge variant="outline">{String(value)}</Badge>
196
289
  }
197
290
 
291
+ /**
292
+ * Generic avatar-style cell: round/rounded photo (or initials fallback) +
293
+ * primary name + optional subtitle. Backs the `avatar`/`search` columns as
294
+ * well as the `creator`/`user` cellStyles. Paths are parameterised so the same
295
+ * JSX serves every variant.
296
+ */
297
+ const AvatarCell: React.FC<{
298
+ name: string
299
+ desc?: string
300
+ avatarSrc?: string
301
+ getImageUrl: (path: string) => string
302
+ }> = ({ name, desc, avatarSrc, getImageUrl }) => (
303
+ <div className="flex items-center gap-3 min-w-0">
304
+ <Avatar className="h-8 w-8 rounded-lg ring-1 ring-border/50">
305
+ <AvatarImage
306
+ src={avatarSrc ? getImageUrl(avatarSrc) : ''}
307
+ alt={name}
308
+ className="object-cover"
309
+ />
310
+ <AvatarFallback className="text-[10px] font-bold bg-primary/5 text-primary rounded-lg">
311
+ {getInitials(name)}
312
+ </AvatarFallback>
313
+ </Avatar>
314
+ <div className="flex flex-col min-w-0 overflow-hidden">
315
+ <span className="font-medium text-sm truncate leading-none mb-0.5 text-foreground/90">
316
+ {name}
317
+ </span>
318
+ {desc && (
319
+ <span className="text-[11px] text-muted-foreground truncate leading-none">
320
+ {desc}
321
+ </span>
322
+ )}
323
+ </div>
324
+ </div>
325
+ )
326
+
198
327
  /**
199
328
  * Builds the canonical column factory used by `<DynamicTable>` when the host
200
329
  * does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
@@ -301,7 +430,30 @@ export function makeDefaultGetDynamicColumns(
301
430
  return renderRelationBadges(value, col)
302
431
  }
303
432
 
304
- switch (col.type) {
433
+ // Generic badge (no options/endpoint) — still pill it.
434
+ if (renderAs === 'badge') {
435
+ if (!value && value !== 0) return <EmptyCell />
436
+ return <Badge variant="outline">{String(value)}</Badge>
437
+ }
438
+
439
+ // Status — semantic color by value, options color wins.
440
+ if (renderAs === 'status') {
441
+ if (!value && value !== 0) return <EmptyCell />
442
+ const sv = String(value)
443
+ const option = col.options?.find((o) => o.value === sv)
444
+ if (option) return <OptionBadge option={option} fallback={sv} />
445
+ const isDark =
446
+ typeof document !== 'undefined' &&
447
+ document.documentElement.classList.contains('dark')
448
+ const styles = generateBadgeStyles(statusColorFor(sv), { isDark })
449
+ return (
450
+ <Badge variant="outline" className="border-0 capitalize" style={styles}>
451
+ {sv}
452
+ </Badge>
453
+ )
454
+ }
455
+
456
+ switch (renderAs) {
305
457
  case 'date': {
306
458
  if (!value) return <span className="text-muted-foreground">-</span>
307
459
  try {
@@ -323,62 +475,245 @@ export function makeDefaultGetDynamicColumns(
323
475
  }
324
476
 
325
477
  case 'search':
326
- case 'avatar': {
327
- const namePath = col.tooltip || col.key
478
+ case 'avatar':
479
+ case 'creator':
480
+ case 'user': {
481
+ // `creator`/`user` resolve the name from an explicit
482
+ // styleConfig.name_field first, then the legacy
483
+ // tooltip/displayField hints, then the column key.
484
+ const namePath =
485
+ styleCfg(col, 'name_field', 'nameField') ||
486
+ col.tooltip ||
487
+ col.displayField ||
488
+ col.key
328
489
  const name = getNestedValue(row.original, namePath) || 'N/A'
329
490
  const desc = getNestedValue(row.original, col.description || '')
330
491
 
492
+ const basePath = styleCfg(col, 'base_path', 'basePath') ?? col.basePath ?? ''
331
493
  let avatarSrc: string | undefined
332
494
  if (col.key.includes('.')) {
495
+ // Look for a sibling `.avatar` or `.photo` field.
333
496
  const parentPath = col.key.split('.').slice(0, -1).join('.')
334
- const avatarPath = `${parentPath}.avatar`
335
- const possibleAvatar = getNestedValue(row.original, avatarPath)
336
- if (possibleAvatar) avatarSrc = String(possibleAvatar)
337
- } else if (
338
- value &&
339
- (String(value).startsWith('http') || String(value).startsWith('https'))
340
- ) {
341
- avatarSrc = String(value)
342
- } else if (value) {
343
- avatarSrc = `${apiBaseUrl}${col.basePath || ''}${value}`
497
+ const sibling =
498
+ getNestedValue(row.original, `${parentPath}.avatar`) ||
499
+ getNestedValue(row.original, `${parentPath}.photo`)
500
+ if (sibling) avatarSrc = String(sibling)
501
+ }
502
+ if (!avatarSrc && value) {
503
+ if (String(value).startsWith('http')) {
504
+ avatarSrc = String(value)
505
+ } else {
506
+ avatarSrc = `${apiBaseUrl}${basePath}${value}`
507
+ }
344
508
  }
345
509
 
346
510
  return (
347
- <div className="flex items-center gap-3 min-w-0">
348
- <Avatar className="h-8 w-8 rounded-lg ring-1 ring-border/50">
349
- <AvatarImage
350
- src={getImageUrl(avatarSrc || '')}
351
- alt={String(name)}
352
- className="object-cover"
353
- />
354
- <AvatarFallback className="text-[10px] font-bold bg-primary/5 text-primary rounded-lg">
355
- {getInitials(String(name))}
356
- </AvatarFallback>
357
- </Avatar>
358
- <div className="flex flex-col min-w-0 overflow-hidden">
359
- <span className="font-medium text-sm truncate leading-none mb-0.5 text-foreground/90">
360
- {String(name)}
361
- </span>
362
- {desc && (
363
- <span className="text-[11px] text-muted-foreground truncate leading-none">
364
- {String(desc)}
365
- </span>
366
- )}
367
- </div>
368
- </div>
511
+ <AvatarCell
512
+ name={String(name)}
513
+ desc={desc ? String(desc) : undefined}
514
+ avatarSrc={avatarSrc}
515
+ getImageUrl={getImageUrl}
516
+ />
369
517
  )
370
518
  }
371
519
 
372
520
  case 'relation-badge-list':
373
521
  return renderRelationBadges(value, col)
374
522
 
523
+ case 'url':
524
+ case 'link': {
525
+ const labelField = styleCfg(col, 'label_field', 'labelField')
526
+ const urlField = styleCfg(col, 'url_field', 'urlField')
527
+ const rawUrl = urlField
528
+ ? getNestedValue(row.original, urlField)
529
+ : value
530
+ if (!rawUrl) return <EmptyCell />
531
+ const urlStr = String(rawUrl)
532
+ const href = /^https?:\/\//i.test(urlStr) ? urlStr : `https://${urlStr}`
533
+ let label: string
534
+ if (labelField) {
535
+ label = String(getNestedValue(row.original, labelField) ?? href)
536
+ } else {
537
+ try {
538
+ label = new URL(href).hostname
539
+ } catch {
540
+ label = urlStr
541
+ }
542
+ }
543
+ const isExternal = !/^https?:\/\/(localhost|127\.)/i.test(href)
544
+ const newTab =
545
+ styleCfg(col, 'new_tab', 'newTab') === true || isExternal
546
+ const iconName = styleCfg(col, 'icon') || 'ExternalLink'
547
+ return (
548
+ <a
549
+ href={href}
550
+ {...(newTab
551
+ ? { target: '_blank', rel: 'noopener noreferrer' }
552
+ : {})}
553
+ className="inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline"
554
+ onClick={(e) => e.stopPropagation()}
555
+ >
556
+ <DynamicIcon name={iconName} className="h-3.5 w-3.5 shrink-0" />
557
+ <span className="truncate max-w-[260px]">{label}</span>
558
+ </a>
559
+ )
560
+ }
561
+
562
+ case 'email': {
563
+ if (!value) return <EmptyCell />
564
+ const email = String(value)
565
+ return (
566
+ <a
567
+ href={`mailto:${email}`}
568
+ className="inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline"
569
+ onClick={(e) => e.stopPropagation()}
570
+ >
571
+ <icons.Mail className="h-3.5 w-3.5 shrink-0 opacity-70" />
572
+ <span className="truncate max-w-[260px]">{email}</span>
573
+ </a>
574
+ )
575
+ }
576
+
577
+ case 'currency': {
578
+ const num =
579
+ typeof value === 'number' ? value : Number(value)
580
+ if (value === null || value === undefined || isNaN(num))
581
+ return (
582
+ <div className="text-right">
583
+ <EmptyCell />
584
+ </div>
585
+ )
586
+ const decimals = styleCfg(col, 'decimals') ?? 2
587
+ return (
588
+ <span className="block text-right font-medium tabular-nums">
589
+ {formatNumber(
590
+ num,
591
+ {
592
+ style: 'currency',
593
+ currency: resolveCurrency(col),
594
+ minimumFractionDigits: decimals,
595
+ maximumFractionDigits: decimals,
596
+ },
597
+ currentLanguage,
598
+ )}
599
+ </span>
600
+ )
601
+ }
602
+
603
+ case 'number': {
604
+ const num =
605
+ typeof value === 'number' ? value : Number(value)
606
+ if (value === null || value === undefined || isNaN(num))
607
+ return (
608
+ <div className="text-right">
609
+ <EmptyCell />
610
+ </div>
611
+ )
612
+ const decimals = styleCfg(col, 'decimals')
613
+ return (
614
+ <span className="block text-right font-medium tabular-nums">
615
+ {formatNumber(
616
+ num,
617
+ decimals !== undefined
618
+ ? {
619
+ minimumFractionDigits: decimals,
620
+ maximumFractionDigits: decimals,
621
+ }
622
+ : {},
623
+ currentLanguage,
624
+ )}
625
+ </span>
626
+ )
627
+ }
628
+
629
+ case 'percent':
630
+ case 'progress': {
631
+ const num =
632
+ typeof value === 'number' ? value : Number(value)
633
+ if (value === null || value === undefined || isNaN(num))
634
+ return <EmptyCell />
635
+ const pct = Math.max(0, Math.min(100, num))
636
+ return (
637
+ <div className="flex items-center gap-2 min-w-[120px]">
638
+ <Progress value={pct} className="flex-1" />
639
+ <span className="text-xs font-medium tabular-nums text-muted-foreground w-9 text-right">
640
+ {Math.round(pct)}%
641
+ </span>
642
+ </div>
643
+ )
644
+ }
645
+
646
+ case 'tags': {
647
+ const list: string[] = Array.isArray(value)
648
+ ? value.map(String)
649
+ : value
650
+ ? String(value)
651
+ .split(',')
652
+ .map((s) => s.trim())
653
+ .filter(Boolean)
654
+ : []
655
+ if (list.length === 0) return <EmptyCell />
656
+ return (
657
+ <div className="flex flex-wrap gap-1">
658
+ {list.map((tag, i) => (
659
+ <Badge
660
+ key={`${col.key}-${i}`}
661
+ variant="secondary"
662
+ className="px-1.5 py-0 text-[10px]"
663
+ >
664
+ {tag}
665
+ </Badge>
666
+ ))}
667
+ </div>
668
+ )
669
+ }
670
+
671
+ case 'color': {
672
+ if (!value) return <EmptyCell />
673
+ const hex = String(value)
674
+ return (
675
+ <div className="flex items-center gap-2">
676
+ <span
677
+ className="h-4 w-4 rounded border border-border/60 shrink-0"
678
+ style={{ background: hex }}
679
+ />
680
+ <code className="font-mono text-xs text-muted-foreground">
681
+ {hex}
682
+ </code>
683
+ </div>
684
+ )
685
+ }
686
+
687
+ case 'code':
688
+ case 'truncate-text': {
689
+ if (value === null || value === undefined || value === '')
690
+ return <EmptyCell />
691
+ const maxLength = styleCfg(col, 'max_length', 'maxLength')
692
+ return <CodeCell text={String(value)} maxLength={maxLength} />
693
+ }
694
+
375
695
  case 'phone': {
376
696
  if (!value) return <span className="text-muted-foreground">-</span>
377
697
  return <span className="font-medium text-sm">{String(value)}</span>
378
698
  }
379
699
 
380
- case 'boolean':
381
- return value ? <Badge>Sí</Badge> : <Badge variant="secondary">No</Badge>
700
+ case 'boolean': {
701
+ const showText = styleCfg(col, 'show_text', 'showText') !== false
702
+ return (
703
+ <span className="inline-flex items-center gap-1.5">
704
+ {value ? (
705
+ <icons.Check className="h-4 w-4 text-green-500" />
706
+ ) : (
707
+ <icons.Minus className="h-4 w-4 text-muted-foreground" />
708
+ )}
709
+ {showText && (
710
+ <span className="text-sm text-muted-foreground">
711
+ {value ? 'Sí' : 'No'}
712
+ </span>
713
+ )}
714
+ </span>
715
+ )
716
+ }
382
717
 
383
718
  case 'media-gallery': {
384
719
  if (!value || (Array.isArray(value) && value.length === 0)) {
package/src/types.ts CHANGED
@@ -77,7 +77,33 @@ export type ColumnVisibility = 'all' | 'table' | 'modal' | 'list' | (string & {}
77
77
  export interface ColumnDefinition {
78
78
  key: string
79
79
  label: string
80
- type: 'text' | 'number' | 'date' | 'select' | 'search' | 'relation-badge-list' | 'avatar' | 'boolean' | 'phone' | 'media-gallery' | 'image'
80
+ type:
81
+ | 'text'
82
+ | 'number'
83
+ | 'date'
84
+ | 'select'
85
+ | 'search'
86
+ | 'relation-badge-list'
87
+ | 'avatar'
88
+ | 'boolean'
89
+ | 'phone'
90
+ | 'media-gallery'
91
+ | 'image'
92
+ // Declarative pro cell renderers (resolved via `cellStyle ?? type`).
93
+ | 'url'
94
+ | 'link'
95
+ | 'email'
96
+ | 'currency'
97
+ | 'percent'
98
+ | 'progress'
99
+ | 'badge'
100
+ | 'status'
101
+ | 'tags'
102
+ | 'color'
103
+ | 'code'
104
+ | 'truncate-text'
105
+ | 'creator'
106
+ | 'user'
81
107
  sortable: boolean
82
108
  filterable: boolean
83
109
  hidden?: boolean