@visns-studio/visns-components 5.12.5 → 5.12.7
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/Field.jsx +57 -41
- package/src/components/generic/GenericDetail.jsx +175 -28
- package/src/components/modals/GalleryModal.jsx +29 -1
- package/src/components/styles/GenericDetail.module.scss +33 -11
- 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 +2 -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.7",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
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)
|
|
@@ -54,10 +54,16 @@ import {
|
|
|
54
54
|
Eye as EyeOpen,
|
|
55
55
|
EyeOff as EyeSlashed,
|
|
56
56
|
File,
|
|
57
|
+
FileText,
|
|
58
|
+
FileSpreadsheet,
|
|
59
|
+
FileImage,
|
|
60
|
+
FileCode,
|
|
61
|
+
FileArchive,
|
|
57
62
|
Edit as Pencil,
|
|
58
63
|
Trash2 as TrashCan,
|
|
64
|
+
X as CircleX,
|
|
59
65
|
} from 'lucide-react';
|
|
60
|
-
import { confirmDialog } from '
|
|
66
|
+
import { confirmDialog } from '../utils/ConfirmDialog';
|
|
61
67
|
|
|
62
68
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
|
63
69
|
import 'react-big-calendar/lib/addons/dragAndDrop/styles.css';
|
|
@@ -79,6 +85,9 @@ import TableFilter from '../TableFilter';
|
|
|
79
85
|
import ProposalTemplateSectionManager from '../proposal/ProposalTemplateSectionManager';
|
|
80
86
|
import ProposalTemplatePreview from '../proposal/ProposalTemplatePreview';
|
|
81
87
|
|
|
88
|
+
// Gallery Modal
|
|
89
|
+
import GalleryModal from '../modals/GalleryModal';
|
|
90
|
+
|
|
82
91
|
import styles from '../styles/GenericDetail.module.scss';
|
|
83
92
|
|
|
84
93
|
const localizer = momentLocalizer(moment);
|
|
@@ -127,6 +136,88 @@ const optimizeImageIfNeeded = async (file) => {
|
|
|
127
136
|
return file;
|
|
128
137
|
};
|
|
129
138
|
|
|
139
|
+
// Helper function to get accept types based on filetype configuration
|
|
140
|
+
const getAcceptTypes = (filetype) => {
|
|
141
|
+
switch (filetype) {
|
|
142
|
+
case 'document':
|
|
143
|
+
return {
|
|
144
|
+
'application/pdf': ['.pdf'],
|
|
145
|
+
'application/msword': ['.doc'],
|
|
146
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
|
|
147
|
+
'application/vnd.ms-excel': ['.xls'],
|
|
148
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
|
|
149
|
+
'application/vnd.ms-powerpoint': ['.ppt'],
|
|
150
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'],
|
|
151
|
+
'text/plain': ['.txt'],
|
|
152
|
+
'text/csv': ['.csv'],
|
|
153
|
+
'application/zip': ['.zip'],
|
|
154
|
+
'application/x-rar-compressed': ['.rar'],
|
|
155
|
+
'application/x-7z-compressed': ['.7z']
|
|
156
|
+
};
|
|
157
|
+
case 'image':
|
|
158
|
+
return {
|
|
159
|
+
'image/jpeg': ['.jpg', '.jpeg'],
|
|
160
|
+
'image/png': ['.png'],
|
|
161
|
+
'image/gif': ['.gif'],
|
|
162
|
+
'image/webp': ['.webp'],
|
|
163
|
+
'image/svg+xml': ['.svg'],
|
|
164
|
+
'image/bmp': ['.bmp'],
|
|
165
|
+
'image/tiff': ['.tiff', '.tif']
|
|
166
|
+
};
|
|
167
|
+
default:
|
|
168
|
+
return undefined; // Accept all file types
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Helper function to get the appropriate icon based on file extension
|
|
173
|
+
const getFileIcon = (filename) => {
|
|
174
|
+
const extension = filename.split('.').pop().toLowerCase();
|
|
175
|
+
|
|
176
|
+
switch (extension) {
|
|
177
|
+
case 'pdf':
|
|
178
|
+
return <FileText strokeWidth={2} size={20} color="#dc2626" />;
|
|
179
|
+
case 'doc':
|
|
180
|
+
case 'docx':
|
|
181
|
+
return <FileText strokeWidth={2} size={20} color="#2563eb" />;
|
|
182
|
+
case 'xls':
|
|
183
|
+
case 'xlsx':
|
|
184
|
+
return <FileSpreadsheet strokeWidth={2} size={20} color="#16a34a" />;
|
|
185
|
+
case 'ppt':
|
|
186
|
+
case 'pptx':
|
|
187
|
+
return <FileText strokeWidth={2} size={20} color="#ea580c" />;
|
|
188
|
+
case 'txt':
|
|
189
|
+
case 'csv':
|
|
190
|
+
return <FileCode strokeWidth={2} size={20} color="#6b7280" />;
|
|
191
|
+
case 'zip':
|
|
192
|
+
case 'rar':
|
|
193
|
+
case '7z':
|
|
194
|
+
return <FileArchive strokeWidth={2} size={20} color="#7c3aed" />;
|
|
195
|
+
case 'jpg':
|
|
196
|
+
case 'jpeg':
|
|
197
|
+
case 'png':
|
|
198
|
+
case 'gif':
|
|
199
|
+
case 'webp':
|
|
200
|
+
case 'svg':
|
|
201
|
+
case 'bmp':
|
|
202
|
+
case 'tiff':
|
|
203
|
+
case 'tif':
|
|
204
|
+
return <FileImage strokeWidth={2} size={20} color="#059669" />;
|
|
205
|
+
default:
|
|
206
|
+
return <File strokeWidth={2} size={20} />;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Helper function to format file size
|
|
211
|
+
const formatFileSize = (bytes) => {
|
|
212
|
+
if (!bytes || bytes === 0) return '';
|
|
213
|
+
|
|
214
|
+
const k = 1024;
|
|
215
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
216
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
217
|
+
|
|
218
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
219
|
+
};
|
|
220
|
+
|
|
130
221
|
function GenericDetail({
|
|
131
222
|
extraUrlParam,
|
|
132
223
|
layout = 'full',
|
|
@@ -158,6 +249,19 @@ function GenericDetail({
|
|
|
158
249
|
const [total, setTotal] = useState(0);
|
|
159
250
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
|
|
160
251
|
|
|
252
|
+
/** Gallery Modal States */
|
|
253
|
+
const [modalGalleryShow, setModalGalleryShow] = useState(false);
|
|
254
|
+
const [galleryData, setGalleryData] = useState([]);
|
|
255
|
+
const [currentItem, setCurrentItem] = useState(null);
|
|
256
|
+
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
257
|
+
const [lightboxIndex, setLightboxIndex] = useState(0);
|
|
258
|
+
|
|
259
|
+
// Gallery modal close function
|
|
260
|
+
const modalGalleryClose = () => {
|
|
261
|
+
setModalGalleryShow(false);
|
|
262
|
+
setLightboxOpen(false);
|
|
263
|
+
};
|
|
264
|
+
|
|
161
265
|
/** Stage Popup States */
|
|
162
266
|
const [showStagePopup, setShowStagePopup] = useState(false);
|
|
163
267
|
const [stagePopupData, setStagePopupData] = useState(null);
|
|
@@ -1445,7 +1549,7 @@ function GenericDetail({
|
|
|
1445
1549
|
message:
|
|
1446
1550
|
'Are you sure you want to delete ' +
|
|
1447
1551
|
name,
|
|
1448
|
-
data: { name: name },
|
|
1552
|
+
data: { name: name },
|
|
1449
1553
|
buttons: [
|
|
1450
1554
|
{
|
|
1451
1555
|
label: 'Yes',
|
|
@@ -1473,7 +1577,9 @@ function GenericDetail({
|
|
|
1473
1577
|
},
|
|
1474
1578
|
{
|
|
1475
1579
|
label: 'No',
|
|
1476
|
-
onClick: () =>
|
|
1580
|
+
onClick: () => {
|
|
1581
|
+
// Do nothing on cancel
|
|
1582
|
+
},
|
|
1477
1583
|
},
|
|
1478
1584
|
],
|
|
1479
1585
|
});
|
|
@@ -1501,6 +1607,7 @@ function GenericDetail({
|
|
|
1501
1607
|
/>
|
|
1502
1608
|
</div>
|
|
1503
1609
|
<Dropzone
|
|
1610
|
+
accept={getAcceptTypes(activeTabConfig.filetype)}
|
|
1504
1611
|
onDrop={(acceptedFiles) => {
|
|
1505
1612
|
const uploadFile = async (file) => {
|
|
1506
1613
|
// Optimize image if it's an image file
|
|
@@ -1695,20 +1802,9 @@ function GenericDetail({
|
|
|
1695
1802
|
styles.sortlist
|
|
1696
1803
|
}
|
|
1697
1804
|
>
|
|
1698
|
-
{
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
] &&
|
|
1702
|
-
Array.isArray(
|
|
1703
|
-
data[
|
|
1704
|
-
activeTabConfig
|
|
1705
|
-
.key
|
|
1706
|
-
]
|
|
1707
|
-
) &&
|
|
1708
|
-
data[
|
|
1709
|
-
activeTabConfig
|
|
1710
|
-
.key
|
|
1711
|
-
].map(
|
|
1805
|
+
{files &&
|
|
1806
|
+
Array.isArray(files) &&
|
|
1807
|
+
files.map(
|
|
1712
1808
|
(a, b) => {
|
|
1713
1809
|
const imageSource =
|
|
1714
1810
|
a.base64_encoded_image ||
|
|
@@ -1732,6 +1828,33 @@ function GenericDetail({
|
|
|
1732
1828
|
alt={
|
|
1733
1829
|
a.file_name
|
|
1734
1830
|
}
|
|
1831
|
+
onClick={(e) => {
|
|
1832
|
+
e.stopPropagation();
|
|
1833
|
+
e.preventDefault();
|
|
1834
|
+
|
|
1835
|
+
// Prepare gallery data in the format expected by Lightbox
|
|
1836
|
+
const galleryArray = files
|
|
1837
|
+
.filter(file => {
|
|
1838
|
+
const imageSource = file.base64_encoded_image || file.s3_url || file.file_url;
|
|
1839
|
+
return imageSource;
|
|
1840
|
+
})
|
|
1841
|
+
.map((file) => ({
|
|
1842
|
+
src: file.base64_encoded_image || file.s3_url || file.file_url,
|
|
1843
|
+
caption: file.file_name,
|
|
1844
|
+
width: 320,
|
|
1845
|
+
height: 240,
|
|
1846
|
+
}));
|
|
1847
|
+
|
|
1848
|
+
// Set gallery data for lightbox
|
|
1849
|
+
setGalleryData(galleryArray);
|
|
1850
|
+
|
|
1851
|
+
// Set initial lightbox index
|
|
1852
|
+
setLightboxIndex(b);
|
|
1853
|
+
|
|
1854
|
+
// Open lightbox directly
|
|
1855
|
+
setLightboxOpen(true);
|
|
1856
|
+
}}
|
|
1857
|
+
style={{ cursor: 'pointer' }}
|
|
1735
1858
|
/>
|
|
1736
1859
|
<TrashCan
|
|
1737
1860
|
onClick={(
|
|
@@ -1788,18 +1911,16 @@ function GenericDetail({
|
|
|
1788
1911
|
);
|
|
1789
1912
|
}}
|
|
1790
1913
|
>
|
|
1791
|
-
|
|
1792
|
-
strokeWidth={
|
|
1793
|
-
2
|
|
1794
|
-
}
|
|
1795
|
-
size={
|
|
1796
|
-
20
|
|
1797
|
-
}
|
|
1798
|
-
/>
|
|
1914
|
+
{getFileIcon(a.file_name)}
|
|
1799
1915
|
<div className="filename">
|
|
1800
|
-
|
|
1801
|
-
a.file_name
|
|
1802
|
-
|
|
1916
|
+
<div className="file-name">
|
|
1917
|
+
{a.file_name}
|
|
1918
|
+
</div>
|
|
1919
|
+
{a.file_size && (
|
|
1920
|
+
<div className="file-size">
|
|
1921
|
+
{formatFileSize(a.file_size)}
|
|
1922
|
+
</div>
|
|
1923
|
+
)}
|
|
1803
1924
|
</div>
|
|
1804
1925
|
</div>
|
|
1805
1926
|
<button
|
|
@@ -2390,6 +2511,7 @@ function GenericDetail({
|
|
|
2390
2511
|
}
|
|
2391
2512
|
>
|
|
2392
2513
|
<Dropzone
|
|
2514
|
+
accept={getAcceptTypes(activeTabConfig.filetype)}
|
|
2393
2515
|
onDrop={(
|
|
2394
2516
|
acceptedFiles
|
|
2395
2517
|
) => {
|
|
@@ -2718,6 +2840,17 @@ function GenericDetail({
|
|
|
2718
2840
|
}
|
|
2719
2841
|
}, [subnav, tabs]);
|
|
2720
2842
|
|
|
2843
|
+
// Update files when active tab changes for dropzone tabs
|
|
2844
|
+
useEffect(() => {
|
|
2845
|
+
if (activeTabConfig && activeTabConfig.type === 'dropzone' && data) {
|
|
2846
|
+
if (activeTabConfig.file_relationship && data[activeTabConfig.file_relationship]) {
|
|
2847
|
+
setFiles(data[activeTabConfig.file_relationship]);
|
|
2848
|
+
} else if (data.files) {
|
|
2849
|
+
setFiles(data.files);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}, [activeTabConfig, data]);
|
|
2853
|
+
|
|
2721
2854
|
useEffect(() => {
|
|
2722
2855
|
const fetchDropdownOptions = async () => {
|
|
2723
2856
|
if (activeTabConfig?.columns) {
|
|
@@ -3167,6 +3300,20 @@ function GenericDetail({
|
|
|
3167
3300
|
entityData={data}
|
|
3168
3301
|
routeParams={routeParams}
|
|
3169
3302
|
/>
|
|
3303
|
+
|
|
3304
|
+
{/* Gallery Modal for image viewing */}
|
|
3305
|
+
<GalleryModal
|
|
3306
|
+
modalGalleryShow={modalGalleryShow}
|
|
3307
|
+
modalGalleryClose={modalGalleryClose}
|
|
3308
|
+
galleryData={galleryData}
|
|
3309
|
+
setGalleryData={setGalleryData}
|
|
3310
|
+
currentItem={currentItem}
|
|
3311
|
+
form={{}}
|
|
3312
|
+
lightboxOpen={lightboxOpen}
|
|
3313
|
+
setLightboxOpen={setLightboxOpen}
|
|
3314
|
+
lightboxIndex={lightboxIndex}
|
|
3315
|
+
setLightboxIndex={setLightboxIndex}
|
|
3316
|
+
/>
|
|
3170
3317
|
</>
|
|
3171
3318
|
);
|
|
3172
3319
|
}
|
|
@@ -4,6 +4,7 @@ import { toast } from 'react-toastify';
|
|
|
4
4
|
import imageCompression from 'browser-image-compression';
|
|
5
5
|
import Vapor from 'laravel-vapor';
|
|
6
6
|
import Lightbox from 'yet-another-react-lightbox';
|
|
7
|
+
import 'yet-another-react-lightbox/styles.css';
|
|
7
8
|
import { X, Image as ImageIcon, CloudUpload } from 'lucide-react';
|
|
8
9
|
import styles from '../styles/DataGrid.module.scss';
|
|
9
10
|
import CustomFetch from '../Fetch';
|
|
@@ -396,7 +397,34 @@ const GalleryModal = ({
|
|
|
396
397
|
open={lightboxOpen}
|
|
397
398
|
close={() => setLightboxOpen(false)}
|
|
398
399
|
index={lightboxIndex}
|
|
399
|
-
slides={galleryData.map((img) => ({
|
|
400
|
+
slides={galleryData.map((img, index) => ({
|
|
401
|
+
src: img.src,
|
|
402
|
+
alt: img.caption || `Image ${index + 1}`,
|
|
403
|
+
title: img.caption
|
|
404
|
+
}))}
|
|
405
|
+
styles={{
|
|
406
|
+
container: {
|
|
407
|
+
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
|
408
|
+
zIndex: 999999
|
|
409
|
+
}
|
|
410
|
+
}}
|
|
411
|
+
controller={{
|
|
412
|
+
closeOnBackdropClick: true,
|
|
413
|
+
closeOnPullUp: true,
|
|
414
|
+
closeOnPullDown: true
|
|
415
|
+
}}
|
|
416
|
+
carousel={{
|
|
417
|
+
finite: true,
|
|
418
|
+
preload: 1,
|
|
419
|
+
padding: "16px",
|
|
420
|
+
spacing: "30%",
|
|
421
|
+
imageFit: "contain"
|
|
422
|
+
}}
|
|
423
|
+
animation={{
|
|
424
|
+
fade: 300,
|
|
425
|
+
swipe: 400
|
|
426
|
+
}}
|
|
427
|
+
portal={{ root: document.body }}
|
|
400
428
|
/>
|
|
401
429
|
</>
|
|
402
430
|
);
|
|
@@ -333,9 +333,25 @@
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
.filename {
|
|
336
|
-
|
|
336
|
+
display: flex;
|
|
337
|
+
flex-direction: column;
|
|
337
338
|
overflow: hidden;
|
|
338
|
-
|
|
339
|
+
flex: 1;
|
|
340
|
+
|
|
341
|
+
.file-name {
|
|
342
|
+
white-space: nowrap;
|
|
343
|
+
overflow: hidden;
|
|
344
|
+
text-overflow: ellipsis;
|
|
345
|
+
font-weight: 500;
|
|
346
|
+
line-height: 1.2;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.file-size {
|
|
350
|
+
font-size: 0.75rem;
|
|
351
|
+
color: rgba(var(--primary-rgb), 0.7);
|
|
352
|
+
margin-top: 2px;
|
|
353
|
+
font-weight: 400;
|
|
354
|
+
}
|
|
339
355
|
}
|
|
340
356
|
}
|
|
341
357
|
|
|
@@ -364,26 +380,25 @@
|
|
|
364
380
|
}
|
|
365
381
|
|
|
366
382
|
.sortlist {
|
|
367
|
-
display: grid;
|
|
368
|
-
grid-template-columns: repeat(4, 1fr); /* 4 columns by default */
|
|
369
|
-
gap: 16px;
|
|
383
|
+
display: grid !important;
|
|
384
|
+
grid-template-columns: repeat(4, 1fr) !important; /* 4 columns by default */
|
|
385
|
+
gap: 16px !important;
|
|
370
386
|
padding: 16px 0;
|
|
387
|
+
margin: 0;
|
|
371
388
|
list-style: none;
|
|
389
|
+
box-sizing: border-box;
|
|
372
390
|
|
|
373
391
|
/* Responsive grid layout */
|
|
374
392
|
@media (max-width: 1200px) {
|
|
375
|
-
grid-template-columns: repeat(3, 1fr); /* 3 columns on medium screens */
|
|
393
|
+
grid-template-columns: repeat(3, 1fr) !important; /* 3 columns on medium screens */
|
|
376
394
|
}
|
|
377
395
|
|
|
378
396
|
@media (max-width: 768px) {
|
|
379
|
-
grid-template-columns: repeat(2, 1fr); /* 2 columns on small screens */
|
|
397
|
+
grid-template-columns: repeat(2, 1fr) !important; /* 2 columns on small screens */
|
|
380
398
|
}
|
|
381
399
|
|
|
382
400
|
@media (max-width: 480px) {
|
|
383
|
-
grid-template-columns: repeat(
|
|
384
|
-
1,
|
|
385
|
-
1fr
|
|
386
|
-
); /* 1 column on very small screens */
|
|
401
|
+
grid-template-columns: repeat(1, 1fr) !important; /* 1 column on very small screens */
|
|
387
402
|
}
|
|
388
403
|
|
|
389
404
|
li {
|
|
@@ -413,6 +428,12 @@
|
|
|
413
428
|
width: 100%;
|
|
414
429
|
height: 100%;
|
|
415
430
|
object-fit: cover;
|
|
431
|
+
cursor: pointer;
|
|
432
|
+
transition: opacity 0.2s ease;
|
|
433
|
+
|
|
434
|
+
&:hover {
|
|
435
|
+
opacity: 0.9;
|
|
436
|
+
}
|
|
416
437
|
}
|
|
417
438
|
|
|
418
439
|
.delete {
|
|
@@ -819,3 +840,4 @@
|
|
|
819
840
|
overflow-x: auto;
|
|
820
841
|
white-space: pre-wrap;
|
|
821
842
|
}
|
|
843
|
+
|
|
@@ -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
|
@@ -29,6 +29,7 @@ import DataGridSearch from './components/controls/DataGridSearch';
|
|
|
29
29
|
import AutoRefreshControls from './components/controls/AutoRefreshControls';
|
|
30
30
|
import AudioPlayer from './components/controls/AudioPlayer';
|
|
31
31
|
import GalleryModal from './components/modals/GalleryModal';
|
|
32
|
+
import DatePickerPortal from './components/utils/DatePickerPortal';
|
|
32
33
|
export * from './components/columns/ColumnRenderers.jsx';
|
|
33
34
|
|
|
34
35
|
/** Auth Specific Components */
|
|
@@ -96,6 +97,7 @@ export {
|
|
|
96
97
|
CustomFetch,
|
|
97
98
|
DataGrid,
|
|
98
99
|
DataGridSearch,
|
|
100
|
+
DatePickerPortal,
|
|
99
101
|
Download,
|
|
100
102
|
DropZone,
|
|
101
103
|
Field,
|