@visns-studio/visns-components 5.12.6 → 5.12.8
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 +11 -11
- package/src/components/CategorizedDropZone.jsx +286 -0
- package/src/components/Field.jsx +57 -41
- package/src/components/generic/GenericDetail.jsx +25 -0
- package/src/components/generic/StagePopupModal.jsx +14 -1
- package/src/components/styles/CategorizedDropZone.module.scss +217 -0
- package/src/components/styles/DropZone.module.css +1 -1
- package/src/components/styles/GenericDynamic.module.scss +1 -9
- package/src/components/styles/GenericFormBuilder.module.scss +1 -9
- package/src/components/styles/global.css +204 -47
- package/src/components/utils/DatePickerPortal.jsx +127 -0
- package/src/index.js +4 -0
package/package.json
CHANGED
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"dayjs": "^1.11.13",
|
|
25
25
|
"fabric": "^6.7.0",
|
|
26
26
|
"file-saver": "^2.0.5",
|
|
27
|
-
"framer-motion": "^12.
|
|
27
|
+
"framer-motion": "^12.23.0",
|
|
28
28
|
"html-react-parser": "^5.2.5",
|
|
29
29
|
"lodash": "^4.17.21",
|
|
30
30
|
"lodash.debounce": "^4.0.8",
|
|
31
|
-
"lucide-react": "^0.
|
|
31
|
+
"lucide-react": "^0.525.0",
|
|
32
32
|
"moment": "^2.30.1",
|
|
33
|
-
"motion": "^12.
|
|
33
|
+
"motion": "^12.23.0",
|
|
34
34
|
"numeral": "^2.0.6",
|
|
35
35
|
"pluralize": "^8.0.0",
|
|
36
36
|
"qrcode.react": "^4.2.0",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
"react-reveal": "^1.2.2",
|
|
48
48
|
"react-router-dom": "^6.30.1",
|
|
49
49
|
"react-select": "^5.10.1",
|
|
50
|
-
"react-signature-pad-wrapper": "^4.1.
|
|
50
|
+
"react-signature-pad-wrapper": "^4.1.1",
|
|
51
51
|
"react-slugify": "^4.0.1",
|
|
52
52
|
"react-sortable-hoc": "^2.0.0",
|
|
53
|
-
"react-to-print": "^3.1.
|
|
53
|
+
"react-to-print": "^3.1.1",
|
|
54
54
|
"react-toastify": "^11.0.5",
|
|
55
55
|
"react-toggle": "^4.1.3",
|
|
56
56
|
"react-tooltip": "^5.29.1",
|
|
@@ -58,19 +58,19 @@
|
|
|
58
58
|
"reactjs-popup": "^2.0.6",
|
|
59
59
|
"style-loader": "^4.0.0",
|
|
60
60
|
"swapy": "^1.0.5",
|
|
61
|
-
"sweetalert2": "^11.22.
|
|
61
|
+
"sweetalert2": "^11.22.2",
|
|
62
62
|
"tesseract.js": "^6.0.1",
|
|
63
63
|
"truncate": "^3.0.0",
|
|
64
64
|
"uuid": "^11.1.0",
|
|
65
65
|
"validator": "^13.15.15",
|
|
66
66
|
"vite": "^6.3.5",
|
|
67
67
|
"yarn": "^1.22.22",
|
|
68
|
-
"yet-another-react-lightbox": "^3.
|
|
68
|
+
"yet-another-react-lightbox": "^3.24.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@babel/core": "^7.
|
|
72
|
-
"@babel/plugin-transform-runtime": "^7.
|
|
73
|
-
"@babel/preset-env": "^7.
|
|
71
|
+
"@babel/core": "^7.28.0",
|
|
72
|
+
"@babel/plugin-transform-runtime": "^7.28.0",
|
|
73
|
+
"@babel/preset-env": "^7.28.0",
|
|
74
74
|
"@babel/preset-react": "^7.27.1",
|
|
75
75
|
"babel-loader": "^10.0.0",
|
|
76
76
|
"copy-webpack-plugin": "^13.0.0",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
88
88
|
},
|
|
89
89
|
"name": "@visns-studio/visns-components",
|
|
90
|
-
"version": "5.12.
|
|
90
|
+
"version": "5.12.8",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import DropZone from './DropZone';
|
|
3
|
+
import CustomFetch from './Fetch';
|
|
4
|
+
import styles from './styles/CategorizedDropZone.module.scss';
|
|
5
|
+
|
|
6
|
+
const CategorizedDropZone = ({
|
|
7
|
+
filetype = 'image',
|
|
8
|
+
folder = 'uploads',
|
|
9
|
+
dataKey = 'files',
|
|
10
|
+
file_relationship = 'files',
|
|
11
|
+
url = '/ajax/files',
|
|
12
|
+
method = 'POST',
|
|
13
|
+
deleteUrl = '/ajax/files/delete',
|
|
14
|
+
categories = [],
|
|
15
|
+
categoriesUrl = null,
|
|
16
|
+
categoriesWhere = [],
|
|
17
|
+
entityData = {},
|
|
18
|
+
routeParams = {},
|
|
19
|
+
fetchData = null
|
|
20
|
+
}) => {
|
|
21
|
+
const [activeCategory, setActiveCategory] = useState(null);
|
|
22
|
+
const [categorizedFiles, setCategorizedFiles] = useState({});
|
|
23
|
+
const [dynamicCategories, setDynamicCategories] = useState([]);
|
|
24
|
+
const [loadingCategories, setLoadingCategories] = useState(false);
|
|
25
|
+
|
|
26
|
+
// Get final categories (dynamic or static)
|
|
27
|
+
const finalCategories = dynamicCategories.length > 0 ? dynamicCategories : categories;
|
|
28
|
+
|
|
29
|
+
// Fetch categories from API if categoriesUrl is provided
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (categoriesUrl) {
|
|
32
|
+
setLoadingCategories(true);
|
|
33
|
+
|
|
34
|
+
const requestData = {};
|
|
35
|
+
if (categoriesWhere && categoriesWhere.length > 0) {
|
|
36
|
+
requestData.where = categoriesWhere;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
CustomFetch(categoriesUrl, 'POST', requestData)
|
|
40
|
+
.then(response => {
|
|
41
|
+
if (response.data && !response.data.error) {
|
|
42
|
+
const categoriesData = Array.isArray(response.data.data) ? response.data.data :
|
|
43
|
+
Array.isArray(response.data) ? response.data : [];
|
|
44
|
+
|
|
45
|
+
// Transform API data to match expected format
|
|
46
|
+
const transformedCategories = categoriesData.map(cat => ({
|
|
47
|
+
id: cat.slug || cat.id,
|
|
48
|
+
label: cat.name || cat.label,
|
|
49
|
+
description: cat.description || ''
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
setDynamicCategories(transformedCategories);
|
|
53
|
+
|
|
54
|
+
// Set first category as active if none selected
|
|
55
|
+
if (!activeCategory && transformedCategories.length > 0) {
|
|
56
|
+
setActiveCategory(transformedCategories[0].id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.catch(error => {
|
|
61
|
+
console.error('Failed to load categories:', error);
|
|
62
|
+
})
|
|
63
|
+
.finally(() => {
|
|
64
|
+
setLoadingCategories(false);
|
|
65
|
+
});
|
|
66
|
+
} else if (categories.length > 0 && !activeCategory) {
|
|
67
|
+
// Set first static category as active
|
|
68
|
+
setActiveCategory(categories[0].id);
|
|
69
|
+
}
|
|
70
|
+
}, [categoriesUrl, categoriesWhere, categories, activeCategory]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
console.log('CategorizedDropZone: Initializing files', {
|
|
74
|
+
finalCategories,
|
|
75
|
+
entityData: entityData[dataKey],
|
|
76
|
+
dataKey
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Initialize categorized files structure
|
|
80
|
+
const initialCategorizedFiles = {};
|
|
81
|
+
finalCategories.forEach(category => {
|
|
82
|
+
initialCategorizedFiles[category.id] = [];
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// If there are existing files, categorize them based on file_description
|
|
86
|
+
if (entityData[dataKey] && Array.isArray(entityData[dataKey])) {
|
|
87
|
+
console.log('CategorizedDropZone: Processing files', entityData[dataKey]);
|
|
88
|
+
|
|
89
|
+
entityData[dataKey].forEach(file => {
|
|
90
|
+
console.log('CategorizedDropZone: Processing file', {
|
|
91
|
+
file_name: file.file_name,
|
|
92
|
+
file_description: file.file_description,
|
|
93
|
+
id: file.id
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const category = getCategoryFromDescription(file.file_description);
|
|
97
|
+
console.log('CategorizedDropZone: Category extracted', category);
|
|
98
|
+
|
|
99
|
+
if (category && initialCategorizedFiles[category]) {
|
|
100
|
+
initialCategorizedFiles[category].push(file);
|
|
101
|
+
console.log(`CategorizedDropZone: Added file ${file.file_name} to category ${category}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log(`CategorizedDropZone: No category match for file ${file.file_name}`, {
|
|
104
|
+
extractedCategory: category,
|
|
105
|
+
availableCategories: Object.keys(initialCategorizedFiles)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log('CategorizedDropZone: Final categorized files', initialCategorizedFiles);
|
|
112
|
+
setCategorizedFiles(initialCategorizedFiles);
|
|
113
|
+
}, [entityData, dataKey, finalCategories]);
|
|
114
|
+
|
|
115
|
+
const getCategoryFromDescription = (description) => {
|
|
116
|
+
if (!description) return null;
|
|
117
|
+
|
|
118
|
+
// Check if the description contains a category identifier (handles both slugs and numeric IDs)
|
|
119
|
+
const categoryMatch = description.match(/^category:([^-\s]+)/i);
|
|
120
|
+
if (categoryMatch) {
|
|
121
|
+
const extractedId = categoryMatch[1];
|
|
122
|
+
|
|
123
|
+
// First, try to find by exact ID match
|
|
124
|
+
const exactMatch = finalCategories.find(cat => cat.id === extractedId);
|
|
125
|
+
if (exactMatch) return extractedId;
|
|
126
|
+
|
|
127
|
+
// If no exact match, try to find by numeric ID if extracted ID is numeric
|
|
128
|
+
if (/^\d+$/.test(extractedId)) {
|
|
129
|
+
const numericMatch = finalCategories.find(cat => cat.id == extractedId);
|
|
130
|
+
if (numericMatch) return numericMatch.id;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Log for debugging
|
|
134
|
+
console.log('Category extraction debug:', {
|
|
135
|
+
description,
|
|
136
|
+
extractedId,
|
|
137
|
+
availableCategories: finalCategories.map(c => ({ id: c.id, label: c.label }))
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fallback: check if description matches any category label
|
|
142
|
+
const matchedCategory = finalCategories.find(cat =>
|
|
143
|
+
description.toLowerCase().includes(cat.label.toLowerCase())
|
|
144
|
+
);
|
|
145
|
+
return matchedCategory?.id || null;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleCategoryChange = (categoryId) => {
|
|
149
|
+
setActiveCategory(categoryId);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const getCurrentCategoryFiles = () => {
|
|
153
|
+
return categorizedFiles[activeCategory] || [];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const handleFileUpload = (files) => {
|
|
157
|
+
// Add category prefix to file descriptions
|
|
158
|
+
const activeCategoryData = finalCategories.find(cat => cat.id === activeCategory);
|
|
159
|
+
const categoryPrefix = `category:${activeCategory}`;
|
|
160
|
+
|
|
161
|
+
// Update the files with category information
|
|
162
|
+
const updatedFiles = files.map(file => ({
|
|
163
|
+
...file,
|
|
164
|
+
file_description: file.file_description ?
|
|
165
|
+
`${categoryPrefix} - ${file.file_description}` :
|
|
166
|
+
`${categoryPrefix} - ${activeCategoryData?.description || activeCategoryData?.label}`
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
// Update the categorized files state
|
|
170
|
+
setCategorizedFiles(prev => ({
|
|
171
|
+
...prev,
|
|
172
|
+
[activeCategory]: [...(prev[activeCategory] || []), ...updatedFiles]
|
|
173
|
+
}));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleFileDelete = (fileId) => {
|
|
177
|
+
// Remove file from the appropriate category
|
|
178
|
+
setCategorizedFiles(prev => {
|
|
179
|
+
const updated = { ...prev };
|
|
180
|
+
Object.keys(updated).forEach(categoryId => {
|
|
181
|
+
updated[categoryId] = updated[categoryId].filter(file => file.id !== fileId);
|
|
182
|
+
});
|
|
183
|
+
return updated;
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleFileUpdate = (updatedFile) => {
|
|
188
|
+
// Update file in the appropriate category
|
|
189
|
+
const newCategory = getCategoryFromDescription(updatedFile.file_description);
|
|
190
|
+
|
|
191
|
+
setCategorizedFiles(prev => {
|
|
192
|
+
const updated = { ...prev };
|
|
193
|
+
|
|
194
|
+
// Remove from old category
|
|
195
|
+
Object.keys(updated).forEach(categoryId => {
|
|
196
|
+
updated[categoryId] = updated[categoryId].filter(file => file.id !== updatedFile.id);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Add to new category
|
|
200
|
+
if (newCategory && updated[newCategory]) {
|
|
201
|
+
updated[newCategory].push(updatedFile);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return updated;
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Prepare entity data for the current category
|
|
209
|
+
const currentCategoryEntityData = {
|
|
210
|
+
...entityData,
|
|
211
|
+
[dataKey]: getCurrentCategoryFiles()
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (loadingCategories) {
|
|
215
|
+
return (
|
|
216
|
+
<div className={styles.categorizedDropZone}>
|
|
217
|
+
<div className={styles.loadingState}>
|
|
218
|
+
Loading categories...
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (finalCategories.length === 0) {
|
|
225
|
+
return (
|
|
226
|
+
<div className={styles.categorizedDropZone}>
|
|
227
|
+
<div className={styles.emptyState}>
|
|
228
|
+
No categories available. Please configure site photo categories in settings.
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className={styles.categorizedDropZone}>
|
|
236
|
+
<div className={styles.categoryTabs}>
|
|
237
|
+
{finalCategories.map(category => (
|
|
238
|
+
<button
|
|
239
|
+
key={category.id}
|
|
240
|
+
className={`${styles.categoryTab} ${activeCategory === category.id ? styles.active : ''}`}
|
|
241
|
+
onClick={() => handleCategoryChange(category.id)}
|
|
242
|
+
>
|
|
243
|
+
{category.label}
|
|
244
|
+
{categorizedFiles[category.id] && categorizedFiles[category.id].length > 0 && (
|
|
245
|
+
<span className={styles.fileCount}>
|
|
246
|
+
({categorizedFiles[category.id].length})
|
|
247
|
+
</span>
|
|
248
|
+
)}
|
|
249
|
+
</button>
|
|
250
|
+
))}
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div className={styles.categoryContent}>
|
|
254
|
+
{activeCategory && (
|
|
255
|
+
<div className={styles.categoryPanel}>
|
|
256
|
+
{finalCategories.find(cat => cat.id === activeCategory)?.description && (
|
|
257
|
+
<p className={styles.categoryDescription}>
|
|
258
|
+
{finalCategories.find(cat => cat.id === activeCategory)?.description}
|
|
259
|
+
</p>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
<DropZone
|
|
263
|
+
fetchData={fetchData || (() => {
|
|
264
|
+
// Default fetch function - refresh current category files
|
|
265
|
+
console.log('Files updated for category:', activeCategory);
|
|
266
|
+
})}
|
|
267
|
+
files={getCurrentCategoryFiles()}
|
|
268
|
+
settings={{
|
|
269
|
+
url: url,
|
|
270
|
+
method: method,
|
|
271
|
+
type: 'gallery',
|
|
272
|
+
data: {
|
|
273
|
+
file_relationship: file_relationship,
|
|
274
|
+
file_description: `category:${activeCategory} - ${finalCategories.find(cat => cat.id === activeCategory)?.label || activeCategory}`,
|
|
275
|
+
},
|
|
276
|
+
model: entityData?.constructor?.name || 'Design'
|
|
277
|
+
}}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export default CategorizedDropZone;
|
package/src/components/Field.jsx
CHANGED
|
@@ -37,6 +37,7 @@ import Tools from './sketch/tools';
|
|
|
37
37
|
import VisnsAsyncSelect from './AsyncSelect';
|
|
38
38
|
import VisnsAutocomplete from './Autocomplete';
|
|
39
39
|
import VisnsDropZone from './DropZone';
|
|
40
|
+
import DatePickerPortal from './utils/DatePickerPortal';
|
|
40
41
|
|
|
41
42
|
import 'react-toggle/style.css';
|
|
42
43
|
import 'react-datepicker/dist/react-datepicker.css';
|
|
@@ -72,6 +73,7 @@ function Field({
|
|
|
72
73
|
}) {
|
|
73
74
|
const editorRef = useRef(null);
|
|
74
75
|
const fileImageInputRef = useRef(null);
|
|
76
|
+
const containerRef = useRef(null);
|
|
75
77
|
const [autoValue, setAutoValue] = useState('');
|
|
76
78
|
const [colorPickerShow, setColorPickerShow] = useState(false);
|
|
77
79
|
const [counter, setCounter] = useState(0);
|
|
@@ -2129,31 +2131,38 @@ function Field({
|
|
|
2129
2131
|
case 'date':
|
|
2130
2132
|
case 'datetime':
|
|
2131
2133
|
return (
|
|
2132
|
-
<
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
settings.type === '
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2134
|
+
<DatePickerPortal
|
|
2135
|
+
containerRef={containerRef}
|
|
2136
|
+
centerScreen={settings.centerScreen}
|
|
2137
|
+
modalContext={settings.modalContext}
|
|
2138
|
+
usePortal={settings.usePortal}
|
|
2139
|
+
>
|
|
2140
|
+
<DatePicker
|
|
2141
|
+
dateFormat={
|
|
2142
|
+
settings.format
|
|
2143
|
+
? settings.format
|
|
2144
|
+
: settings.type === 'datetime'
|
|
2145
|
+
? 'dd-MM-yyyy hh:mm aa'
|
|
2146
|
+
: 'dd-MM-yyyy'
|
|
2147
|
+
}
|
|
2148
|
+
isClearable
|
|
2149
|
+
className={inputClass[settings.id]}
|
|
2150
|
+
selected={inputValue || ''}
|
|
2151
|
+
showMonthDropdown
|
|
2152
|
+
showYearDropdown
|
|
2153
|
+
scrollableYearDropdown
|
|
2154
|
+
onChange={(date) => onChangeDate(date, settings.id)}
|
|
2155
|
+
showTimeSelect={settings.type === 'datetime'}
|
|
2156
|
+
showTimeSelectOnly={settings.type === 'time'}
|
|
2157
|
+
timeIntervals={
|
|
2158
|
+
settings.type === 'time' ? 15 : undefined
|
|
2159
|
+
}
|
|
2160
|
+
timeCaption={
|
|
2161
|
+
settings.type === 'time' ? 'Time' : undefined
|
|
2162
|
+
}
|
|
2163
|
+
shouldCloseOnSelect={true}
|
|
2164
|
+
/>
|
|
2165
|
+
</DatePickerPortal>
|
|
2157
2166
|
);
|
|
2158
2167
|
case 'plaintext':
|
|
2159
2168
|
if (settings.description) {
|
|
@@ -2254,21 +2263,28 @@ function Field({
|
|
|
2254
2263
|
);
|
|
2255
2264
|
case 'time':
|
|
2256
2265
|
return (
|
|
2257
|
-
<
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2266
|
+
<DatePickerPortal
|
|
2267
|
+
containerRef={containerRef}
|
|
2268
|
+
centerScreen={settings.centerScreen}
|
|
2269
|
+
modalContext={settings.modalContext}
|
|
2270
|
+
usePortal={settings.usePortal}
|
|
2271
|
+
>
|
|
2272
|
+
<DatePicker
|
|
2273
|
+
dateFormat="hh:mm aa"
|
|
2274
|
+
isClearable
|
|
2275
|
+
className={inputClass[settings.id]}
|
|
2276
|
+
selected={inputValue || ''}
|
|
2277
|
+
showMonthDropdown
|
|
2278
|
+
showYearDropdown
|
|
2279
|
+
scrollableYearDropdown
|
|
2280
|
+
onChange={(date) => onChangeDate(date, settings.id)}
|
|
2281
|
+
showTimeSelect={true}
|
|
2282
|
+
showTimeSelectOnly={true}
|
|
2283
|
+
timeIntervals={15}
|
|
2284
|
+
timeCaption={'Time'}
|
|
2285
|
+
shouldCloseOnSelect={true}
|
|
2286
|
+
/>
|
|
2287
|
+
</DatePickerPortal>
|
|
2272
2288
|
);
|
|
2273
2289
|
case 'toggle':
|
|
2274
2290
|
return (
|
|
@@ -2522,7 +2538,7 @@ function Field({
|
|
|
2522
2538
|
}, [counter]);
|
|
2523
2539
|
|
|
2524
2540
|
return (
|
|
2525
|
-
<div className={formItemClass} style={formItemStyle}>
|
|
2541
|
+
<div className={formItemClass} style={formItemStyle} ref={containerRef}>
|
|
2526
2542
|
<label
|
|
2527
2543
|
className={
|
|
2528
2544
|
['richeditor', 'textarea'].includes(settings.type)
|
|
@@ -80,6 +80,7 @@ import QrCode from '../QrCode';
|
|
|
80
80
|
import StagePopupModal from './StagePopupModal';
|
|
81
81
|
import Table from '../DataGrid';
|
|
82
82
|
import TableFilter from '../TableFilter';
|
|
83
|
+
import CategorizedDropZone from '../CategorizedDropZone';
|
|
83
84
|
|
|
84
85
|
// Proposal components
|
|
85
86
|
import ProposalTemplateSectionManager from '../proposal/ProposalTemplateSectionManager';
|
|
@@ -2748,6 +2749,30 @@ function GenericDetail({
|
|
|
2748
2749
|
} else {
|
|
2749
2750
|
return null;
|
|
2750
2751
|
}
|
|
2752
|
+
case 'categorized_dropzone':
|
|
2753
|
+
// Replace URL parameters like {dataId} with actual values
|
|
2754
|
+
const replaceUrlParams = (url) => {
|
|
2755
|
+
if (!url) return url;
|
|
2756
|
+
return url.replace(/{dataId}/g, routeParams[urlParam] || '');
|
|
2757
|
+
};
|
|
2758
|
+
|
|
2759
|
+
return (
|
|
2760
|
+
<CategorizedDropZone
|
|
2761
|
+
filetype={activeTabConfig.filetype}
|
|
2762
|
+
folder={activeTabConfig.folder}
|
|
2763
|
+
dataKey={activeTabConfig.key}
|
|
2764
|
+
file_relationship={activeTabConfig.file_relationship}
|
|
2765
|
+
url={replaceUrlParams(activeTabConfig.url)}
|
|
2766
|
+
method={activeTabConfig.method}
|
|
2767
|
+
deleteUrl={replaceUrlParams(activeTabConfig.deleteUrl)}
|
|
2768
|
+
categories={activeTabConfig.categories || []}
|
|
2769
|
+
categoriesUrl={activeTabConfig.categoriesUrl}
|
|
2770
|
+
categoriesWhere={activeTabConfig.categoriesWhere}
|
|
2771
|
+
entityData={data}
|
|
2772
|
+
routeParams={routeParams}
|
|
2773
|
+
fetchData={handleReload}
|
|
2774
|
+
/>
|
|
2775
|
+
);
|
|
2751
2776
|
case 'scheduling':
|
|
2752
2777
|
return (
|
|
2753
2778
|
<GenericEditableTable
|
|
@@ -56,7 +56,20 @@ const StagePopupModal = ({
|
|
|
56
56
|
url = url.replace(`{${key}}`, routeParams[key]);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// Prepare request data with where and fields parameters
|
|
60
|
+
const requestData = {};
|
|
61
|
+
|
|
62
|
+
// Add where parameter if available
|
|
63
|
+
if (field.where) {
|
|
64
|
+
requestData.where = field.where;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add fields parameter if available
|
|
68
|
+
if (field.fields) {
|
|
69
|
+
requestData.fields = field.fields;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const response = await CustomFetch(url, 'POST', requestData);
|
|
60
73
|
|
|
61
74
|
if (response.data && !response.data.error) {
|
|
62
75
|
// Handle both nested (response.data.data) and direct (response.data) array formats
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// CategorizedDropZone.module.scss
|
|
2
|
+
.categorizedDropZone {
|
|
3
|
+
width: 100%;
|
|
4
|
+
margin-bottom: 20px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.categoryTabs {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
gap: 4px;
|
|
11
|
+
margin-bottom: 24px;
|
|
12
|
+
padding: 6px;
|
|
13
|
+
background-color: #f8f9fa;
|
|
14
|
+
border-radius: 8px;
|
|
15
|
+
border: 1px solid #e9ecef;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.categoryTab {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 8px;
|
|
22
|
+
padding: 10px 16px;
|
|
23
|
+
background-color: #ffffff;
|
|
24
|
+
border: 1px solid #dee2e6;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
font-weight: 500;
|
|
29
|
+
color: #6c757d;
|
|
30
|
+
transition: all 0.2s ease;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
outline: none;
|
|
33
|
+
|
|
34
|
+
&:hover {
|
|
35
|
+
color: #495057;
|
|
36
|
+
background-color: #f8f9fa;
|
|
37
|
+
border-color: #adb5bd;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&.active {
|
|
41
|
+
color: #ffffff;
|
|
42
|
+
background-color: #3498db;
|
|
43
|
+
border-color: #3498db;
|
|
44
|
+
|
|
45
|
+
.fileCount {
|
|
46
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
47
|
+
color: white;
|
|
48
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:focus {
|
|
53
|
+
outline: 2px solid #3498db;
|
|
54
|
+
outline-offset: 2px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.fileCount {
|
|
59
|
+
background-color: #e9ecef;
|
|
60
|
+
color: #495057;
|
|
61
|
+
padding: 3px 7px;
|
|
62
|
+
border-radius: 10px;
|
|
63
|
+
font-size: 11px;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
min-width: 20px;
|
|
66
|
+
text-align: center;
|
|
67
|
+
border: 1px solid #ced4da;
|
|
68
|
+
transition: all 0.2s ease;
|
|
69
|
+
line-height: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.categoryContent {
|
|
73
|
+
min-height: 300px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.categoryPanel {
|
|
77
|
+
width: 100%;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.categoryTitle {
|
|
81
|
+
font-size: 20px;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
color: #2c3e50;
|
|
84
|
+
margin: 0 20px 8px 20px;
|
|
85
|
+
padding-bottom: 8px;
|
|
86
|
+
border-bottom: 2px solid #3498db;
|
|
87
|
+
display: block;
|
|
88
|
+
width: calc(100% - 40px);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.categoryDescription {
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
color: #6c757d;
|
|
94
|
+
margin: 0 0 24px 0;
|
|
95
|
+
padding: 0 20px;
|
|
96
|
+
font-style: italic;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.loadingState,
|
|
100
|
+
.emptyState {
|
|
101
|
+
text-align: center;
|
|
102
|
+
padding: 40px 20px;
|
|
103
|
+
color: #666;
|
|
104
|
+
font-size: 16px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.loadingState {
|
|
108
|
+
background-color: #f8f9fa;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
border: 1px dashed #ddd;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.emptyState {
|
|
114
|
+
background-color: #fff3cd;
|
|
115
|
+
border: 1px solid #ffeaa7;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
color: #856404;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Responsive design
|
|
121
|
+
@media (max-width: 768px) {
|
|
122
|
+
.categoryTabs {
|
|
123
|
+
gap: 3px;
|
|
124
|
+
padding: 4px;
|
|
125
|
+
margin-bottom: 20px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.categoryTab {
|
|
129
|
+
flex: 1;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
padding: 8px 12px;
|
|
132
|
+
font-size: 13px;
|
|
133
|
+
min-width: 0; // Allow tabs to shrink
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.fileCount {
|
|
137
|
+
font-size: 10px;
|
|
138
|
+
padding: 2px 5px;
|
|
139
|
+
min-width: 16px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.categoryTitle {
|
|
143
|
+
font-size: 18px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.categoryDescription {
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
margin-bottom: 20px;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Very small screens
|
|
153
|
+
@media (max-width: 480px) {
|
|
154
|
+
.categoryTabs {
|
|
155
|
+
flex-direction: column;
|
|
156
|
+
gap: 2px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.categoryTab {
|
|
160
|
+
width: 100%;
|
|
161
|
+
padding: 12px 16px;
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Dark theme support
|
|
167
|
+
@media (prefers-color-scheme: dark) {
|
|
168
|
+
.categoryTabs {
|
|
169
|
+
background-color: #343a40;
|
|
170
|
+
border-color: #495057;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.categoryTab {
|
|
174
|
+
background-color: #495057;
|
|
175
|
+
border-color: #6c757d;
|
|
176
|
+
color: #adb5bd;
|
|
177
|
+
|
|
178
|
+
&:hover {
|
|
179
|
+
background-color: #6c757d;
|
|
180
|
+
border-color: #adb5bd;
|
|
181
|
+
color: #f8f9fa;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&.active {
|
|
185
|
+
background-color: #3498db;
|
|
186
|
+
border-color: #3498db;
|
|
187
|
+
color: #ffffff;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.fileCount {
|
|
192
|
+
background-color: #6c757d;
|
|
193
|
+
color: #f8f9fa;
|
|
194
|
+
border-color: #adb5bd;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.categoryTitle {
|
|
198
|
+
color: #f8f9fa;
|
|
199
|
+
border-bottom-color: #3498db;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.categoryDescription {
|
|
203
|
+
color: #adb5bd;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.loadingState {
|
|
207
|
+
background-color: #343a40;
|
|
208
|
+
border-color: #495057;
|
|
209
|
+
color: #adb5bd;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.emptyState {
|
|
213
|
+
background-color: #856404;
|
|
214
|
+
border-color: #ffc107;
|
|
215
|
+
color: #fff3cd;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -451,15 +451,7 @@ select:not(:placeholder-shown) + .fi__span {
|
|
|
451
451
|
width: 100% !important;
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
|
|
455
|
-
z-index: 99999 !important;
|
|
456
|
-
position: fixed !important;
|
|
457
|
-
transform: none !important;
|
|
458
|
-
top: 50% !important;
|
|
459
|
-
left: 50% !important;
|
|
460
|
-
margin-top: -150px !important;
|
|
461
|
-
margin-left: -150px !important;
|
|
462
|
-
}
|
|
454
|
+
// DatePicker positioning removed - handled by global.css
|
|
463
455
|
|
|
464
456
|
.react-datepicker__close-icon:after {
|
|
465
457
|
background-color: rgba(var(--primary-rgb), 0.45) !important;
|
|
@@ -828,15 +828,7 @@ select:not(:placeholder-shown) + .fi__span {
|
|
|
828
828
|
width: 100% !important;
|
|
829
829
|
}
|
|
830
830
|
|
|
831
|
-
|
|
832
|
-
z-index: 99999 !important;
|
|
833
|
-
position: fixed !important;
|
|
834
|
-
transform: none !important;
|
|
835
|
-
top: 50% !important;
|
|
836
|
-
left: 50% !important;
|
|
837
|
-
margin-top: -150px !important;
|
|
838
|
-
margin-left: -150px !important;
|
|
839
|
-
}
|
|
831
|
+
// DatePicker positioning removed - handled by global.css
|
|
840
832
|
|
|
841
833
|
.react-datepicker__close-icon:after {
|
|
842
834
|
background-color: rgba(var(--primary-rgb), 0.45) !important;
|
|
@@ -196,7 +196,7 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
196
196
|
.inovua-react-toolkit-text-input__input
|
|
197
197
|
):not(.inovua-react-toolkit-numeric-input__input):not(
|
|
198
198
|
.variable-inserter-search-input
|
|
199
|
-
) {
|
|
199
|
+
):not(.react-datepicker__month-select):not(.react-datepicker__year-select) {
|
|
200
200
|
background: white;
|
|
201
201
|
border-radius: var(--br);
|
|
202
202
|
border: 1px solid rgba(var(--paragraph-color-rgb), 0.15);
|
|
@@ -339,29 +339,38 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
339
339
|
z-index: 999 !important;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
/* DatePicker z-index
|
|
342
|
+
/* DatePicker proper z-index hierarchy */
|
|
343
343
|
.react-datepicker-popper {
|
|
344
|
-
z-index:
|
|
344
|
+
z-index: 1060 !important; /* Above Bootstrap modals (1050) */
|
|
345
|
+
position: absolute !important; /* Use absolute positioning for proper context */
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* Center-screen positioning for modal contexts */
|
|
349
|
+
.react-datepicker-popper.center-screen {
|
|
345
350
|
position: fixed !important;
|
|
346
|
-
transform: none !important;
|
|
347
351
|
top: 50% !important;
|
|
348
352
|
left: 50% !important;
|
|
349
|
-
|
|
350
|
-
|
|
353
|
+
transform: translate(-50%, -50%) !important;
|
|
354
|
+
z-index: 1060 !important;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Portal mode for modal contexts */
|
|
358
|
+
.react-datepicker-popper.portal-mode {
|
|
359
|
+
position: fixed !important;
|
|
360
|
+
z-index: 1060 !important;
|
|
351
361
|
}
|
|
352
362
|
|
|
353
363
|
.react-datepicker {
|
|
354
|
-
position:
|
|
364
|
+
position: relative !important;
|
|
355
365
|
display: inline-block !important;
|
|
356
|
-
z-index:
|
|
366
|
+
z-index: 1 !important;
|
|
357
367
|
background-color: white !important;
|
|
358
368
|
border-radius: 0.3rem !important;
|
|
359
369
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3) !important;
|
|
360
370
|
}
|
|
361
371
|
|
|
362
372
|
.react-datepicker-portal {
|
|
363
|
-
|
|
364
|
-
z-index: 99999 !important;
|
|
373
|
+
z-index: 1060 !important;
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
/* Fix for specific modal header that's causing issues */
|
|
@@ -369,12 +378,9 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
369
378
|
z-index: 1 !important;
|
|
370
379
|
}
|
|
371
380
|
|
|
372
|
-
/*
|
|
373
|
-
.popup-content
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.modal__content {
|
|
381
|
+
/* Conditional overflow visibility only for modal contexts */
|
|
382
|
+
.modal-with-datepicker .popup-content,
|
|
383
|
+
.modal-with-datepicker .modal__content {
|
|
378
384
|
overflow: visible !important;
|
|
379
385
|
}
|
|
380
386
|
|
|
@@ -384,26 +390,179 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
384
390
|
border: 1px solid #aeaeae !important;
|
|
385
391
|
border-radius: 0.3rem !important;
|
|
386
392
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
|
|
393
|
+
float: left !important;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.react-datepicker {
|
|
397
|
+
position: relative !important;
|
|
398
|
+
display: inline-block !important;
|
|
399
|
+
background-color: white !important;
|
|
400
|
+
border-radius: 0.3rem !important;
|
|
401
|
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3) !important;
|
|
402
|
+
min-width: 385px !important; /* Wider to accommodate month/year dropdowns */
|
|
403
|
+
width: auto !important;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Ensure time container appears on the right side */
|
|
407
|
+
.react-datepicker__time-container {
|
|
408
|
+
float: right !important;
|
|
409
|
+
border-left: 1px solid #aeaeae !important;
|
|
410
|
+
width: 85px !important;
|
|
411
|
+
display: block !important;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* Fix navigation button positioning */
|
|
415
|
+
.react-datepicker__navigation {
|
|
416
|
+
position: absolute !important;
|
|
417
|
+
top: 2px !important;
|
|
418
|
+
z-index: 1 !important;
|
|
419
|
+
height: 32px !important;
|
|
420
|
+
width: 32px !important;
|
|
421
|
+
cursor: pointer !important;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.react-datepicker__navigation--previous {
|
|
425
|
+
left: 2px !important;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.react-datepicker__navigation--next {
|
|
429
|
+
right: 2px !important;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* Fix next button positioning when time selector is present */
|
|
433
|
+
.react-datepicker__navigation--next--with-time:not(
|
|
434
|
+
.react-datepicker__navigation--next--with-today-button
|
|
435
|
+
) {
|
|
436
|
+
right: 87px !important; /* Account for time container width + border */
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Ensure proper clearfix for float layout */
|
|
440
|
+
.react-datepicker::after {
|
|
441
|
+
content: '' !important;
|
|
442
|
+
display: table !important;
|
|
443
|
+
clear: both !important;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* Fix header layout to accommodate navigation buttons */
|
|
447
|
+
.react-datepicker__header {
|
|
448
|
+
text-align: center !important;
|
|
449
|
+
background-color: #f0f0f0 !important;
|
|
450
|
+
border-bottom: 1px solid #aeaeae !important;
|
|
451
|
+
border-top-left-radius: 0.3rem !important;
|
|
452
|
+
border-top-right-radius: 0.3rem !important;
|
|
453
|
+
padding: 8px 0 !important;
|
|
454
|
+
position: relative !important;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Ensure current month text doesn't interfere with navigation */
|
|
458
|
+
.react-datepicker__current-month,
|
|
459
|
+
.react-datepicker-time__header,
|
|
460
|
+
.react-datepicker-year-header {
|
|
461
|
+
margin-top: 0 !important;
|
|
462
|
+
color: #000 !important;
|
|
463
|
+
font-weight: bold !important;
|
|
464
|
+
font-size: 0.944rem !important;
|
|
465
|
+
padding: 0 40px !important; /* Add padding to avoid navigation button overlap */
|
|
387
466
|
}
|
|
388
467
|
|
|
389
|
-
/*
|
|
468
|
+
/* Alternative approach: Force specific width for month container */
|
|
469
|
+
.react-datepicker__month-container {
|
|
470
|
+
width: calc(
|
|
471
|
+
100% - 87px
|
|
472
|
+
) !important; /* Full width minus time container and border */
|
|
473
|
+
box-sizing: border-box !important;
|
|
474
|
+
min-width: 295px !important; /* Ensure enough space for month/year dropdowns */
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* Ensure time container has proper sizing and positioning */
|
|
478
|
+
.react-datepicker__time-container {
|
|
479
|
+
height: auto !important;
|
|
480
|
+
min-height: 250px !important; /* Match typical calendar height */
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* Force container to maintain layout */
|
|
484
|
+
.react-datepicker {
|
|
485
|
+
overflow: hidden !important; /* Contain floated children */
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Make month and year select dropdowns more compact */
|
|
489
|
+
.react-datepicker__month-dropdown-container,
|
|
490
|
+
.react-datepicker__year-dropdown-container {
|
|
491
|
+
display: inline-block !important;
|
|
492
|
+
margin: 0 5px !important;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* Target the actual select elements with higher specificity */
|
|
496
|
+
.react-datepicker .react-datepicker__header .react-datepicker__month-select,
|
|
497
|
+
.react-datepicker .react-datepicker__header .react-datepicker__year-select {
|
|
498
|
+
width: auto !important;
|
|
499
|
+
min-width: 70px !important;
|
|
500
|
+
max-width: 90px !important;
|
|
501
|
+
padding: 4px 8px !important;
|
|
502
|
+
font-size: 0.9rem !important;
|
|
503
|
+
border: 1px solid #aeaeae !important;
|
|
504
|
+
border-radius: 4px !important;
|
|
505
|
+
background-color: white !important;
|
|
506
|
+
cursor: pointer !important;
|
|
507
|
+
text-align: center !important;
|
|
508
|
+
margin: 0 2px !important;
|
|
509
|
+
box-sizing: border-box !important;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.react-datepicker .react-datepicker__header .react-datepicker__year-select {
|
|
513
|
+
min-width: 60px !important;
|
|
514
|
+
max-width: 70px !important;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* Also target read-view elements for dropdowns */
|
|
518
|
+
.react-datepicker__month-read-view,
|
|
519
|
+
.react-datepicker__year-read-view {
|
|
520
|
+
width: auto !important;
|
|
521
|
+
min-width: 70px !important;
|
|
522
|
+
max-width: 90px !important;
|
|
523
|
+
padding: 4px 8px !important;
|
|
524
|
+
font-size: 0.9rem !important;
|
|
525
|
+
border: 1px solid #aeaeae !important;
|
|
526
|
+
border-radius: 4px !important;
|
|
527
|
+
background-color: white !important;
|
|
528
|
+
cursor: pointer !important;
|
|
529
|
+
text-align: center !important;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.react-datepicker__year-read-view {
|
|
533
|
+
min-width: 60px !important;
|
|
534
|
+
max-width: 70px !important;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* Ensure dropdowns fit in header */
|
|
538
|
+
.react-datepicker__header {
|
|
539
|
+
display: flex !important;
|
|
540
|
+
flex-direction: column !important;
|
|
541
|
+
align-items: center !important;
|
|
542
|
+
gap: 8px !important;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* Container for month/year dropdowns */
|
|
546
|
+
.react-datepicker__current-month {
|
|
547
|
+
display: flex !important;
|
|
548
|
+
align-items: center !important;
|
|
549
|
+
justify-content: center !important;
|
|
550
|
+
gap: 8px !important;
|
|
551
|
+
padding: 0 40px !important;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* Show arrow for proper positioning context */
|
|
390
555
|
.react-datepicker__triangle {
|
|
391
|
-
display:
|
|
556
|
+
display: block !important;
|
|
392
557
|
}
|
|
393
558
|
|
|
394
|
-
/*
|
|
395
|
-
.
|
|
396
|
-
|
|
397
|
-
.modal__content,
|
|
398
|
-
.formItem,
|
|
399
|
-
.fi__label,
|
|
400
|
-
.dynamicform,
|
|
401
|
-
.dynamic__left {
|
|
402
|
-
overflow: visible !important;
|
|
559
|
+
/* Hide arrow only for center-screen mode */
|
|
560
|
+
.react-datepicker-popper.center-screen .react-datepicker__triangle {
|
|
561
|
+
display: none !important;
|
|
403
562
|
}
|
|
404
563
|
|
|
405
|
-
/*
|
|
406
|
-
.react-datepicker-popper::before {
|
|
564
|
+
/* Backdrop only for center-screen mode */
|
|
565
|
+
.react-datepicker-popper.center-screen::before {
|
|
407
566
|
content: '' !important;
|
|
408
567
|
position: fixed !important;
|
|
409
568
|
top: 0 !important;
|
|
@@ -466,8 +625,7 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
466
625
|
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
467
626
|
|
|
468
627
|
/* Checkerboard pattern for transparency */
|
|
469
|
-
background-image:
|
|
470
|
-
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
628
|
+
background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
471
629
|
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
|
|
472
630
|
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
|
|
473
631
|
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
|
|
@@ -478,8 +636,7 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
478
636
|
.relation-color-preview:hover {
|
|
479
637
|
border-color: #adb5bd;
|
|
480
638
|
transform: scale(1.1);
|
|
481
|
-
box-shadow:
|
|
482
|
-
inset 0 1px 2px rgba(0, 0, 0, 0.1),
|
|
639
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1),
|
|
483
640
|
0 2px 8px rgba(0, 0, 0, 0.15);
|
|
484
641
|
}
|
|
485
642
|
|
|
@@ -535,8 +692,7 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
535
692
|
flex-shrink: 0;
|
|
536
693
|
|
|
537
694
|
/* Checkerboard pattern for transparency */
|
|
538
|
-
background-image:
|
|
539
|
-
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
695
|
+
background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
|
|
540
696
|
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
|
|
541
697
|
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
|
|
542
698
|
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
|
|
@@ -545,18 +701,19 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
545
701
|
}
|
|
546
702
|
|
|
547
703
|
/* When no color is selected */
|
|
548
|
-
.cpicker__box[style=
|
|
549
|
-
background:
|
|
550
|
-
|
|
551
|
-
transparent 40%,
|
|
552
|
-
#dc3545 42%,
|
|
553
|
-
#dc3545 58%,
|
|
704
|
+
.cpicker__box[style=''] {
|
|
705
|
+
background: linear-gradient(
|
|
706
|
+
45deg,
|
|
707
|
+
transparent 40%,
|
|
708
|
+
#dc3545 42%,
|
|
709
|
+
#dc3545 58%,
|
|
554
710
|
transparent 60%
|
|
555
711
|
),
|
|
556
|
-
linear-gradient(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
#dc3545
|
|
712
|
+
linear-gradient(
|
|
713
|
+
-45deg,
|
|
714
|
+
transparent 40%,
|
|
715
|
+
#dc3545 42%,
|
|
716
|
+
#dc3545 58%,
|
|
560
717
|
transparent 60%
|
|
561
718
|
),
|
|
562
719
|
#ffffff;
|
|
@@ -600,7 +757,7 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
600
757
|
.cpicker-container {
|
|
601
758
|
padding: 6px;
|
|
602
759
|
}
|
|
603
|
-
|
|
760
|
+
|
|
604
761
|
.cpicker-container .w-color-sketch {
|
|
605
762
|
transform: scale(0.75) !important;
|
|
606
763
|
}
|
|
@@ -610,7 +767,7 @@ form div:has(.react-toggle) input[type='checkbox'] {
|
|
|
610
767
|
.cpicker-container {
|
|
611
768
|
padding: 4px;
|
|
612
769
|
}
|
|
613
|
-
|
|
770
|
+
|
|
614
771
|
.cpicker-container .w-color-sketch {
|
|
615
772
|
transform: scale(0.65) !important;
|
|
616
773
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DatePickerPortal - Smart portal component for DatePicker positioning
|
|
6
|
+
*
|
|
7
|
+
* Automatically detects if the DatePicker is inside a modal context
|
|
8
|
+
* and renders it appropriately to prevent z-index conflicts
|
|
9
|
+
*/
|
|
10
|
+
const DatePickerPortal = ({
|
|
11
|
+
children,
|
|
12
|
+
usePortal = null, // null = auto-detect, true = force portal, false = no portal
|
|
13
|
+
modalContext = null, // null = auto-detect, true = force modal styling, false = normal
|
|
14
|
+
centerScreen = false, // true = force center-screen positioning
|
|
15
|
+
containerRef = null // reference to the input container for positioning
|
|
16
|
+
}) => {
|
|
17
|
+
const [shouldUsePortal, setShouldUsePortal] = useState(false);
|
|
18
|
+
const [isModalContext, setIsModalContext] = useState(false);
|
|
19
|
+
const [portalContainer, setPortalContainer] = useState(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Auto-detect modal context if not explicitly set
|
|
23
|
+
if (modalContext === null) {
|
|
24
|
+
const detectModalContext = () => {
|
|
25
|
+
if (containerRef && containerRef.current) {
|
|
26
|
+
// Check if the container is inside a modal
|
|
27
|
+
const modalSelectors = [
|
|
28
|
+
'.modal',
|
|
29
|
+
'.popup-content',
|
|
30
|
+
'.modal__content',
|
|
31
|
+
'.swal2-container',
|
|
32
|
+
'[role="dialog"]',
|
|
33
|
+
'[aria-modal="true"]'
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
for (const selector of modalSelectors) {
|
|
37
|
+
if (containerRef.current.closest(selector)) {
|
|
38
|
+
setIsModalContext(true);
|
|
39
|
+
setShouldUsePortal(true);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
setIsModalContext(false);
|
|
45
|
+
setShouldUsePortal(false);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
detectModalContext();
|
|
49
|
+
} else {
|
|
50
|
+
setIsModalContext(modalContext);
|
|
51
|
+
setShouldUsePortal(modalContext);
|
|
52
|
+
}
|
|
53
|
+
}, [modalContext, containerRef]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
// Override auto-detection if explicitly set
|
|
57
|
+
if (usePortal !== null) {
|
|
58
|
+
setShouldUsePortal(usePortal);
|
|
59
|
+
}
|
|
60
|
+
}, [usePortal]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
// Create portal container
|
|
64
|
+
if (shouldUsePortal) {
|
|
65
|
+
const container = document.createElement('div');
|
|
66
|
+
container.className = 'datepicker-portal-container';
|
|
67
|
+
|
|
68
|
+
// Add appropriate classes based on context
|
|
69
|
+
if (isModalContext || centerScreen) {
|
|
70
|
+
container.classList.add('modal-context');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
document.body.appendChild(container);
|
|
74
|
+
setPortalContainer(container);
|
|
75
|
+
|
|
76
|
+
// Add modal-context class to body for CSS targeting
|
|
77
|
+
if (isModalContext) {
|
|
78
|
+
document.body.classList.add('modal-with-datepicker');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
document.body.removeChild(container);
|
|
83
|
+
document.body.classList.remove('modal-with-datepicker');
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}, [shouldUsePortal, isModalContext, centerScreen]);
|
|
87
|
+
|
|
88
|
+
// Clone children and add appropriate classes
|
|
89
|
+
const enhancedChildren = React.Children.map(children, child => {
|
|
90
|
+
if (React.isValidElement(child)) {
|
|
91
|
+
const additionalProps = {};
|
|
92
|
+
|
|
93
|
+
// Add popperClassName for react-datepicker
|
|
94
|
+
if (child.props.popperClassName) {
|
|
95
|
+
additionalProps.popperClassName = child.props.popperClassName;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Add classes based on context
|
|
99
|
+
const classes = [];
|
|
100
|
+
if (isModalContext || centerScreen) {
|
|
101
|
+
classes.push('center-screen');
|
|
102
|
+
}
|
|
103
|
+
if (shouldUsePortal) {
|
|
104
|
+
classes.push('portal-mode');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (classes.length > 0) {
|
|
108
|
+
additionalProps.popperClassName = [
|
|
109
|
+
child.props.popperClassName,
|
|
110
|
+
...classes
|
|
111
|
+
].filter(Boolean).join(' ');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return React.cloneElement(child, additionalProps);
|
|
115
|
+
}
|
|
116
|
+
return child;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Render with or without portal
|
|
120
|
+
if (shouldUsePortal && portalContainer) {
|
|
121
|
+
return createPortal(enhancedChildren, portalContainer);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return enhancedChildren;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default DatePickerPortal;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** CMS Components */
|
|
2
2
|
import DataGrid from './components/DataGrid';
|
|
3
3
|
import DropZone from './components/DropZone';
|
|
4
|
+
import CategorizedDropZone from './components/CategorizedDropZone';
|
|
4
5
|
import SortableList from './components/sorting/List';
|
|
5
6
|
|
|
6
7
|
/** CRM Components */
|
|
@@ -29,6 +30,7 @@ import DataGridSearch from './components/controls/DataGridSearch';
|
|
|
29
30
|
import AutoRefreshControls from './components/controls/AutoRefreshControls';
|
|
30
31
|
import AudioPlayer from './components/controls/AudioPlayer';
|
|
31
32
|
import GalleryModal from './components/modals/GalleryModal';
|
|
33
|
+
import DatePickerPortal from './components/utils/DatePickerPortal';
|
|
32
34
|
export * from './components/columns/ColumnRenderers.jsx';
|
|
33
35
|
|
|
34
36
|
/** Auth Specific Components */
|
|
@@ -96,8 +98,10 @@ export {
|
|
|
96
98
|
CustomFetch,
|
|
97
99
|
DataGrid,
|
|
98
100
|
DataGridSearch,
|
|
101
|
+
DatePickerPortal,
|
|
99
102
|
Download,
|
|
100
103
|
DropZone,
|
|
104
|
+
CategorizedDropZone,
|
|
101
105
|
Field,
|
|
102
106
|
Form,
|
|
103
107
|
GalleryModal,
|