@visns-studio/visns-components 5.13.2 → 5.13.4
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 +1 -1
- package/src/components/CategorizedDropZone.jsx +3 -20
- package/src/components/DropZone.jsx +1 -0
- package/src/components/Gallery.jsx +4 -3
- package/src/components/columns/ColumnRenderers.jsx +349 -7
- package/src/components/generic/GenericDetail.jsx +21 -5
- package/src/components/generic/GenericEditableTable.jsx +64 -96
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.13.
|
|
90
|
+
"version": "5.13.4",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -17,6 +17,7 @@ const CategorizedDropZone = ({
|
|
|
17
17
|
entityData = {},
|
|
18
18
|
routeParams = {},
|
|
19
19
|
fetchData = null,
|
|
20
|
+
showDescription = true,
|
|
20
21
|
}) => {
|
|
21
22
|
useEffect(() => {
|
|
22
23
|
// Initialize component
|
|
@@ -118,33 +119,17 @@ const CategorizedDropZone = ({
|
|
|
118
119
|
console.log('CategorizedDropZone: Processing files', currentFiles);
|
|
119
120
|
|
|
120
121
|
currentFiles.forEach((file) => {
|
|
121
|
-
console.log('CategorizedDropZone: Processing file', {
|
|
122
|
-
file_name: file.file_name,
|
|
123
|
-
file_description: file.file_description,
|
|
124
|
-
id: file.id,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
122
|
const category = getCategoryFromDescription(
|
|
128
123
|
file.file_description
|
|
129
124
|
);
|
|
130
|
-
console.log(
|
|
131
|
-
'CategorizedDropZone: Category extracted',
|
|
132
|
-
category
|
|
133
|
-
);
|
|
134
125
|
|
|
135
126
|
if (category && initialCategorizedFiles[category]) {
|
|
136
127
|
initialCategorizedFiles[category].push(file);
|
|
137
|
-
console.log(
|
|
138
|
-
`CategorizedDropZone: Added file ${file.file_name} to category ${category}`
|
|
139
|
-
);
|
|
140
128
|
} else {
|
|
141
129
|
// For uncategorized files, add them to the first available category as fallback
|
|
142
130
|
if (finalCategories.length > 0) {
|
|
143
131
|
const firstCategory = finalCategories[0].id;
|
|
144
132
|
initialCategorizedFiles[firstCategory].push(file);
|
|
145
|
-
console.log(
|
|
146
|
-
`CategorizedDropZone: Added uncategorized file ${file.file_name} to first category ${firstCategory}`
|
|
147
|
-
);
|
|
148
133
|
} else {
|
|
149
134
|
console.log(
|
|
150
135
|
`CategorizedDropZone: No category match for file ${file.file_name}`,
|
|
@@ -160,10 +145,6 @@ const CategorizedDropZone = ({
|
|
|
160
145
|
});
|
|
161
146
|
}
|
|
162
147
|
|
|
163
|
-
console.log(
|
|
164
|
-
'CategorizedDropZone: Final categorized files',
|
|
165
|
-
initialCategorizedFiles
|
|
166
|
-
);
|
|
167
148
|
setCategorizedFiles(initialCategorizedFiles);
|
|
168
149
|
}, [entityData, dataKey, finalCategories]);
|
|
169
150
|
|
|
@@ -329,6 +310,7 @@ const CategorizedDropZone = ({
|
|
|
329
310
|
folder: folder,
|
|
330
311
|
filetype: filetype,
|
|
331
312
|
deleteUrl: deleteUrl,
|
|
313
|
+
showDescription: showDescription,
|
|
332
314
|
}}
|
|
333
315
|
/>
|
|
334
316
|
</div>
|
|
@@ -399,6 +381,7 @@ const CategorizedDropZone = ({
|
|
|
399
381
|
},
|
|
400
382
|
model:
|
|
401
383
|
entityData?.constructor?.name || 'Design',
|
|
384
|
+
showDescription: showDescription,
|
|
402
385
|
}}
|
|
403
386
|
/>
|
|
404
387
|
</div>
|
|
@@ -24,7 +24,7 @@ const truncateText = (text, maxLength) => {
|
|
|
24
24
|
|
|
25
25
|
// Sortable gallery item
|
|
26
26
|
const SortableGalleryItem = SortableElement(
|
|
27
|
-
({ value, handleEdit, handleDelete }) => {
|
|
27
|
+
({ value, handleEdit, handleDelete, showDescription = true }) => {
|
|
28
28
|
const imageUrl = value.image_url || value.file_url;
|
|
29
29
|
const title =
|
|
30
30
|
value.file_title || value.label || value.file_name || 'Untitled';
|
|
@@ -49,7 +49,7 @@ const SortableGalleryItem = SortableElement(
|
|
|
49
49
|
<h3 className={styles.galleryTitle}>
|
|
50
50
|
{truncateText(title, 30)}
|
|
51
51
|
</h3>
|
|
52
|
-
{description && (
|
|
52
|
+
{showDescription && description && (
|
|
53
53
|
<p className={styles.galleryDescription}>
|
|
54
54
|
{truncateText(description, 100)}
|
|
55
55
|
</p>
|
|
@@ -94,7 +94,7 @@ const SortableGalleryItem = SortableElement(
|
|
|
94
94
|
);
|
|
95
95
|
|
|
96
96
|
// Sortable gallery container
|
|
97
|
-
const Gallery = SortableContainer(({ items, handleEdit, handleDelete }) => {
|
|
97
|
+
const Gallery = SortableContainer(({ items, handleEdit, handleDelete, showDescription = true }) => {
|
|
98
98
|
if (!items || items.length === 0) {
|
|
99
99
|
return (
|
|
100
100
|
<div className={styles.galleryEmpty}>
|
|
@@ -138,6 +138,7 @@ const Gallery = SortableContainer(({ items, handleEdit, handleDelete }) => {
|
|
|
138
138
|
value={value}
|
|
139
139
|
handleEdit={handleEdit}
|
|
140
140
|
handleDelete={handleDelete}
|
|
141
|
+
showDescription={showDescription}
|
|
141
142
|
/>
|
|
142
143
|
))}
|
|
143
144
|
</div>
|
|
@@ -449,16 +449,358 @@ export const renderImageColumn = ({ column, commonProps }) => {
|
|
|
449
449
|
name: column.id,
|
|
450
450
|
render: ({ data }) => {
|
|
451
451
|
if (data && data[column.id]) {
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
const imageData = data[column.id];
|
|
453
|
+
|
|
454
|
+
// Handle both array and single object cases
|
|
455
|
+
const imageArray = Array.isArray(imageData) ? imageData : [imageData];
|
|
456
|
+
|
|
457
|
+
// Filter out items without file_url
|
|
458
|
+
const validImages = imageArray.filter(item => item && item.file_url);
|
|
459
|
+
|
|
460
|
+
if (validImages.length === 0) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Function to handle image click
|
|
465
|
+
const handleImageClick = (initialImageUrl, event) => {
|
|
466
|
+
event.stopPropagation(); // Prevent edit modal from opening
|
|
467
|
+
|
|
468
|
+
let currentImageIndex = validImages.findIndex(img => img.file_url === initialImageUrl);
|
|
469
|
+
|
|
470
|
+
// Create a modal backdrop
|
|
471
|
+
const modalBackdrop = document.createElement('div');
|
|
472
|
+
modalBackdrop.style.cssText = `
|
|
473
|
+
position: fixed;
|
|
474
|
+
top: 0;
|
|
475
|
+
left: 0;
|
|
476
|
+
width: 100%;
|
|
477
|
+
height: 100%;
|
|
478
|
+
background-color: rgba(0, 0, 0, 0.8);
|
|
479
|
+
display: flex;
|
|
480
|
+
justify-content: center;
|
|
481
|
+
align-items: center;
|
|
482
|
+
z-index: 9999;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
`;
|
|
485
|
+
|
|
486
|
+
// Create modal container
|
|
487
|
+
const modalContainer = document.createElement('div');
|
|
488
|
+
modalContainer.style.cssText = `
|
|
489
|
+
position: relative;
|
|
490
|
+
max-width: 90vw;
|
|
491
|
+
max-height: 90vh;
|
|
492
|
+
display: flex;
|
|
493
|
+
flex-direction: column;
|
|
494
|
+
align-items: center;
|
|
495
|
+
cursor: default;
|
|
496
|
+
`;
|
|
497
|
+
|
|
498
|
+
// Create close button
|
|
499
|
+
const closeButton = document.createElement('button');
|
|
500
|
+
closeButton.innerHTML = '×';
|
|
501
|
+
closeButton.style.cssText = `
|
|
502
|
+
position: absolute;
|
|
503
|
+
top: -40px;
|
|
504
|
+
right: 0;
|
|
505
|
+
background: rgba(255, 255, 255, 0.9);
|
|
506
|
+
border: none;
|
|
507
|
+
border-radius: 50%;
|
|
508
|
+
width: 35px;
|
|
509
|
+
height: 35px;
|
|
510
|
+
font-size: 24px;
|
|
511
|
+
font-weight: bold;
|
|
512
|
+
color: #333;
|
|
513
|
+
cursor: pointer;
|
|
514
|
+
display: flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
justify-content: center;
|
|
517
|
+
z-index: 10001;
|
|
518
|
+
transition: all 0.2s ease;
|
|
519
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
520
|
+
`;
|
|
521
|
+
|
|
522
|
+
// Create enlarged image
|
|
523
|
+
const enlargedImage = document.createElement('img');
|
|
524
|
+
enlargedImage.src = initialImageUrl;
|
|
525
|
+
enlargedImage.style.cssText = `
|
|
526
|
+
max-width: 100%;
|
|
527
|
+
max-height: 100%;
|
|
528
|
+
border-radius: 8px;
|
|
529
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
530
|
+
object-fit: contain;
|
|
531
|
+
`;
|
|
532
|
+
|
|
533
|
+
// Create navigation controls (only if multiple images)
|
|
534
|
+
let prevButton, nextButton, imageCounter;
|
|
535
|
+
if (validImages.length > 1) {
|
|
536
|
+
// Previous button
|
|
537
|
+
prevButton = document.createElement('button');
|
|
538
|
+
prevButton.innerHTML = '‹';
|
|
539
|
+
prevButton.style.cssText = `
|
|
540
|
+
position: absolute;
|
|
541
|
+
left: -50px;
|
|
542
|
+
top: 50%;
|
|
543
|
+
transform: translateY(-50%);
|
|
544
|
+
background: rgba(255, 255, 255, 0.9);
|
|
545
|
+
border: none;
|
|
546
|
+
border-radius: 50%;
|
|
547
|
+
width: 40px;
|
|
548
|
+
height: 40px;
|
|
549
|
+
font-size: 24px;
|
|
550
|
+
font-weight: bold;
|
|
551
|
+
color: #333;
|
|
552
|
+
cursor: pointer;
|
|
553
|
+
display: flex;
|
|
554
|
+
align-items: center;
|
|
555
|
+
justify-content: center;
|
|
556
|
+
z-index: 10001;
|
|
557
|
+
transition: all 0.2s ease;
|
|
558
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
559
|
+
`;
|
|
560
|
+
|
|
561
|
+
// Next button
|
|
562
|
+
nextButton = document.createElement('button');
|
|
563
|
+
nextButton.innerHTML = '›';
|
|
564
|
+
nextButton.style.cssText = `
|
|
565
|
+
position: absolute;
|
|
566
|
+
right: -50px;
|
|
567
|
+
top: 50%;
|
|
568
|
+
transform: translateY(-50%);
|
|
569
|
+
background: rgba(255, 255, 255, 0.9);
|
|
570
|
+
border: none;
|
|
571
|
+
border-radius: 50%;
|
|
572
|
+
width: 40px;
|
|
573
|
+
height: 40px;
|
|
574
|
+
font-size: 24px;
|
|
575
|
+
font-weight: bold;
|
|
576
|
+
color: #333;
|
|
577
|
+
cursor: pointer;
|
|
578
|
+
display: flex;
|
|
579
|
+
align-items: center;
|
|
580
|
+
justify-content: center;
|
|
581
|
+
z-index: 10001;
|
|
582
|
+
transition: all 0.2s ease;
|
|
583
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
584
|
+
`;
|
|
585
|
+
|
|
586
|
+
// Image counter
|
|
587
|
+
imageCounter = document.createElement('div');
|
|
588
|
+
imageCounter.style.cssText = `
|
|
589
|
+
position: absolute;
|
|
590
|
+
bottom: -40px;
|
|
591
|
+
left: 50%;
|
|
592
|
+
transform: translateX(-50%);
|
|
593
|
+
background: rgba(255, 255, 255, 0.9);
|
|
594
|
+
padding: 8px 16px;
|
|
595
|
+
border-radius: 20px;
|
|
596
|
+
font-size: 14px;
|
|
597
|
+
color: #333;
|
|
598
|
+
font-weight: 500;
|
|
599
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
600
|
+
`;
|
|
601
|
+
|
|
602
|
+
// Update image counter
|
|
603
|
+
const updateCounter = () => {
|
|
604
|
+
imageCounter.textContent = `${currentImageIndex + 1} / ${validImages.length}`;
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
updateCounter();
|
|
608
|
+
|
|
609
|
+
// Navigation functions
|
|
610
|
+
const updateImage = () => {
|
|
611
|
+
enlargedImage.src = validImages[currentImageIndex].file_url;
|
|
612
|
+
updateCounter();
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const goToPrevious = () => {
|
|
616
|
+
currentImageIndex = (currentImageIndex - 1 + validImages.length) % validImages.length;
|
|
617
|
+
updateImage();
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const goToNext = () => {
|
|
621
|
+
currentImageIndex = (currentImageIndex + 1) % validImages.length;
|
|
622
|
+
updateImage();
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// Add event listeners
|
|
626
|
+
prevButton.addEventListener('click', goToPrevious);
|
|
627
|
+
nextButton.addEventListener('click', goToNext);
|
|
628
|
+
|
|
629
|
+
// Add buttons to container
|
|
630
|
+
modalContainer.appendChild(prevButton);
|
|
631
|
+
modalContainer.appendChild(nextButton);
|
|
632
|
+
modalContainer.appendChild(imageCounter);
|
|
633
|
+
|
|
634
|
+
// Keyboard navigation
|
|
635
|
+
const handleKeyDown = (e) => {
|
|
636
|
+
if (e.key === 'ArrowLeft') {
|
|
637
|
+
goToPrevious();
|
|
638
|
+
} else if (e.key === 'ArrowRight') {
|
|
639
|
+
goToNext();
|
|
640
|
+
} else if (e.key === 'Escape') {
|
|
641
|
+
closeModal();
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
645
|
+
|
|
646
|
+
// Store cleanup function
|
|
647
|
+
modalBackdrop.keydownHandler = handleKeyDown;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Close modal function
|
|
651
|
+
const closeModal = () => {
|
|
652
|
+
document.body.removeChild(modalBackdrop);
|
|
653
|
+
if (modalBackdrop.keydownHandler) {
|
|
654
|
+
document.removeEventListener('keydown', modalBackdrop.keydownHandler);
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// Close button event
|
|
659
|
+
closeButton.addEventListener('click', closeModal);
|
|
660
|
+
|
|
661
|
+
// Close modal when clicking backdrop
|
|
662
|
+
modalBackdrop.addEventListener('click', (e) => {
|
|
663
|
+
if (e.target === modalBackdrop) {
|
|
664
|
+
closeModal();
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// Close modal on escape key (for single image)
|
|
669
|
+
if (validImages.length === 1) {
|
|
670
|
+
const handleKeyDown = (e) => {
|
|
671
|
+
if (e.key === 'Escape') {
|
|
672
|
+
closeModal();
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
676
|
+
modalBackdrop.keydownHandler = handleKeyDown;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Add hover effects
|
|
680
|
+
const addHoverEffects = (button) => {
|
|
681
|
+
button.addEventListener('mouseenter', () => {
|
|
682
|
+
button.style.background = 'rgba(255, 255, 255, 1)';
|
|
683
|
+
button.style.transform = button.style.transform.replace('translateY(-50%)', 'translateY(-50%) scale(1.1)');
|
|
684
|
+
});
|
|
685
|
+
button.addEventListener('mouseleave', () => {
|
|
686
|
+
button.style.background = 'rgba(255, 255, 255, 0.9)';
|
|
687
|
+
button.style.transform = button.style.transform.replace('scale(1.1)', '');
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
addHoverEffects(closeButton);
|
|
692
|
+
if (prevButton) addHoverEffects(prevButton);
|
|
693
|
+
if (nextButton) addHoverEffects(nextButton);
|
|
694
|
+
|
|
695
|
+
// Assemble modal
|
|
696
|
+
modalContainer.appendChild(closeButton);
|
|
697
|
+
modalContainer.appendChild(enlargedImage);
|
|
698
|
+
modalBackdrop.appendChild(modalContainer);
|
|
699
|
+
document.body.appendChild(modalBackdrop);
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// If single image, display it as thumbnail
|
|
703
|
+
if (validImages.length === 1) {
|
|
456
704
|
return (
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
705
|
+
<div
|
|
706
|
+
style={{
|
|
707
|
+
position: 'relative',
|
|
708
|
+
display: 'inline-block',
|
|
709
|
+
borderRadius: '4px',
|
|
710
|
+
overflow: 'hidden',
|
|
711
|
+
cursor: 'pointer',
|
|
712
|
+
transition: 'all 0.2s ease'
|
|
713
|
+
}}
|
|
714
|
+
onClick={(event) => handleImageClick(validImages[0].file_url, event)}
|
|
715
|
+
onMouseEnter={(e) => {
|
|
716
|
+
e.target.style.transform = 'scale(1.05)';
|
|
717
|
+
e.target.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
|
|
718
|
+
}}
|
|
719
|
+
onMouseLeave={(e) => {
|
|
720
|
+
e.target.style.transform = 'scale(1)';
|
|
721
|
+
e.target.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.1)';
|
|
722
|
+
}}
|
|
723
|
+
>
|
|
724
|
+
<img
|
|
725
|
+
src={validImages[0].file_url}
|
|
726
|
+
alt="Click to enlarge"
|
|
727
|
+
style={{
|
|
728
|
+
width: '50px',
|
|
729
|
+
height: '50px',
|
|
730
|
+
objectFit: 'cover',
|
|
731
|
+
borderRadius: '4px',
|
|
732
|
+
border: '1px solid #ddd',
|
|
733
|
+
display: 'block'
|
|
734
|
+
}}
|
|
735
|
+
/>
|
|
736
|
+
</div>
|
|
460
737
|
);
|
|
461
738
|
}
|
|
739
|
+
|
|
740
|
+
// If multiple images, display them in a grid
|
|
741
|
+
return (
|
|
742
|
+
<div style={{
|
|
743
|
+
display: 'grid',
|
|
744
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(40px, 1fr))',
|
|
745
|
+
gap: '2px',
|
|
746
|
+
maxWidth: '120px'
|
|
747
|
+
}}>
|
|
748
|
+
{validImages.map((image, index) => (
|
|
749
|
+
<div
|
|
750
|
+
key={index}
|
|
751
|
+
style={{
|
|
752
|
+
position: 'relative',
|
|
753
|
+
display: 'inline-block',
|
|
754
|
+
borderRadius: '4px',
|
|
755
|
+
overflow: 'hidden',
|
|
756
|
+
cursor: 'pointer',
|
|
757
|
+
transition: 'all 0.2s ease'
|
|
758
|
+
}}
|
|
759
|
+
onClick={(event) => handleImageClick(image.file_url, event)}
|
|
760
|
+
onMouseEnter={(e) => {
|
|
761
|
+
e.currentTarget.style.transform = 'scale(1.1)';
|
|
762
|
+
e.currentTarget.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
|
|
763
|
+
e.currentTarget.style.zIndex = '10';
|
|
764
|
+
}}
|
|
765
|
+
onMouseLeave={(e) => {
|
|
766
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
767
|
+
e.currentTarget.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.1)';
|
|
768
|
+
e.currentTarget.style.zIndex = '1';
|
|
769
|
+
}}
|
|
770
|
+
>
|
|
771
|
+
<img
|
|
772
|
+
src={image.file_url}
|
|
773
|
+
alt={`Click to enlarge (${index + 1}/${validImages.length})`}
|
|
774
|
+
style={{
|
|
775
|
+
width: '40px',
|
|
776
|
+
height: '40px',
|
|
777
|
+
objectFit: 'cover',
|
|
778
|
+
borderRadius: '4px',
|
|
779
|
+
border: '1px solid #ddd',
|
|
780
|
+
display: 'block'
|
|
781
|
+
}}
|
|
782
|
+
/>
|
|
783
|
+
{index === 0 && validImages.length > 1 && (
|
|
784
|
+
<div
|
|
785
|
+
style={{
|
|
786
|
+
position: 'absolute',
|
|
787
|
+
bottom: '1px',
|
|
788
|
+
left: '1px',
|
|
789
|
+
background: 'rgba(0, 0, 0, 0.7)',
|
|
790
|
+
color: 'white',
|
|
791
|
+
borderRadius: '8px',
|
|
792
|
+
padding: '1px 4px',
|
|
793
|
+
fontSize: '8px',
|
|
794
|
+
fontWeight: 'bold'
|
|
795
|
+
}}
|
|
796
|
+
>
|
|
797
|
+
+{validImages.length - 1}
|
|
798
|
+
</div>
|
|
799
|
+
)}
|
|
800
|
+
</div>
|
|
801
|
+
))}
|
|
802
|
+
</div>
|
|
803
|
+
);
|
|
462
804
|
}
|
|
463
805
|
|
|
464
806
|
return null;
|
|
@@ -2857,13 +2857,28 @@ function GenericDetail({
|
|
|
2857
2857
|
return null;
|
|
2858
2858
|
}
|
|
2859
2859
|
case 'categorized_dropzone':
|
|
2860
|
-
// Replace URL parameters like {
|
|
2860
|
+
// Replace URL parameters like {key} with actual values
|
|
2861
2861
|
const replaceUrlParams = (url) => {
|
|
2862
2862
|
if (!url) return url;
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2863
|
+
|
|
2864
|
+
// Handle the new urlKey parameter
|
|
2865
|
+
const urlKey = activeTabConfig.urlKey || 'dataId';
|
|
2866
|
+
|
|
2867
|
+
let replacementValue;
|
|
2868
|
+
if (urlKey === 'dataId') {
|
|
2869
|
+
// Use routeParams for dataId (backward compatibility)
|
|
2870
|
+
replacementValue = routeParams[urlParam] || '';
|
|
2871
|
+
} else {
|
|
2872
|
+
// Use data object for other keys, supporting dot notation
|
|
2873
|
+
replacementValue = urlKey.split('.').reduce((obj, key) => {
|
|
2874
|
+
return obj && obj[key] !== undefined ? obj[key] : '';
|
|
2875
|
+
}, data);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
// Replace both {key} and {dataId} patterns for backward compatibility
|
|
2879
|
+
return url
|
|
2880
|
+
.replace(/{key}/g, replacementValue)
|
|
2881
|
+
.replace(/{dataId}/g, replacementValue);
|
|
2867
2882
|
};
|
|
2868
2883
|
|
|
2869
2884
|
return (
|
|
@@ -2891,6 +2906,7 @@ function GenericDetail({
|
|
|
2891
2906
|
entityData={data}
|
|
2892
2907
|
routeParams={routeParams}
|
|
2893
2908
|
fetchData={handleReload}
|
|
2909
|
+
showDescription={activeTabConfig.showDescription}
|
|
2894
2910
|
/>
|
|
2895
2911
|
);
|
|
2896
2912
|
case 'scheduling':
|
|
@@ -362,53 +362,44 @@ const GenericEditableTable = ({
|
|
|
362
362
|
const sortBySortOrder = (dataArray) =>
|
|
363
363
|
[...dataArray].sort((a, b) => a.sort_order - b.sort_order);
|
|
364
364
|
|
|
365
|
-
// Return background color from row_colour field or colours column
|
|
365
|
+
// Return background color from row_colour field or colours column, or status color
|
|
366
366
|
const getRowBackground = (entry) => {
|
|
367
367
|
// Check if there's a colour picked from the colours column
|
|
368
368
|
const coloursValue = entry.colours || entry.row_colour;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
// Helper function to get background color for dropdown and total cells
|
|
373
|
-
const getCellBackground = (entry, column) => {
|
|
374
|
-
// For dropdown columns, get color from selected option
|
|
375
|
-
if (column.type === 'dropdown' && column.options) {
|
|
376
|
-
const selectedValue = entry[column.id];
|
|
377
|
-
if (!selectedValue) return undefined;
|
|
378
|
-
|
|
379
|
-
const selectedOption = column.options.find(
|
|
380
|
-
(option) => option.id === selectedValue
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
return selectedOption?.colour || undefined;
|
|
369
|
+
if (coloursValue) {
|
|
370
|
+
return `${coloursValue}40`; // Add 40% opacity
|
|
384
371
|
}
|
|
372
|
+
|
|
373
|
+
// Check for status color if no manual color is set
|
|
374
|
+
const statusColor = getStatusColor(entry);
|
|
375
|
+
return statusColor ? `${statusColor}40` : undefined; // Add 40% opacity
|
|
376
|
+
};
|
|
385
377
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
);
|
|
378
|
+
// Helper function to get status color for an entry
|
|
379
|
+
const getStatusColor = (entry) => {
|
|
380
|
+
// Find the status column (typically named 'status' and has dropdown type with options)
|
|
381
|
+
const statusColumn = columns.find(
|
|
382
|
+
(col) =>
|
|
383
|
+
col.type === 'dropdown' &&
|
|
384
|
+
col.options &&
|
|
385
|
+
col.options.some((option) => option.colour) &&
|
|
386
|
+
(col.id === 'status' || col.id.toLowerCase().includes('status'))
|
|
387
|
+
);
|
|
397
388
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
389
|
+
if (statusColumn) {
|
|
390
|
+
const statusValue = entry[statusColumn.id];
|
|
391
|
+
if (statusValue) {
|
|
392
|
+
const statusOption = statusColumn.options.find(
|
|
393
|
+
(option) => option.id === statusValue
|
|
394
|
+
);
|
|
395
|
+
return statusOption?.colour || undefined;
|
|
406
396
|
}
|
|
407
397
|
}
|
|
408
|
-
|
|
398
|
+
|
|
409
399
|
return undefined;
|
|
410
400
|
};
|
|
411
401
|
|
|
402
|
+
|
|
412
403
|
// Calculate total project cost (sum of all entries excluding margin row)
|
|
413
404
|
const calculateTotalProjectCost = (dataEntries) => {
|
|
414
405
|
const totalColIndex = columns.findIndex((c) => c.type === 'total');
|
|
@@ -1485,36 +1476,24 @@ const GenericEditableTable = ({
|
|
|
1485
1476
|
}}
|
|
1486
1477
|
>
|
|
1487
1478
|
{renderRowCheckbox(entry)}
|
|
1488
|
-
{columns.map((column) =>
|
|
1489
|
-
|
|
1490
|
-
|
|
1479
|
+
{columns.map((column) => (
|
|
1480
|
+
<td
|
|
1481
|
+
key={column.id}
|
|
1482
|
+
style={{
|
|
1483
|
+
textAlign:
|
|
1484
|
+
column.type ===
|
|
1485
|
+
'currency'
|
|
1486
|
+
? 'right'
|
|
1487
|
+
: 'left',
|
|
1488
|
+
}}
|
|
1489
|
+
>
|
|
1490
|
+
{renderScheduledTableField(
|
|
1491
1491
|
entry,
|
|
1492
|
-
column
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
style={{
|
|
1498
|
-
textAlign:
|
|
1499
|
-
column.type ===
|
|
1500
|
-
'currency'
|
|
1501
|
-
? 'right'
|
|
1502
|
-
: 'left',
|
|
1503
|
-
backgroundColor:
|
|
1504
|
-
cellBgColor,
|
|
1505
|
-
color: cellBgColor
|
|
1506
|
-
? '#000'
|
|
1507
|
-
: 'inherit',
|
|
1508
|
-
}}
|
|
1509
|
-
>
|
|
1510
|
-
{renderScheduledTableField(
|
|
1511
|
-
entry,
|
|
1512
|
-
column,
|
|
1513
|
-
entry.keyCounter
|
|
1514
|
-
)}
|
|
1515
|
-
</td>
|
|
1516
|
-
);
|
|
1517
|
-
})}
|
|
1492
|
+
column,
|
|
1493
|
+
entry.keyCounter
|
|
1494
|
+
)}
|
|
1495
|
+
</td>
|
|
1496
|
+
))}
|
|
1518
1497
|
<td>
|
|
1519
1498
|
<div
|
|
1520
1499
|
className={styles.tdactions}
|
|
@@ -1677,37 +1656,26 @@ const GenericEditableTable = ({
|
|
|
1677
1656
|
}}
|
|
1678
1657
|
>
|
|
1679
1658
|
{renderRowCheckbox(entry)}
|
|
1680
|
-
{columns.map((column) =>
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
: 'inherit',
|
|
1701
|
-
}}
|
|
1702
|
-
>
|
|
1703
|
-
{renderScheduledTableField(
|
|
1704
|
-
entry,
|
|
1705
|
-
column,
|
|
1706
|
-
idx
|
|
1707
|
-
)}
|
|
1708
|
-
</td>
|
|
1709
|
-
);
|
|
1710
|
-
})}
|
|
1659
|
+
{columns.map((column) => (
|
|
1660
|
+
<td
|
|
1661
|
+
key={column.id}
|
|
1662
|
+
style={{
|
|
1663
|
+
width:
|
|
1664
|
+
column.width || 'auto',
|
|
1665
|
+
textAlign:
|
|
1666
|
+
column.type ===
|
|
1667
|
+
'currency'
|
|
1668
|
+
? 'right'
|
|
1669
|
+
: 'left',
|
|
1670
|
+
}}
|
|
1671
|
+
>
|
|
1672
|
+
{renderScheduledTableField(
|
|
1673
|
+
entry,
|
|
1674
|
+
column,
|
|
1675
|
+
idx
|
|
1676
|
+
)}
|
|
1677
|
+
</td>
|
|
1678
|
+
))}
|
|
1711
1679
|
<td>
|
|
1712
1680
|
<div className={styles.tdactions}>
|
|
1713
1681
|
{idx > 0 && (
|