@visns-studio/visns-components 5.15.11 → 5.15.12

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/package.json CHANGED
@@ -13,10 +13,10 @@
13
13
  "@nivo/line": "^0.99.0",
14
14
  "@nivo/pie": "^0.99.0",
15
15
  "@react-pdf/renderer": "^4.3.0",
16
- "@tanstack/react-query": "^5.84.2",
16
+ "@tanstack/react-query": "^5.85.5",
17
17
  "@tinymce/miniature": "^6.0.0",
18
18
  "@tinymce/tinymce-react": "^6.3.0",
19
- "@uiw/react-color": "^2.7.3",
19
+ "@uiw/react-color": "^2.8.0",
20
20
  "@visns-studio/visns-datagrid-community": "1.1.0",
21
21
  "@visns-studio/visns-datagrid-enterprise": "1.1.0",
22
22
  "@vitejs/plugin-react": "^4.7.0",
@@ -32,7 +32,7 @@
32
32
  "html2canvas": "^1.4.1",
33
33
  "lodash": "^4.17.21",
34
34
  "lodash.debounce": "^4.0.8",
35
- "lucide-react": "^0.536.0",
35
+ "lucide-react": "^0.540.0",
36
36
  "mapbox-gl": "^3.14.0",
37
37
  "moment": "^2.30.1",
38
38
  "motion": "^12.23.12",
@@ -43,7 +43,7 @@
43
43
  "react-big-calendar": "^1.19.4",
44
44
  "react-copy-to-clipboard": "^5.1.0",
45
45
  "react-currency-input-field": "^3.10.0",
46
- "react-datepicker": "^8.4.0",
46
+ "react-datepicker": "^8.7.0",
47
47
  "react-dropzone": "^14.3.8",
48
48
  "react-grid-gallery": "^1.0.1",
49
49
  "react-number-format": "^5.4.4",
@@ -64,7 +64,7 @@
64
64
  "reactjs-popup": "^2.0.6",
65
65
  "style-loader": "^4.0.0",
66
66
  "swapy": "^1.0.5",
67
- "sweetalert2": "^11.22.3",
67
+ "sweetalert2": "^11.22.4",
68
68
  "tesseract.js": "^6.0.1",
69
69
  "truncate": "^3.0.0",
70
70
  "uuid": "^11.1.0",
@@ -75,12 +75,12 @@
75
75
  "yet-another-react-lightbox": "^3.25.0"
76
76
  },
77
77
  "devDependencies": {
78
- "@babel/core": "^7.28.0",
79
- "@babel/plugin-transform-runtime": "^7.28.0",
80
- "@babel/preset-env": "^7.28.0",
78
+ "@babel/core": "^7.28.3",
79
+ "@babel/plugin-transform-runtime": "^7.28.3",
80
+ "@babel/preset-env": "^7.28.3",
81
81
  "@babel/preset-react": "^7.27.1",
82
82
  "babel-loader": "^10.0.0",
83
- "copy-webpack-plugin": "^13.0.0",
83
+ "copy-webpack-plugin": "^13.0.1",
84
84
  "css-loader": "^7.1.2",
85
85
  "css-minimizer-webpack-plugin": "^7.0.2",
86
86
  "mini-css-extract-plugin": "^2.9.4",
@@ -94,7 +94,7 @@
94
94
  "react-dom": "^17.0.0 || ^18.0.0"
95
95
  },
96
96
  "name": "@visns-studio/visns-components",
97
- "version": "5.15.11",
97
+ "version": "5.15.12",
98
98
  "description": "Various packages to assist in the development of our Custom Applications.",
99
99
  "main": "src/index.js",
100
100
  "files": [
@@ -309,11 +309,6 @@ const DataGrid = forwardRef(
309
309
 
310
310
  /** Modal Functions */
311
311
  const modalOpen = (formType, formId) => {
312
- console.log('🔍 modalOpen called:', {
313
- formType: formType,
314
- formId: formId,
315
- timestamp: new Date().toISOString(),
316
- });
317
312
  setModalShow(true);
318
313
  setFormType(formType);
319
314
  setFormId(formId);
@@ -23,6 +23,7 @@ import {
23
23
  ArrowLeft,
24
24
  Trash2,
25
25
  X,
26
+ Lightbulb,
26
27
  } from 'lucide-react';
27
28
  import { toast } from 'react-toastify';
28
29
  import validator from 'validator';
@@ -38,6 +39,7 @@ import Tools from './sketch/tools';
38
39
  import VisnsAsyncSelect from './AsyncSelect';
39
40
  import VisnsAutocomplete from './Autocomplete';
40
41
  import VisnsDropZone from './DropZone';
42
+ import MultiCheckbox from './MultiCheckbox';
41
43
  import DatePickerPortal from './utils/DatePickerPortal';
42
44
 
43
45
  import 'react-toggle/style.css';
@@ -338,6 +340,7 @@ function Field({
338
340
  break;
339
341
  case 'dropdown-ajax':
340
342
  case 'multi-dropdown-ajax':
343
+ case 'multi-checkbox-ajax':
341
344
  case 'radio-ajax':
342
345
  // Only fetch if there are no dependent fields (no 'where' conditions)
343
346
  if (!settings.where || settings.where.length === 0) {
@@ -375,7 +378,7 @@ function Field({
375
378
 
376
379
  // Separate useEffect for handling dependent form data changes for dropdown refetch
377
380
  useEffect(() => {
378
- if (['dropdown-ajax', 'multi-dropdown-ajax', 'radio-ajax'].includes(settings.type) && settings.where?.length > 0) {
381
+ if (['dropdown-ajax', 'multi-dropdown-ajax', 'multi-checkbox-ajax', 'radio-ajax'].includes(settings.type) && settings.where?.length > 0) {
379
382
  let filter = {};
380
383
 
381
384
  const processedWhere = settings.where.map((whereItem) => {
@@ -567,6 +570,7 @@ function Field({
567
570
  'image',
568
571
  'multi-dropdown',
569
572
  'multi-dropdown-ajax',
573
+ 'multi-checkbox-ajax',
570
574
  'plaindate',
571
575
  'plaindatetime',
572
576
  'plainrelation',
@@ -708,6 +712,7 @@ function Field({
708
712
  options={options}
709
713
  dataOptions={dataOptions}
710
714
  isCreatable={settings.isCreatable || false}
715
+ creatableConfig={settings.creatableConfig || {}}
711
716
  />
712
717
  {settings.create && (
713
718
  <button
@@ -1655,7 +1660,7 @@ function Field({
1655
1660
  return (
1656
1661
  <div style={{ display: 'flex', justifyContent: 'center' }}>
1657
1662
  <Toggle
1658
- checked={cellValue || false}
1663
+ checked={Boolean(cellValue) || false}
1659
1664
  onChange={(e) =>
1660
1665
  onChangeJsonTable && onChangeJsonTable(e, rowIndex, column.id, 'toggle')
1661
1666
  }
@@ -1888,6 +1893,7 @@ function Field({
1888
1893
  }
1889
1894
  isSearchable={settings.isSearchable ?? true}
1890
1895
  isCreatable={settings.isCreatable ?? false}
1896
+ creatableConfig={settings.creatableConfig || {}}
1891
1897
  dataOptions={dataOptions}
1892
1898
  style={style}
1893
1899
  />
@@ -1922,6 +1928,35 @@ function Field({
1922
1928
  )}
1923
1929
  </div>
1924
1930
  );
1931
+ case 'multi-checkbox-ajax':
1932
+ return (
1933
+ <MultiCheckbox
1934
+ settings={settings}
1935
+ className={inputClass[settings.id]}
1936
+ inputValue={
1937
+ inputValue === null || inputValue === undefined
1938
+ ? []
1939
+ : inputValue
1940
+ }
1941
+ onChange={(newSelected, action, fieldId) => {
1942
+ // Call the original onChangeSelect handler
1943
+ if (onChangeSelect) {
1944
+ onChangeSelect(newSelected, action, fieldId);
1945
+ }
1946
+
1947
+ // If a new option was created, refresh the options list
1948
+ if (action && action.action === 'create-option') {
1949
+ setCounter(prev => prev + 1);
1950
+ }
1951
+ }}
1952
+ options={options}
1953
+ isCreatable={settings.isCreatable ?? false}
1954
+ isEditable={settings.isEditable ?? false}
1955
+ creatableConfig={settings.creatableConfig || {}}
1956
+ editableConfig={settings.editableConfig || {}}
1957
+ style={style}
1958
+ />
1959
+ );
1925
1960
  case 'percentage':
1926
1961
  return (
1927
1962
  <NumericFormat
@@ -2250,7 +2285,7 @@ function Field({
2250
2285
  ? false
2251
2286
  : cell[cellKey] === 'on'
2252
2287
  ? false
2253
- : cell[cellKey]
2288
+ : Boolean(cell[cellKey])
2254
2289
  : false
2255
2290
  }
2256
2291
  onChange={(e) => {
@@ -2448,7 +2483,7 @@ function Field({
2448
2483
  ? false
2449
2484
  : cell === 'on'
2450
2485
  ? false
2451
- : cell
2486
+ : Boolean(cell)
2452
2487
  : false
2453
2488
  }
2454
2489
  onChange={(e) => {
@@ -2644,6 +2679,7 @@ function Field({
2644
2679
  'line-break',
2645
2680
  'multi-dropdown',
2646
2681
  'multi-dropdown-ajax',
2682
+ 'multi-checkbox-ajax',
2647
2683
  'plaindate',
2648
2684
  'plaindatetime',
2649
2685
  'plainrelation',
@@ -2847,7 +2883,7 @@ function Field({
2847
2883
  ? false
2848
2884
  : inputValue === 'on'
2849
2885
  ? false
2850
- : inputValue
2886
+ : Boolean(inputValue)
2851
2887
  : false
2852
2888
  }
2853
2889
  onChange={onChangeToggle}
@@ -2906,7 +2942,7 @@ function Field({
2906
2942
  // Get options based on dropdown type
2907
2943
  if (['dropdown', 'multi-dropdown'].includes(settings.type) && settings.options) {
2908
2944
  existingOptions = settings.options;
2909
- } else if (['dropdown-ajax', 'multi-dropdown-ajax', 'async-dropdown-ajax'].includes(settings.type) && options) {
2945
+ } else if (['dropdown-ajax', 'multi-dropdown-ajax', 'multi-checkbox-ajax', 'async-dropdown-ajax'].includes(settings.type) && options) {
2910
2946
  existingOptions = options;
2911
2947
  }
2912
2948
 
@@ -2977,7 +3013,7 @@ function Field({
2977
3013
  [settings.id]: createdItem.id
2978
3014
  }));
2979
3015
  }
2980
- } else if (['multi-dropdown', 'multi-dropdown-ajax', 'async-dropdown-ajax'].includes(settings.type)) {
3016
+ } else if (['multi-dropdown', 'multi-dropdown-ajax', 'multi-checkbox-ajax', 'async-dropdown-ajax'].includes(settings.type)) {
2981
3017
  // For multi-select fields, we need to handle existing selections
2982
3018
  if (onChangeSelect) {
2983
3019
  // Get current value to determine if it's multi-select
@@ -3068,7 +3104,7 @@ function Field({
3068
3104
 
3069
3105
  useEffect(() => {
3070
3106
  if (counter > 0) {
3071
- if (settings.create.parent_id !== '') {
3107
+ if (settings.create && settings.create.parent_id !== '') {
3072
3108
  formSettings.fields.forEach((item) => {
3073
3109
  if (item.id === settings.create.parent_id) {
3074
3110
  fetchChildDropdownData(
@@ -3094,6 +3130,20 @@ function Field({
3094
3130
  {renderInput()}
3095
3131
  {renderFieldLabel()}
3096
3132
  </label>
3133
+ {(settings.type === 'multi-dropdown-ajax' || settings.type === 'multi-dropdown') && settings.isCreatable && (
3134
+ <div style={{
3135
+ fontSize: '0.75rem',
3136
+ color: '#6b7280',
3137
+ marginTop: '4px',
3138
+ fontStyle: 'italic',
3139
+ display: 'flex',
3140
+ alignItems: 'center',
3141
+ gap: '4px'
3142
+ }}>
3143
+ <Lightbulb size={12} />
3144
+ Tip: Type to create new entries and press Enter
3145
+ </div>
3146
+ )}
3097
3147
  {renderAdditionalContainer()}
3098
3148
  <StandardModal
3099
3149
  isOpen={childFormShow}
@@ -204,10 +204,11 @@ function Form({
204
204
  if (!_.isEmpty(_dataset)) {
205
205
  Object.keys(_dataset).forEach((c) => {
206
206
  if (formData.hasOwnProperty(c)) {
207
- setFormData((prevState) => ({
208
- ...prevState,
209
- [c]: _dataset[c],
210
- }));
207
+ if (c !== 'name' && c !== 'label')
208
+ setFormData((prevState) => ({
209
+ ...prevState,
210
+ [c]: _dataset[c],
211
+ }));
211
212
  }
212
213
  });
213
214
  }
@@ -407,6 +408,7 @@ function Form({
407
408
  switch (action.action) {
408
409
  case 'select-option':
409
410
  case 'create-option': // New case to handle create-option
411
+ case 'deselect-option': // Handle unchecking tags
410
412
  handleSelectOption();
411
413
  break;
412
414
  case 'clear':
@@ -1659,7 +1661,17 @@ function Form({
1659
1661
 
1660
1662
  const res = await CustomFetch(url, method, payload);
1661
1663
 
1664
+
1662
1665
  if (res.data.error === '') {
1666
+ // Update form data with fresh response data after successful submission
1667
+ if (res.data.data && typeof res.data.data === 'object') {
1668
+
1669
+ setFormData(prevFormData => ({
1670
+ ...prevFormData,
1671
+ ...res.data.data
1672
+ }));
1673
+ }
1674
+
1663
1675
  if (formSettings?.save?.pageReload) {
1664
1676
  setTimeout(() => {
1665
1677
  location.reload();
@@ -1895,7 +1907,12 @@ function Form({
1895
1907
  method = 'GET';
1896
1908
  }
1897
1909
 
1898
- const url = `${formSettings.url}${urlSuffix}`;
1910
+ // Add cache-busting parameter for GET requests to ensure fresh data
1911
+ let url = `${formSettings.url}${urlSuffix}`;
1912
+ if (method === 'GET' && formType === 'update') {
1913
+ url += `?_t=${Date.now()}`;
1914
+ }
1915
+
1899
1916
  const result = await CustomFetch(url, method, payload);
1900
1917
 
1901
1918
  const data = result.hasOwnProperty('data')
@@ -2141,7 +2158,7 @@ function Form({
2141
2158
 
2142
2159
  useEffect(() => {
2143
2160
  fetchData();
2144
- }, []);
2161
+ }, [columnId, formType]); // Re-fetch data whenever columnId or formType changes
2145
2162
 
2146
2163
  // Update dataRef when tableData changes
2147
2164
  useEffect(() => {
@@ -0,0 +1,335 @@
1
+ import React, { useEffect, useState, useRef } from 'react';
2
+ import { Plus, Edit3, X, Check } from 'lucide-react';
3
+ import CustomFetch from './Fetch';
4
+ import { toast } from 'react-toastify';
5
+ import styles from './styles/MultiCheckbox.module.scss';
6
+
7
+ function MultiCheckbox({
8
+ className,
9
+ inputValue = [],
10
+ onChange,
11
+ options = [],
12
+ placeholder,
13
+ settings,
14
+ style,
15
+ isCreatable = false,
16
+ isEditable = false,
17
+ creatableConfig = {},
18
+ editableConfig = {},
19
+ }) {
20
+ const [checkboxOptions, setCheckboxOptions] = useState([]);
21
+ const [selectedValues, setSelectedValues] = useState([]);
22
+ const [isCreating, setIsCreating] = useState(false);
23
+ const [isEditing, setIsEditing] = useState(null);
24
+ const [newTagName, setNewTagName] = useState('');
25
+ const [editTagName, setEditTagName] = useState('');
26
+ const prevInputValueRef = useRef();
27
+
28
+ // Transform options to checkbox format
29
+ useEffect(() => {
30
+ const _options = Array.isArray(options) && options.length > 0
31
+ ? options.map((item) => {
32
+ if (item && typeof item === 'object') {
33
+ return {
34
+ id: item.id || 'unknown-id',
35
+ label: item.label || item.name || item.description || 'Unknown',
36
+ value: item.id || 'unknown-id',
37
+ ...item,
38
+ };
39
+ }
40
+ return {
41
+ id: 'unknown-id',
42
+ label: 'Unknown',
43
+ value: 'unknown-id',
44
+ };
45
+ })
46
+ : [];
47
+ setCheckboxOptions(_options);
48
+ }, [options]);
49
+
50
+ // Transform inputValue to selected values
51
+ useEffect(() => {
52
+ if (JSON.stringify(prevInputValueRef.current) !== JSON.stringify(inputValue)) {
53
+ const _selected = Array.isArray(inputValue)
54
+ ? inputValue.map((item) => ({
55
+ id: item.id,
56
+ label: item.label || item.name || item.description || 'Unknown',
57
+ value: item.id,
58
+ ...item,
59
+ }))
60
+ : inputValue && typeof inputValue === 'object'
61
+ ? [{
62
+ id: inputValue.id,
63
+ label: inputValue.label || inputValue.name || inputValue.description || 'Unknown',
64
+ value: inputValue.id,
65
+ ...inputValue,
66
+ }]
67
+ : [];
68
+
69
+ setSelectedValues(_selected);
70
+ prevInputValueRef.current = inputValue;
71
+ }
72
+ }, [inputValue, checkboxOptions]);
73
+
74
+ // Use all options since search is removed
75
+ const filteredOptions = checkboxOptions;
76
+
77
+ const handleCheckboxChange = (option, isChecked) => {
78
+ let newSelected;
79
+
80
+ if (isChecked) {
81
+ // Add to selection
82
+ newSelected = [...selectedValues, option];
83
+ } else {
84
+ // Remove from selection
85
+ newSelected = selectedValues.filter(item => item.id !== option.id);
86
+ }
87
+
88
+ setSelectedValues(newSelected);
89
+
90
+ // Call parent onChange with the new selection
91
+ if (onChange) {
92
+ onChange(newSelected, { action: isChecked ? 'select-option' : 'deselect-option' }, settings.id);
93
+ }
94
+ };
95
+
96
+ const handleCreateTag = async () => {
97
+ if (!newTagName.trim() || !creatableConfig.url) return;
98
+
99
+ try {
100
+ const response = await CustomFetch(
101
+ creatableConfig.url,
102
+ creatableConfig.method || 'POST',
103
+ { label: newTagName.trim() }
104
+ );
105
+
106
+ if (response && response.data) {
107
+ const newOption = {
108
+ id: response.data.id,
109
+ label: response.data.label || newTagName.trim(),
110
+ value: response.data.id,
111
+ ...response.data,
112
+ };
113
+
114
+ // Add to options
115
+ setCheckboxOptions(prev => [...prev, newOption]);
116
+
117
+ // Auto-select the new tag
118
+ const newSelected = [...selectedValues, newOption];
119
+ setSelectedValues(newSelected);
120
+
121
+ if (onChange) {
122
+ onChange(newSelected, { action: 'create-option' }, settings.id);
123
+ }
124
+
125
+ setNewTagName('');
126
+ setIsCreating(false);
127
+ toast.success('Tag created successfully');
128
+ }
129
+ } catch (error) {
130
+ toast.error('Failed to create tag');
131
+ console.error('Error creating tag:', error);
132
+ }
133
+ };
134
+
135
+ const handleEditTag = async (tagId) => {
136
+ if (!editTagName.trim() || !editableConfig.url) return;
137
+
138
+ console.log('Editing tag:', { tagId, editableConfig, editTagName });
139
+
140
+ try {
141
+ const url = editableConfig.url.replace('{id}', tagId);
142
+ console.log('Edit URL:', url);
143
+
144
+ const response = await CustomFetch(
145
+ url,
146
+ editableConfig.method || 'PUT',
147
+ { label: editTagName.trim() }
148
+ );
149
+
150
+ if (response && response.data) {
151
+ // Update in options
152
+ setCheckboxOptions(prev =>
153
+ prev.map(opt =>
154
+ opt.id === tagId
155
+ ? { ...opt, label: response.data.label || editTagName.trim() }
156
+ : opt
157
+ )
158
+ );
159
+
160
+ // Update in selected values
161
+ setSelectedValues(prev =>
162
+ prev.map(sel =>
163
+ sel.id === tagId
164
+ ? { ...sel, label: response.data.label || editTagName.trim() }
165
+ : sel
166
+ )
167
+ );
168
+
169
+ setEditTagName('');
170
+ setIsEditing(null);
171
+ toast.success('Tag updated successfully');
172
+ }
173
+ } catch (error) {
174
+ toast.error('Failed to update tag');
175
+ console.error('Error updating tag:', error);
176
+ }
177
+ };
178
+
179
+ const isSelected = (optionId) => {
180
+ return selectedValues.some(selected => selected.id === optionId);
181
+ };
182
+
183
+ const maxColumns = settings.maxColumns || 3;
184
+
185
+ return (
186
+ <div className={`${styles.multiCheckbox} ${className || ''}`} style={style}>
187
+
188
+ {/* Create new tag section */}
189
+ {isCreatable && (
190
+ <div className={styles.createSection}>
191
+ {!isCreating ? (
192
+ <button
193
+ type="button"
194
+ onClick={() => setIsCreating(true)}
195
+ className={styles.createButton}
196
+ >
197
+ <Plus size={16} />
198
+ Create New Tag
199
+ </button>
200
+ ) : (
201
+ <div className={styles.createForm}>
202
+ <input
203
+ type="text"
204
+ placeholder="Enter tag name..."
205
+ value={newTagName}
206
+ onChange={(e) => setNewTagName(e.target.value)}
207
+ onKeyDown={(e) => {
208
+ if (e.key === 'Enter') {
209
+ e.preventDefault();
210
+ handleCreateTag();
211
+ } else if (e.key === 'Escape') {
212
+ e.preventDefault();
213
+ setIsCreating(false);
214
+ setNewTagName('');
215
+ }
216
+ }}
217
+ className={styles.createInput}
218
+ autoFocus
219
+ />
220
+ <button
221
+ type="button"
222
+ onClick={handleCreateTag}
223
+ className={styles.saveButton}
224
+ disabled={!newTagName.trim()}
225
+ title="Save tag"
226
+ >
227
+ <Check size={16} />
228
+ </button>
229
+ <button
230
+ type="button"
231
+ onClick={() => {
232
+ setIsCreating(false);
233
+ setNewTagName('');
234
+ }}
235
+ className={styles.cancelButton}
236
+ >
237
+ <X size={16} />
238
+ </button>
239
+ </div>
240
+ )}
241
+ </div>
242
+ )}
243
+
244
+ {/* Checkbox grid */}
245
+ <div
246
+ className={styles.checkboxGrid}
247
+ style={{
248
+ gridTemplateColumns: `repeat(${maxColumns}, 1fr)`
249
+ }}
250
+ >
251
+ {filteredOptions.map((option, index) => (
252
+ <div key={option.id || `option-${index}`} className={styles.checkboxItem}>
253
+ {isEditing === option.id ? (
254
+ <div className={styles.editForm}>
255
+ <input
256
+ type="text"
257
+ value={editTagName}
258
+ onChange={(e) => setEditTagName(e.target.value)}
259
+ onKeyPress={(e) => {
260
+ if (e.key === 'Enter') {
261
+ handleEditTag(option.id);
262
+ } else if (e.key === 'Escape') {
263
+ setIsEditing(null);
264
+ setEditTagName('');
265
+ }
266
+ }}
267
+ className={styles.editInput}
268
+ autoFocus
269
+ />
270
+ <button
271
+ type="button"
272
+ onClick={() => handleEditTag(option.id)}
273
+ className={styles.saveButton}
274
+ disabled={!editTagName.trim()}
275
+ title="Save changes"
276
+ >
277
+ <Check size={16} />
278
+ </button>
279
+ <button
280
+ type="button"
281
+ onClick={() => {
282
+ setIsEditing(null);
283
+ setEditTagName('');
284
+ }}
285
+ className={styles.cancelButton}
286
+ >
287
+ <X size={16} />
288
+ </button>
289
+ </div>
290
+ ) : (
291
+ <label className={styles.checkboxLabel}>
292
+ <input
293
+ type="checkbox"
294
+ checked={isSelected(option.id)}
295
+ onChange={(e) => handleCheckboxChange(option, e.target.checked)}
296
+ className={styles.checkbox}
297
+ />
298
+ <span className={styles.labelText}>{option.label}</span>
299
+ {isEditable && (
300
+ <button
301
+ type="button"
302
+ onClick={(e) => {
303
+ e.preventDefault();
304
+ console.log('Starting edit for option:', option);
305
+ setIsEditing(option.id);
306
+ setEditTagName(option.label);
307
+ }}
308
+ className={styles.editButton}
309
+ >
310
+ <Edit3 size={12} />
311
+ </button>
312
+ )}
313
+ </label>
314
+ )}
315
+ </div>
316
+ ))}
317
+ </div>
318
+
319
+ {/* No options message */}
320
+ {filteredOptions.length === 0 && (
321
+ <div className={styles.noOptions}>
322
+ No tags available
323
+ {isCreatable && (
324
+ <div style={{ marginTop: '8px', fontSize: '12px', color: '#64748b' }}>
325
+ Click "Create New Tag" above to add your first tag
326
+ </div>
327
+ )}
328
+ </div>
329
+ )}
330
+
331
+ </div>
332
+ );
333
+ }
334
+
335
+ export default MultiCheckbox;
@@ -112,6 +112,27 @@ const CustomMultiValueRemove = (props) => {
112
112
  );
113
113
  };
114
114
 
115
+ // Custom NoOptionsMessage component to guide users about creation capability
116
+ const CustomNoOptionsMessage = (props) => {
117
+ const { isCreatable, inputValue } = props.selectProps;
118
+
119
+ if (isCreatable && inputValue) {
120
+ return (
121
+ <components.NoOptionsMessage {...props}>
122
+ <div style={{ textAlign: 'center', padding: '8px' }}>
123
+ Press Enter to create "<strong>{inputValue}</strong>"
124
+ </div>
125
+ </components.NoOptionsMessage>
126
+ );
127
+ }
128
+
129
+ return (
130
+ <components.NoOptionsMessage {...props}>
131
+ No options found
132
+ </components.NoOptionsMessage>
133
+ );
134
+ };
135
+
115
136
  function MultiSelect({
116
137
  className,
117
138
  inputValue = [], // Default to an empty array
@@ -275,12 +296,14 @@ function MultiSelect({
275
296
  isClearable
276
297
  isSearchable={isSearchable}
277
298
  isMulti={multi}
299
+ isCreatable={isCreatable}
278
300
  filterOption={createFilter({ ignoreAccents: false })}
279
301
  components={{
280
302
  SelectList,
281
303
  Option: CustomOption,
282
304
  MultiValue: (props) => <CustomMultiValue {...props} settings={settings} />,
283
305
  MultiValueRemove: CustomMultiValueRemove,
306
+ NoOptionsMessage: CustomNoOptionsMessage,
284
307
  }}
285
308
  options={selectOptions}
286
309
  menuPortalTarget={document.body}
@@ -411,7 +434,13 @@ function MultiSelect({
411
434
  }}
412
435
  value={selectValue}
413
436
  onCreateOption={handleCreate}
414
- placeholder={placeholder ? placeholder : 'Select...'}
437
+ placeholder={
438
+ placeholder
439
+ ? placeholder
440
+ : isCreatable
441
+ ? 'Select or type to create new...'
442
+ : 'Select...'
443
+ }
415
444
  isOptionDisabled={
416
445
  settings.limit && multi && settings.limit > 1
417
446
  ? () => selectValue.length >= settings.limit
@@ -1255,25 +1255,7 @@ export const renderRelationColumn = ({
1255
1255
  );
1256
1256
 
1257
1257
  // Log sorting analysis in development
1258
- if (
1259
- intelligentSortingConfig.logAnalysis &&
1260
- process.env.NODE_ENV === 'development'
1261
- ) {
1262
- const relationshipPath = getRelationshipPath(column);
1263
- console.log(
1264
- `Relationship sorting analysis for "${relationshipPath}":`,
1265
- {
1266
- sortable: isSortable,
1267
- fieldName: column.nameFrom,
1268
- relationshipDepth: Array.isArray(column.id)
1269
- ? column.id.length
1270
- : 1,
1271
- explicitSortable: column.hasOwnProperty('sortable')
1272
- ? column.sortable
1273
- : 'not specified',
1274
- }
1275
- );
1276
- }
1258
+ // Debug logging removed for cleaner console output
1277
1259
 
1278
1260
  return {
1279
1261
  ...commonProps,
@@ -1386,25 +1368,7 @@ export const renderRelationArrayColumn = ({
1386
1368
  );
1387
1369
 
1388
1370
  // Log sorting analysis in development
1389
- if (
1390
- intelligentSortingConfig.logAnalysis &&
1391
- process.env.NODE_ENV === 'development'
1392
- ) {
1393
- const relationshipPath = getRelationshipPath(column);
1394
- console.log(
1395
- `Relationship array sorting analysis for "${relationshipPath}":`,
1396
- {
1397
- sortable: isSortable,
1398
- fieldName: column.nameFrom,
1399
- relationshipDepth: Array.isArray(column.id)
1400
- ? column.id.length
1401
- : 1,
1402
- explicitSortable: column.hasOwnProperty('sortable')
1403
- ? column.sortable
1404
- : 'not specified',
1405
- }
1406
- );
1407
- }
1371
+ // Debug logging removed for cleaner console output
1408
1372
 
1409
1373
  return {
1410
1374
  ...commonProps,
@@ -243,16 +243,6 @@ function GenericDetail({
243
243
  /** Fileupload states */
244
244
  const [files, setFiles] = useState([]);
245
245
 
246
- // Debug logging for files state changes
247
- useEffect(() => {
248
- console.log('GenericDetail: Files state changed:', {
249
- files,
250
- filesCount: files ? files.length : 0,
251
- filesType: typeof files,
252
- isArray: Array.isArray(files),
253
- firstFile: files && files.length > 0 ? files[0] : null,
254
- });
255
- }, [files]);
256
246
  const [loadingProgress, setLoadingProgress] = useState(0);
257
247
 
258
248
  /** General States */
@@ -0,0 +1,321 @@
1
+ .multiCheckbox {
2
+ width: 100%;
3
+ border: 1px solid #e2e8f0;
4
+ border-radius: 8px;
5
+ padding: 16px;
6
+ background: white;
7
+ min-height: 100px;
8
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
9
+
10
+ .searchContainer {
11
+ display: flex;
12
+ align-items: center;
13
+ margin-bottom: 12px;
14
+ position: relative;
15
+
16
+ .searchIcon {
17
+ position: absolute;
18
+ right: 12px;
19
+ top: 50%;
20
+ transform: translateY(-50%);
21
+ color: #6b7280;
22
+ z-index: 1;
23
+ pointer-events: none;
24
+ }
25
+
26
+ .searchInput {
27
+ width: 100%;
28
+ padding: 12px 45px 12px 16px;
29
+ border: 1px solid #d1d5db;
30
+ border-radius: 8px;
31
+ font-size: 14px;
32
+ background: #f8fafc;
33
+ transition: all 0.2s ease;
34
+
35
+ &:focus {
36
+ outline: none;
37
+ border-color: #3b82f6;
38
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
39
+ background: white;
40
+ }
41
+
42
+ &::placeholder {
43
+ color: #94a3b8;
44
+ }
45
+ }
46
+ }
47
+
48
+ .createSection {
49
+ margin-bottom: 12px;
50
+
51
+ .createButton {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 8px;
55
+ padding: 12px 16px;
56
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
57
+ border: 2px dashed #cbd5e1;
58
+ border-radius: 8px;
59
+ color: #475569;
60
+ cursor: pointer;
61
+ transition: all 0.2s ease;
62
+ font-size: 14px;
63
+ font-weight: 600;
64
+ width: 100%;
65
+ justify-content: center;
66
+
67
+ &:hover {
68
+ background: linear-gradient(135deg, #e2e8f0 0%, #cbd5e1 100%);
69
+ border-color: #3b82f6;
70
+ color: #3b82f6;
71
+ transform: translateY(-1px);
72
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
73
+ }
74
+ }
75
+
76
+ .createForm {
77
+ display: flex;
78
+ gap: 8px;
79
+ align-items: center;
80
+
81
+ .createInput {
82
+ flex: 1;
83
+ padding: 10px 12px;
84
+ border: 1px solid #d1d5db;
85
+ border-radius: 6px;
86
+ font-size: 14px;
87
+ transition: all 0.2s ease;
88
+
89
+ &:focus {
90
+ outline: none;
91
+ border-color: #3b82f6;
92
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ .checkboxGrid {
99
+ display: grid;
100
+ gap: 8px;
101
+ margin-bottom: 12px;
102
+
103
+ .checkboxItem {
104
+ .checkboxLabel {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 10px;
108
+ padding: 10px 12px;
109
+ border: 1px solid #e2e8f0;
110
+ border-radius: 6px;
111
+ cursor: pointer;
112
+ transition: all 0.2s ease;
113
+ position: relative;
114
+ background: #fafbfc;
115
+
116
+ &:hover {
117
+ background: #f1f5f9;
118
+ border-color: #3b82f6;
119
+ transform: translateY(-1px);
120
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
121
+
122
+ .editButton {
123
+ opacity: 1;
124
+ }
125
+ }
126
+
127
+ .checkbox {
128
+ margin: 0;
129
+ cursor: pointer;
130
+ width: 18px;
131
+ height: 18px;
132
+ accent-color: #3b82f6;
133
+
134
+ &:checked + .labelText {
135
+ font-weight: 600;
136
+ color: #1e40af;
137
+ }
138
+ }
139
+
140
+ .labelText {
141
+ flex: 1;
142
+ font-size: 14px;
143
+ color: #374151;
144
+ user-select: none;
145
+ line-height: 1.4;
146
+ font-weight: 500;
147
+ }
148
+
149
+ .editButton {
150
+ opacity: 0;
151
+ padding: 6px;
152
+ background: none;
153
+ border: none;
154
+ color: #6b7280;
155
+ cursor: pointer;
156
+ border-radius: 6px;
157
+ transition: all 0.2s ease;
158
+
159
+ &:hover {
160
+ background: #e2e8f0;
161
+ color: #374151;
162
+ transform: scale(1.1);
163
+ }
164
+ }
165
+ }
166
+
167
+ .editForm {
168
+ display: flex;
169
+ gap: 6px;
170
+ align-items: center;
171
+
172
+ .editInput {
173
+ flex: 1;
174
+ padding: 8px 12px;
175
+ border: 1px solid #d1d5db;
176
+ border-radius: 6px;
177
+ font-size: 14px;
178
+ transition: all 0.2s ease;
179
+
180
+ &:focus {
181
+ outline: none;
182
+ border-color: #3b82f6;
183
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ .saveButton {
191
+ padding: 8px;
192
+ background: #3b82f6;
193
+ color: white;
194
+ border: none;
195
+ border-radius: 6px;
196
+ cursor: pointer;
197
+ transition: all 0.2s ease;
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ min-width: 32px;
202
+ height: 32px;
203
+
204
+ &:hover:not(:disabled) {
205
+ background: #2563eb;
206
+ transform: scale(1.05);
207
+ }
208
+
209
+ &:disabled {
210
+ background: #9ca3af;
211
+ cursor: not-allowed;
212
+ opacity: 0.6;
213
+ }
214
+ }
215
+
216
+ .cancelButton {
217
+ padding: 8px;
218
+ background: #f1f5f9;
219
+ color: #64748b;
220
+ border: none;
221
+ border-radius: 6px;
222
+ cursor: pointer;
223
+ transition: all 0.2s ease;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+
228
+ &:hover {
229
+ background: #e2e8f0;
230
+ color: #475569;
231
+ }
232
+ }
233
+
234
+ .noOptions {
235
+ text-align: center;
236
+ padding: 24px 20px;
237
+ color: #64748b;
238
+ font-size: 14px;
239
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
240
+ border-radius: 8px;
241
+ border: 2px dashed #cbd5e1;
242
+ margin: 12px 0;
243
+ line-height: 1.5;
244
+
245
+ .createFromSearch {
246
+ display: block;
247
+ margin: 16px auto 0;
248
+ padding: 10px 20px;
249
+ background: #3b82f6;
250
+ color: white;
251
+ border: none;
252
+ border-radius: 6px;
253
+ font-size: 13px;
254
+ font-weight: 600;
255
+ cursor: pointer;
256
+ transition: all 0.2s ease;
257
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
258
+ max-width: fit-content;
259
+
260
+ &:hover {
261
+ background: #2563eb;
262
+ transform: translateY(-1px);
263
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
264
+ }
265
+ }
266
+ }
267
+
268
+ .selectedCount {
269
+ padding: 10px 16px;
270
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
271
+ border: 2px solid #3b82f6;
272
+ border-radius: 8px;
273
+ color: #1e40af;
274
+ font-size: 13px;
275
+ font-weight: 600;
276
+ text-align: center;
277
+ margin-top: 12px;
278
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
279
+ position: relative;
280
+
281
+ &::before {
282
+ content: "✓";
283
+ display: inline-block;
284
+ margin-right: 6px;
285
+ color: #3b82f6;
286
+ font-weight: bold;
287
+ }
288
+ }
289
+ }
290
+
291
+ // Responsive adjustments
292
+ @media (max-width: 768px) {
293
+ .multiCheckbox {
294
+ .checkboxGrid {
295
+ grid-template-columns: 1fr !important;
296
+ }
297
+
298
+ .createForm {
299
+ flex-direction: column;
300
+ align-items: stretch;
301
+
302
+ .createInput {
303
+ margin-bottom: 8px;
304
+ }
305
+
306
+ .saveButton, .cancelButton {
307
+ align-self: stretch;
308
+ justify-content: center;
309
+ }
310
+ }
311
+
312
+ .editForm {
313
+ flex-direction: column;
314
+ align-items: stretch;
315
+
316
+ .editInput {
317
+ margin-bottom: 6px;
318
+ }
319
+ }
320
+ }
321
+ }
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ import Form from './components/Form';
24
24
  import Loader from './components/Loader';
25
25
  import MergeEntity from './components/MergeEntity';
26
26
  import MultiSelect from './components/MultiSelect';
27
+ import MultiCheckbox from './components/MultiCheckbox';
27
28
  import Navigation from './components/Navigation';
28
29
  import Notification from './components/Notification';
29
30
  import SelectList from './components/SelectList';
@@ -140,6 +141,7 @@ export {
140
141
  Login,
141
142
  MergeEntity,
142
143
  MultiSelect,
144
+ MultiCheckbox,
143
145
  Navigation,
144
146
  Notification,
145
147
  NotificationList,