@airoom/nextmin-react 1.4.5 → 2.0.1

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.
Files changed (56) hide show
  1. package/README.md +29 -3
  2. package/dist/auth/SignInForm.js +4 -2
  3. package/dist/components/AdminApp.js +15 -38
  4. package/dist/components/ArchitectureDemo.d.ts +1 -0
  5. package/dist/components/ArchitectureDemo.js +45 -0
  6. package/dist/components/PhoneInput.d.ts +3 -0
  7. package/dist/components/PhoneInput.js +23 -19
  8. package/dist/components/RefSelect.d.ts +16 -0
  9. package/dist/components/RefSelect.js +225 -0
  10. package/dist/components/SchemaForm.js +131 -51
  11. package/dist/components/Sidebar.js +6 -13
  12. package/dist/components/TableFilters.js +2 -0
  13. package/dist/components/editor/TiptapEditor.js +1 -1
  14. package/dist/components/editor/Toolbar.js +13 -2
  15. package/dist/components/editor/components/DistrictGridModal.js +2 -3
  16. package/dist/components/editor/components/SchemaInsertionModal.js +2 -2
  17. package/dist/components/viewer/DynamicViewer.js +70 -9
  18. package/dist/hooks/useRealtime.d.ts +8 -0
  19. package/dist/hooks/useRealtime.js +30 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +6 -0
  22. package/dist/lib/AuthClient.d.ts +15 -0
  23. package/dist/lib/AuthClient.js +63 -0
  24. package/dist/lib/QueryBuilder.d.ts +29 -0
  25. package/dist/lib/QueryBuilder.js +74 -0
  26. package/dist/lib/RealtimeClient.d.ts +16 -0
  27. package/dist/lib/RealtimeClient.js +56 -0
  28. package/dist/lib/api.d.ts +15 -3
  29. package/dist/lib/api.js +71 -58
  30. package/dist/lib/auth.js +7 -2
  31. package/dist/lib/types.d.ts +16 -0
  32. package/dist/nextmin.css +1 -1
  33. package/dist/providers/NextMinProvider.d.ts +8 -1
  34. package/dist/providers/NextMinProvider.js +40 -8
  35. package/dist/router/NextMinRouter.d.ts +1 -1
  36. package/dist/router/NextMinRouter.js +1 -1
  37. package/dist/state/schemasSlice.js +8 -2
  38. package/dist/views/DashboardPage.js +56 -42
  39. package/dist/views/ListPage.js +34 -4
  40. package/dist/views/SettingsEdit.js +25 -2
  41. package/dist/views/list/DataTableHero.js +103 -46
  42. package/dist/views/list/ListHeader.d.ts +3 -1
  43. package/dist/views/list/ListHeader.js +2 -2
  44. package/dist/views/list/jsonSummary.d.ts +3 -3
  45. package/dist/views/list/jsonSummary.js +47 -20
  46. package/dist/views/list/useListData.js +5 -1
  47. package/package.json +8 -4
  48. package/dist/components/RefMultiSelect.d.ts +0 -22
  49. package/dist/components/RefMultiSelect.js +0 -113
  50. package/dist/components/RefSingleSelect.d.ts +0 -17
  51. package/dist/components/RefSingleSelect.js +0 -110
  52. package/dist/lib/schemaService.d.ts +0 -2
  53. package/dist/lib/schemaService.js +0 -39
  54. package/dist/state/schemaLive.d.ts +0 -2
  55. package/dist/state/schemaLive.js +0 -19
  56. /package/dist/{editor.css → components/editor/editor.css} +0 -0
@@ -1,8 +1,10 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from 'react';
4
4
  import Link from 'next/link';
5
- import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell, Pagination, Select, SelectItem, Skeleton, Button, Tooltip, Spinner, } from '@heroui/react';
5
+ import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell, Pagination, PaginationItem, Select, SelectItem, Skeleton, Button, Tooltip, Spinner, } from '@heroui/react';
6
+ import * as LucideIcons from 'lucide-react';
7
+ const { ChevronLeft, ChevronRight } = LucideIcons;
6
8
  import { formatCell } from './formatters';
7
9
  import { summarizeAny } from './jsonSummary';
8
10
  import { api } from '../../lib/api';
@@ -13,61 +15,63 @@ const IMG_COLUMN_RE = /(image|photo|avatar|logo|picture|thumbnail|icon)/i;
13
15
  function isUrlLike(s) {
14
16
  return /^https?:\/\//i.test(s) || s.startsWith('/') || s.startsWith('data:');
15
17
  }
16
- function isLikelyImageUrl(s) {
18
+ function isLikelyImageUrl(s, columnKey, attr) {
17
19
  if (!isUrlLike(s))
18
20
  return false;
19
21
  if (s.startsWith('data:image/'))
20
22
  return true;
21
- try {
22
- const url = new URL(s, 'http://x'); // base for relative paths
23
- const pathname = url.pathname || '';
24
- if (IMG_EXT_RE.test(pathname))
25
- return true;
26
- }
27
- catch {
28
- if (IMG_EXT_RE.test(s.split('?')[0]))
29
- return true;
30
- }
23
+ // 1) Explicit column name hint
24
+ if (columnKey && IMG_COLUMN_RE.test(columnKey))
25
+ return true;
26
+ // 2) Schema attribute hint
27
+ const a = Array.isArray(attr) ? attr[0] : attr;
28
+ const fmt = String(a?.format || '').toLowerCase();
29
+ const type = String(a?.type || '').toLowerCase();
30
+ if (fmt === 'file' || type === 'file' || fmt === 'image' || type === 'image')
31
+ return true;
32
+ // 3) Extension check (permissive like DynamicViewer)
33
+ const pure = s.split('?')[0].split('#')[0];
34
+ if (IMG_EXT_RE.test(pure))
35
+ return true;
31
36
  return false;
32
37
  }
33
- function extractUrl(v) {
38
+ function extractUrl(v, columnKey, attr) {
34
39
  if (!v)
35
40
  return null;
36
- if (typeof v === 'string' && isLikelyImageUrl(v))
41
+ if (typeof v === 'string' && isLikelyImageUrl(v, columnKey, attr))
37
42
  return v;
38
43
  if (typeof v === 'object') {
39
44
  const u = (typeof v.url === 'string' && v.url) ||
40
45
  (typeof v.src === 'string' && v.src) ||
41
46
  (typeof v.value === 'string' && v.value) ||
42
47
  null;
43
- return u && isLikelyImageUrl(u) ? u : null;
48
+ return u && isLikelyImageUrl(u, columnKey, attr) ? u : null;
44
49
  }
45
50
  return null;
46
51
  }
47
- function extractAllImageUrls(val) {
52
+ function extractAllImageUrls(val, columnKey, attr) {
48
53
  if (Array.isArray(val)) {
49
- const list = val.map(extractUrl).filter(Boolean);
54
+ const list = val.map((v) => extractUrl(v, columnKey, attr)).filter(Boolean);
50
55
  if (!list.length) {
51
- return (val.filter((s) => typeof s === 'string' && isLikelyImageUrl(s)) ??
56
+ return (val.filter((s) => typeof s === 'string' && isLikelyImageUrl(s, columnKey, attr)) ??
52
57
  []);
53
58
  }
54
59
  return list;
55
60
  }
56
- const one = extractUrl(val);
61
+ const one = extractUrl(val, columnKey, attr);
57
62
  return one ? [one] : [];
58
63
  }
59
64
  function ImageCell({ urls, alt, size = 40, }) {
60
65
  const shown = urls.slice(0, 1);
61
66
  const rest = urls.length - shown.length;
62
- return (_jsxs("div", { className: "flex items-center justify-center gap-1", children: [shown.map((u, i) => (_jsx("img", { src: u, alt: alt, width: size, height: size, className: "h-10 w-10 rounded-md object-cover border border-default-200 bg-default-100", loading: "lazy" }, `${u}-${i}`))), rest > 0 ? (_jsxs("div", { className: "h-10 w-10 rounded-md border border-default-200 bg-default-50 text-xs flex items-center justify-center", children: ["+", rest] })) : null] }));
67
+ return (_jsxs("div", { className: "flex items-center justify-start gap-1", children: [shown.map((u, i) => (_jsx("img", { src: u, alt: alt, width: size, height: size, className: "h-10 w-10 rounded-md object-cover border border-default-200 bg-default-100 shadow-sm", loading: "lazy" }, `${u}-${i}`))), rest > 0 ? (_jsxs("div", { className: "h-10 w-10 rounded-md border border-default-200 bg-default-50 text-xs flex items-center justify-center font-semibold text-foreground/70", children: ["+", rest] })) : null] }));
63
68
  }
64
69
  /** decide if a column should be treated as image by header hint or value */
65
- function maybeRenderImageCell(item, columnKey) {
66
- const val = item?.[columnKey];
67
- const urls = extractAllImageUrls(val);
68
- if (urls.length || IMG_COLUMN_RE.test(String(columnKey))) {
69
- if (!urls.length)
70
- return null;
70
+ function maybeRenderImageCell(val, columnKey, attr) {
71
+ if (val == null)
72
+ return null;
73
+ const urls = extractAllImageUrls(val, columnKey, attr);
74
+ if (urls.length) {
71
75
  return _jsx(ImageCell, { urls: urls, alt: `${columnKey}` });
72
76
  }
73
77
  return null;
@@ -199,24 +203,31 @@ function getByPath(obj, path) {
199
203
  }
200
204
  return cur;
201
205
  }
202
- function extractShowFromObject(o) {
206
+ function extractShowFromObject(o, showKey) {
203
207
  if (!o || typeof o !== 'object')
204
208
  return null;
205
- // explicit hints
206
- const showKey = (typeof o.show === 'string' && o.show) ||
207
- (typeof o.showKey === 'string' && o.showKey) ||
208
- null;
209
+ // 1) Explicit showKey from schema (top priority)
209
210
  if (showKey) {
210
211
  const v = getByPath(o, showKey);
211
212
  if (v != null && (typeof v === 'string' || typeof v === 'number')) {
212
213
  return String(v);
213
214
  }
214
- const vv = o[showKey];
215
+ }
216
+ // 2) explicit hints within the object itself
217
+ const internalShowKey = (typeof o.show === 'string' && o.show) ||
218
+ (typeof o.showKey === 'string' && o.showKey) ||
219
+ null;
220
+ if (internalShowKey) {
221
+ const v = getByPath(o, internalShowKey);
222
+ if (v != null && (typeof v === 'string' || typeof v === 'number')) {
223
+ return String(v);
224
+ }
225
+ const vv = o[internalShowKey];
215
226
  if (vv != null && (typeof vv === 'string' || typeof vv === 'number')) {
216
227
  return String(vv);
217
228
  }
218
229
  }
219
- // smart fallbacks (domain-aware)
230
+ // 3) smart fallbacks (domain-aware)
220
231
  const first = o.firstName;
221
232
  const last = o.lastName;
222
233
  if (typeof first === 'string' && typeof last === 'string') {
@@ -243,19 +254,20 @@ function extractShowFromObject(o) {
243
254
  }
244
255
  return null;
245
256
  }
246
- function formatShowValue(val) {
257
+ function formatShowValue(val, attr) {
247
258
  if (val == null)
248
259
  return null;
249
260
  // If it's a JSON-like string, parse first
250
261
  const parsed = maybeParseJson(val);
262
+ const showKey = (Array.isArray(attr) ? attr[0] : attr)?.show;
251
263
  if (Array.isArray(parsed)) {
252
264
  const parts = parsed
253
- .map((x) => (typeof x === 'object' ? extractShowFromObject(x) : null))
265
+ .map((x) => (typeof x === 'object' ? extractShowFromObject(x, showKey) : null))
254
266
  .filter((s) => !!s);
255
267
  return parts.length ? parts.join(', ') : null;
256
268
  }
257
269
  if (typeof parsed === 'object') {
258
- return extractShowFromObject(parsed);
270
+ return extractShowFromObject(parsed, showKey);
259
271
  }
260
272
  return null;
261
273
  }
@@ -330,11 +342,27 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
330
342
  setIsDialogOpen(false);
331
343
  }
332
344
  };
333
- return (_jsxs(_Fragment, { children: [_jsxs(Table, { "aria-label": "Model table", removeWrapper: true, bottomContent: _jsxs("div", { className: "flex items-center justify-between px-2 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-foreground/70", children: "Rows per page:" }), _jsx(Select, { size: "sm", "aria-label": "Rows per page", selectedKeys: new Set([String(pageSize)]), onSelectionChange: (keys) => {
345
+ return (_jsxs("div", { className: "flex-grow overflow-hidden flex flex-col min-h-0", children: [_jsxs(Table, { "aria-label": "Model table", isHeaderSticky: true, removeWrapper: false, classNames: {
346
+ base: 'max-h-full flex flex-col min-h-0 overflow-visible',
347
+ wrapper: 'flex-grow min-h-0 overflow-auto !p-0 !bg-transparent !shadow-none !rounded-none relative',
348
+ table: 'min-w-full', // Allow auto-sizing to prevent clipping
349
+ thead: '[&>tr]:first:rounded-none',
350
+ th: 'bg-default-100 text-default-600',
351
+ }, bottomContent: _jsxs("div", { className: "flex items-center justify-between px-2 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-foreground/70", children: "Rows per page:" }), _jsx(Select, { size: "sm", "aria-label": "Rows per page", selectedKeys: new Set([String(pageSize)]), onSelectionChange: (keys) => {
334
352
  const val = Array.from(keys)[0];
335
353
  if (val)
336
354
  onPageSizeChange(Number(val));
337
- }, className: "w-24", children: [10, 20, 50, 100].map((n) => (_jsx(SelectItem, { textValue: String(n), children: n }, String(n)))) })] }), _jsx(Pagination, { size: "sm", total: pageCount, page: Math.min(page, pageCount), onChange: onPageChange, showControls: true })] }), topContent: topContent, children: [_jsx(TableHeader, { columns: headerItems, children: (col) => (_jsx(TableColumn, { className: "text-xs uppercase tracking-wide", children: col.label }, col.key)) }), _jsx(TableBody, { items: bodyItems, emptyContent: loading ? undefined : error || 'No data', children: (item) => (_jsx(TableRow, { children: (columnKey) => {
355
+ }, className: "w-24", children: [10, 20, 50, 100].map((n) => (_jsx(SelectItem, { textValue: String(n), children: n }, String(n)))) })] }), _jsx(Pagination, { size: "sm", total: pageCount, page: page, onChange: onPageChange, showControls: true, variant: "flat", renderItem: (props) => {
356
+ if (props.value === 'prev') {
357
+ return (_jsx(Button, { isIconOnly: true, size: "sm", variant: "flat", onPress: props.onPress, isDisabled: props.isDisabled, children: _jsx(ChevronLeft, { size: 16 }) }));
358
+ }
359
+ if (props.value === 'next') {
360
+ return (_jsx(Button, { isIconOnly: true, size: "sm", variant: "flat", onPress: props.onPress, isDisabled: props.isDisabled, children: _jsx(ChevronRight, { size: 16 }) }));
361
+ }
362
+ return _jsx(PaginationItem, { ...props });
363
+ } })] }), bottomContentPlacement: "outside", topContent: topContent, topContentPlacement: "outside", children: [_jsx(TableHeader, { columns: headerItems, children: (col) => (_jsx(TableColumn, { className: `text-xs uppercase tracking-wide ${col.key === 'actions'
364
+ ? 'sticky right-0 bg-default-100 z-30 shadow-[-1px_0_2px_rgba(0,0,0,0.05)] border-l border-default-200 text-center'
365
+ : ''}`, width: col.key === 'actions' ? 130 : 200, children: col.label }, col.key)) }), _jsx(TableBody, { items: bodyItems, emptyContent: loading ? undefined : error || 'No data', children: (item) => (_jsx(TableRow, { children: (columnKey) => {
338
366
  const key = String(columnKey);
339
367
  if (loading) {
340
368
  return (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-3 w-3/4 rounded-md" }) }, `${item.id}-${key}`));
@@ -350,7 +378,27 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
350
378
  ? typeVal.name
351
379
  : '';
352
380
  const isSystemRow = String(typeString ?? '').toLowerCase() === 'system';
353
- return (_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Tooltip, { content: "View", children: _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", "aria-label": "View", onPress: () => onRequestView?.(item), children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] }) }) }), _jsx(Tooltip, { content: isSystemRow
381
+ return (_jsx(TableCell, { className: "sticky right-0 !bg-background dark:!bg-content1 z-20 shadow-[-1px_0_2px_rgba(0,0,0,0.05)] border-l border-default-200", children: _jsxs("div", { className: "flex items-center justify-center gap-1", children: [schema?.actions?.map((action, idx) => {
382
+ let href = action.href;
383
+ href = href.replace('{id}', String(item.id || item._id || ''));
384
+ const relatedId = item._actionsData?.[action.relatedModel || ''];
385
+ if (relatedId) {
386
+ href = href.replace('{relatedId}', relatedId);
387
+ }
388
+ // Condition: if {relatedId} placeholder is used but NO relatedId found, hide it
389
+ if (action.href.includes('{relatedId}') &&
390
+ !relatedId) {
391
+ return null;
392
+ }
393
+ // Or if strictly specified relatedModel but no data
394
+ if (action.relatedModel && !relatedId) {
395
+ return null;
396
+ }
397
+ const IconComponent = action.icon
398
+ ? LucideIcons[action.icon]
399
+ : null;
400
+ return (_jsx(Tooltip, { content: action.label, children: _jsx(Button, { as: Link, href: href, size: "sm", variant: action.variant || 'light', color: action.color || 'default', isIconOnly: !!IconComponent, className: IconComponent ? '' : 'min-w-fit h-8 px-3', children: IconComponent ? (_jsx(IconComponent, { size: 18 })) : (action.label) }) }, `custom-action-${idx}`));
401
+ }), _jsx(Tooltip, { content: "View", children: _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", "aria-label": "View", onPress: () => onRequestView?.(item), children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] }) }) }), _jsx(Tooltip, { content: isSystemRow
354
402
  ? 'System record — editing disabled'
355
403
  : 'Edit', children: _jsx(Button, { as: isSystemRow ? undefined : Link, href: isSystemRow ? undefined : `${baseHref}/${item.id}`, isIconOnly: true, size: "sm", variant: "light", isDisabled: isSystemRow, "aria-disabled": isSystemRow, children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { d: "M12 20h9" }), _jsx("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" })] }) }) }), _jsx(Tooltip, { content: isSystemRow
356
404
  ? 'System record — deletion disabled'
@@ -361,11 +409,18 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
361
409
  // Trash icon
362
410
  _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [_jsx("polyline", { points: "3 6 5 6 21 6" }), _jsx("path", { d: "M19 6l-1 14H6L5 6" }), _jsx("path", { d: "M10 11v6" }), _jsx("path", { d: "M14 11v6" })] })) }) })] }) }, `${item.id}-actions`));
363
411
  }
364
- // Image-aware cell; fallback to formatter
365
- const maybeImg = maybeRenderImageCell(item, key);
366
412
  // Prepare raw value (parse JSON-like strings first)
367
- const rawRawVal = item[key];
413
+ let rawRawVal = item[key];
414
+ if (rawRawVal === undefined && item.baseId && typeof item.baseId === 'object') {
415
+ rawRawVal = item.baseId[key];
416
+ }
368
417
  const rawVal = maybeParseJson(rawRawVal);
418
+ // Image-aware cell; PRIORITY check
419
+ const attributes = (schema?.attributes ?? {});
420
+ const maybeImg = maybeRenderImageCell(rawVal, key, attributes[key]);
421
+ if (maybeImg) {
422
+ return (_jsx(TableCell, { children: maybeImg }, `${item.id ?? item._id}-${key}`));
423
+ }
369
424
  // 0) Date fields (createdAt, updatedAt, deletedAt)
370
425
  if (isDateField(key)) {
371
426
  const d = tryParseDate(rawVal);
@@ -373,18 +428,20 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
373
428
  return (_jsx(TableCell, { children: formatDateLocal(d) }, `${item.id ?? item._id}-${key}`));
374
429
  }
375
430
  }
431
+ // ... the rest of the formatters ...
376
432
  // 1) Time-only ranges like "15:00..00:00 17:00..23:00"
377
433
  const timeRangePretty = formatTimeOnlyRanges(rawVal);
378
434
  if (timeRangePretty) {
379
435
  return (_jsx(TableCell, { children: timeRangePretty }, `${item.id ?? item._id}-${key}`));
380
436
  }
381
437
  // 2) Object/array with "show"/"showKey" or common label fields
382
- const showPretty = formatShowValue(rawVal);
438
+ const showPretty = formatShowValue(rawVal, attributes[key]);
383
439
  if (showPretty) {
384
440
  return (_jsx(TableCell, { children: truncateText(showPretty, 35) }, `${item.id ?? item._id}-${key}`));
385
441
  }
386
442
  // 3) Generic JSON/array/object summarizer (supports raw JSON string too)
387
- const jsonSummary = summarizeAny(rawVal);
443
+ const showKey = (Array.isArray(attributes[key]) ? attributes[key][0] : attributes[key])?.show;
444
+ const jsonSummary = summarizeAny(rawVal, showKey);
388
445
  if (typeof jsonSummary === 'string' && jsonSummary.length) {
389
446
  return (_jsx(TableCell, { children: truncateText(jsonSummary, 35) }, `${item.id ?? item._id}-${key}`));
390
447
  }
@@ -395,6 +452,6 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
395
452
  const displayValue = isSimple
396
453
  ? truncateText(String(formatted), 35)
397
454
  : formatted;
398
- return (_jsx(TableCell, { children: maybeImg ?? displayValue }, `${item.id ?? item._id}-${key}`));
455
+ return (_jsx(TableCell, { children: displayValue }, `${item.id ?? item._id}-${key}`));
399
456
  } }, item.id)) })] }), _jsx(ConfirmDialog, { isOpen: isDialogOpen, onClose: () => setIsDialogOpen(false), onConfirm: handleConfirmDelete, isLoading: isDeleting, title: "Delete this record?", description: "This action cannot be undone. The record will be permanently removed.", confirmText: "Delete", cancelText: "Cancel" })] }));
400
457
  }
@@ -1,8 +1,10 @@
1
+ import React from 'react';
1
2
  type Props = {
2
3
  title: string;
3
4
  createHref?: string;
4
5
  hideCreate?: boolean;
5
6
  loading?: boolean;
7
+ children?: React.ReactNode;
6
8
  };
7
- export declare function ListHeader({ title, createHref, hideCreate, loading, }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export declare function ListHeader({ title, createHref, hideCreate, loading, children, }: Props): import("react/jsx-runtime").JSX.Element;
8
10
  export {};
@@ -2,6 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import Link from 'next/link';
4
4
  import { Button, Divider } from '@heroui/react';
5
- export function ListHeader({ title, createHref, hideCreate = false, loading, }) {
6
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsx("h2", { className: "m-0 text-xl font-semibold", children: title }), _jsx("div", { className: "flex items-center gap-3", children: !hideCreate && createHref ? (_jsx(Link, { href: createHref, children: _jsx(Button, { size: "sm", color: "primary", isDisabled: loading, children: "Create" }) })) : null })] }), _jsx(Divider, { className: "my-3" })] }));
5
+ export function ListHeader({ title, createHref, hideCreate = false, loading, children, }) {
6
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsx("h2", { className: "m-0 text-xl font-semibold", children: title }), _jsxs("div", { className: "flex items-center gap-3", children: [!hideCreate && createHref ? (_jsx(Link, { href: createHref, children: _jsx(Button, { size: "sm", color: "primary", isDisabled: loading, children: "Create" }) })) : null, children] })] }), _jsx(Divider, { className: "my-3" })] }));
7
7
  }
@@ -1,6 +1,6 @@
1
- export declare function summarizeObject(obj: any): {
1
+ export declare function summarizeObject(obj: any, showKey?: string): {
2
2
  text: string;
3
3
  primary?: string;
4
4
  } | null;
5
- export declare function summarizeArrayOfObjects(arr: any[], maxItems?: number): string | null;
6
- export declare function summarizeAny(val: any): string | null;
5
+ export declare function summarizeArrayOfObjects(arr: any[], maxItems?: number, showKey?: string): string | null;
6
+ export declare function summarizeAny(val: any, showKey?: string): string | null;
@@ -12,7 +12,7 @@ function isSecretKey(k) {
12
12
  n.includes('passcode') ||
13
13
  n.includes('secret'));
14
14
  }
15
- function toLabel(v) {
15
+ function toLabel(v, showKey) {
16
16
  if (v == null)
17
17
  return null;
18
18
  if (typeof v === 'string')
@@ -20,14 +20,20 @@ function toLabel(v) {
20
20
  if (typeof v === 'number' || typeof v === 'boolean')
21
21
  return String(v);
22
22
  if (typeof v === 'object') {
23
- // common label keys
23
+ // 1) Explicit showKey from schema (top priority)
24
+ if (showKey) {
25
+ const val = v[showKey];
26
+ if (val != null)
27
+ return String(val).trim() || null;
28
+ }
29
+ // 2) common label keys
24
30
  const candidates = ['name', 'title', 'label', 'display', 'show', 'value'];
25
31
  for (const k of candidates) {
26
32
  const val = v[k];
27
33
  if (typeof val === 'string' && val.trim())
28
34
  return val;
29
35
  }
30
- // keys ending with "name" (e.g., chamberName, specialityName)
36
+ // 3) keys ending with "name" (e.g., chamberName, specialityName)
31
37
  for (const [k, val] of Object.entries(v)) {
32
38
  if (typeof val !== 'string')
33
39
  continue;
@@ -35,17 +41,17 @@ function toLabel(v) {
35
41
  if (nk.endsWith('name') && val.trim())
36
42
  return val;
37
43
  }
38
- // id last
44
+ // 4) id last
39
45
  if (typeof v.id === 'string')
40
46
  return v.id;
41
47
  }
42
48
  return null;
43
49
  }
44
- function listLabels(arr) {
50
+ function listLabels(arr, showKey) {
45
51
  if (!Array.isArray(arr))
46
52
  return null;
47
53
  const labels = arr
48
- .map((it) => toLabel(it))
54
+ .map((it) => toLabel(it, showKey))
49
55
  .filter((s) => !!s && !!s.trim());
50
56
  return labels.length ? labels : null;
51
57
  }
@@ -89,12 +95,33 @@ function pickSupportingValues(obj, primaryKey) {
89
95
  const nk = normKey(k);
90
96
  const pri = SUPPORT_PRIORITY.findIndex((p) => nk.includes(p));
91
97
  const score = pri >= 0 ? 100 - pri : 0; // higher better
92
- const str = Array.isArray(v)
93
- ? (listLabels(v) || []).join(', ')
94
- : typeof v === 'object'
95
- ? toLabel(v) || ''
96
- : String(v);
97
- return { k, str: str.trim(), score, len: str.length };
98
+ let str = '';
99
+ if (Array.isArray(v)) {
100
+ str = (listLabels(v) || []).join(', ');
101
+ }
102
+ else if (typeof v === 'object') {
103
+ str = toLabel(v) || '';
104
+ }
105
+ else {
106
+ str = String(v);
107
+ }
108
+ str = str.trim();
109
+ // Universally block bad candidates from being used as supporting string text
110
+ if (Array.isArray(v) || typeof v === 'object')
111
+ return { k, str: '', score: -1, len: 0 };
112
+ if (str.length > 50)
113
+ return { k, str: '', score: -1, len: 0 };
114
+ if (str.startsWith('http'))
115
+ return { k, str: '', score: -1, len: 0 };
116
+ if (/^[a-fA-F0-9]{24}$/.test(str))
117
+ return { k, str: '', score: -1, len: 0 };
118
+ if (str.includes(',') && str.length > 30)
119
+ return { k, str: '', score: -1, len: 0 };
120
+ // If it doesn't match a known priority, limit accepted strings
121
+ if (score === 0 && str.length > 25) {
122
+ return { k, str: '', score: -1, len: 0 };
123
+ }
124
+ return { k, str, score, len: str.length };
98
125
  })
99
126
  .filter((x) => !!x.str);
100
127
  scored.sort((a, b) => {
@@ -109,10 +136,10 @@ function pickSupportingValues(obj, primaryKey) {
109
136
  }
110
137
  return out;
111
138
  }
112
- export function summarizeObject(obj) {
139
+ export function summarizeObject(obj, showKey) {
113
140
  if (!obj || typeof obj !== 'object')
114
141
  return null;
115
- const primary = toLabel(obj);
142
+ const primary = toLabel(obj, showKey);
116
143
  if (!primary)
117
144
  return null;
118
145
  // try to find the actual key that produced primary
@@ -127,12 +154,12 @@ export function summarizeObject(obj) {
127
154
  const text = supports.length ? `${primary} — ${supports.join(', ')}` : primary;
128
155
  return { text, primary: primaryKey };
129
156
  }
130
- export function summarizeArrayOfObjects(arr, maxItems = 10) {
157
+ export function summarizeArrayOfObjects(arr, maxItems = 10, showKey) {
131
158
  const parts = [];
132
159
  for (const it of arr) {
133
160
  if (!it || typeof it !== 'object')
134
161
  return null; // not uniform
135
- const s = summarizeObject(it);
162
+ const s = summarizeObject(it, showKey);
136
163
  if (!s)
137
164
  return null;
138
165
  parts.push(s.text);
@@ -144,21 +171,21 @@ export function summarizeArrayOfObjects(arr, maxItems = 10) {
144
171
  const extra = arr.length - parts.length;
145
172
  return extra > 0 ? `${parts.join(', ')}, +${extra} more` : parts.join(', ');
146
173
  }
147
- export function summarizeAny(val) {
174
+ export function summarizeAny(val, showKey) {
148
175
  if (val == null)
149
176
  return null;
150
177
  if (Array.isArray(val)) {
151
178
  if (val.length === 0)
152
179
  return '';
153
180
  if (typeof val[0] === 'object') {
154
- return summarizeArrayOfObjects(val);
181
+ return summarizeArrayOfObjects(val, 10, showKey);
155
182
  }
156
183
  // primitives
157
- const labels = val.map((v) => (typeof v === 'object' ? toLabel(v) : String(v))).filter(Boolean);
184
+ const labels = val.map((v) => (typeof v === 'object' ? toLabel(v, showKey) : String(v))).filter(Boolean);
158
185
  return labels.length ? summarizeList(labels) : null;
159
186
  }
160
187
  if (typeof val === 'object') {
161
- const s = summarizeObject(val);
188
+ const s = summarizeObject(val, showKey);
162
189
  return s?.text ?? null;
163
190
  }
164
191
  return null;
@@ -19,7 +19,11 @@ pageSize, filters = {}) {
19
19
  return;
20
20
  }
21
21
  try {
22
- const res = await api.list(model, Math.max(0, page - 1), pageSize, filters);
22
+ const res = await api.list(model, {
23
+ page,
24
+ limit: pageSize,
25
+ ...filters,
26
+ });
23
27
  if (cancelledRef.current)
24
28
  return;
25
29
  // Support both {data, pagination} and {rows, total}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airoom/nextmin-react",
3
- "version": "1.4.5",
3
+ "version": "2.0.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,8 +11,12 @@
11
11
  "import": "./dist/index.js",
12
12
  "default": "./dist/index.js"
13
13
  },
14
- "./styles.css": "./dist/nextmin.css",
15
- "./editor.css": "./dist/editor.css"
14
+ "./styles.css": {
15
+ "default": "./dist/nextmin.css"
16
+ },
17
+ "./editor.css": {
18
+ "default": "./dist/components/editor/editor.css"
19
+ }
16
20
  },
17
21
  "files": [
18
22
  "dist",
@@ -22,7 +26,7 @@
22
26
  "README.md"
23
27
  ],
24
28
  "scripts": {
25
- "build": "rm -rf dist && tsc -p tsconfig.json && npm run build:css && cp src/components/editor/editor.css dist/editor.css",
29
+ "build": "rm -rf dist && tsc -p tsconfig.json && npm run build:css && mkdir -p dist/components/editor && cp src/components/editor/editor.css dist/components/editor/editor.css",
26
30
  "build:css": "tailwindcss -c tailwind.config.js -i ./src/styles.css -o ./dist/nextmin.css --minify",
27
31
  "dev": "tsc -w -p tsconfig.json",
28
32
  "prepublishOnly": "npm run build && npm pack --dry-run | (! grep -E '\\bsrc/|\\.map$')"
@@ -1,22 +0,0 @@
1
- import React from 'react';
2
- type RefOption = {
3
- id?: string;
4
- _id?: string;
5
- [k: string]: any;
6
- };
7
- export type RefMultiSelectProps = {
8
- name: string;
9
- label: string;
10
- refModel: string;
11
- showKey?: string;
12
- value: RefOption[] | string[];
13
- onChange: (ids: string[]) => void;
14
- description?: string;
15
- disabled?: boolean;
16
- required?: boolean;
17
- pageSize?: number | undefined;
18
- error?: boolean | undefined;
19
- errorMessage?: string | undefined;
20
- };
21
- export declare const RefMultiSelect: React.FC<RefMultiSelectProps>;
22
- export {};