@visns-studio/visns-components 5.12.2 → 5.12.3

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/README.md CHANGED
@@ -343,7 +343,7 @@ The DataGrid component utilizes a modular column renderer system with 28 special
343
343
  - `relation` - Related data display
344
344
  - `relationArray` - Array of related data
345
345
  - `richtext` - HTML content rendering
346
- - `stage` - Stage-based displays
346
+ - `stage` - Stage-based displays with optional toggle functionality
347
347
  - `time` - Time formatting
348
348
  - `timer` - Interactive timer controls
349
349
  - `url` - Clickable links
@@ -351,7 +351,7 @@ The DataGrid component utilizes a modular column renderer system with 28 special
351
351
  - `address` - Formatted address display
352
352
  - `createdBy` - User creation tracking
353
353
  - `daterange` - Date range formatting
354
- - `stageCounter` - Visual stage progression
354
+ - `stageCounter` - Interactive visual stage progression with toggle functionality
355
355
 
356
356
  All column renderers are exported from `@visns-studio/visns-components` and can be used individually or as part of the DataGrid component.
357
357
 
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.2",
90
+ "version": "5.12.3",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -2658,6 +2658,7 @@ const DataGrid = forwardRef(
2658
2658
  return renderStageColumn({
2659
2659
  column,
2660
2660
  commonProps,
2661
+ onUpdate: handleReload,
2661
2662
  });
2662
2663
  case 'time':
2663
2664
  return renderTimeColumn({
@@ -2678,6 +2679,7 @@ const DataGrid = forwardRef(
2678
2679
  column,
2679
2680
  commonProps,
2680
2681
  navigate,
2682
+ onUpdate: handleReload,
2681
2683
  });
2682
2684
  case 'url':
2683
2685
  return renderUrlColumn({ column, commonProps });
@@ -2748,8 +2750,8 @@ const DataGrid = forwardRef(
2748
2750
  );
2749
2751
  }
2750
2752
 
2751
- // If removeFormat is true, strip HTML tags
2752
- if (column.removeFormat) {
2753
+ // If removeFormat or stripHtml is true, strip HTML tags
2754
+ if (column.removeFormat || column.stripHtml) {
2753
2755
  content = content.replace(
2754
2756
  /<\/?[^>]+(>|$)/g,
2755
2757
  ''
@@ -10,6 +10,127 @@ import {
10
10
  DEFAULT_INTELLIGENT_SORTING_CONFIG,
11
11
  } from '../../utils/relationshipSortingUtils';
12
12
 
13
+ // Stage Toggle Utility Functions
14
+ const buildToggleUrl = (urlTemplate, stageItem, rowData) => {
15
+ if (!urlTemplate) return null;
16
+
17
+ let url = urlTemplate;
18
+
19
+ // Replace common placeholders
20
+ url = url.replace('{id}', rowData.id || '');
21
+ url = url.replace('{stage_id}', stageItem?.id || '');
22
+
23
+ // Replace any other field placeholders from rowData
24
+ Object.keys(rowData).forEach(key => {
25
+ url = url.replace(`{${key}}`, rowData[key] || '');
26
+ });
27
+
28
+ // Replace any field placeholders from stageItem
29
+ if (stageItem) {
30
+ Object.keys(stageItem).forEach(key => {
31
+ url = url.replace(`{stage_${key}}`, stageItem[key] || '');
32
+ });
33
+ }
34
+
35
+ return url;
36
+ };
37
+
38
+ const getStageStatus = (stageItem, toggleConfig) => {
39
+ if (!toggleConfig || !toggleConfig.statusField) return null;
40
+
41
+ const currentValue = stageItem[toggleConfig.statusField];
42
+ const isActive = currentValue === toggleConfig.activeStatusValue;
43
+ const isInactive = currentValue === toggleConfig.inactiveStatusValue;
44
+
45
+ return {
46
+ isActive,
47
+ isInactive,
48
+ currentValue,
49
+ targetValue: isActive ? toggleConfig.inactiveStatusValue : toggleConfig.activeStatusValue
50
+ };
51
+ };
52
+
53
+ const handleStageToggle = async (stageItem, toggleConfig, rowData, onUpdate) => {
54
+ if (!toggleConfig || !toggleConfig.updateUrl) {
55
+ console.warn('Stage toggle configuration missing updateUrl');
56
+ return;
57
+ }
58
+
59
+ try {
60
+ const stageStatus = getStageStatus(stageItem, toggleConfig);
61
+ if (!stageStatus) {
62
+ console.warn('Unable to determine stage status');
63
+ return;
64
+ }
65
+
66
+ // Debug logging
67
+ console.log('Stage toggle debug:', {
68
+ stageItem,
69
+ toggleConfig,
70
+ stageStatus,
71
+ currentValue: stageStatus.currentValue,
72
+ targetValue: stageStatus.targetValue,
73
+ isActive: stageStatus.isActive
74
+ });
75
+
76
+ const url = buildToggleUrl(toggleConfig.updateUrl, stageItem, rowData);
77
+ if (!url) {
78
+ console.warn('Unable to build toggle URL');
79
+ return;
80
+ }
81
+
82
+ const method = toggleConfig.updateMethod || 'PATCH';
83
+ const updateKey = toggleConfig.updateKey || toggleConfig.statusField;
84
+
85
+ // Prepare request body
86
+ const body = {
87
+ [updateKey]: stageStatus.targetValue
88
+ };
89
+
90
+ // Get CSRF token if available
91
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
92
+
93
+ // Prepare headers
94
+ const headers = {
95
+ 'Content-Type': 'application/json',
96
+ 'Accept': 'application/json',
97
+ };
98
+
99
+ if (csrfToken) {
100
+ headers['X-CSRF-TOKEN'] = csrfToken;
101
+ }
102
+
103
+ // Make API call
104
+ const response = await fetch(url, {
105
+ method,
106
+ headers,
107
+ body: JSON.stringify(body),
108
+ });
109
+
110
+ if (!response.ok) {
111
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
112
+ }
113
+
114
+ const result = await response.json();
115
+
116
+ // Show success message
117
+ const actionText = stageStatus.isActive ? 'disabled' : 'enabled';
118
+ toast.success(`Stage ${actionText} successfully`);
119
+
120
+ // Call update callback if provided
121
+ if (onUpdate && typeof onUpdate === 'function') {
122
+ onUpdate(result);
123
+ }
124
+
125
+ return result;
126
+
127
+ } catch (error) {
128
+ console.error('Stage toggle error:', error);
129
+ toast.error(`Failed to toggle stage: ${error.message}`);
130
+ throw error;
131
+ }
132
+ };
133
+
13
134
  // Boolean Column Renderer
14
135
  export const renderBooleanColumn = ({
15
136
  column,
@@ -1030,20 +1151,139 @@ export const renderRichTextColumn = ({ column, commonProps }) => {
1030
1151
  };
1031
1152
 
1032
1153
  // Stage Column Renderer
1033
- export const renderStageColumn = ({ column, commonProps }) => {
1154
+ export const renderStageColumn = ({ column, commonProps, onUpdate }) => {
1034
1155
  return {
1035
1156
  ...commonProps,
1036
1157
  name: column.id,
1037
1158
  sortable: false,
1038
1159
  render: ({ data }) => {
1039
1160
  let stage = '';
1161
+ let activeStageIndex = -1;
1040
1162
 
1041
1163
  column.keyIds.forEach((a, b) => {
1042
1164
  if (data[a] === 1) {
1043
1165
  stage = column.valueIds[b];
1166
+ activeStageIndex = b;
1044
1167
  }
1045
1168
  });
1046
1169
 
1170
+ // Check if toggle functionality is enabled
1171
+ const isToggleable = column.toggleable && column.toggleConfig;
1172
+
1173
+ // Handle stage click for toggling
1174
+ const handleStageClick = async (e, stageIndex) => {
1175
+ e.stopPropagation();
1176
+ e.preventDefault();
1177
+
1178
+ if (!isToggleable) return;
1179
+
1180
+ try {
1181
+ // For basic stage column, we toggle the specific stage field
1182
+ const stageKey = column.keyIds[stageIndex];
1183
+ const currentValue = data[stageKey];
1184
+ const targetValue = currentValue === 1 ? 0 : 1;
1185
+
1186
+ const url = buildToggleUrl(column.toggleConfig.updateUrl, null, data);
1187
+ if (!url) {
1188
+ console.warn('Unable to build toggle URL for stage column');
1189
+ return;
1190
+ }
1191
+
1192
+ const method = column.toggleConfig.updateMethod || 'PATCH';
1193
+ const updateKey = column.toggleConfig.updateKey || stageKey;
1194
+
1195
+ // Prepare request body
1196
+ const body = {
1197
+ [updateKey]: targetValue
1198
+ };
1199
+
1200
+ // Get CSRF token if available
1201
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
1202
+
1203
+ // Prepare headers
1204
+ const headers = {
1205
+ 'Content-Type': 'application/json',
1206
+ 'Accept': 'application/json',
1207
+ };
1208
+
1209
+ if (csrfToken) {
1210
+ headers['X-CSRF-TOKEN'] = csrfToken;
1211
+ }
1212
+
1213
+ // Make API call
1214
+ const response = await fetch(url, {
1215
+ method,
1216
+ headers,
1217
+ body: JSON.stringify(body),
1218
+ });
1219
+
1220
+ if (!response.ok) {
1221
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1222
+ }
1223
+
1224
+ const result = await response.json();
1225
+
1226
+ // Show success message
1227
+ const stageLabel = column.valueIds[stageIndex];
1228
+ const actionText = currentValue === 1 ? 'disabled' : 'enabled';
1229
+ toast.success(`Stage "${stageLabel}" ${actionText} successfully`);
1230
+
1231
+ // Call update callback if provided
1232
+ if (onUpdate && typeof onUpdate === 'function') {
1233
+ onUpdate(result);
1234
+ }
1235
+
1236
+ } catch (error) {
1237
+ console.error('Stage toggle error:', error);
1238
+ toast.error(`Failed to toggle stage: ${error.message}`);
1239
+ }
1240
+ };
1241
+
1242
+ // Create clickable stage buttons if toggleable
1243
+ if (isToggleable) {
1244
+ const stageButtons = column.keyIds.map((keyId, index) => {
1245
+ const isActive = data[keyId] === 1;
1246
+ const stageLabel = column.valueIds[index];
1247
+
1248
+ return (
1249
+ <button
1250
+ key={index}
1251
+ onClick={(e) => handleStageClick(e, index)}
1252
+ style={{
1253
+ padding: '4px 8px',
1254
+ margin: '0 2px',
1255
+ borderRadius: '3px',
1256
+ border: '1px solid #ccc',
1257
+ backgroundColor: isActive ? 'var(--primary-color, #4a90e2)' : '#f8f9fa',
1258
+ color: isActive ? 'white' : '#333',
1259
+ fontSize: '11px',
1260
+ fontWeight: 'bold',
1261
+ cursor: 'pointer',
1262
+ opacity: isActive ? 1 : 0.7,
1263
+ transition: 'all 0.2s ease',
1264
+ }}
1265
+ onMouseEnter={(e) => {
1266
+ e.target.style.transform = 'scale(1.05)';
1267
+ }}
1268
+ onMouseLeave={(e) => {
1269
+ e.target.style.transform = 'scale(1)';
1270
+ }}
1271
+ data-tooltip-id="system-tooltip"
1272
+ data-tooltip-content={`Click to ${isActive ? 'disable' : 'enable'} ${stageLabel}`}
1273
+ >
1274
+ {stageLabel}
1275
+ </button>
1276
+ );
1277
+ });
1278
+
1279
+ return (
1280
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '2px' }}>
1281
+ {stageButtons}
1282
+ </div>
1283
+ );
1284
+ }
1285
+
1286
+ // Default non-toggleable behavior
1047
1287
  return (
1048
1288
  <CellWithTooltip value={stage} columnType="stage">
1049
1289
  <span>{stage}</span>
@@ -1592,7 +1832,7 @@ export const renderGalleryColumn = ({
1592
1832
  };
1593
1833
  };
1594
1834
 
1595
- export const renderStageCounterColumn = ({ column, commonProps, navigate }) => {
1835
+ export const renderStageCounterColumn = ({ column, commonProps, navigate, onUpdate }) => {
1596
1836
  return {
1597
1837
  ...commonProps,
1598
1838
  name: column.id,
@@ -1605,16 +1845,21 @@ export const renderStageCounterColumn = ({ column, commonProps, navigate }) => {
1605
1845
  if (stageData && Array.isArray(stageData)) {
1606
1846
  const stageCount = stageData.length;
1607
1847
 
1608
- // Create a clickable element if link is provided
1609
- const handleClick = (e) => {
1848
+ // Check if toggle functionality is enabled
1849
+ const isToggleable = column.stageConfig?.toggleable && column.stageConfig?.toggleConfig;
1850
+
1851
+ // Container click handler for navigation
1852
+ const handleContainerClick = (e) => {
1610
1853
  e.stopPropagation();
1611
1854
  e.preventDefault();
1612
1855
 
1856
+ // Only navigate if toggle is not enabled or if link is specifically configured
1613
1857
  if (
1614
1858
  column.link &&
1615
1859
  column.link.url &&
1616
1860
  column.link.key &&
1617
- navigate
1861
+ navigate &&
1862
+ !isToggleable
1618
1863
  ) {
1619
1864
  navigate(
1620
1865
  `${column.link.url}${data[column.link.key]}`
@@ -1622,6 +1867,26 @@ export const renderStageCounterColumn = ({ column, commonProps, navigate }) => {
1622
1867
  }
1623
1868
  };
1624
1869
 
1870
+ // Individual stage click handler for toggling
1871
+ const handleStageClick = async (e, stageItem, stageIndex) => {
1872
+ e.stopPropagation();
1873
+ e.preventDefault();
1874
+
1875
+ if (isToggleable) {
1876
+ try {
1877
+ await handleStageToggle(
1878
+ stageItem,
1879
+ column.stageConfig.toggleConfig,
1880
+ data,
1881
+ onUpdate
1882
+ );
1883
+ } catch (error) {
1884
+ // Error already handled in handleStageToggle
1885
+ console.error('Stage toggle failed:', error);
1886
+ }
1887
+ }
1888
+ };
1889
+
1625
1890
  // Create an array of squares based on the stage data
1626
1891
  const squares = [];
1627
1892
  for (let i = 0; i < stageCount; i++) {
@@ -1730,24 +1995,61 @@ export const renderStageCounterColumn = ({ column, commonProps, navigate }) => {
1730
1995
  }
1731
1996
  }
1732
1997
 
1998
+ // Get stage status for visual feedback
1999
+ const stageStatus = isToggleable ? getStageStatus(stageItem, column.stageConfig.toggleConfig) : null;
2000
+ const isStageActive = stageStatus?.isActive ?? true; // Default to active if not toggleable
2001
+
2002
+ // Debug logging for visual state
2003
+ if (isToggleable) {
2004
+ console.log('Visual state debug:', {
2005
+ stageItem,
2006
+ stageStatus,
2007
+ isStageActive,
2008
+ toggleConfig: column.stageConfig.toggleConfig
2009
+ });
2010
+ }
2011
+
2012
+ // Apply visual feedback for inactive stages
2013
+ const stageStyle = {
2014
+ width: '20px',
2015
+ height: '20px',
2016
+ backgroundColor: backgroundColor,
2017
+ borderRadius: '3px',
2018
+ margin: '0 2px',
2019
+ display: 'flex',
2020
+ justifyContent: 'center',
2021
+ alignItems: 'center',
2022
+ color: 'white',
2023
+ fontSize: '11px',
2024
+ fontWeight: 'bold',
2025
+ cursor: isToggleable ? 'pointer' : 'default',
2026
+ opacity: isStageActive ? 1 : 0.5,
2027
+ transition: 'opacity 0.2s ease, transform 0.1s ease',
2028
+ border: isToggleable ? '1px solid rgba(255,255,255,0.3)' : 'none',
2029
+ };
2030
+
2031
+ // Enhanced tooltip for toggleable stages
2032
+ const enhancedTooltipContent = isToggleable
2033
+ ? `${tooltipContent} - Click to ${isStageActive ? 'disable' : 'enable'}`
2034
+ : tooltipContent;
2035
+
1733
2036
  squares.push(
1734
2037
  <div
1735
2038
  key={i}
1736
- style={{
1737
- width: '20px',
1738
- height: '20px',
1739
- backgroundColor: backgroundColor,
1740
- borderRadius: '3px',
1741
- margin: '0 2px',
1742
- display: 'flex',
1743
- justifyContent: 'center',
1744
- alignItems: 'center',
1745
- color: 'white',
1746
- fontSize: '11px',
1747
- fontWeight: 'bold',
2039
+ style={stageStyle}
2040
+ onClick={(e) => isToggleable ? handleStageClick(e, stageItem, i) : undefined}
2041
+ onMouseEnter={(e) => {
2042
+ if (isToggleable) {
2043
+ e.target.style.transform = 'scale(1.1)';
2044
+ }
2045
+ }}
2046
+ onMouseLeave={(e) => {
2047
+ if (isToggleable) {
2048
+ e.target.style.transform = 'scale(1)';
2049
+ }
1748
2050
  }}
1749
2051
  data-tooltip-id="system-tooltip"
1750
- data-tooltip-content={tooltipContent}
2052
+ data-tooltip-content={enhancedTooltipContent}
1751
2053
  >
1752
2054
  {labelValue}
1753
2055
  </div>
@@ -1756,13 +2058,13 @@ export const renderStageCounterColumn = ({ column, commonProps, navigate }) => {
1756
2058
 
1757
2059
  return (
1758
2060
  <div
1759
- onClick={handleClick}
2061
+ onClick={handleContainerClick}
1760
2062
  style={{
1761
2063
  display: 'flex',
1762
2064
  flexWrap: 'wrap',
1763
2065
  justifyContent: 'center',
1764
2066
  alignItems: 'center',
1765
- cursor: column.link ? 'pointer' : 'default',
2067
+ cursor: (column.link && !isToggleable) ? 'pointer' : 'default',
1766
2068
  gap: '4px',
1767
2069
  padding: '2px 0',
1768
2070
  maxWidth: '100%',
@@ -305,7 +305,121 @@ function GenericDetail({
305
305
 
306
306
  if (type === 'view') {
307
307
  setActiveStage(stage.id);
308
+ } else if (type === 'stageCounter') {
309
+ // Handle new stageCounter functionality
310
+ console.log('handleStageUpdate called with stageCounter type:', { stage, data, stages });
311
+
312
+ const stageConfig = stages.stageConfig || activeTabConfig.stages?.stageConfig;
313
+ if (!stageConfig?.toggleable || !stageConfig?.toggleConfig) {
314
+ console.warn('Stage is not configured for toggling', { stageConfig });
315
+ return;
316
+ }
317
+
318
+ // Get current stage data
319
+ const stageDataArray = data[stageConfig.key] || [];
320
+ const currentStage = stageDataArray.find(s => s.id === stage.id);
321
+
322
+ console.log('Stage data lookup:', {
323
+ stageDataKey: stageConfig.key,
324
+ stageDataArray,
325
+ searchingForId: stage.id,
326
+ currentStage
327
+ });
328
+
329
+ if (!currentStage) {
330
+ console.warn('Stage not found in data');
331
+ return;
332
+ }
333
+
334
+ // Determine current and target status
335
+ const currentStatus = currentStage[stageConfig.toggleConfig.statusField];
336
+ const isActive = currentStatus === stageConfig.toggleConfig.activeStatusValue;
337
+ const targetStatus = isActive
338
+ ? stageConfig.toggleConfig.inactiveStatusValue
339
+ : stageConfig.toggleConfig.activeStatusValue;
340
+
341
+ console.log('Status toggle logic:', {
342
+ currentStatus,
343
+ activeStatusValue: stageConfig.toggleConfig.activeStatusValue,
344
+ inactiveStatusValue: stageConfig.toggleConfig.inactiveStatusValue,
345
+ isActive,
346
+ targetStatus
347
+ });
348
+
349
+ // Build API URL
350
+ let url = stageConfig.toggleConfig.updateUrl;
351
+ url = url.replace('{id}', routeParams[stages.urlParam]);
352
+ url = url.replace('{stage_id}', stage.id);
353
+
354
+ // Make API call
355
+ const res = await CustomFetch(
356
+ url,
357
+ stageConfig.toggleConfig.updateMethod || 'PUT',
358
+ {
359
+ [stageConfig.toggleConfig.updateKey]: targetStatus,
360
+ }
361
+ );
362
+
363
+ if (res.data.error === '') {
364
+ handleReload();
365
+ const actionText = isActive ? 'disabled' : 'enabled';
366
+ toast.success(`Stage "${stage.label}" ${actionText} successfully`);
367
+ } else {
368
+ toast.error(res.data.error);
369
+ }
370
+ } else if (type === 'legacy_toggle') {
371
+ // Handle legacy toggle functionality - toggle between current stage and previous
372
+ console.log('handleStageUpdate called with legacy_toggle type:', { stage, data, stages });
373
+
374
+ if (
375
+ stages &&
376
+ stages.url &&
377
+ stages.url !== '' &&
378
+ stages.key &&
379
+ stages.key !== '' &&
380
+ stages.urlParam &&
381
+ stages.urlParam !== ''
382
+ ) {
383
+ const currentStatus = data[stages.key];
384
+ let targetStatus;
385
+
386
+ // If clicking on the current stage (completed), move back one step
387
+ // If clicking on a future stage, move to that stage
388
+ if (currentStatus >= stage.id) {
389
+ // Stage is completed, move back one step (or to 0 if clicking stage 1)
390
+ targetStatus = stage.id > 1 ? stage.id - 1 : 0;
391
+ } else {
392
+ // Stage is not completed, move to this stage
393
+ targetStatus = stage.id;
394
+ }
395
+
396
+ console.log('Legacy toggle logic:', {
397
+ currentStatus,
398
+ clickedStageId: stage.id,
399
+ targetStatus,
400
+ isToggleBack: currentStatus >= stage.id
401
+ });
402
+
403
+ const url = `${stages.url}/${routeParams[stages.urlParam]}`;
404
+
405
+ const res = await CustomFetch(
406
+ url,
407
+ stages.method || 'PUT',
408
+ {
409
+ [stages.key]: targetStatus,
410
+ }
411
+ );
412
+
413
+ if (res.data.error === '') {
414
+ handleReload();
415
+ const actionText = currentStatus >= stage.id ? 'moved back from' : 'progressed to';
416
+ toast.success(`Stage ${actionText} "${stage.label}" successfully`);
417
+ } else {
418
+ toast.error(res.data.error);
419
+ }
420
+ }
308
421
  } else {
422
+ // Handle legacy stage functionality
309
423
  if (
310
424
  activeTabConfig.stages &&
311
425
  activeTabConfig.stages.url &&
@@ -2878,8 +2992,46 @@ function GenericDetail({
2878
2992
  <div className={styles.oppstatus}>
2879
2993
  <ul className={styles.opppath}>
2880
2994
  {stages.data.map((stage) => {
2881
- const isStageComplete =
2882
- data[stages.key] >= stage.id;
2995
+ let isStageComplete = false;
2996
+
2997
+ if (stages.type === 'stageCounter') {
2998
+ // For stageCounter, check individual stage status
2999
+ const stageConfig = stages.stageConfig;
3000
+ if (stageConfig && data[stageConfig.key]) {
3001
+ const stageDataArray = data[stageConfig.key];
3002
+ const currentStage = stageDataArray.find(s => s.id === stage.id);
3003
+ if (currentStage && stageConfig.toggleConfig) {
3004
+ const currentStatus = currentStage[stageConfig.toggleConfig.statusField];
3005
+ isStageComplete = currentStatus === stageConfig.toggleConfig.activeStatusValue;
3006
+
3007
+ // Debug logging
3008
+ console.log('Stage completion check:', {
3009
+ stageId: stage.id,
3010
+ stageLabel: stage.label,
3011
+ currentStatus,
3012
+ activeStatusValue: stageConfig.toggleConfig.activeStatusValue,
3013
+ isStageComplete
3014
+ });
3015
+ }
3016
+ }
3017
+ } else {
3018
+ // Legacy stage completion check
3019
+ isStageComplete = data[stages.key] >= stage.id;
3020
+ }
3021
+
3022
+ // Get color for stageCounter type
3023
+ let stageColor = null;
3024
+ if (stages.type === 'stageCounter' && stages.stageConfig?.stageColour) {
3025
+ const stageDataArray = data[stages.stageConfig.key] || [];
3026
+ const currentStage = stageDataArray.find(s => s.id === stage.id);
3027
+ if (currentStage) {
3028
+ const currentStatus = currentStage[stages.stageConfig.stageColour.key];
3029
+ const colorOption = stages.stageConfig.stageColour.options?.find(
3030
+ opt => opt.id === currentStatus
3031
+ );
3032
+ stageColor = colorOption?.colour;
3033
+ }
3034
+ }
2883
3035
 
2884
3036
  return (
2885
3037
  <li
@@ -2889,6 +3041,11 @@ function GenericDetail({
2889
3041
  ? `${styles['opp-complete']}`
2890
3042
  : ''
2891
3043
  }
3044
+ style={stageColor ? {
3045
+ borderColor: stageColor,
3046
+ color: isStageComplete ? '#fff' : stageColor,
3047
+ backgroundColor: isStageComplete ? stageColor : 'transparent'
3048
+ } : {}}
2892
3049
  onClick={(e) => {
2893
3050
  e.preventDefault();
2894
3051
 
@@ -2898,12 +3055,15 @@ function GenericDetail({
2898
3055
  );
2899
3056
  }}
2900
3057
  >
2901
- <a>
3058
+ <a style={stageColor ? {
3059
+ color: isStageComplete ? '#fff' : stageColor
3060
+ } : {}}>
2902
3061
  {stage.label}
2903
3062
  {isStageComplete && (
2904
3063
  <CircleCheck
2905
3064
  strokeWidth={2}
2906
3065
  size={16}
3066
+ color={stageColor ? '#fff' : undefined}
2907
3067
  />
2908
3068
  )}
2909
3069
  </a>
@@ -1,11 +1,6 @@
1
1
  import '../styles/global.css';
2
2
 
3
- import React, {
4
- useCallback,
5
- useEffect,
6
- useRef,
7
- useState,
8
- } from 'react';
3
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
9
4
  import { useParams, Outlet } from 'react-router-dom';
10
5
  import { toast } from 'react-toastify';
11
6
  import { saveAs } from 'file-saver';
@@ -223,7 +218,6 @@ function useConfig(setting, tabs, filters, setRowsSelected, tableSetting) {
223
218
  return { config, setConfig, subnav, setSubnav };
224
219
  }
225
220
 
226
-
227
221
  function GenericIndex({
228
222
  layout = 'full',
229
223
  setting,
@@ -494,26 +488,38 @@ function GenericIndex({
494
488
  let payload = {};
495
489
  if (config.export.payload) {
496
490
  payload = { ...config.export.payload };
497
-
491
+
498
492
  // Process special values in the payload
499
- Object.keys(payload).forEach(key => {
493
+ Object.keys(payload).forEach((key) => {
500
494
  if (payload[key] === 'currentActive()') {
501
495
  // Get the current active filter value
502
- const activeFilter = subnav?.find(filter => {
496
+ const activeFilter = subnav?.find((filter) => {
503
497
  if (filter.isParent) {
504
- return filter.children && filter.children.some(child => child.show);
498
+ return (
499
+ filter.children &&
500
+ filter.children.some((child) => child.show)
501
+ );
505
502
  }
506
503
  return filter.show === true;
507
504
  });
508
505
 
509
506
  if (activeFilter) {
510
- if (activeFilter.isParent && activeFilter.children && activeFilter.children.length > 0) {
507
+ if (
508
+ activeFilter.isParent &&
509
+ activeFilter.children &&
510
+ activeFilter.children.length > 0
511
+ ) {
511
512
  // If it's a parent filter, get the active child's value
512
- const activeChild = activeFilter.children.find(child => child.show);
513
- payload[key] = activeChild ? activeChild.value || activeChild.id : '';
513
+ const activeChild = activeFilter.children.find(
514
+ (child) => child.show
515
+ );
516
+ payload[key] = activeChild
517
+ ? activeChild.value || activeChild.id
518
+ : '';
514
519
  } else {
515
520
  // For non-parent filters, use the filter's value or id
516
- payload[key] = activeFilter.value || activeFilter.id;
521
+ payload[key] =
522
+ activeFilter.value || activeFilter.id;
517
523
  }
518
524
  } else {
519
525
  // If no active filter is found, set to empty string
@@ -571,7 +577,7 @@ function GenericIndex({
571
577
  {...configWithStyle}
572
578
  ref={gridRef}
573
579
  hoverColor={configWithStyle.style?.hoverColor}
574
- gridHeight={windowHeight * 0.65}
580
+ gridHeight={windowHeight * 0.8}
575
581
  setConfig={setConfig}
576
582
  setTableData={setTableData}
577
583
  setTotal={setTotal}
@@ -606,11 +612,18 @@ function GenericIndex({
606
612
  const renderContentBasedOnType = useCallback(() => {
607
613
  switch (config.type) {
608
614
  case 'grid':
609
- return <GenericGrid config={config} userProfile={userProfile} />;
615
+ return (
616
+ <GenericGrid config={config} userProfile={userProfile} />
617
+ );
610
618
  case 'table':
611
619
  return renderTable();
612
620
  case 'report':
613
- return <GenericReportForm config={config} userProfile={userProfile} />;
621
+ return (
622
+ <GenericReportForm
623
+ config={config}
624
+ userProfile={userProfile}
625
+ />
626
+ );
614
627
  default:
615
628
  return renderTable(); // Fallback to table if no type is specified or recognized
616
629
  }