@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` -
|
|
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.
|
|
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
|
-
//
|
|
1609
|
-
const
|
|
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
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
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={
|
|
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={
|
|
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
|
-
|
|
2882
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
513
|
-
|
|
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] =
|
|
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.
|
|
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
|
|
615
|
+
return (
|
|
616
|
+
<GenericGrid config={config} userProfile={userProfile} />
|
|
617
|
+
);
|
|
610
618
|
case 'table':
|
|
611
619
|
return renderTable();
|
|
612
620
|
case 'report':
|
|
613
|
-
return
|
|
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
|
}
|