@airoom/nextmin-react 1.4.6 → 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.
- package/README.md +29 -3
- package/dist/auth/SignInForm.js +4 -2
- package/dist/components/AdminApp.js +15 -38
- package/dist/components/ArchitectureDemo.d.ts +1 -0
- package/dist/components/ArchitectureDemo.js +45 -0
- package/dist/components/PhoneInput.d.ts +3 -0
- package/dist/components/PhoneInput.js +23 -19
- package/dist/components/RefSelect.d.ts +16 -0
- package/dist/components/RefSelect.js +225 -0
- package/dist/components/SchemaForm.js +125 -50
- package/dist/components/Sidebar.js +6 -13
- package/dist/components/TableFilters.js +2 -0
- package/dist/components/editor/TiptapEditor.js +1 -1
- package/dist/components/editor/Toolbar.js +13 -2
- package/dist/components/editor/components/DistrictGridModal.js +2 -3
- package/dist/components/editor/components/SchemaInsertionModal.js +2 -2
- package/dist/components/viewer/DynamicViewer.js +70 -9
- package/dist/hooks/useRealtime.d.ts +8 -0
- package/dist/hooks/useRealtime.js +30 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/AuthClient.d.ts +15 -0
- package/dist/lib/AuthClient.js +63 -0
- package/dist/lib/QueryBuilder.d.ts +29 -0
- package/dist/lib/QueryBuilder.js +74 -0
- package/dist/lib/RealtimeClient.d.ts +16 -0
- package/dist/lib/RealtimeClient.js +56 -0
- package/dist/lib/api.d.ts +15 -3
- package/dist/lib/api.js +71 -58
- package/dist/lib/auth.js +7 -2
- package/dist/lib/types.d.ts +16 -0
- package/dist/nextmin.css +1 -1
- package/dist/providers/NextMinProvider.d.ts +8 -1
- package/dist/providers/NextMinProvider.js +40 -8
- package/dist/router/NextMinRouter.d.ts +1 -1
- package/dist/router/NextMinRouter.js +1 -1
- package/dist/state/schemasSlice.js +4 -27
- package/dist/views/DashboardPage.js +56 -42
- package/dist/views/ListPage.js +34 -4
- package/dist/views/SettingsEdit.js +25 -2
- package/dist/views/list/DataTableHero.js +103 -46
- package/dist/views/list/ListHeader.d.ts +3 -1
- package/dist/views/list/ListHeader.js +2 -2
- package/dist/views/list/jsonSummary.d.ts +3 -3
- package/dist/views/list/jsonSummary.js +47 -20
- package/dist/views/list/useListData.js +5 -1
- package/package.json +8 -4
- package/dist/components/RefMultiSelect.d.ts +0 -22
- package/dist/components/RefMultiSelect.js +0 -113
- package/dist/components/RefSingleSelect.d.ts +0 -17
- package/dist/components/RefSingleSelect.js +0 -110
- package/dist/lib/schemaService.d.ts +0 -2
- package/dist/lib/schemaService.js +0 -39
- package/dist/state/schemaLive.d.ts +0 -2
- package/dist/state/schemaLive.js +0 -19
- /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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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-
|
|
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(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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, {
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 }),
|
|
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
|
-
//
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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,
|
|
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": "
|
|
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":
|
|
15
|
-
|
|
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 {};
|