@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.
@@ -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
- const load = React.useCallback(async () => {
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 res = await api.list(refModelLC, 0, pageSize, {});
19
- setItems(Array.isArray(res.data) ? res.data : []);
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
- void load();
31
- }, [load]);
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) => setForm((f) => ({ ...f, [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: [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) => {
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(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) => {
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("label", { className: "text-sm font-medium", children: "Select Districts" }), _jsx(RefMultiSelect, { name: "districts", label: "Search & Select Districts", refModel: "Districts", value: selectedDistricts, onChange: (ids) => setSelectedDistricts(ids), pageSize: 64 }), _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("label", { className: "text-sm font-medium", children: "Select Speciality (Optional)" }), _jsx(RefSingleSelect, { name: "speciality", label: "Search & Select Speciality", refModel: "Specialities", showKey: "name", value: selectedSpecialityId, onChange: (id) => setSelectedSpecialityId(id || ''), pageSize: 50 }), _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" })] })] }) }) }));
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("label", { className: "text-sm font-medium", children: "Select Items" }), _jsx(RefMultiSelect, { name: "items", label: "Search & Select", refModel: schema.modelName, value: selectedIds, onChange: (ids) => setSelectedIds(ids), pageSize: 20 }), _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'
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"
@@ -6,6 +6,7 @@ export type AttributeBase = {
6
6
  ref?: string;
7
7
  longtext?: boolean;
8
8
  show?: string;
9
+ populateSlugFrom?: string;
9
10
  };
10
11
  export type ArrayAttribute = AttributeBase[];
11
12
  export type Attribute = AttributeBase | ArrayAttribute;