@visns-studio/visns-components 5.12.4 → 5.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -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.6",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -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
|
}
|
|
@@ -25,14 +25,7 @@ const StagePopupModal = ({
|
|
|
25
25
|
|
|
26
26
|
// Re-render form fields when fieldOptions change
|
|
27
27
|
useEffect(() => {
|
|
28
|
-
console.log('[StagePopupModal] useEffect triggered:', {
|
|
29
|
-
show,
|
|
30
|
-
loading,
|
|
31
|
-
fieldOptionsKeys: Object.keys(fieldOptions),
|
|
32
|
-
fieldOptions
|
|
33
|
-
});
|
|
34
28
|
if (show && !loading && Object.keys(fieldOptions).length > 0) {
|
|
35
|
-
console.log('[StagePopupModal] Re-rendering form fields after fieldOptions update');
|
|
36
29
|
renderFormFields();
|
|
37
30
|
}
|
|
38
31
|
}, [fieldOptions, loading, formData, validationErrors, show]);
|
|
@@ -64,14 +57,11 @@ const StagePopupModal = ({
|
|
|
64
57
|
});
|
|
65
58
|
|
|
66
59
|
const response = await CustomFetch(url, 'POST');
|
|
67
|
-
console.log(`[StagePopupModal] Response for ${field.id}:`, response);
|
|
68
|
-
console.log(`[StagePopupModal] Response.data:`, response.data);
|
|
69
60
|
|
|
70
61
|
if (response.data && !response.data.error) {
|
|
71
62
|
// Handle both nested (response.data.data) and direct (response.data) array formats
|
|
72
63
|
const optionsData = Array.isArray(response.data.data) ? response.data.data :
|
|
73
64
|
Array.isArray(response.data) ? response.data : [];
|
|
74
|
-
console.log(`[StagePopupModal] Processed options for ${field.id}:`, optionsData);
|
|
75
65
|
options[field.id] = optionsData;
|
|
76
66
|
}
|
|
77
67
|
} catch (error) {
|
|
@@ -81,9 +71,6 @@ const StagePopupModal = ({
|
|
|
81
71
|
}
|
|
82
72
|
}
|
|
83
73
|
|
|
84
|
-
console.log(`[StagePopupModal] Final options object:`, options);
|
|
85
|
-
console.log(`[StagePopupModal] Setting fieldOptions state:`, options);
|
|
86
|
-
|
|
87
74
|
setFormData(initialData);
|
|
88
75
|
setFieldOptions(options);
|
|
89
76
|
setLoading(false);
|
|
@@ -106,15 +93,24 @@ const StagePopupModal = ({
|
|
|
106
93
|
|
|
107
94
|
const validateForm = () => {
|
|
108
95
|
const errors = {};
|
|
96
|
+
const fieldsContainer = document.getElementById('stage-popup-fields');
|
|
109
97
|
|
|
110
98
|
popupConfig.fields?.forEach(field => {
|
|
111
|
-
|
|
99
|
+
let value = formData[field.id];
|
|
100
|
+
|
|
101
|
+
// Get current value from DOM if available
|
|
102
|
+
if (fieldsContainer) {
|
|
103
|
+
const input = fieldsContainer.querySelector(`[data-field-id="${field.id}"]`);
|
|
104
|
+
if (input) {
|
|
105
|
+
value = input.value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (field.required && (!value || value === '')) {
|
|
112
110
|
errors[field.id] = `${field.label} is required`;
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
if (field.validation) {
|
|
116
|
-
const value = formData[field.id];
|
|
117
|
-
|
|
118
114
|
if (field.validation.minLength && value && value.length < field.validation.minLength) {
|
|
119
115
|
errors[field.id] = `${field.label} must be at least ${field.validation.minLength} characters`;
|
|
120
116
|
}
|
|
@@ -135,13 +131,24 @@ const StagePopupModal = ({
|
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
const dataToSend = {};
|
|
134
|
+
const fieldsContainer = document.getElementById('stage-popup-fields');
|
|
138
135
|
|
|
139
136
|
// Only include fields specified in saveFields, or all fields if not specified
|
|
140
137
|
const fieldsToSave = popupConfig.saveFields || popupConfig.fields?.map(f => f.id) || [];
|
|
141
138
|
|
|
142
139
|
fieldsToSave.forEach(fieldId => {
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
let value = formData[fieldId];
|
|
141
|
+
|
|
142
|
+
// Get current value from DOM if available
|
|
143
|
+
if (fieldsContainer) {
|
|
144
|
+
const input = fieldsContainer.querySelector(`[data-field-id="${fieldId}"]`);
|
|
145
|
+
if (input) {
|
|
146
|
+
value = input.value;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (value !== undefined && value !== '') {
|
|
151
|
+
dataToSend[fieldId] = value;
|
|
145
152
|
}
|
|
146
153
|
});
|
|
147
154
|
|
|
@@ -168,14 +175,11 @@ const StagePopupModal = ({
|
|
|
168
175
|
disabled={loading}
|
|
169
176
|
>
|
|
170
177
|
<option value="">-- Select {field.label} --</option>
|
|
171
|
-
{((
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
</option>
|
|
177
|
-
));
|
|
178
|
-
})()}
|
|
178
|
+
{(fieldOptions[field.id] || []).map(option => (
|
|
179
|
+
<option key={option.id || option.value} value={option.id || option.value}>
|
|
180
|
+
{option.label || option.name || option.text}
|
|
181
|
+
</option>
|
|
182
|
+
))}
|
|
179
183
|
</select>
|
|
180
184
|
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
181
185
|
</div>
|
|
@@ -303,13 +307,12 @@ const StagePopupModal = ({
|
|
|
303
307
|
onCancel();
|
|
304
308
|
}
|
|
305
309
|
});
|
|
306
|
-
} else if (popupConfig.type === 'form') {
|
|
307
|
-
// Custom form dialog
|
|
310
|
+
} else if (popupConfig.type === 'form' && !loading) {
|
|
311
|
+
// Custom form dialog - only show when not loading
|
|
308
312
|
const formHTML = `
|
|
309
313
|
<div class="${styles.stagePopupForm}">
|
|
310
314
|
${popupConfig.message ? `<p class="${styles.message}">${popupConfig.message}</p>` : ''}
|
|
311
315
|
<div id="stage-popup-fields" class="${styles.fields}">
|
|
312
|
-
${loading ? '<div class="' + styles.loading + '">Loading...</div>' : ''}
|
|
313
316
|
</div>
|
|
314
317
|
</div>
|
|
315
318
|
`;
|
|
@@ -330,6 +333,9 @@ const StagePopupModal = ({
|
|
|
330
333
|
renderFormFields();
|
|
331
334
|
},
|
|
332
335
|
preConfirm: () => {
|
|
336
|
+
// Update form data from DOM before validation
|
|
337
|
+
updateFormDataFromDOM();
|
|
338
|
+
|
|
333
339
|
if (!validateForm()) {
|
|
334
340
|
Swal.showValidationMessage('Please correct the errors below');
|
|
335
341
|
return false;
|
|
@@ -347,28 +353,36 @@ const StagePopupModal = ({
|
|
|
347
353
|
}
|
|
348
354
|
}, [show, popupConfig, loading, formData, fieldOptions]);
|
|
349
355
|
|
|
356
|
+
const updateFormDataFromDOM = () => {
|
|
357
|
+
const fieldsContainer = document.getElementById('stage-popup-fields');
|
|
358
|
+
if (!fieldsContainer) return;
|
|
359
|
+
|
|
360
|
+
const newFormData = { ...formData };
|
|
361
|
+
|
|
362
|
+
popupConfig.fields?.forEach(field => {
|
|
363
|
+
const input = fieldsContainer.querySelector(`[data-field-id="${field.id}"]`);
|
|
364
|
+
if (input) {
|
|
365
|
+
newFormData[field.id] = input.value;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
setFormData(newFormData);
|
|
370
|
+
};
|
|
371
|
+
|
|
350
372
|
const renderFormFields = () => {
|
|
351
|
-
console.log('[StagePopupModal] renderFormFields called');
|
|
352
373
|
const fieldsContainer = document.getElementById('stage-popup-fields');
|
|
353
|
-
console.log('[StagePopupModal] fieldsContainer:', fieldsContainer);
|
|
354
|
-
console.log('[StagePopupModal] loading:', loading);
|
|
355
374
|
|
|
356
375
|
if (!fieldsContainer || loading) {
|
|
357
|
-
console.log('[StagePopupModal] Early return - no container or loading');
|
|
358
376
|
return;
|
|
359
377
|
}
|
|
360
378
|
|
|
361
|
-
console.log('[StagePopupModal] Clearing container and rendering fields');
|
|
362
379
|
fieldsContainer.innerHTML = '';
|
|
363
380
|
|
|
364
|
-
console.log('[StagePopupModal] popupConfig.fields:', popupConfig.fields);
|
|
365
381
|
popupConfig.fields?.forEach(field => {
|
|
366
|
-
console.log(`[StagePopupModal] Processing field: ${field.id}, type: ${field.type}`);
|
|
367
382
|
const fieldElement = document.createElement('div');
|
|
368
383
|
fieldElement.className = styles.fieldWrapper;
|
|
369
384
|
|
|
370
385
|
const fieldHTML = renderFieldHTML(field);
|
|
371
|
-
console.log(`[StagePopupModal] Generated HTML for ${field.id}:`, fieldHTML);
|
|
372
386
|
fieldElement.innerHTML = fieldHTML;
|
|
373
387
|
|
|
374
388
|
fieldsContainer.appendChild(fieldElement);
|
|
@@ -379,6 +393,9 @@ const StagePopupModal = ({
|
|
|
379
393
|
input.addEventListener('change', (e) => {
|
|
380
394
|
handleFieldChange(field.id, e.target.value);
|
|
381
395
|
});
|
|
396
|
+
input.addEventListener('input', (e) => {
|
|
397
|
+
handleFieldChange(field.id, e.target.value);
|
|
398
|
+
});
|
|
382
399
|
}
|
|
383
400
|
});
|
|
384
401
|
};
|
|
@@ -390,7 +407,6 @@ const StagePopupModal = ({
|
|
|
390
407
|
|
|
391
408
|
switch (field.type) {
|
|
392
409
|
case 'select':
|
|
393
|
-
console.log(`[StagePopupModal] Rendering HTML options for ${field.id}:`, fieldOptions[field.id]);
|
|
394
410
|
const options = (fieldOptions[field.id] || []).map(option =>
|
|
395
411
|
`<option value="${option.id || option.value}" ${value === (option.id || option.value) ? 'selected' : ''}>
|
|
396
412
|
${option.label || option.name || option.text}
|
|
@@ -399,7 +415,7 @@ const StagePopupModal = ({
|
|
|
399
415
|
|
|
400
416
|
return `
|
|
401
417
|
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
402
|
-
<select class="${styles.select}" data-field-id="${field.id}">
|
|
418
|
+
<select class="${styles.select}" data-field-id="${field.id}" ${loading ? 'disabled' : ''}>
|
|
403
419
|
<option value="">-- Select ${field.label} --</option>
|
|
404
420
|
${options}
|
|
405
421
|
</select>
|
|
@@ -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
|
+
|