@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 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.2",
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>
@@ -310,6 +310,7 @@ function VisnsDropZone({ fetchData, files, settings }) {
310
310
  handleEdit={handleEdit}
311
311
  useDragHandle
312
312
  distance={1}
313
+ showDescription={settings.showDescription}
313
314
  />
314
315
  </div>
315
316
  ) : null}
@@ -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 fileUrl =
453
- data[column.id][0]?.file_url || data[column.id]?.file_url;
454
-
455
- if (fileUrl) {
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
- <span>
458
- {parse(`<img width="150" src="${fileUrl}" />`)}
459
- </span>
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 {dataId} with actual values
2860
+ // Replace URL parameters like {key} with actual values
2861
2861
  const replaceUrlParams = (url) => {
2862
2862
  if (!url) return url;
2863
- return url.replace(
2864
- /{dataId}/g,
2865
- routeParams[urlParam] || ''
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
- return coloursValue ? `${coloursValue}40` : undefined; // Add 40% opacity
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
- // For total columns, look for a status column with color information
387
- if (column.type === 'total') {
388
- // Find the status column (typically named 'status' and has dropdown type with options)
389
- const statusColumn = columns.find(
390
- (col) =>
391
- col.type === 'dropdown' &&
392
- col.options &&
393
- col.options.some((option) => option.colour) &&
394
- (col.id === 'status' ||
395
- col.id.toLowerCase().includes('status'))
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
- if (statusColumn) {
399
- const statusValue = entry[statusColumn.id];
400
- if (statusValue) {
401
- const statusOption = statusColumn.options.find(
402
- (option) => option.id === statusValue
403
- );
404
- return statusOption?.colour || undefined;
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
- const cellBgColor =
1490
- getCellBackground(
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
- return (
1495
- <td
1496
- key={column.id}
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
- const cellBgColor = getCellBackground(
1682
- entry,
1683
- column
1684
- );
1685
- return (
1686
- <td
1687
- key={column.id}
1688
- style={{
1689
- width:
1690
- column.width || 'auto',
1691
- textAlign:
1692
- column.type ===
1693
- 'currency'
1694
- ? 'right'
1695
- : 'left',
1696
- backgroundColor:
1697
- cellBgColor,
1698
- color: cellBgColor
1699
- ? '#000'
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 && (