@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,113 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import React from 'react';
4
- import { Select, SelectItem, Input, Chip } from '@heroui/react';
5
- import { api } from '../lib/api';
6
- export const RefMultiSelect = ({ name, label, refModel, showKey = 'name', value, onChange, description, disabled, required, pageSize = 10000, error, errorMessage, }) => {
7
- const modelSlug = React.useMemo(() => String(refModel).trim().toLowerCase(), [refModel]);
8
- // normalize an option to always have string `id`
9
- const normalize = React.useCallback((it) => {
10
- const { id: rawId, _id: raw_Id, ...rest } = it || {};
11
- const id = (typeof rawId === 'string' && rawId) ||
12
- (typeof raw_Id === 'string' && raw_Id) ||
13
- String(rawId ?? raw_Id ?? '');
14
- return { id, ...rest };
15
- }, []);
16
- // from props.value (objects or ids) build selected ids + pre-seeded options
17
- const preselected = React.useMemo(() => {
18
- if (Array.isArray(value) && value.length && typeof value[0] === 'object') {
19
- return value.map(normalize);
20
- }
21
- return [];
22
- }, [value, normalize]);
23
- const selectedIds = React.useMemo(() => {
24
- if (preselected.length)
25
- return preselected.map((o) => o.id);
26
- return (value ?? []).map(String);
27
- }, [value, preselected]);
28
- // options start with preselected objects so chips show immediately & no warning
29
- const [options, setOptions] = React.useState(preselected);
30
- // keep options in sync if parent passes new objects later
31
- React.useEffect(() => {
32
- if (!preselected.length)
33
- return;
34
- setOptions((prev) => {
35
- const map = new Map(prev.map((o) => [o.id, o]));
36
- preselected.forEach((o) => map.set(o.id, o));
37
- return Array.from(map.values());
38
- });
39
- }, [preselected]);
40
- const selectedKeys = React.useMemo(() => new Set(selectedIds), [selectedIds]);
41
- const labelOf = React.useCallback((opt) => String(opt?.[showKey] ??
42
- opt?.name ??
43
- opt?.title ??
44
- opt?.key ??
45
- opt?.id ??
46
- opt?._id ??
47
- ''), [showKey]);
48
- const [open, setOpen] = React.useState(false);
49
- const [query, setQuery] = React.useState('');
50
- const [loading, setLoading] = React.useState(false);
51
- // fetch list (large page) and merge with preseeded
52
- const load = React.useCallback(async (q) => {
53
- setLoading(true);
54
- try {
55
- const params = {
56
- limit: pageSize,
57
- sort: showKey,
58
- sortType: 'asc',
59
- ...(q ? { q, searchKey: showKey } : {}),
60
- };
61
- const res = await api.list?.(modelSlug, 0, pageSize, params);
62
- const payload = res?.data ?? res;
63
- const list = payload?.items ??
64
- payload?.docs ??
65
- payload?.results ??
66
- payload?.list ??
67
- (Array.isArray(payload) ? payload : []);
68
- const normalized = list.map(normalize);
69
- // merge by id (keep preselected if present)
70
- setOptions((prev) => {
71
- const map = new Map(prev.map((o) => [o.id, o]));
72
- normalized.forEach((o) => map.set(o.id, { ...(map.get(o.id) || {}), ...o }));
73
- return Array.from(map.values());
74
- });
75
- }
76
- finally {
77
- setLoading(false);
78
- }
79
- }, [modelSlug, pageSize, showKey, normalize]);
80
- // load on open + debounced search
81
- React.useEffect(() => {
82
- if (open)
83
- void load(query);
84
- }, [open]); // eslint-disable-line react-hooks/exhaustive-deps
85
- React.useEffect(() => {
86
- if (!open)
87
- return;
88
- const t = setTimeout(() => void load(query), 250);
89
- return () => clearTimeout(t);
90
- }, [query, open, load]);
91
- const topSearch = (_jsx("div", { className: "p-2", children: _jsx(Input, { size: "sm", variant: "bordered", "aria-label": `Search ${refModel}`, placeholder: `Search ${refModel}…`, value: query, onChange: (e) => setQuery(e.target.value), autoFocus: true }) }));
92
- return (_jsx("div", { className: "w-full", children: _jsx(Select, { name: name, label: label, "aria-label": label, isDisabled: disabled, selectionMode: "multiple", selectedKeys: selectedKeys, onSelectionChange: (keys) => {
93
- if (keys === 'all')
94
- return;
95
- onChange(Array.from(keys).map(String));
96
- }, isClearable: true, items: options, isVirtualized: true, isMultiline: true, isInvalid: !!error, errorMessage: errorMessage || '', variant: "bordered", maxListboxHeight: 288, itemHeight: 36, className: "w-full", placeholder: `Select ${refModel}`, onOpenChange: setOpen, isLoading: loading, description: description || null, listboxProps: {
97
- topContent: topSearch,
98
- emptyContent: loading ? undefined : 'No results',
99
- className: '',
100
- }, scrollShadowProps: {
101
- isEnabled: false,
102
- },
103
- // chips like docs; items is an array (SelectedItems<T>)
104
- renderValue: (items) => {
105
- const arr = Array.isArray(items) ? items : [];
106
- // first render: items may be empty → fall back to selectedIds mapped from preseeded options
107
- if (!arr.length && selectedIds.length) {
108
- const byId = new Map(options.map((o) => [String(o.id), o]));
109
- return (_jsx("div", { className: "flex flex-wrap gap-1", children: selectedIds.map((id) => (_jsx(Chip, { size: "sm", variant: "flat", children: labelOf(byId.get(String(id))) }, id))) }));
110
- }
111
- return (_jsx("div", { className: "flex flex-wrap gap-1", children: arr.map((it) => (_jsx(Chip, { size: "sm", variant: "flat", children: String(it?.props?.textValue ?? it?.props?.children ?? '') }, String(it?.key)))) }));
112
- }, children: (item) => (_jsx(SelectItem, { textValue: labelOf(item), children: labelOf(item) }, item.id)) }) }));
113
- };
@@ -1,17 +0,0 @@
1
- export type RefSingleSelectProps = {
2
- name: string;
3
- label: string;
4
- refModel: string;
5
- showKey?: string;
6
- description?: string;
7
- value?: string | null;
8
- onChange: (id: string | null) => void;
9
- disabled?: boolean;
10
- required?: boolean;
11
- className?: string;
12
- classNames?: {
13
- trigger?: string;
14
- };
15
- pageSize?: number;
16
- };
17
- export declare function RefSingleSelect({ name, label, refModel, showKey, description, value, onChange, disabled, required, className, classNames, pageSize, }: RefSingleSelectProps): import("react/jsx-runtime").JSX.Element;
@@ -1,110 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import * as React from 'react';
4
- import { Select, SelectItem, Input } from '@heroui/react';
5
- import { api } from '../lib/api';
6
- function stripPointerNone(cls) {
7
- return (cls ?? '').replace(/\bpointer-events-none\b/g, '').trim();
8
- }
9
- export function RefSingleSelect({ name, label, refModel, showKey = 'name', description, value, onChange, disabled, required, className, classNames, pageSize = 50, }) {
10
- const [loading, setLoading] = React.useState(false);
11
- const [items, setItems] = React.useState([]);
12
- const [error, setError] = React.useState(null);
13
- const [query, setQuery] = React.useState('');
14
- const [open, setOpen] = React.useState(false);
15
- const refModelLC = React.useMemo(() => refModel.toLowerCase(), [refModel]);
16
- // normalize an option to always have string `id`
17
- const normalize = React.useCallback((it) => {
18
- const { id: rawId, _id: raw_Id, ...rest } = it || {};
19
- const id = (typeof rawId === 'string' && rawId) ||
20
- (typeof raw_Id === 'string' && raw_Id) ||
21
- String(rawId ?? raw_Id ?? '');
22
- return { id, ...rest };
23
- }, []);
24
- const load = React.useCallback(async (q) => {
25
- try {
26
- setLoading(true);
27
- setError(null);
28
- const params = {
29
- limit: pageSize,
30
- sort: showKey,
31
- sortType: 'asc',
32
- ...(q ? { q, searchKey: showKey } : {}),
33
- };
34
- const res = await api.list(refModelLC, 0, pageSize, params);
35
- const payload = res?.data ?? res;
36
- const list = Array.isArray(payload)
37
- ? payload
38
- : payload?.items ?? payload?.docs ?? [];
39
- const normalized = list.map(normalize);
40
- setItems((prev) => {
41
- // Find currently selected item in previous list to ensure we don't lose it
42
- // (which would break the label display)
43
- const selectedId = value ? String(value) : null;
44
- const selectedItem = selectedId
45
- ? prev.find((p) => String(p.id) === selectedId)
46
- : null;
47
- // Start with the new list/search results
48
- const newItems = [...normalized];
49
- // If we have a selected item and it's not in the new results, add it back
50
- if (selectedItem &&
51
- !newItems.some((i) => String(i.id) === String(selectedItem.id))) {
52
- newItems.push(selectedItem);
53
- }
54
- return newItems;
55
- });
56
- }
57
- catch (e) {
58
- setError(e?.message || 'Failed to load options');
59
- }
60
- finally {
61
- setLoading(false);
62
- }
63
- }, [refModelLC, pageSize, showKey, normalize]);
64
- // load on open
65
- React.useEffect(() => {
66
- if (open)
67
- void load(query);
68
- }, [open]); // eslint-disable-line react-hooks/exhaustive-deps
69
- // debounced search
70
- React.useEffect(() => {
71
- if (!open)
72
- return;
73
- const t = setTimeout(() => void load(query), 250);
74
- return () => clearTimeout(t);
75
- }, [query, open, load]);
76
- // If we have a value but no items, try to load once to get the label
77
- React.useEffect(() => {
78
- if (value && items.length === 0 && !loading) {
79
- void load('');
80
- }
81
- }, [value]); // eslint-disable-line react-hooks/exhaustive-deps
82
- const getId = (r) => (typeof r.id === 'string' && r.id) ||
83
- (typeof r._id === 'string' && r._id) ||
84
- '';
85
- const getLabel = (r) => {
86
- const v = r?.[showKey];
87
- return v == null ? getId(r) : String(v);
88
- };
89
- const topSearch = (_jsx("div", { className: "p-2", children: _jsx(Input, { size: "sm", variant: "bordered", "aria-label": `Search ${refModel}`, placeholder: `Search ${refModel}…`, value: query, onChange: (e) => setQuery(e.target.value), isClearable: true, onClear: () => setQuery(''), autoFocus: true, onKeyDown: (e) => e.stopPropagation() }) }));
90
- return (_jsx("div", { className: "w-full", children: _jsx(Select, { name: name, label: label, placeholder: label, labelPlacement: "outside", className: className, classNames: {
91
- // Make sure the whole trigger is clickable
92
- trigger: `cursor-pointer ${stripPointerNone(classNames?.trigger)}`,
93
- }, variant: "bordered", isDisabled: disabled || (loading && !items.length), isLoading: loading, isRequired: required, description: error || description, items: items, selectionMode: "single", selectedKeys: value ? new Set([String(value)]) : new Set(), onSelectionChange: (keys) => {
94
- if (keys === 'all')
95
- return;
96
- const v = Array.from(keys)[0];
97
- onChange(v ?? null);
98
- }, scrollShadowProps: {
99
- isEnabled: false,
100
- }, onOpenChange: setOpen, listboxProps: {
101
- topContent: topSearch,
102
- emptyContent: loading ? undefined : 'No results',
103
- }, renderValue: (items) => {
104
- return items.map((item) => (_jsx("span", { children: item.textValue }, item.key)));
105
- }, children: (opt) => {
106
- const key = getId(opt);
107
- const text = getLabel(opt);
108
- return (_jsx(SelectItem, { textValue: text, children: text }, key));
109
- } }) }));
110
- }
@@ -1,2 +0,0 @@
1
- import { type Socket } from 'socket.io-client';
2
- export declare function getSchemaService(): Socket | null;
@@ -1,39 +0,0 @@
1
- import { Manager, } from 'socket.io-client';
2
- const SOCKET_PATH = '/__nextmin__/schema';
3
- const NAMESPACE = '/schema';
4
- let schemaSocket = null;
5
- export function getSchemaService() {
6
- if (typeof window === 'undefined')
7
- return null; // SSR guard
8
- const urlStr = process.env.NEXT_PUBLIC_NEXTMIN_API_URL || 'http://localhost:8081/rest';
9
- let origin = 'http://localhost:8081';
10
- try {
11
- const u = new URL(urlStr);
12
- origin = u.origin;
13
- }
14
- catch {
15
- // keep default
16
- }
17
- // Keep a single Manager across HMR / route changes
18
- const MGR_KEY = '__NM_SCHEMA_MGR__';
19
- let mgr = window[MGR_KEY];
20
- if (!mgr) {
21
- const mgrOpts = {
22
- path: SOCKET_PATH,
23
- transports: ['websocket'], // avoid polling aborts on nav
24
- reconnection: true,
25
- reconnectionAttempts: Infinity,
26
- reconnectionDelay: 500,
27
- reconnectionDelayMax: 4000,
28
- };
29
- mgr = new Manager(origin, mgrOpts);
30
- window[MGR_KEY] = mgr;
31
- }
32
- if (!schemaSocket) {
33
- const sockOpts = {
34
- auth: { apiKey: process.env.NEXT_PUBLIC_NEXTMIN_API_KEY || '' }, // <-- auth goes here
35
- };
36
- schemaSocket = mgr.socket(NAMESPACE, sockOpts);
37
- }
38
- return schemaSocket;
39
- }
@@ -1,2 +0,0 @@
1
- import { NextMinStore } from './store';
2
- export declare function startSchemaLive(store: NextMinStore): void;
@@ -1,19 +0,0 @@
1
- 'use client';
2
- import { setSchemas } from './schemasSlice';
3
- import { getSchemaService } from '../lib/schemaService';
4
- export function startSchemaLive(store) {
5
- const sock = getSchemaService();
6
- if (!sock)
7
- return;
8
- if (sock.__nm_bound)
9
- return;
10
- sock.__nm_bound = true;
11
- const push = (all) => {
12
- const data = Array.isArray(all) ? all : Object.values(all || {});
13
- store.dispatch(setSchemas(data));
14
- };
15
- sock.on('schemasData', push);
16
- sock.on('schemasUpdated', push);
17
- sock.on('connect', () => console.log('[nextmin] schema service connected', sock.id));
18
- sock.on('connect_error', (e) => console.warn('[nextmin] schema service connect_error:', e?.message));
19
- }