@airoom/nextmin-react 2.0.2 → 2.0.3
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 +57 -0
- package/dist/components/AddressAutocomplete.d.ts +4 -1
- package/dist/components/AddressAutocomplete.js +2 -2
- package/dist/components/AdminApp.js +86 -1
- package/dist/components/FileUploader.d.ts +3 -0
- package/dist/components/FileUploader.js +84 -28
- package/dist/components/PasswordInput.d.ts +2 -0
- package/dist/components/PasswordInput.js +2 -2
- package/dist/components/PhoneInput.d.ts +2 -0
- package/dist/components/PhoneInput.js +2 -2
- package/dist/components/QRCodeModal.d.ts +10 -0
- package/dist/components/QRCodeModal.js +153 -0
- package/dist/components/RefSelect.d.ts +4 -1
- package/dist/components/RefSelect.js +163 -65
- package/dist/components/SchemaForm.d.ts +2 -1
- package/dist/components/SchemaForm.js +132 -55
- package/dist/components/Sidebar.js +96 -7
- package/dist/components/TableFilters.js +1 -1
- package/dist/components/editor/TiptapEditor.js +43 -12
- package/dist/components/editor/Toolbar.js +195 -70
- package/dist/components/editor/components/CustomFontSizeModal.d.ts +13 -0
- package/dist/components/editor/components/CustomFontSizeModal.js +81 -0
- package/dist/components/editor/components/DistrictGridModal.js +1 -1
- package/dist/components/editor/components/SchemaInsertionModal.js +1 -1
- package/dist/components/editor/editor.css +2 -0
- package/dist/components/editor/extensions/FontSize.d.ts +19 -0
- package/dist/components/editor/extensions/FontSize.js +41 -0
- package/dist/components/viewer/DynamicViewer.js +50 -15
- package/dist/components/viewer/useViewDrawer.js +42 -2
- package/dist/hooks/useAppNavigation.d.ts +3 -0
- package/dist/hooks/useAppNavigation.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lib/QueryBuilder.d.ts +3 -1
- package/dist/lib/QueryBuilder.js +10 -0
- package/dist/lib/api.d.ts +6 -0
- package/dist/lib/api.js +45 -24
- package/dist/lib/schemaUtils.js +1 -1
- package/dist/lib/types.d.ts +6 -1
- package/dist/nextmin.css +1 -1
- package/dist/providers/NextMinProvider.d.ts +5 -1
- package/dist/providers/NextMinProvider.js +12 -2
- package/dist/providers/themeResolver.d.ts +25 -0
- package/dist/providers/themeResolver.js +174 -0
- package/dist/router/NextMinRouter.d.ts +0 -1
- package/dist/router/NextMinRouter.js +0 -1
- package/dist/views/CreateEditPage.js +20 -11
- package/dist/views/DashboardPage.js +1 -1
- package/dist/views/ListPage.js +37 -6
- package/dist/views/SettingsEdit.js +1 -1
- package/dist/views/list/DataTableHero.d.ts +5 -5
- package/dist/views/list/DataTableHero.js +154 -41
- package/dist/views/list/ListHeader.js +9 -1
- package/package.json +4 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { QRCodeModal } from '../../components/QRCodeModal';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import Link from 'next/link';
|
|
5
6
|
import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell, Pagination, PaginationItem, Select, SelectItem, Skeleton, Button, Tooltip, Spinner, } from '@heroui/react';
|
|
@@ -9,6 +10,7 @@ import { formatCell } from './formatters';
|
|
|
9
10
|
import { summarizeAny } from './jsonSummary';
|
|
10
11
|
import { api } from '../../lib/api';
|
|
11
12
|
import { ConfirmDialog } from '../../components/ConfirmDialog';
|
|
13
|
+
import { useAppNavigation } from '../../hooks/useAppNavigation';
|
|
12
14
|
/** ---------- helpers: image detection/rendering ---------- */
|
|
13
15
|
const IMG_EXT_RE = /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic|heif)$/i;
|
|
14
16
|
const IMG_COLUMN_RE = /(image|photo|avatar|logo|picture|thumbnail|icon)/i;
|
|
@@ -27,7 +29,13 @@ function isLikelyImageUrl(s, columnKey, attr) {
|
|
|
27
29
|
const a = Array.isArray(attr) ? attr[0] : attr;
|
|
28
30
|
const fmt = String(a?.format || '').toLowerCase();
|
|
29
31
|
const type = String(a?.type || '').toLowerCase();
|
|
30
|
-
|
|
32
|
+
const fieldType = String(a?.fieldType || a?.filedType || '').toLowerCase();
|
|
33
|
+
if (fmt === 'file' ||
|
|
34
|
+
type === 'file' ||
|
|
35
|
+
fmt === 'image' ||
|
|
36
|
+
type === 'image' ||
|
|
37
|
+
fieldType === 'file' ||
|
|
38
|
+
fieldType === 'image')
|
|
31
39
|
return true;
|
|
32
40
|
// 3) Extension check (permissive like DynamicViewer)
|
|
33
41
|
const pure = s.split('?')[0].split('#')[0];
|
|
@@ -308,12 +316,94 @@ function formatDateLocal(d) {
|
|
|
308
316
|
return d.toString();
|
|
309
317
|
}
|
|
310
318
|
}
|
|
319
|
+
function isSystemRow(item, columns) {
|
|
320
|
+
if (!item || typeof item !== 'object')
|
|
321
|
+
return false;
|
|
322
|
+
const typeCol = columns.find((c) => c.toLowerCase() === 'type');
|
|
323
|
+
const typeVal = typeCol ? item?.[typeCol] : undefined;
|
|
324
|
+
const typeString = typeof typeVal === 'string'
|
|
325
|
+
? typeVal
|
|
326
|
+
: typeof typeVal?.value === 'string'
|
|
327
|
+
? typeVal.value
|
|
328
|
+
: typeof typeVal?.name === 'string'
|
|
329
|
+
? typeVal.name
|
|
330
|
+
: '';
|
|
331
|
+
return String(typeString ?? '').toLowerCase() === 'system';
|
|
332
|
+
}
|
|
311
333
|
/** ---------- component ---------- */
|
|
312
|
-
export function DataTableHero({ modelName, columns, rows, total, page, pageSize, onPageChange, onPageSizeChange, baseHref, loading, error, onDeleted, topContent, schema, onRequestView, }) {
|
|
334
|
+
export function DataTableHero({ modelName, columns, rows, total, page, pageSize, onPageChange, onPageSizeChange, baseHref, loading, error, onDeleted, topContent, schema, onRequestView, sort, sortType, onSortChange, }) {
|
|
335
|
+
const navigateTo = useAppNavigation();
|
|
313
336
|
const pageCount = Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
|
|
314
337
|
const [pendingDeleteId, setPendingDeleteId] = React.useState(null);
|
|
315
338
|
const [isDeleting, setIsDeleting] = React.useState(false);
|
|
316
339
|
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
|
340
|
+
const [qrModalState, setQrModalState] = React.useState(null);
|
|
341
|
+
const [selectedKeys, setSelectedKeys] = React.useState(new Set());
|
|
342
|
+
const [isBulkDeleting, setIsBulkDeleting] = React.useState(false);
|
|
343
|
+
const [isBulkDialogOpen, setIsBulkDialogOpen] = React.useState(false);
|
|
344
|
+
const canDelete = schema?.allowedMethods?.delete !== false;
|
|
345
|
+
const selectionMode = !loading && canDelete ? 'multiple' : undefined;
|
|
346
|
+
React.useEffect(() => {
|
|
347
|
+
setSelectedKeys(new Set());
|
|
348
|
+
}, [page, pageSize, modelName, rows]);
|
|
349
|
+
const selectedIds = React.useMemo(() => {
|
|
350
|
+
if (!canDelete || loading)
|
|
351
|
+
return [];
|
|
352
|
+
if (selectedKeys === 'all') {
|
|
353
|
+
return rows
|
|
354
|
+
.filter((r) => !isSystemRow(r, columns))
|
|
355
|
+
.map((r) => String(r.id || r._id));
|
|
356
|
+
}
|
|
357
|
+
if (selectedKeys instanceof Set) {
|
|
358
|
+
const keysArr = Array.from(selectedKeys);
|
|
359
|
+
return keysArr
|
|
360
|
+
.map((k) => String(k))
|
|
361
|
+
.filter((id) => {
|
|
362
|
+
const row = rows.find((r) => String(r.id || r._id) === id);
|
|
363
|
+
return row ? !isSystemRow(row, columns) : true;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return [];
|
|
367
|
+
}, [selectedKeys, rows, columns, canDelete, loading]);
|
|
368
|
+
const disabledKeys = React.useMemo(() => {
|
|
369
|
+
const disabled = new Set();
|
|
370
|
+
rows.forEach((r) => {
|
|
371
|
+
if (isSystemRow(r, columns)) {
|
|
372
|
+
disabled.add(String(r.id || r._id));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return disabled;
|
|
376
|
+
}, [rows, columns]);
|
|
377
|
+
const handleConfirmBulkDelete = async () => {
|
|
378
|
+
if (selectedIds.length === 0) {
|
|
379
|
+
setIsBulkDialogOpen(false);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
setIsBulkDeleting(true);
|
|
384
|
+
await Promise.all(selectedIds.map((id) => api.remove(modelName, id)));
|
|
385
|
+
setSelectedKeys(new Set());
|
|
386
|
+
onDeleted?.();
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
console.error('Bulk delete failed:', err);
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
setIsBulkDeleting(false);
|
|
393
|
+
setIsBulkDialogOpen(false);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
const combinedTopContent = React.useMemo(() => {
|
|
397
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [topContent, selectedIds.length > 0 && (_jsxs("div", { className: "flex items-center justify-between px-4 py-2.5 rounded-medium bg-danger-50 dark:bg-danger-900/20 border border-danger-200 dark:border-danger-800", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-danger-700 dark:text-danger-300", children: [_jsx(LucideIcons.CheckSquare, { size: 18 }), _jsxs("span", { children: [selectedIds.length, " ", selectedIds.length === 1 ? 'item' : 'items', " selected"] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { size: "sm", variant: "flat", color: "default", onPress: () => setSelectedKeys(new Set()), children: "Cancel" }), _jsxs(Button, { size: "sm", color: "danger", variant: "solid", startContent: !isBulkDeleting ? _jsx(LucideIcons.Trash2, { size: 16 }) : undefined, isLoading: isBulkDeleting, onPress: () => setIsBulkDialogOpen(true), children: ["Delete Selected (", selectedIds.length, ")"] })] })] }))] }));
|
|
398
|
+
}, [topContent, selectedIds.length, isBulkDeleting]);
|
|
399
|
+
const sortDescriptor = React.useMemo(() => {
|
|
400
|
+
if (!sort)
|
|
401
|
+
return undefined;
|
|
402
|
+
return {
|
|
403
|
+
column: sort,
|
|
404
|
+
direction: sortType === 'asc' ? 'ascending' : 'descending',
|
|
405
|
+
};
|
|
406
|
+
}, [sort, sortType]);
|
|
317
407
|
const headerItems = React.useMemo(() => [
|
|
318
408
|
...columns.map((c) => ({ key: c, label: c })),
|
|
319
409
|
{ key: 'actions', label: 'Actions' },
|
|
@@ -342,72 +432,95 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
|
|
|
342
432
|
setIsDialogOpen(false);
|
|
343
433
|
}
|
|
344
434
|
};
|
|
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,
|
|
435
|
+
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, selectionMode: selectionMode, selectedKeys: selectedKeys, onSelectionChange: setSelectedKeys, disabledKeys: disabledKeys, sortDescriptor: sortDescriptor, onSortChange: (descriptor) => {
|
|
436
|
+
if (onSortChange && descriptor.column) {
|
|
437
|
+
const dir = descriptor.direction === 'ascending' ? 'asc' : 'desc';
|
|
438
|
+
onSortChange(String(descriptor.column), dir);
|
|
439
|
+
}
|
|
440
|
+
}, classNames: {
|
|
346
441
|
base: 'max-h-full flex flex-col min-h-0 overflow-visible',
|
|
347
442
|
wrapper: 'flex-grow min-h-0 overflow-auto !p-0 !bg-transparent !shadow-none !rounded-none relative',
|
|
348
443
|
table: 'min-w-full', // Allow auto-sizing to prevent clipping
|
|
349
444
|
thead: '[&>tr]:first:rounded-none',
|
|
350
|
-
th: 'bg-default-100 text-default-600',
|
|
445
|
+
th: 'bg-default-100 text-default-600 [&[data-selection-cell=true]]:w-12 [&[data-selection-cell=true]]:min-w-[48px] [&[data-selection-cell=true]]:max-w-[48px]',
|
|
446
|
+
td: '[&[data-selection-cell=true]]:w-12 [&[data-selection-cell=true]]:min-w-[48px] [&[data-selection-cell=true]]:max-w-[48px]',
|
|
351
447
|
}, 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) => {
|
|
352
448
|
const val = Array.from(keys)[0];
|
|
353
449
|
if (val)
|
|
354
450
|
onPageSizeChange(Number(val));
|
|
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: (
|
|
356
|
-
if (
|
|
357
|
-
return (_jsx(Button, { isIconOnly: true, size: "sm", variant: "flat", onPress:
|
|
451
|
+
}, 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: ({ key, ...itemProps }) => {
|
|
452
|
+
if (itemProps.value === 'prev') {
|
|
453
|
+
return (_jsx(Button, { isIconOnly: true, size: "sm", variant: "flat", onPress: itemProps.onPress, isDisabled: itemProps.isDisabled, children: _jsx(ChevronLeft, { size: 16 }) }, key));
|
|
358
454
|
}
|
|
359
|
-
if (
|
|
360
|
-
return (_jsx(Button, { isIconOnly: true, size: "sm", variant: "flat", onPress:
|
|
455
|
+
if (itemProps.value === 'next') {
|
|
456
|
+
return (_jsx(Button, { isIconOnly: true, size: "sm", variant: "flat", onPress: itemProps.onPress, isDisabled: itemProps.isDisabled, children: _jsx(ChevronRight, { size: 16 }) }, key));
|
|
361
457
|
}
|
|
362
|
-
return _jsx(PaginationItem, { ...
|
|
363
|
-
} })] }), bottomContentPlacement: "outside", topContent:
|
|
458
|
+
return _jsx(PaginationItem, { ...itemProps }, key);
|
|
459
|
+
} })] }), bottomContentPlacement: "outside", topContent: combinedTopContent, topContentPlacement: "outside", children: [_jsx(TableHeader, { columns: headerItems, children: (col) => (_jsx(TableColumn, { allowsSorting: col.key !== 'actions', className: `text-xs uppercase tracking-wide ${col.key === 'actions'
|
|
364
460
|
? '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
461
|
: ''}`, 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) => {
|
|
366
462
|
const key = String(columnKey);
|
|
463
|
+
const stopRowSelectionProps = {
|
|
464
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
465
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
466
|
+
onClick: (e) => e.stopPropagation(),
|
|
467
|
+
};
|
|
367
468
|
if (loading) {
|
|
368
|
-
return (_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-3 w-3/4 rounded-md" }) }, `${item.id}-${key}`));
|
|
469
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: _jsx(Skeleton, { className: "h-3 w-3/4 rounded-md" }) }, `${item.id}-${key}`));
|
|
369
470
|
}
|
|
370
471
|
if (key === 'actions') {
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
? typeVal.name
|
|
379
|
-
: '';
|
|
380
|
-
const isSystemRow = String(typeString ?? '').toLowerCase() === 'system';
|
|
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;
|
|
472
|
+
const isSystemRowItem = isSystemRow(item, columns);
|
|
473
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, 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) => {
|
|
474
|
+
const isQr = action?.type === 'qr' ||
|
|
475
|
+
action?.actionType === 'qr' ||
|
|
476
|
+
action.icon === 'QrCode' ||
|
|
477
|
+
action.label?.toLowerCase() === 'qr code';
|
|
478
|
+
let href = action.href || '';
|
|
383
479
|
href = href.replace('{id}', String(item.id || item._id || ''));
|
|
384
480
|
const relatedId = item._actionsData?.[action.relatedModel || ''];
|
|
385
481
|
if (relatedId) {
|
|
386
482
|
href = href.replace('{relatedId}', relatedId);
|
|
387
483
|
}
|
|
388
|
-
|
|
389
|
-
|
|
484
|
+
if (!isQr &&
|
|
485
|
+
action.href &&
|
|
486
|
+
action.href.includes('{relatedId}') &&
|
|
390
487
|
!relatedId) {
|
|
391
488
|
return null;
|
|
392
489
|
}
|
|
393
|
-
|
|
394
|
-
if (action.relatedModel && !relatedId) {
|
|
490
|
+
if (!isQr && action.relatedModel && !relatedId) {
|
|
395
491
|
return null;
|
|
396
492
|
}
|
|
397
493
|
const IconComponent = action.icon
|
|
398
494
|
? LucideIcons[action.icon]
|
|
399
495
|
: null;
|
|
400
|
-
return (_jsx(Tooltip, { content: action.label, children: _jsx(Button, { as:
|
|
401
|
-
|
|
496
|
+
return (_jsx(Tooltip, { content: action.label, children: _jsx(Button, { as: isQr ? undefined : Link, href: isQr ? undefined : href, onPress: () => {
|
|
497
|
+
if (isQr) {
|
|
498
|
+
setQrModalState({ item, action });
|
|
499
|
+
}
|
|
500
|
+
else if (href) {
|
|
501
|
+
navigateTo(href);
|
|
502
|
+
}
|
|
503
|
+
}, 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}`));
|
|
504
|
+
}), _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" })] }) }) }), schema?.allowedMethods?.update !== false && (_jsx(Tooltip, { content: isSystemRowItem
|
|
402
505
|
? 'System record — editing disabled'
|
|
403
|
-
: 'Edit', children: _jsx(
|
|
506
|
+
: 'Edit', children: isSystemRowItem ? (_jsx("span", { className: "inline-flex items-center justify-center min-w-8 w-8 h-8 rounded-small text-default-400 opacity-50 cursor-not-allowed", "aria-disabled": "true", 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(Link, { href: `${baseHref}/${item.id ?? item._id}`, onClick: (e) => {
|
|
507
|
+
if (e.metaKey ||
|
|
508
|
+
e.ctrlKey ||
|
|
509
|
+
e.shiftKey ||
|
|
510
|
+
e.altKey ||
|
|
511
|
+
e.button !== 0) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
e.preventDefault();
|
|
515
|
+
navigateTo(`${baseHref}/${item.id ?? item._id}`);
|
|
516
|
+
}, className: "inline-flex items-center justify-center min-w-8 w-8 h-8 rounded-small text-default-foreground hover:bg-default/40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-focus", "aria-label": "Edit", 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" })] }) })) })), schema?.allowedMethods?.delete !== false && (_jsx(Tooltip, { content: isSystemRowItem
|
|
404
517
|
? 'System record — deletion disabled'
|
|
405
|
-
: 'Delete', children: _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", color: "danger", isDisabled:
|
|
406
|
-
if (!
|
|
518
|
+
: 'Delete', children: _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", color: "danger", isDisabled: isSystemRowItem, "aria-disabled": isSystemRowItem, onPress: () => {
|
|
519
|
+
if (!isSystemRowItem)
|
|
407
520
|
requestDelete(item.id);
|
|
408
521
|
}, children: isDeleting && pendingDeleteId === item.id ? (_jsx(Spinner, { size: "sm" })) : (
|
|
409
522
|
// Trash icon
|
|
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`));
|
|
523
|
+
_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`));
|
|
411
524
|
}
|
|
412
525
|
// Prepare raw value (parse JSON-like strings first)
|
|
413
526
|
let rawRawVal = item[key];
|
|
@@ -419,31 +532,31 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
|
|
|
419
532
|
const attributes = (schema?.attributes ?? {});
|
|
420
533
|
const maybeImg = maybeRenderImageCell(rawVal, key, attributes[key]);
|
|
421
534
|
if (maybeImg) {
|
|
422
|
-
return (_jsx(TableCell, { children: maybeImg }, `${item.id ?? item._id}-${key}`));
|
|
535
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: maybeImg }, `${item.id ?? item._id}-${key}`));
|
|
423
536
|
}
|
|
424
537
|
// 0) Date fields (createdAt, updatedAt, deletedAt)
|
|
425
538
|
if (isDateField(key)) {
|
|
426
539
|
const d = tryParseDate(rawVal);
|
|
427
540
|
if (d) {
|
|
428
|
-
return (_jsx(TableCell, { children: formatDateLocal(d) }, `${item.id ?? item._id}-${key}`));
|
|
541
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: formatDateLocal(d) }, `${item.id ?? item._id}-${key}`));
|
|
429
542
|
}
|
|
430
543
|
}
|
|
431
544
|
// ... the rest of the formatters ...
|
|
432
545
|
// 1) Time-only ranges like "15:00..00:00 17:00..23:00"
|
|
433
546
|
const timeRangePretty = formatTimeOnlyRanges(rawVal);
|
|
434
547
|
if (timeRangePretty) {
|
|
435
|
-
return (_jsx(TableCell, { children: timeRangePretty }, `${item.id ?? item._id}-${key}`));
|
|
548
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: timeRangePretty }, `${item.id ?? item._id}-${key}`));
|
|
436
549
|
}
|
|
437
550
|
// 2) Object/array with "show"/"showKey" or common label fields
|
|
438
551
|
const showPretty = formatShowValue(rawVal, attributes[key]);
|
|
439
552
|
if (showPretty) {
|
|
440
|
-
return (_jsx(TableCell, { children: truncateText(showPretty, 35) }, `${item.id ?? item._id}-${key}`));
|
|
553
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: truncateText(showPretty, 35) }, `${item.id ?? item._id}-${key}`));
|
|
441
554
|
}
|
|
442
555
|
// 3) Generic JSON/array/object summarizer (supports raw JSON string too)
|
|
443
556
|
const showKey = (Array.isArray(attributes[key]) ? attributes[key][0] : attributes[key])?.show;
|
|
444
557
|
const jsonSummary = summarizeAny(rawVal, showKey);
|
|
445
558
|
if (typeof jsonSummary === 'string' && jsonSummary.length) {
|
|
446
|
-
return (_jsx(TableCell, { children: truncateText(jsonSummary, 35) }, `${item.id ?? item._id}-${key}`));
|
|
559
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: truncateText(jsonSummary, 35) }, `${item.id ?? item._id}-${key}`));
|
|
447
560
|
}
|
|
448
561
|
// 4) Existing formatter pipeline
|
|
449
562
|
const formatted = formatCell(rawVal, key);
|
|
@@ -452,6 +565,6 @@ export function DataTableHero({ modelName, columns, rows, total, page, pageSize,
|
|
|
452
565
|
const displayValue = isSimple
|
|
453
566
|
? truncateText(String(formatted), 35)
|
|
454
567
|
: formatted;
|
|
455
|
-
return (_jsx(TableCell, { children: displayValue }, `${item.id ?? item._id}-${key}`));
|
|
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" })] }));
|
|
568
|
+
return (_jsx(TableCell, { ...stopRowSelectionProps, children: displayValue }, `${item.id ?? item._id}-${key}`));
|
|
569
|
+
} }, 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" }), _jsx(QRCodeModal, { isOpen: !!qrModalState, onClose: () => setQrModalState(null), item: qrModalState?.item, action: qrModalState?.action, modelName: modelName }), _jsx(ConfirmDialog, { isOpen: isBulkDialogOpen, onClose: () => setIsBulkDialogOpen(false), onConfirm: handleConfirmBulkDelete, isLoading: isBulkDeleting, title: `Delete ${selectedIds.length} selected ${selectedIds.length === 1 ? 'record' : 'records'}?`, description: `Are you sure you want to delete ${selectedIds.length} selected ${selectedIds.length === 1 ? 'record' : 'records'}? This action cannot be undone.`, confirmText: `Delete ${selectedIds.length} ${selectedIds.length === 1 ? 'record' : 'records'}`, cancelText: "Cancel" })] }));
|
|
457
570
|
}
|
|
@@ -2,6 +2,14 @@
|
|
|
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
|
+
import { useAppNavigation } from '../../hooks/useAppNavigation';
|
|
5
6
|
export function ListHeader({ title, createHref, hideCreate = false, loading, children, }) {
|
|
6
|
-
|
|
7
|
+
const navigateTo = useAppNavigation();
|
|
8
|
+
const handleCreate = (e) => {
|
|
9
|
+
if (!createHref)
|
|
10
|
+
return;
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
navigateTo(createHref);
|
|
13
|
+
};
|
|
14
|
+
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, onClick: handleCreate, children: _jsx(Button, { size: "sm", color: "primary", isDisabled: loading, onPress: () => navigateTo(createHref), children: "Create" }) })) : null, children] })] }), _jsx(Divider, { className: "my-3" })] }));
|
|
7
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airoom/nextmin-react",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -56,11 +56,13 @@
|
|
|
56
56
|
"@tiptap/react": "^3.13.0",
|
|
57
57
|
"@tiptap/starter-kit": "^3.13.0",
|
|
58
58
|
"@tiptap/suggestion": "^3.13.0",
|
|
59
|
+
"@types/qrcode": "^1.5.6",
|
|
59
60
|
"@types/react-resizable": "^3.0.8",
|
|
60
61
|
"clsx": "^2.1.1",
|
|
61
62
|
"dompurify": "^3.3.0",
|
|
62
63
|
"framer-motion": "^12.23.12",
|
|
63
64
|
"lucide-react": "^0.556.0",
|
|
65
|
+
"qrcode": "^1.5.4",
|
|
64
66
|
"react": "^19.2.1",
|
|
65
67
|
"react-dom": "^19.2.1",
|
|
66
68
|
"react-redux": ">=9",
|
|
@@ -79,4 +81,4 @@
|
|
|
79
81
|
"publishConfig": {
|
|
80
82
|
"access": "public"
|
|
81
83
|
}
|
|
82
|
-
}
|
|
84
|
+
}
|