@visns-studio/visns-components 5.15.11 → 5.15.13
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 +10 -10
- package/src/components/DataGrid.jsx +5 -5
- package/src/components/Field.jsx +58 -8
- package/src/components/Form.jsx +23 -6
- package/src/components/MultiCheckbox.jsx +335 -0
- package/src/components/MultiSelect.jsx +30 -1
- package/src/components/columns/ColumnRenderers.jsx +2 -38
- package/src/components/generic/GenericDetail.jsx +0 -10
- package/src/components/styles/MultiCheckbox.module.scss +321 -0
- package/src/index.js +2 -0
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.
|
|
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.
|
|
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.
|
|
35
|
+
"lucide-react": "^0.541.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.
|
|
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.
|
|
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.
|
|
79
|
-
"@babel/plugin-transform-runtime": "^7.28.
|
|
80
|
-
"@babel/preset-env": "^7.28.
|
|
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.
|
|
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.
|
|
97
|
+
"version": "5.15.13",
|
|
98
98
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
99
99
|
"main": "src/index.js",
|
|
100
100
|
"files": [
|
|
@@ -177,6 +177,11 @@ const loadData = async (
|
|
|
177
177
|
params.where = ajaxSetting.where;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// Add columns parameter if specified in ajaxSetting
|
|
181
|
+
if (ajaxSetting && ajaxSetting.columns) {
|
|
182
|
+
params.columns = ajaxSetting.columns;
|
|
183
|
+
}
|
|
184
|
+
|
|
180
185
|
if (filterValue) {
|
|
181
186
|
filterValue.forEach((fv) => {
|
|
182
187
|
const identifier = fv.whereHas?.column || fv.key || fv.name;
|
|
@@ -309,11 +314,6 @@ const DataGrid = forwardRef(
|
|
|
309
314
|
|
|
310
315
|
/** Modal Functions */
|
|
311
316
|
const modalOpen = (formType, formId) => {
|
|
312
|
-
console.log('🔍 modalOpen called:', {
|
|
313
|
-
formType: formType,
|
|
314
|
-
formId: formId,
|
|
315
|
-
timestamp: new Date().toISOString(),
|
|
316
|
-
});
|
|
317
317
|
setModalShow(true);
|
|
318
318
|
setFormType(formType);
|
|
319
319
|
setFormId(formId);
|
package/src/components/Field.jsx
CHANGED
|
@@ -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}
|
package/src/components/Form.jsx
CHANGED
|
@@ -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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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,
|