@visns-studio/visns-components 5.12.5 → 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
|
}
|
|
@@ -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
|
+
|