@airoom/nextmin-react 1.4.3 → 1.4.5
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/dist/components/RefMultiSelect.js +5 -1
- package/dist/components/RefSingleSelect.js +67 -9
- package/dist/components/SchemaForm.js +104 -4
- package/dist/components/editor/components/DistrictGridModal.js +1 -1
- package/dist/components/editor/components/SchemaInsertionModal.js +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/nextmin.css +1 -1
- package/package.json +2 -2
|
@@ -54,6 +54,8 @@ export const RefMultiSelect = ({ name, label, refModel, showKey = 'name', value,
|
|
|
54
54
|
try {
|
|
55
55
|
const params = {
|
|
56
56
|
limit: pageSize,
|
|
57
|
+
sort: showKey,
|
|
58
|
+
sortType: 'asc',
|
|
57
59
|
...(q ? { q, searchKey: showKey } : {}),
|
|
58
60
|
};
|
|
59
61
|
const res = await api.list?.(modelSlug, 0, pageSize, params);
|
|
@@ -91,10 +93,12 @@ export const RefMultiSelect = ({ name, label, refModel, showKey = 'name', value,
|
|
|
91
93
|
if (keys === 'all')
|
|
92
94
|
return;
|
|
93
95
|
onChange(Array.from(keys).map(String));
|
|
94
|
-
}, 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: {
|
|
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: {
|
|
95
97
|
topContent: topSearch,
|
|
96
98
|
emptyContent: loading ? undefined : 'No results',
|
|
97
99
|
className: '',
|
|
100
|
+
}, scrollShadowProps: {
|
|
101
|
+
isEnabled: false,
|
|
98
102
|
},
|
|
99
103
|
// chips like docs; items is an array (SelectedItems<T>)
|
|
100
104
|
renderValue: (items) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { Select, SelectItem } from '@heroui/react';
|
|
4
|
+
import { Select, SelectItem, Input } from '@heroui/react';
|
|
5
5
|
import { api } from '../lib/api';
|
|
6
6
|
function stripPointerNone(cls) {
|
|
7
7
|
return (cls ?? '').replace(/\bpointer-events-none\b/g, '').trim();
|
|
@@ -10,25 +10,75 @@ export function RefSingleSelect({ name, label, refModel, showKey = 'name', descr
|
|
|
10
10
|
const [loading, setLoading] = React.useState(false);
|
|
11
11
|
const [items, setItems] = React.useState([]);
|
|
12
12
|
const [error, setError] = React.useState(null);
|
|
13
|
+
const [query, setQuery] = React.useState('');
|
|
14
|
+
const [open, setOpen] = React.useState(false);
|
|
13
15
|
const refModelLC = React.useMemo(() => refModel.toLowerCase(), [refModel]);
|
|
14
|
-
|
|
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) => {
|
|
15
25
|
try {
|
|
16
26
|
setLoading(true);
|
|
17
27
|
setError(null);
|
|
18
|
-
const
|
|
19
|
-
|
|
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
|
+
});
|
|
20
56
|
}
|
|
21
57
|
catch (e) {
|
|
22
58
|
setError(e?.message || 'Failed to load options');
|
|
23
|
-
setItems([]);
|
|
24
59
|
}
|
|
25
60
|
finally {
|
|
26
61
|
setLoading(false);
|
|
27
62
|
}
|
|
28
|
-
}, [refModelLC, pageSize]);
|
|
63
|
+
}, [refModelLC, pageSize, showKey, normalize]);
|
|
64
|
+
// load on open
|
|
29
65
|
React.useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
32
82
|
const getId = (r) => (typeof r.id === 'string' && r.id) ||
|
|
33
83
|
(typeof r._id === 'string' && r._id) ||
|
|
34
84
|
'';
|
|
@@ -36,14 +86,22 @@ export function RefSingleSelect({ name, label, refModel, showKey = 'name', descr
|
|
|
36
86
|
const v = r?.[showKey];
|
|
37
87
|
return v == null ? getId(r) : String(v);
|
|
38
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() }) }));
|
|
39
90
|
return (_jsx("div", { className: "w-full", children: _jsx(Select, { name: name, label: label, placeholder: label, labelPlacement: "outside", className: className, classNames: {
|
|
40
91
|
// Make sure the whole trigger is clickable
|
|
41
92
|
trigger: `cursor-pointer ${stripPointerNone(classNames?.trigger)}`,
|
|
42
|
-
}, variant: "bordered", isDisabled: disabled || loading, isLoading: loading, isRequired: required, description: error || description, items: items, selectionMode: "single", selectedKeys: value ? new Set([String(value)]) : new Set(), onSelectionChange: (keys) => {
|
|
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) => {
|
|
43
94
|
if (keys === 'all')
|
|
44
95
|
return;
|
|
45
96
|
const v = Array.from(keys)[0];
|
|
46
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)));
|
|
47
105
|
}, children: (opt) => {
|
|
48
106
|
const key = getId(opt);
|
|
49
107
|
const text = getLabel(opt);
|
|
@@ -11,6 +11,7 @@ import { PhoneInput } from './PhoneInput';
|
|
|
11
11
|
import { FileUploader } from './FileUploader';
|
|
12
12
|
import AddressAutocompleteGoogle from './AddressAutocomplete';
|
|
13
13
|
import { useGoogleMapsKey } from '../hooks/useGoogleMapsKey';
|
|
14
|
+
import { api } from '../lib/api';
|
|
14
15
|
import { parseDate, parseDateTime, parseTime, } from '@internationalized/date';
|
|
15
16
|
// import RichTextEditor from './editor/RichTextEditor';
|
|
16
17
|
// import ShadcnEditor from './editor/ShadcnEditor';
|
|
@@ -277,6 +278,30 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
277
278
|
const schema = useMemo(() => schemaOverride ??
|
|
278
279
|
items.find((s) => s.modelName.toLowerCase() === model.toLowerCase()), [items, model, schemaOverride]);
|
|
279
280
|
const [form, setForm] = useState(initialValues ?? {});
|
|
281
|
+
// For auto-slugging
|
|
282
|
+
const [manuallyEditedSlugs, setManuallyEditedSlugs] = useState(new Set());
|
|
283
|
+
const [pendingSlugChecks, setPendingSlugChecks] = useState({});
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
const timer = setTimeout(async () => {
|
|
286
|
+
const entries = Object.entries(pendingSlugChecks);
|
|
287
|
+
if (entries.length === 0)
|
|
288
|
+
return;
|
|
289
|
+
const newValues = {};
|
|
290
|
+
for (const [targetName, baseSlug] of entries) {
|
|
291
|
+
if (!baseSlug)
|
|
292
|
+
continue;
|
|
293
|
+
const uniqueSlug = await findUniqueSlug(model, baseSlug, form.id || form._id);
|
|
294
|
+
if (uniqueSlug !== form[targetName]) {
|
|
295
|
+
newValues[targetName] = uniqueSlug;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (Object.keys(newValues).length > 0) {
|
|
299
|
+
setForm((f) => ({ ...f, ...newValues }));
|
|
300
|
+
}
|
|
301
|
+
setPendingSlugChecks({});
|
|
302
|
+
}, 600); // 600ms debounce
|
|
303
|
+
return () => clearTimeout(timer);
|
|
304
|
+
}, [pendingSlugChecks, model, form.id, form._id, form]);
|
|
280
305
|
const [error, setError] = useState();
|
|
281
306
|
const isEdit = useMemo(() => !!(initialValues &&
|
|
282
307
|
(initialValues.id || initialValues._id)), [initialValues]);
|
|
@@ -318,7 +343,44 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
318
343
|
.filter(([name, attr]) => !(isEdit && isPasswordAttr(name, attr)))
|
|
319
344
|
.map(([name, attr]) => ({ name, attr })), [schema, canBypassPrivacy, isEdit]);
|
|
320
345
|
const gridClass = 'grid gap-4 grid-cols-2';
|
|
321
|
-
const handleChange = (name, value) =>
|
|
346
|
+
const handleChange = (name, value) => {
|
|
347
|
+
setForm((f) => {
|
|
348
|
+
const next = { ...f, [name]: value };
|
|
349
|
+
if (schema) {
|
|
350
|
+
// 1. If we edited a target field directly, mark it as manually edited
|
|
351
|
+
const attr = schema.attributes[name];
|
|
352
|
+
const head = Array.isArray(attr) ? attr[0] : attr;
|
|
353
|
+
if (head && head.populateSlugFrom) {
|
|
354
|
+
setManuallyEditedSlugs((prev) => new Set(prev).add(name));
|
|
355
|
+
}
|
|
356
|
+
// 2. If we edited a source field, update its targets
|
|
357
|
+
for (const [fname, fattr] of Object.entries(schema.attributes)) {
|
|
358
|
+
const fhead = Array.isArray(fattr) ? fattr[0] : fattr;
|
|
359
|
+
const populateFrom = fhead?.populateSlugFrom;
|
|
360
|
+
if (populateFrom) {
|
|
361
|
+
const sources = String(populateFrom)
|
|
362
|
+
.split(',')
|
|
363
|
+
.map((s) => s.trim());
|
|
364
|
+
if (sources.includes(name) && !manuallyEditedSlugs.has(fname)) {
|
|
365
|
+
const baseText = sources
|
|
366
|
+
.map((s) => next[s] || '')
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
.join(' ');
|
|
369
|
+
if (baseText) {
|
|
370
|
+
const baseSlug = slugify(baseText);
|
|
371
|
+
next[fname] = baseSlug;
|
|
372
|
+
setPendingSlugChecks((prev) => ({ ...prev, [fname]: baseSlug }));
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
next[fname] = '';
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return next;
|
|
382
|
+
});
|
|
383
|
+
};
|
|
322
384
|
const handleSubmit = async (e) => {
|
|
323
385
|
e.preventDefault();
|
|
324
386
|
setError(undefined);
|
|
@@ -363,7 +425,9 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
363
425
|
: parseJsonGroupValue(form[name], 'array');
|
|
364
426
|
const canAdd = spec.maxItems == null || items.length < spec.maxItems;
|
|
365
427
|
const canRemove = spec.minItems == null || items.length > spec.minItems;
|
|
366
|
-
return (_jsxs("div", { className: "col-span-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [attr?.label || spec.label || formatLabel(name), attr?.required ? ' *' : ''] }), _jsxs("div", { className: "flex flex-col gap-4 mt-2", children: [items.map((it, idx) => (_jsxs("div", { className: "grid grid-cols-2 gap-3 p-3 rounded-lg border border-default-200", children: [
|
|
428
|
+
return (_jsxs("div", { className: "col-span-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [attr?.label || spec.label || formatLabel(name), attr?.required ? ' *' : ''] }), _jsxs("div", { className: "flex flex-col gap-4 mt-2", children: [items.map((it, idx) => (_jsxs("div", { className: "grid grid-cols-2 gap-3 p-3 rounded-lg border border-default-200", children: [_jsxs("div", { className: "col-span-2 font-medium text-default-600", children: [(attr?.label ||
|
|
429
|
+
spec.label ||
|
|
430
|
+
formatLabel(name)).replace(/s$/, ''), ' ', idx + 1] }), Object.entries(itemSchema).map(([k, a]) => (_jsx(Input, { variant: "bordered", classNames: inputClassNames, id: `${name}-${idx}-${k}`, name: `${name}.${idx}.${k}`, label: a?.label ?? formatLabel(k), labelPlacement: "outside-top", type: "text", value: typeof it?.[k] === 'string' ? it[k] : '', onChange: (e) => {
|
|
367
431
|
const next = items.slice();
|
|
368
432
|
next[idx] = {
|
|
369
433
|
...(next[idx] || {}),
|
|
@@ -384,7 +448,10 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
384
448
|
!Array.isArray(form[name])
|
|
385
449
|
? form[name]
|
|
386
450
|
: parseJsonGroupValue(form[name], 'single');
|
|
387
|
-
return (_jsxs("div", { className: "col-span-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [attr?.label || spec.label || formatLabel(name), attr?.required ? ' *' : ''] }), _jsx("div", { className: "grid grid-cols-2 gap-3 p-3 rounded-lg border border-default-200 mt-2", children: Object.entries(itemSchema).map(([k, a]) => (_jsx(
|
|
451
|
+
return (_jsxs("div", { className: "col-span-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [attr?.label || spec.label || formatLabel(name), attr?.required ? ' *' : ''] }), _jsx("div", { className: "grid grid-cols-2 gap-3 p-3 rounded-lg border border-default-200 mt-2", children: Object.entries(itemSchema).map(([k, a]) => a?.format === 'textarea' ? (_jsx(Textarea, { variant: "bordered", classNames: inputClassNames, id: `${name}-${k}`, name: `${name}.${k}`, label: a?.label ?? formatLabel(k), labelPlacement: "outside", value: typeof obj?.[k] === 'string' ? obj[k] : '', onChange: (e) => {
|
|
452
|
+
const next = { ...(obj || {}), [k]: e.target.value };
|
|
453
|
+
handleChange(name, next);
|
|
454
|
+
}, minRows: 3, maxRows: 20, isDisabled: busy, description: a?.description, className: "w-full col-span-2", isRequired: !!a?.required }, `${name}-${k}`)) : (_jsx(Input, { variant: "bordered", classNames: inputClassNames, id: `${name}-${k}`, name: `${name}.${k}`, label: a?.label ?? formatLabel(k), labelPlacement: "outside-top", type: "text", value: typeof obj?.[k] === 'string' ? obj[k] : '', onChange: (e) => {
|
|
388
455
|
const next = { ...(obj || {}), [k]: e.target.value };
|
|
389
456
|
handleChange(name, next);
|
|
390
457
|
}, isDisabled: busy, description: a?.description, className: "w-full", isRequired: !!a?.required }, `${name}-${k}`))) })] }, name));
|
|
@@ -416,7 +483,9 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
416
483
|
function SchemaField({ uid, name, attr, value, onChange, disabled, inputClassNames, selectClassNames, mapsKey, availableSchemas, formState, }) {
|
|
417
484
|
const id = `${uid}-${name}`;
|
|
418
485
|
const label = attr?.label ?? formatLabel(name);
|
|
419
|
-
const required = !!attr?.required
|
|
486
|
+
const required = !!attr?.required ||
|
|
487
|
+
name.toLowerCase() === 'slug' ||
|
|
488
|
+
!!attr?.populateSlugFrom;
|
|
420
489
|
const description = attr?.description;
|
|
421
490
|
// Prefer ref over enum. Only use enum when there is NO ref.
|
|
422
491
|
const enumVals = attr?.ref
|
|
@@ -713,3 +782,34 @@ function getPopulateTarget(attr) {
|
|
|
713
782
|
const t = head?.populate ?? attr?.populate;
|
|
714
783
|
return typeof t === 'string' && t.trim() ? t.trim() : null;
|
|
715
784
|
}
|
|
785
|
+
function slugify(text) {
|
|
786
|
+
return text
|
|
787
|
+
.toLowerCase()
|
|
788
|
+
.trim()
|
|
789
|
+
.replace(/[\s_]+/g, '-')
|
|
790
|
+
.replace(/[^\w\-]+/g, '')
|
|
791
|
+
.replace(/-+/g, '-')
|
|
792
|
+
.replace(/^-+|-+$/g, '');
|
|
793
|
+
}
|
|
794
|
+
async function findUniqueSlug(model, baseSlug, currentId) {
|
|
795
|
+
let attempt = 0;
|
|
796
|
+
while (true) {
|
|
797
|
+
const checkSlug = attempt === 0 ? baseSlug : `${baseSlug}-${attempt}`;
|
|
798
|
+
try {
|
|
799
|
+
// Check if slug exists in the model
|
|
800
|
+
const res = await api.list(model, 0, 1, { slug: checkSlug });
|
|
801
|
+
const existing = res.data?.[0];
|
|
802
|
+
if (!existing ||
|
|
803
|
+
(currentId && String(existing.id || existing._id) === String(currentId))) {
|
|
804
|
+
return checkSlug;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch (e) {
|
|
808
|
+
console.error('Slug check failed', e);
|
|
809
|
+
return checkSlug; // Fallback to current if API fails
|
|
810
|
+
}
|
|
811
|
+
attempt++;
|
|
812
|
+
if (attempt > 100)
|
|
813
|
+
return checkSlug; // Safety break
|
|
814
|
+
}
|
|
815
|
+
}
|
|
@@ -68,5 +68,5 @@ export const DistrictGridModal = ({ isOpen, onClose, onInsert, currentSpeciality
|
|
|
68
68
|
? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
|
|
69
69
|
: "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900"), children: "Doctors" }), _jsx("button", { onClick: () => setBaseType('hospitals'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", baseType === 'hospitals'
|
|
70
70
|
? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
|
|
71
|
-
: "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900"), children: "Hospitals" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(
|
|
71
|
+
: "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900"), children: "Hospitals" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(RefMultiSelect, { name: "districts", label: "Search & Select Districts", refModel: "Districts", value: selectedDistricts, onChange: (ids) => setSelectedDistricts(ids), pageSize: 1000 }), _jsx("p", { className: "text-xs text-gray-400", children: "Select one or more districts to create grid items." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(RefSingleSelect, { name: "speciality", label: "Search & Select Speciality", refModel: "Specialities", showKey: "name", value: selectedSpecialityId, onChange: (id) => setSelectedSpecialityId(id || ''), pageSize: 10000 }), _jsx("p", { className: "text-xs text-gray-400", children: "Leave empty to create URLs without speciality." }), specialitySlug && (_jsx("div", { className: "mt-1 px-2 py-1 bg-primary-50 dark:bg-primary-900/20 rounded border border-primary-200 dark:border-primary-800", children: _jsxs("p", { className: "text-xs text-primary-700 dark:text-primary-400", children: ["Slug: ", _jsx("code", { className: "font-mono", children: specialitySlug })] }) }))] }), selectedDistricts.length > 0 && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "URL Preview" }), _jsxs("div", { className: "px-3 py-2 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 max-h-32 overflow-y-auto", children: [_jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Example URL pattern (actual district slugs will be used):" }), _jsx("code", { className: "text-xs text-gray-700 dark:text-gray-300", children: getPreviewUrl('[district-slug]') })] })] }))] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "flat", onPress: onClose, children: "Cancel" }), _jsx(Button, { color: "primary", onPress: handleInsert, isDisabled: selectedDistricts.length === 0, children: "Insert Grid" })] })] }) }) }));
|
|
72
72
|
};
|
|
@@ -83,7 +83,7 @@ export const SchemaInsertionModal = ({ isOpen, onClose, onInsert, schema, availa
|
|
|
83
83
|
base: "bg-white dark:bg-zinc-950 border border-gray-200 dark:border-gray-800",
|
|
84
84
|
header: "border-b border-gray-200 dark:border-gray-800",
|
|
85
85
|
footer: "border-t border-gray-200 dark:border-gray-800",
|
|
86
|
-
}, children: _jsx(ModalContent, { children: _jsxs(_Fragment, { children: [_jsxs(ModalHeader, { children: ["Insert ", schema.modelName] }), _jsxs(ModalBody, { className: "py-6 flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(
|
|
86
|
+
}, children: _jsx(ModalContent, { children: _jsxs(_Fragment, { children: [_jsxs(ModalHeader, { children: ["Insert ", schema.modelName] }), _jsxs(ModalBody, { className: "py-6 flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(RefMultiSelect, { name: "items", label: "Search & Select", refModel: schema.modelName, value: selectedIds, onChange: (ids) => setSelectedIds(ids), pageSize: 10000 }), _jsx("p", { className: "text-xs text-gray-400", children: "Leave empty to fetch latest items automatically." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "View Type" }), _jsxs("div", { className: "flex gap-4", children: [_jsx("button", { onClick: () => setViewType('grid'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", viewType === 'grid'
|
|
87
87
|
? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
|
|
88
88
|
: "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50"), children: "Grid (Pills)" }), _jsx("button", { onClick: () => setViewType('list'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", viewType === 'list'
|
|
89
89
|
? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
|