@visns-studio/visns-components 5.12.14 → 5.12.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
88
88
|
},
|
|
89
89
|
"name": "@visns-studio/visns-components",
|
|
90
|
-
"version": "5.12.
|
|
90
|
+
"version": "5.12.16",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -16,48 +16,78 @@ const CategorizedDropZone = ({
|
|
|
16
16
|
categoriesWhere = [],
|
|
17
17
|
entityData = {},
|
|
18
18
|
routeParams = {},
|
|
19
|
-
fetchData = null
|
|
19
|
+
fetchData = null,
|
|
20
20
|
}) => {
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Initialize component
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
// Helper function to get nested object value using dot notation
|
|
26
|
+
const getNestedValue = (obj, path) => {
|
|
27
|
+
return path.split('.').reduce((current, key) => {
|
|
28
|
+
return current && current[key] !== undefined ? current[key] : null;
|
|
29
|
+
}, obj);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Helper function to set nested object value using dot notation
|
|
33
|
+
const setNestedValue = (obj, path, value) => {
|
|
34
|
+
const keys = path.split('.');
|
|
35
|
+
const lastKey = keys.pop();
|
|
36
|
+
const target = keys.reduce((current, key) => {
|
|
37
|
+
if (!current[key]) current[key] = {};
|
|
38
|
+
return current[key];
|
|
39
|
+
}, obj);
|
|
40
|
+
target[lastKey] = value;
|
|
41
|
+
};
|
|
21
42
|
const [activeCategory, setActiveCategory] = useState(null);
|
|
22
43
|
const [categorizedFiles, setCategorizedFiles] = useState({});
|
|
23
44
|
const [dynamicCategories, setDynamicCategories] = useState([]);
|
|
24
45
|
const [loadingCategories, setLoadingCategories] = useState(false);
|
|
25
46
|
|
|
26
47
|
// Get final categories (dynamic or static)
|
|
27
|
-
const finalCategories =
|
|
48
|
+
const finalCategories =
|
|
49
|
+
dynamicCategories.length > 0 ? dynamicCategories : categories;
|
|
28
50
|
|
|
29
51
|
// Fetch categories from API if categoriesUrl is provided
|
|
30
52
|
useEffect(() => {
|
|
31
53
|
if (categoriesUrl) {
|
|
32
54
|
setLoadingCategories(true);
|
|
33
|
-
|
|
55
|
+
|
|
34
56
|
const requestData = {};
|
|
35
57
|
if (categoriesWhere && categoriesWhere.length > 0) {
|
|
36
58
|
requestData.where = categoriesWhere;
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
CustomFetch(categoriesUrl, 'POST', requestData)
|
|
40
|
-
.then(response => {
|
|
62
|
+
.then((response) => {
|
|
41
63
|
if (response.data && !response.data.error) {
|
|
42
|
-
const categoriesData = Array.isArray(response.data.data)
|
|
43
|
-
|
|
44
|
-
|
|
64
|
+
const categoriesData = Array.isArray(response.data.data)
|
|
65
|
+
? response.data.data
|
|
66
|
+
: Array.isArray(response.data)
|
|
67
|
+
? response.data
|
|
68
|
+
: [];
|
|
69
|
+
|
|
45
70
|
// Transform API data to match expected format
|
|
46
|
-
const transformedCategories = categoriesData.map(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
const transformedCategories = categoriesData.map(
|
|
72
|
+
(cat) => ({
|
|
73
|
+
id: cat.slug || cat.id,
|
|
74
|
+
label: cat.name || cat.label,
|
|
75
|
+
description: cat.description || '',
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
|
|
52
79
|
setDynamicCategories(transformedCategories);
|
|
53
|
-
|
|
80
|
+
|
|
54
81
|
// Set first category as active if none selected
|
|
55
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
!activeCategory &&
|
|
84
|
+
transformedCategories.length > 0
|
|
85
|
+
) {
|
|
56
86
|
setActiveCategory(transformedCategories[0].id);
|
|
57
87
|
}
|
|
58
88
|
}
|
|
59
89
|
})
|
|
60
|
-
.catch(error => {
|
|
90
|
+
.catch((error) => {
|
|
61
91
|
console.error('Failed to load categories:', error);
|
|
62
92
|
})
|
|
63
93
|
.finally(() => {
|
|
@@ -70,76 +100,108 @@ const CategorizedDropZone = ({
|
|
|
70
100
|
}, [categoriesUrl, categoriesWhere, categories, activeCategory]);
|
|
71
101
|
|
|
72
102
|
useEffect(() => {
|
|
103
|
+
const currentFiles = getNestedValue(entityData, dataKey);
|
|
73
104
|
console.log('CategorizedDropZone: Initializing files', {
|
|
74
105
|
finalCategories,
|
|
75
|
-
entityData:
|
|
76
|
-
dataKey
|
|
106
|
+
entityData: currentFiles,
|
|
107
|
+
dataKey,
|
|
77
108
|
});
|
|
78
|
-
|
|
109
|
+
|
|
79
110
|
// Initialize categorized files structure
|
|
80
111
|
const initialCategorizedFiles = {};
|
|
81
|
-
finalCategories.forEach(category => {
|
|
112
|
+
finalCategories.forEach((category) => {
|
|
82
113
|
initialCategorizedFiles[category.id] = [];
|
|
83
114
|
});
|
|
84
|
-
|
|
115
|
+
|
|
85
116
|
// If there are existing files, categorize them based on file_description
|
|
86
|
-
if (
|
|
87
|
-
console.log('CategorizedDropZone: Processing files',
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
if (currentFiles && Array.isArray(currentFiles)) {
|
|
118
|
+
console.log('CategorizedDropZone: Processing files', currentFiles);
|
|
119
|
+
|
|
120
|
+
currentFiles.forEach((file) => {
|
|
90
121
|
console.log('CategorizedDropZone: Processing file', {
|
|
91
122
|
file_name: file.file_name,
|
|
92
123
|
file_description: file.file_description,
|
|
93
|
-
id: file.id
|
|
124
|
+
id: file.id,
|
|
94
125
|
});
|
|
95
|
-
|
|
96
|
-
const category = getCategoryFromDescription(
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
|
|
127
|
+
const category = getCategoryFromDescription(
|
|
128
|
+
file.file_description
|
|
129
|
+
);
|
|
130
|
+
console.log(
|
|
131
|
+
'CategorizedDropZone: Category extracted',
|
|
132
|
+
category
|
|
133
|
+
);
|
|
134
|
+
|
|
99
135
|
if (category && initialCategorizedFiles[category]) {
|
|
100
136
|
initialCategorizedFiles[category].push(file);
|
|
101
|
-
console.log(
|
|
137
|
+
console.log(
|
|
138
|
+
`CategorizedDropZone: Added file ${file.file_name} to category ${category}`
|
|
139
|
+
);
|
|
102
140
|
} else {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
141
|
+
// For uncategorized files, add them to the first available category as fallback
|
|
142
|
+
if (finalCategories.length > 0) {
|
|
143
|
+
const firstCategory = finalCategories[0].id;
|
|
144
|
+
initialCategorizedFiles[firstCategory].push(file);
|
|
145
|
+
console.log(
|
|
146
|
+
`CategorizedDropZone: Added uncategorized file ${file.file_name} to first category ${firstCategory}`
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
console.log(
|
|
150
|
+
`CategorizedDropZone: No category match for file ${file.file_name}`,
|
|
151
|
+
{
|
|
152
|
+
extractedCategory: category,
|
|
153
|
+
availableCategories: Object.keys(
|
|
154
|
+
initialCategorizedFiles
|
|
155
|
+
),
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
107
159
|
}
|
|
108
160
|
});
|
|
109
161
|
}
|
|
110
|
-
|
|
111
|
-
console.log(
|
|
162
|
+
|
|
163
|
+
console.log(
|
|
164
|
+
'CategorizedDropZone: Final categorized files',
|
|
165
|
+
initialCategorizedFiles
|
|
166
|
+
);
|
|
112
167
|
setCategorizedFiles(initialCategorizedFiles);
|
|
113
168
|
}, [entityData, dataKey, finalCategories]);
|
|
114
169
|
|
|
115
170
|
const getCategoryFromDescription = (description) => {
|
|
116
171
|
if (!description) return null;
|
|
117
|
-
|
|
172
|
+
|
|
118
173
|
// Check if the description contains a category identifier (handles both slugs and numeric IDs)
|
|
119
174
|
const categoryMatch = description.match(/^category:([^-\s]+)/i);
|
|
120
175
|
if (categoryMatch) {
|
|
121
176
|
const extractedId = categoryMatch[1];
|
|
122
|
-
|
|
177
|
+
|
|
123
178
|
// First, try to find by exact ID match
|
|
124
|
-
const exactMatch = finalCategories.find(
|
|
179
|
+
const exactMatch = finalCategories.find(
|
|
180
|
+
(cat) => cat.id === extractedId
|
|
181
|
+
);
|
|
125
182
|
if (exactMatch) return extractedId;
|
|
126
|
-
|
|
183
|
+
|
|
127
184
|
// If no exact match, try to find by numeric ID if extracted ID is numeric
|
|
128
185
|
if (/^\d+$/.test(extractedId)) {
|
|
129
|
-
const numericMatch = finalCategories.find(
|
|
186
|
+
const numericMatch = finalCategories.find(
|
|
187
|
+
(cat) => cat.id == extractedId
|
|
188
|
+
);
|
|
130
189
|
if (numericMatch) return numericMatch.id;
|
|
131
190
|
}
|
|
132
|
-
|
|
191
|
+
|
|
133
192
|
// Log for debugging
|
|
134
193
|
console.log('Category extraction debug:', {
|
|
135
194
|
description,
|
|
136
195
|
extractedId,
|
|
137
|
-
availableCategories: finalCategories.map(c => ({
|
|
196
|
+
availableCategories: finalCategories.map((c) => ({
|
|
197
|
+
id: c.id,
|
|
198
|
+
label: c.label,
|
|
199
|
+
})),
|
|
138
200
|
});
|
|
139
201
|
}
|
|
140
|
-
|
|
202
|
+
|
|
141
203
|
// Fallback: check if description matches any category label
|
|
142
|
-
const matchedCategory = finalCategories.find(cat =>
|
|
204
|
+
const matchedCategory = finalCategories.find((cat) =>
|
|
143
205
|
description.toLowerCase().includes(cat.label.toLowerCase())
|
|
144
206
|
);
|
|
145
207
|
return matchedCategory?.id || null;
|
|
@@ -155,30 +217,40 @@ const CategorizedDropZone = ({
|
|
|
155
217
|
|
|
156
218
|
const handleFileUpload = (files) => {
|
|
157
219
|
// Add category prefix to file descriptions
|
|
158
|
-
const activeCategoryData = finalCategories.find(
|
|
220
|
+
const activeCategoryData = finalCategories.find(
|
|
221
|
+
(cat) => cat.id === activeCategory
|
|
222
|
+
);
|
|
159
223
|
const categoryPrefix = `category:${activeCategory}`;
|
|
160
|
-
|
|
224
|
+
|
|
161
225
|
// Update the files with category information
|
|
162
|
-
const updatedFiles = files.map(file => ({
|
|
226
|
+
const updatedFiles = files.map((file) => ({
|
|
163
227
|
...file,
|
|
164
|
-
file_description: file.file_description
|
|
165
|
-
`${categoryPrefix} - ${file.file_description}`
|
|
166
|
-
`${categoryPrefix} - ${
|
|
228
|
+
file_description: file.file_description
|
|
229
|
+
? `${categoryPrefix} - ${file.file_description}`
|
|
230
|
+
: `${categoryPrefix} - ${
|
|
231
|
+
activeCategoryData?.description ||
|
|
232
|
+
activeCategoryData?.label
|
|
233
|
+
}`,
|
|
167
234
|
}));
|
|
168
|
-
|
|
235
|
+
|
|
169
236
|
// Update the categorized files state
|
|
170
|
-
setCategorizedFiles(prev => ({
|
|
237
|
+
setCategorizedFiles((prev) => ({
|
|
171
238
|
...prev,
|
|
172
|
-
[activeCategory]: [
|
|
239
|
+
[activeCategory]: [
|
|
240
|
+
...(prev[activeCategory] || []),
|
|
241
|
+
...updatedFiles,
|
|
242
|
+
],
|
|
173
243
|
}));
|
|
174
244
|
};
|
|
175
245
|
|
|
176
246
|
const handleFileDelete = (fileId) => {
|
|
177
247
|
// Remove file from the appropriate category
|
|
178
|
-
setCategorizedFiles(prev => {
|
|
248
|
+
setCategorizedFiles((prev) => {
|
|
179
249
|
const updated = { ...prev };
|
|
180
|
-
Object.keys(updated).forEach(categoryId => {
|
|
181
|
-
updated[categoryId] = updated[categoryId].filter(
|
|
250
|
+
Object.keys(updated).forEach((categoryId) => {
|
|
251
|
+
updated[categoryId] = updated[categoryId].filter(
|
|
252
|
+
(file) => file.id !== fileId
|
|
253
|
+
);
|
|
182
254
|
});
|
|
183
255
|
return updated;
|
|
184
256
|
});
|
|
@@ -186,57 +258,65 @@ const CategorizedDropZone = ({
|
|
|
186
258
|
|
|
187
259
|
const handleFileUpdate = (updatedFile) => {
|
|
188
260
|
// Update file in the appropriate category
|
|
189
|
-
const newCategory = getCategoryFromDescription(
|
|
190
|
-
|
|
191
|
-
|
|
261
|
+
const newCategory = getCategoryFromDescription(
|
|
262
|
+
updatedFile.file_description
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
setCategorizedFiles((prev) => {
|
|
192
266
|
const updated = { ...prev };
|
|
193
|
-
|
|
267
|
+
|
|
194
268
|
// Remove from old category
|
|
195
|
-
Object.keys(updated).forEach(categoryId => {
|
|
196
|
-
updated[categoryId] = updated[categoryId].filter(
|
|
269
|
+
Object.keys(updated).forEach((categoryId) => {
|
|
270
|
+
updated[categoryId] = updated[categoryId].filter(
|
|
271
|
+
(file) => file.id !== updatedFile.id
|
|
272
|
+
);
|
|
197
273
|
});
|
|
198
|
-
|
|
274
|
+
|
|
199
275
|
// Add to new category
|
|
200
276
|
if (newCategory && updated[newCategory]) {
|
|
201
277
|
updated[newCategory].push(updatedFile);
|
|
202
278
|
}
|
|
203
|
-
|
|
279
|
+
|
|
204
280
|
return updated;
|
|
205
281
|
});
|
|
206
282
|
};
|
|
207
283
|
|
|
208
284
|
// Prepare entity data for the current category
|
|
209
|
-
const currentCategoryEntityData =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
285
|
+
const currentCategoryEntityData = JSON.parse(JSON.stringify(entityData));
|
|
286
|
+
setNestedValue(
|
|
287
|
+
currentCategoryEntityData,
|
|
288
|
+
dataKey,
|
|
289
|
+
getCurrentCategoryFiles()
|
|
290
|
+
);
|
|
213
291
|
|
|
214
292
|
if (loadingCategories) {
|
|
215
293
|
return (
|
|
216
294
|
<div className={styles.categorizedDropZone}>
|
|
217
|
-
<div className={styles.loadingState}>
|
|
218
|
-
Loading categories...
|
|
219
|
-
</div>
|
|
295
|
+
<div className={styles.loadingState}>Loading categories...</div>
|
|
220
296
|
</div>
|
|
221
297
|
);
|
|
222
298
|
}
|
|
223
299
|
|
|
224
300
|
if (finalCategories.length === 0) {
|
|
225
301
|
// No categories available - show a simple dropzone without categorization
|
|
302
|
+
const currentFiles = getNestedValue(entityData, dataKey) || [];
|
|
226
303
|
console.log('CategorizedDropZone: No categories, showing all files', {
|
|
227
304
|
dataKey,
|
|
228
|
-
files:
|
|
229
|
-
entityData
|
|
305
|
+
files: currentFiles,
|
|
306
|
+
entityData,
|
|
230
307
|
});
|
|
231
|
-
|
|
308
|
+
|
|
232
309
|
return (
|
|
233
310
|
<div className={styles.categorizedDropZone}>
|
|
234
311
|
<div className={styles.noCategoryContent}>
|
|
235
312
|
<DropZone
|
|
236
|
-
fetchData={
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
313
|
+
fetchData={
|
|
314
|
+
fetchData ||
|
|
315
|
+
(() => {
|
|
316
|
+
console.log('Files updated (no categories)');
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
files={currentFiles}
|
|
240
320
|
settings={{
|
|
241
321
|
url: url,
|
|
242
322
|
method: method,
|
|
@@ -248,7 +328,7 @@ const CategorizedDropZone = ({
|
|
|
248
328
|
model: entityData?.constructor?.name || 'Design',
|
|
249
329
|
folder: folder,
|
|
250
330
|
filetype: filetype,
|
|
251
|
-
deleteUrl: deleteUrl
|
|
331
|
+
deleteUrl: deleteUrl,
|
|
252
332
|
}}
|
|
253
333
|
/>
|
|
254
334
|
</div>
|
|
@@ -259,36 +339,51 @@ const CategorizedDropZone = ({
|
|
|
259
339
|
return (
|
|
260
340
|
<div className={styles.categorizedDropZone}>
|
|
261
341
|
<div className={styles.categoryTabs}>
|
|
262
|
-
{finalCategories.map(category => (
|
|
342
|
+
{finalCategories.map((category) => (
|
|
263
343
|
<button
|
|
264
344
|
key={category.id}
|
|
265
|
-
className={`${styles.categoryTab} ${
|
|
345
|
+
className={`${styles.categoryTab} ${
|
|
346
|
+
activeCategory === category.id ? styles.active : ''
|
|
347
|
+
}`}
|
|
266
348
|
onClick={() => handleCategoryChange(category.id)}
|
|
267
349
|
>
|
|
268
350
|
{category.label}
|
|
269
|
-
{categorizedFiles[category.id] &&
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
351
|
+
{categorizedFiles[category.id] &&
|
|
352
|
+
categorizedFiles[category.id].length > 0 && (
|
|
353
|
+
<span className={styles.fileCount}>
|
|
354
|
+
({categorizedFiles[category.id].length})
|
|
355
|
+
</span>
|
|
356
|
+
)}
|
|
274
357
|
</button>
|
|
275
358
|
))}
|
|
276
359
|
</div>
|
|
277
|
-
|
|
360
|
+
|
|
278
361
|
<div className={styles.categoryContent}>
|
|
279
362
|
{activeCategory && (
|
|
280
363
|
<div className={styles.categoryPanel}>
|
|
281
|
-
{finalCategories.find(
|
|
364
|
+
{finalCategories.find(
|
|
365
|
+
(cat) => cat.id === activeCategory
|
|
366
|
+
)?.description && (
|
|
282
367
|
<p className={styles.categoryDescription}>
|
|
283
|
-
{
|
|
368
|
+
{
|
|
369
|
+
finalCategories.find(
|
|
370
|
+
(cat) => cat.id === activeCategory
|
|
371
|
+
)?.description
|
|
372
|
+
}
|
|
284
373
|
</p>
|
|
285
374
|
)}
|
|
286
|
-
|
|
375
|
+
|
|
287
376
|
<DropZone
|
|
288
|
-
fetchData={
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
377
|
+
fetchData={
|
|
378
|
+
fetchData ||
|
|
379
|
+
(() => {
|
|
380
|
+
// Default fetch function - refresh current category files
|
|
381
|
+
console.log(
|
|
382
|
+
'Files updated for category:',
|
|
383
|
+
activeCategory
|
|
384
|
+
);
|
|
385
|
+
})
|
|
386
|
+
}
|
|
292
387
|
files={getCurrentCategoryFiles()}
|
|
293
388
|
settings={{
|
|
294
389
|
url: url,
|
|
@@ -296,9 +391,14 @@ const CategorizedDropZone = ({
|
|
|
296
391
|
type: 'gallery',
|
|
297
392
|
data: {
|
|
298
393
|
file_relationship: file_relationship,
|
|
299
|
-
file_description: `category:${activeCategory} - ${
|
|
394
|
+
file_description: `category:${activeCategory} - ${
|
|
395
|
+
finalCategories.find(
|
|
396
|
+
(cat) => cat.id === activeCategory
|
|
397
|
+
)?.label || activeCategory
|
|
398
|
+
}`,
|
|
300
399
|
},
|
|
301
|
-
model:
|
|
400
|
+
model:
|
|
401
|
+
entityData?.constructor?.name || 'Design',
|
|
302
402
|
}}
|
|
303
403
|
/>
|
|
304
404
|
</div>
|
|
@@ -308,4 +408,4 @@ const CategorizedDropZone = ({
|
|
|
308
408
|
);
|
|
309
409
|
};
|
|
310
410
|
|
|
311
|
-
export default CategorizedDropZone;
|
|
411
|
+
export default CategorizedDropZone;
|
|
@@ -144,16 +144,19 @@ const getAcceptTypes = (filetype) => {
|
|
|
144
144
|
return {
|
|
145
145
|
'application/pdf': ['.pdf'],
|
|
146
146
|
'application/msword': ['.doc'],
|
|
147
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
147
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
148
|
+
['.docx'],
|
|
148
149
|
'application/vnd.ms-excel': ['.xls'],
|
|
149
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
|
150
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
|
151
|
+
['.xlsx'],
|
|
150
152
|
'application/vnd.ms-powerpoint': ['.ppt'],
|
|
151
|
-
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
|
153
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
|
154
|
+
['.pptx'],
|
|
152
155
|
'text/plain': ['.txt'],
|
|
153
156
|
'text/csv': ['.csv'],
|
|
154
157
|
'application/zip': ['.zip'],
|
|
155
158
|
'application/x-rar-compressed': ['.rar'],
|
|
156
|
-
'application/x-7z-compressed': ['.7z']
|
|
159
|
+
'application/x-7z-compressed': ['.7z'],
|
|
157
160
|
};
|
|
158
161
|
case 'image':
|
|
159
162
|
return {
|
|
@@ -163,7 +166,7 @@ const getAcceptTypes = (filetype) => {
|
|
|
163
166
|
'image/webp': ['.webp'],
|
|
164
167
|
'image/svg+xml': ['.svg'],
|
|
165
168
|
'image/bmp': ['.bmp'],
|
|
166
|
-
'image/tiff': ['.tiff', '.tif']
|
|
169
|
+
'image/tiff': ['.tiff', '.tif'],
|
|
167
170
|
};
|
|
168
171
|
default:
|
|
169
172
|
return undefined; // Accept all file types
|
|
@@ -173,7 +176,7 @@ const getAcceptTypes = (filetype) => {
|
|
|
173
176
|
// Helper function to get the appropriate icon based on file extension
|
|
174
177
|
const getFileIcon = (filename) => {
|
|
175
178
|
const extension = filename.split('.').pop().toLowerCase();
|
|
176
|
-
|
|
179
|
+
|
|
177
180
|
switch (extension) {
|
|
178
181
|
case 'pdf':
|
|
179
182
|
return <FileText strokeWidth={2} size={20} color="#dc2626" />;
|
|
@@ -182,7 +185,9 @@ const getFileIcon = (filename) => {
|
|
|
182
185
|
return <FileText strokeWidth={2} size={20} color="#2563eb" />;
|
|
183
186
|
case 'xls':
|
|
184
187
|
case 'xlsx':
|
|
185
|
-
return
|
|
188
|
+
return (
|
|
189
|
+
<FileSpreadsheet strokeWidth={2} size={20} color="#16a34a" />
|
|
190
|
+
);
|
|
186
191
|
case 'ppt':
|
|
187
192
|
case 'pptx':
|
|
188
193
|
return <FileText strokeWidth={2} size={20} color="#ea580c" />;
|
|
@@ -211,11 +216,11 @@ const getFileIcon = (filename) => {
|
|
|
211
216
|
// Helper function to format file size
|
|
212
217
|
const formatFileSize = (bytes) => {
|
|
213
218
|
if (!bytes || bytes === 0) return '';
|
|
214
|
-
|
|
219
|
+
|
|
215
220
|
const k = 1024;
|
|
216
221
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
217
222
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
218
|
-
|
|
223
|
+
|
|
219
224
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
220
225
|
};
|
|
221
226
|
|
|
@@ -425,14 +430,14 @@ function GenericDetail({
|
|
|
425
430
|
const handleStagePopupConfirm = async (popupFormData) => {
|
|
426
431
|
try {
|
|
427
432
|
setShowStagePopup(false);
|
|
428
|
-
|
|
433
|
+
|
|
429
434
|
if (!stagePopupData) return;
|
|
430
|
-
|
|
435
|
+
|
|
431
436
|
const { stage, type } = stagePopupData;
|
|
432
|
-
|
|
437
|
+
|
|
433
438
|
// Proceed with normal stage update, but include popup form data
|
|
434
439
|
await performStageUpdate(stage, type, popupFormData);
|
|
435
|
-
|
|
440
|
+
|
|
436
441
|
setStagePopupData(null);
|
|
437
442
|
} catch (error) {
|
|
438
443
|
console.error('Stage popup confirm error:', error);
|
|
@@ -456,56 +461,76 @@ function GenericDetail({
|
|
|
456
461
|
setActiveStage(stage.id);
|
|
457
462
|
} else if (type === 'stageCounter') {
|
|
458
463
|
// Handle new stageCounter functionality
|
|
459
|
-
console.log(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
+
console.log(
|
|
465
|
+
'performStageUpdate called with stageCounter type:',
|
|
466
|
+
{ stage, data, stages }
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const stageConfig =
|
|
470
|
+
stages.stageConfig ||
|
|
471
|
+
activeTabConfig.stages?.stageConfig;
|
|
472
|
+
if (
|
|
473
|
+
!stageConfig?.toggleable ||
|
|
474
|
+
!stageConfig?.toggleConfig
|
|
475
|
+
) {
|
|
476
|
+
console.warn('Stage is not configured for toggling', {
|
|
477
|
+
stageConfig,
|
|
478
|
+
});
|
|
464
479
|
return;
|
|
465
480
|
}
|
|
466
481
|
|
|
467
482
|
// Get current stage data
|
|
468
483
|
const stageDataArray = data[stageConfig.key] || [];
|
|
469
|
-
const currentStage = stageDataArray.find(
|
|
470
|
-
|
|
484
|
+
const currentStage = stageDataArray.find(
|
|
485
|
+
(s) => s.id === stage.id
|
|
486
|
+
);
|
|
487
|
+
|
|
471
488
|
if (!currentStage) {
|
|
472
489
|
console.warn('Stage not found in data');
|
|
473
490
|
return;
|
|
474
491
|
}
|
|
475
492
|
|
|
476
493
|
// Determine current and target status
|
|
477
|
-
const currentStatus =
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
494
|
+
const currentStatus =
|
|
495
|
+
currentStage[stageConfig.toggleConfig.statusField];
|
|
496
|
+
const isActive =
|
|
497
|
+
currentStatus ===
|
|
498
|
+
stageConfig.toggleConfig.activeStatusValue;
|
|
499
|
+
const targetStatus = isActive
|
|
500
|
+
? stageConfig.toggleConfig.inactiveStatusValue
|
|
481
501
|
: stageConfig.toggleConfig.activeStatusValue;
|
|
482
502
|
|
|
483
503
|
// Build API URL
|
|
484
504
|
let url = stageConfig.toggleConfig.updateUrl;
|
|
485
505
|
url = url.replace('{id}', routeParams[stages.urlParam]);
|
|
486
506
|
url = url.replace('{stage_id}', stage.id);
|
|
487
|
-
|
|
507
|
+
|
|
488
508
|
// Make API call with additional data from popup
|
|
489
509
|
const res = await CustomFetch(
|
|
490
510
|
url,
|
|
491
511
|
stageConfig.toggleConfig.updateMethod || 'PUT',
|
|
492
512
|
{
|
|
493
513
|
[stageConfig.toggleConfig.updateKey]: targetStatus,
|
|
494
|
-
...additionalData
|
|
514
|
+
...additionalData,
|
|
495
515
|
}
|
|
496
516
|
);
|
|
497
517
|
|
|
498
518
|
if (res.data.error === '') {
|
|
499
519
|
handleReload();
|
|
500
520
|
const actionText = isActive ? 'disabled' : 'enabled';
|
|
501
|
-
toast.success(
|
|
521
|
+
toast.success(
|
|
522
|
+
`Stage "${stage.label}" ${actionText} successfully`
|
|
523
|
+
);
|
|
502
524
|
} else {
|
|
503
525
|
toast.error(res.data.error);
|
|
504
526
|
}
|
|
505
527
|
} else if (type === 'legacy_toggle') {
|
|
506
528
|
// Handle legacy toggle functionality - toggle between current stage and previous
|
|
507
|
-
console.log(
|
|
508
|
-
|
|
529
|
+
console.log(
|
|
530
|
+
'performStageUpdate called with legacy_toggle type:',
|
|
531
|
+
{ stage, data, stages }
|
|
532
|
+
);
|
|
533
|
+
|
|
509
534
|
if (
|
|
510
535
|
stages &&
|
|
511
536
|
stages.url &&
|
|
@@ -517,7 +542,7 @@ function GenericDetail({
|
|
|
517
542
|
) {
|
|
518
543
|
const currentStatus = data[stages.key];
|
|
519
544
|
let targetStatus;
|
|
520
|
-
|
|
545
|
+
|
|
521
546
|
// If clicking on the current stage (completed), move back one step
|
|
522
547
|
// If clicking on a future stage, move to that stage
|
|
523
548
|
if (currentStatus >= stage.id) {
|
|
@@ -528,7 +553,9 @@ function GenericDetail({
|
|
|
528
553
|
targetStatus = stage.id;
|
|
529
554
|
}
|
|
530
555
|
|
|
531
|
-
const url = `${stages.url}/${
|
|
556
|
+
const url = `${stages.url}/${
|
|
557
|
+
routeParams[stages.urlParam]
|
|
558
|
+
}`;
|
|
532
559
|
|
|
533
560
|
// Include additional data from popup
|
|
534
561
|
const res = await CustomFetch(
|
|
@@ -536,14 +563,19 @@ function GenericDetail({
|
|
|
536
563
|
stages.method || 'PUT',
|
|
537
564
|
{
|
|
538
565
|
[stages.key]: targetStatus,
|
|
539
|
-
...additionalData
|
|
566
|
+
...additionalData,
|
|
540
567
|
}
|
|
541
568
|
);
|
|
542
569
|
|
|
543
570
|
if (res.data.error === '') {
|
|
544
571
|
handleReload();
|
|
545
|
-
const actionText =
|
|
546
|
-
|
|
572
|
+
const actionText =
|
|
573
|
+
currentStatus >= stage.id
|
|
574
|
+
? 'moved back from'
|
|
575
|
+
: 'progressed to';
|
|
576
|
+
toast.success(
|
|
577
|
+
`Stage ${actionText} "${stage.label}" successfully`
|
|
578
|
+
);
|
|
547
579
|
} else {
|
|
548
580
|
toast.error(res.data.error);
|
|
549
581
|
}
|
|
@@ -563,7 +595,7 @@ function GenericDetail({
|
|
|
563
595
|
|
|
564
596
|
const res = await CustomFetch(url, 'POST', {
|
|
565
597
|
[activeTabConfig.stages.key]: stage.id,
|
|
566
|
-
...additionalData
|
|
598
|
+
...additionalData,
|
|
567
599
|
});
|
|
568
600
|
|
|
569
601
|
if (res.data.error === '') {
|
|
@@ -588,7 +620,7 @@ function GenericDetail({
|
|
|
588
620
|
stages.method || 'POST',
|
|
589
621
|
{
|
|
590
622
|
[stages.key]: stage.id,
|
|
591
|
-
...additionalData
|
|
623
|
+
...additionalData,
|
|
592
624
|
}
|
|
593
625
|
);
|
|
594
626
|
|
|
@@ -940,29 +972,48 @@ function GenericDetail({
|
|
|
940
972
|
|
|
941
973
|
const renderJson = () => {
|
|
942
974
|
if (!value) return null;
|
|
943
|
-
|
|
975
|
+
|
|
944
976
|
try {
|
|
945
|
-
const jsonData =
|
|
946
|
-
|
|
977
|
+
const jsonData =
|
|
978
|
+
typeof value === 'string' ? JSON.parse(value) : value;
|
|
979
|
+
|
|
947
980
|
// Check if this looks like a question/answer format
|
|
948
|
-
const isQuestionAnswerFormat = Object.values(jsonData).every(
|
|
949
|
-
item
|
|
950
|
-
|
|
981
|
+
const isQuestionAnswerFormat = Object.values(jsonData).every(
|
|
982
|
+
(item) =>
|
|
983
|
+
item &&
|
|
984
|
+
typeof item === 'object' &&
|
|
985
|
+
(item.hasOwnProperty('question') ||
|
|
986
|
+
item.hasOwnProperty('answer'))
|
|
951
987
|
);
|
|
952
|
-
|
|
988
|
+
|
|
953
989
|
if (isQuestionAnswerFormat) {
|
|
954
990
|
return (
|
|
955
991
|
<div className={styles.jsonQuestionAnswer}>
|
|
956
992
|
{Object.entries(jsonData).map(([key, item]) => {
|
|
957
|
-
if (!item || typeof item !== 'object')
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
993
|
+
if (!item || typeof item !== 'object')
|
|
994
|
+
return null;
|
|
995
|
+
|
|
996
|
+
const question =
|
|
997
|
+
item.question ||
|
|
998
|
+
key
|
|
999
|
+
.replace(/_/g, ' ')
|
|
1000
|
+
.replace(/\b\w/g, (l) =>
|
|
1001
|
+
l.toUpperCase()
|
|
1002
|
+
);
|
|
1003
|
+
const answer =
|
|
1004
|
+
item.answer !== undefined
|
|
1005
|
+
? typeof item.answer === 'boolean'
|
|
1006
|
+
? item.answer
|
|
1007
|
+
? 'Yes'
|
|
1008
|
+
: 'No'
|
|
1009
|
+
: String(item.answer)
|
|
1010
|
+
: 'N/A';
|
|
1011
|
+
|
|
964
1012
|
return (
|
|
965
|
-
<div
|
|
1013
|
+
<div
|
|
1014
|
+
key={key}
|
|
1015
|
+
className={styles.questionAnswerPair}
|
|
1016
|
+
>
|
|
966
1017
|
<div className={styles.question}>
|
|
967
1018
|
<strong>{question}</strong>
|
|
968
1019
|
</div>
|
|
@@ -975,34 +1026,51 @@ function GenericDetail({
|
|
|
975
1026
|
</div>
|
|
976
1027
|
);
|
|
977
1028
|
}
|
|
978
|
-
|
|
1029
|
+
|
|
979
1030
|
// Check if this is a simple key-value object
|
|
980
|
-
const isSimpleKeyValue = Object.values(jsonData).every(
|
|
981
|
-
typeof val !== 'object' || val === null
|
|
1031
|
+
const isSimpleKeyValue = Object.values(jsonData).every(
|
|
1032
|
+
(val) => typeof val !== 'object' || val === null
|
|
982
1033
|
);
|
|
983
|
-
|
|
1034
|
+
|
|
984
1035
|
if (isSimpleKeyValue) {
|
|
985
1036
|
return (
|
|
986
1037
|
<div className={styles.jsonKeyValue}>
|
|
987
1038
|
{Object.entries(jsonData).map(([key, val]) => (
|
|
988
1039
|
<div key={key} className={styles.keyValuePair}>
|
|
989
|
-
<strong>
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1040
|
+
<strong>
|
|
1041
|
+
{key
|
|
1042
|
+
.replace(/_/g, ' ')
|
|
1043
|
+
.replace(/\b\w/g, (l) =>
|
|
1044
|
+
l.toUpperCase()
|
|
1045
|
+
)}
|
|
1046
|
+
:
|
|
1047
|
+
</strong>{' '}
|
|
1048
|
+
{val !== null && val !== undefined
|
|
1049
|
+
? typeof val === 'boolean'
|
|
1050
|
+
? val
|
|
1051
|
+
? 'Yes'
|
|
1052
|
+
: 'No'
|
|
1053
|
+
: String(val)
|
|
1054
|
+
: 'N/A'}
|
|
994
1055
|
</div>
|
|
995
1056
|
))}
|
|
996
1057
|
</div>
|
|
997
1058
|
);
|
|
998
1059
|
}
|
|
999
|
-
|
|
1060
|
+
|
|
1000
1061
|
// For complex nested objects, fall back to formatted JSON
|
|
1001
|
-
return
|
|
1002
|
-
|
|
1062
|
+
return (
|
|
1063
|
+
<pre className={styles.jsonRaw}>
|
|
1064
|
+
{JSON.stringify(jsonData, null, 2)}
|
|
1065
|
+
</pre>
|
|
1066
|
+
);
|
|
1003
1067
|
} catch (error) {
|
|
1004
1068
|
// If parsing fails, show raw value
|
|
1005
|
-
return
|
|
1069
|
+
return (
|
|
1070
|
+
<pre className={styles.jsonRaw}>
|
|
1071
|
+
{JSON.stringify(value, null, 2)}
|
|
1072
|
+
</pre>
|
|
1073
|
+
);
|
|
1006
1074
|
}
|
|
1007
1075
|
};
|
|
1008
1076
|
|
|
@@ -1608,7 +1676,9 @@ function GenericDetail({
|
|
|
1608
1676
|
/>
|
|
1609
1677
|
</div>
|
|
1610
1678
|
<Dropzone
|
|
1611
|
-
accept={getAcceptTypes(
|
|
1679
|
+
accept={getAcceptTypes(
|
|
1680
|
+
activeTabConfig.filetype
|
|
1681
|
+
)}
|
|
1612
1682
|
onDrop={(acceptedFiles) => {
|
|
1613
1683
|
const uploadFile = async (file) => {
|
|
1614
1684
|
// Optimize image if it's an image file
|
|
@@ -1804,7 +1874,9 @@ function GenericDetail({
|
|
|
1804
1874
|
}
|
|
1805
1875
|
>
|
|
1806
1876
|
{files &&
|
|
1807
|
-
Array.isArray(
|
|
1877
|
+
Array.isArray(
|
|
1878
|
+
files
|
|
1879
|
+
) &&
|
|
1808
1880
|
files.map(
|
|
1809
1881
|
(a, b) => {
|
|
1810
1882
|
const imageSource =
|
|
@@ -1829,33 +1901,59 @@ function GenericDetail({
|
|
|
1829
1901
|
alt={
|
|
1830
1902
|
a.file_name
|
|
1831
1903
|
}
|
|
1832
|
-
onClick={(
|
|
1904
|
+
onClick={(
|
|
1905
|
+
e
|
|
1906
|
+
) => {
|
|
1833
1907
|
e.stopPropagation();
|
|
1834
1908
|
e.preventDefault();
|
|
1835
|
-
|
|
1909
|
+
|
|
1836
1910
|
// Prepare gallery data in the format expected by Lightbox
|
|
1837
|
-
const galleryArray =
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1911
|
+
const galleryArray =
|
|
1912
|
+
files
|
|
1913
|
+
.filter(
|
|
1914
|
+
(
|
|
1915
|
+
file
|
|
1916
|
+
) => {
|
|
1917
|
+
const imageSource =
|
|
1918
|
+
file.base64_encoded_image ||
|
|
1919
|
+
file.s3_url ||
|
|
1920
|
+
file.file_url;
|
|
1921
|
+
return imageSource;
|
|
1922
|
+
}
|
|
1923
|
+
)
|
|
1924
|
+
.map(
|
|
1925
|
+
(
|
|
1926
|
+
file
|
|
1927
|
+
) => ({
|
|
1928
|
+
src:
|
|
1929
|
+
file.base64_encoded_image ||
|
|
1930
|
+
file.s3_url ||
|
|
1931
|
+
file.file_url,
|
|
1932
|
+
caption:
|
|
1933
|
+
file.file_name,
|
|
1934
|
+
width: 320,
|
|
1935
|
+
height: 240,
|
|
1936
|
+
})
|
|
1937
|
+
);
|
|
1848
1938
|
|
|
1849
1939
|
// Set gallery data for lightbox
|
|
1850
|
-
setGalleryData(
|
|
1851
|
-
|
|
1940
|
+
setGalleryData(
|
|
1941
|
+
galleryArray
|
|
1942
|
+
);
|
|
1943
|
+
|
|
1852
1944
|
// Set initial lightbox index
|
|
1853
|
-
setLightboxIndex(
|
|
1854
|
-
|
|
1945
|
+
setLightboxIndex(
|
|
1946
|
+
b
|
|
1947
|
+
);
|
|
1948
|
+
|
|
1855
1949
|
// Open lightbox directly
|
|
1856
|
-
setLightboxOpen(
|
|
1950
|
+
setLightboxOpen(
|
|
1951
|
+
true
|
|
1952
|
+
);
|
|
1953
|
+
}}
|
|
1954
|
+
style={{
|
|
1955
|
+
cursor: 'pointer',
|
|
1857
1956
|
}}
|
|
1858
|
-
style={{ cursor: 'pointer' }}
|
|
1859
1957
|
/>
|
|
1860
1958
|
<TrashCan
|
|
1861
1959
|
onClick={(
|
|
@@ -1912,14 +2010,20 @@ function GenericDetail({
|
|
|
1912
2010
|
);
|
|
1913
2011
|
}}
|
|
1914
2012
|
>
|
|
1915
|
-
{getFileIcon(
|
|
2013
|
+
{getFileIcon(
|
|
2014
|
+
a.file_name
|
|
2015
|
+
)}
|
|
1916
2016
|
<div className="filename">
|
|
1917
2017
|
<div className="file-name">
|
|
1918
|
-
{
|
|
2018
|
+
{
|
|
2019
|
+
a.file_name
|
|
2020
|
+
}
|
|
1919
2021
|
</div>
|
|
1920
2022
|
{a.file_size && (
|
|
1921
2023
|
<div className="file-size">
|
|
1922
|
-
{formatFileSize(
|
|
2024
|
+
{formatFileSize(
|
|
2025
|
+
a.file_size
|
|
2026
|
+
)}
|
|
1923
2027
|
</div>
|
|
1924
2028
|
)}
|
|
1925
2029
|
</div>
|
|
@@ -2474,7 +2578,8 @@ function GenericDetail({
|
|
|
2474
2578
|
processed[key] =
|
|
2475
2579
|
routeParams[urlParam];
|
|
2476
2580
|
} else if (
|
|
2477
|
-
typeof value === 'object' &&
|
|
2581
|
+
typeof value === 'object' &&
|
|
2582
|
+
value !== null
|
|
2478
2583
|
) {
|
|
2479
2584
|
processed[key] =
|
|
2480
2585
|
processUrlParams(value);
|
|
@@ -2512,7 +2617,9 @@ function GenericDetail({
|
|
|
2512
2617
|
}
|
|
2513
2618
|
>
|
|
2514
2619
|
<Dropzone
|
|
2515
|
-
accept={getAcceptTypes(
|
|
2620
|
+
accept={getAcceptTypes(
|
|
2621
|
+
activeTabConfig.filetype
|
|
2622
|
+
)}
|
|
2516
2623
|
onDrop={(
|
|
2517
2624
|
acceptedFiles
|
|
2518
2625
|
) => {
|
|
@@ -2753,21 +2860,34 @@ function GenericDetail({
|
|
|
2753
2860
|
// Replace URL parameters like {dataId} with actual values
|
|
2754
2861
|
const replaceUrlParams = (url) => {
|
|
2755
2862
|
if (!url) return url;
|
|
2756
|
-
return url.replace(
|
|
2863
|
+
return url.replace(
|
|
2864
|
+
/{dataId}/g,
|
|
2865
|
+
routeParams[urlParam] || ''
|
|
2866
|
+
);
|
|
2757
2867
|
};
|
|
2758
|
-
|
|
2868
|
+
|
|
2759
2869
|
return (
|
|
2760
2870
|
<CategorizedDropZone
|
|
2761
2871
|
filetype={activeTabConfig.filetype}
|
|
2762
2872
|
folder={activeTabConfig.folder}
|
|
2763
2873
|
dataKey={activeTabConfig.key}
|
|
2764
|
-
file_relationship={
|
|
2874
|
+
file_relationship={
|
|
2875
|
+
activeTabConfig.file_relationship
|
|
2876
|
+
}
|
|
2765
2877
|
url={replaceUrlParams(activeTabConfig.url)}
|
|
2766
2878
|
method={activeTabConfig.method}
|
|
2767
|
-
deleteUrl={replaceUrlParams(
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2879
|
+
deleteUrl={replaceUrlParams(
|
|
2880
|
+
activeTabConfig.deleteUrl
|
|
2881
|
+
)}
|
|
2882
|
+
categories={
|
|
2883
|
+
activeTabConfig.categories || []
|
|
2884
|
+
}
|
|
2885
|
+
categoriesUrl={
|
|
2886
|
+
activeTabConfig.categoriesUrl
|
|
2887
|
+
}
|
|
2888
|
+
categoriesWhere={
|
|
2889
|
+
activeTabConfig.categoriesWhere
|
|
2890
|
+
}
|
|
2771
2891
|
entityData={data}
|
|
2772
2892
|
routeParams={routeParams}
|
|
2773
2893
|
fetchData={handleReload}
|
|
@@ -2786,7 +2906,12 @@ function GenericDetail({
|
|
|
2786
2906
|
);
|
|
2787
2907
|
case 'custom':
|
|
2788
2908
|
// Handle custom components
|
|
2789
|
-
if (
|
|
2909
|
+
if (
|
|
2910
|
+
activeTabConfig.component ===
|
|
2911
|
+
'AssociationManager' ||
|
|
2912
|
+
activeTabConfig.component ===
|
|
2913
|
+
'ClientAssociationManager'
|
|
2914
|
+
) {
|
|
2790
2915
|
return (
|
|
2791
2916
|
<AssociationManager
|
|
2792
2917
|
endpoints={activeTabConfig.endpoints}
|
|
@@ -2806,7 +2931,10 @@ function GenericDetail({
|
|
|
2806
2931
|
/>
|
|
2807
2932
|
);
|
|
2808
2933
|
}
|
|
2809
|
-
if (
|
|
2934
|
+
if (
|
|
2935
|
+
activeTabConfig.component ===
|
|
2936
|
+
'ProposalTemplateSectionManager'
|
|
2937
|
+
) {
|
|
2810
2938
|
return (
|
|
2811
2939
|
<ProposalTemplateSectionManager
|
|
2812
2940
|
{...activeTabConfig.props}
|
|
@@ -2815,7 +2943,10 @@ function GenericDetail({
|
|
|
2815
2943
|
/>
|
|
2816
2944
|
);
|
|
2817
2945
|
}
|
|
2818
|
-
if (
|
|
2946
|
+
if (
|
|
2947
|
+
activeTabConfig.component ===
|
|
2948
|
+
'ProposalTemplatePreview'
|
|
2949
|
+
) {
|
|
2819
2950
|
return (
|
|
2820
2951
|
<ProposalTemplatePreview
|
|
2821
2952
|
{...activeTabConfig.props}
|
|
@@ -2868,7 +2999,10 @@ function GenericDetail({
|
|
|
2868
2999
|
// Update files when active tab changes for dropzone tabs
|
|
2869
3000
|
useEffect(() => {
|
|
2870
3001
|
if (activeTabConfig && activeTabConfig.type === 'dropzone' && data) {
|
|
2871
|
-
if (
|
|
3002
|
+
if (
|
|
3003
|
+
activeTabConfig.file_relationship &&
|
|
3004
|
+
data[activeTabConfig.file_relationship]
|
|
3005
|
+
) {
|
|
2872
3006
|
setFiles(data[activeTabConfig.file_relationship]);
|
|
2873
3007
|
} else if (data.files) {
|
|
2874
3008
|
setFiles(data.files);
|
|
@@ -3178,43 +3312,86 @@ function GenericDetail({
|
|
|
3178
3312
|
<ul className={styles.opppath}>
|
|
3179
3313
|
{stages.data.map((stage) => {
|
|
3180
3314
|
let isStageComplete = false;
|
|
3181
|
-
|
|
3315
|
+
|
|
3182
3316
|
if (stages.type === 'stageCounter') {
|
|
3183
3317
|
// For stageCounter, check individual stage status
|
|
3184
|
-
const stageConfig =
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3318
|
+
const stageConfig =
|
|
3319
|
+
stages.stageConfig;
|
|
3320
|
+
if (
|
|
3321
|
+
stageConfig &&
|
|
3322
|
+
data[stageConfig.key]
|
|
3323
|
+
) {
|
|
3324
|
+
const stageDataArray =
|
|
3325
|
+
data[stageConfig.key];
|
|
3326
|
+
const currentStage =
|
|
3327
|
+
stageDataArray.find(
|
|
3328
|
+
(s) => s.id === stage.id
|
|
3329
|
+
);
|
|
3330
|
+
if (
|
|
3331
|
+
currentStage &&
|
|
3332
|
+
stageConfig.toggleConfig
|
|
3333
|
+
) {
|
|
3334
|
+
const currentStatus =
|
|
3335
|
+
currentStage[
|
|
3336
|
+
stageConfig
|
|
3337
|
+
.toggleConfig
|
|
3338
|
+
.statusField
|
|
3339
|
+
];
|
|
3340
|
+
isStageComplete =
|
|
3341
|
+
currentStatus ===
|
|
3342
|
+
stageConfig.toggleConfig
|
|
3343
|
+
.activeStatusValue;
|
|
3344
|
+
|
|
3192
3345
|
// Debug logging
|
|
3193
|
-
console.log(
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3346
|
+
console.log(
|
|
3347
|
+
'Stage completion check:',
|
|
3348
|
+
{
|
|
3349
|
+
stageId: stage.id,
|
|
3350
|
+
stageLabel:
|
|
3351
|
+
stage.label,
|
|
3352
|
+
currentStatus,
|
|
3353
|
+
activeStatusValue:
|
|
3354
|
+
stageConfig
|
|
3355
|
+
.toggleConfig
|
|
3356
|
+
.activeStatusValue,
|
|
3357
|
+
isStageComplete,
|
|
3358
|
+
}
|
|
3359
|
+
);
|
|
3200
3360
|
}
|
|
3201
3361
|
}
|
|
3202
3362
|
} else {
|
|
3203
3363
|
// Legacy stage completion check
|
|
3204
|
-
isStageComplete =
|
|
3364
|
+
isStageComplete =
|
|
3365
|
+
data[stages.key] >= stage.id;
|
|
3205
3366
|
}
|
|
3206
3367
|
|
|
3207
3368
|
// Get color for stageCounter type
|
|
3208
3369
|
let stageColor = null;
|
|
3209
|
-
if (
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3370
|
+
if (
|
|
3371
|
+
stages.type === 'stageCounter' &&
|
|
3372
|
+
stages.stageConfig?.stageColour
|
|
3373
|
+
) {
|
|
3374
|
+
const stageDataArray =
|
|
3375
|
+
data[stages.stageConfig.key] ||
|
|
3376
|
+
[];
|
|
3377
|
+
const currentStage =
|
|
3378
|
+
stageDataArray.find(
|
|
3379
|
+
(s) => s.id === stage.id
|
|
3216
3380
|
);
|
|
3217
|
-
|
|
3381
|
+
if (currentStage) {
|
|
3382
|
+
const currentStatus =
|
|
3383
|
+
currentStage[
|
|
3384
|
+
stages.stageConfig
|
|
3385
|
+
.stageColour.key
|
|
3386
|
+
];
|
|
3387
|
+
const colorOption =
|
|
3388
|
+
stages.stageConfig.stageColour.options?.find(
|
|
3389
|
+
(opt) =>
|
|
3390
|
+
opt.id ===
|
|
3391
|
+
currentStatus
|
|
3392
|
+
);
|
|
3393
|
+
stageColor =
|
|
3394
|
+
colorOption?.colour;
|
|
3218
3395
|
}
|
|
3219
3396
|
}
|
|
3220
3397
|
|
|
@@ -3226,11 +3403,21 @@ function GenericDetail({
|
|
|
3226
3403
|
? `${styles['opp-complete']}`
|
|
3227
3404
|
: ''
|
|
3228
3405
|
}
|
|
3229
|
-
style={
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3406
|
+
style={
|
|
3407
|
+
stageColor
|
|
3408
|
+
? {
|
|
3409
|
+
borderColor:
|
|
3410
|
+
stageColor,
|
|
3411
|
+
color: isStageComplete
|
|
3412
|
+
? '#fff'
|
|
3413
|
+
: stageColor,
|
|
3414
|
+
backgroundColor:
|
|
3415
|
+
isStageComplete
|
|
3416
|
+
? stageColor
|
|
3417
|
+
: 'transparent',
|
|
3418
|
+
}
|
|
3419
|
+
: {}
|
|
3420
|
+
}
|
|
3234
3421
|
onClick={(e) => {
|
|
3235
3422
|
e.preventDefault();
|
|
3236
3423
|
|
|
@@ -3240,15 +3427,27 @@ function GenericDetail({
|
|
|
3240
3427
|
);
|
|
3241
3428
|
}}
|
|
3242
3429
|
>
|
|
3243
|
-
<a
|
|
3244
|
-
|
|
3245
|
-
|
|
3430
|
+
<a
|
|
3431
|
+
style={
|
|
3432
|
+
stageColor
|
|
3433
|
+
? {
|
|
3434
|
+
color: isStageComplete
|
|
3435
|
+
? '#fff'
|
|
3436
|
+
: stageColor,
|
|
3437
|
+
}
|
|
3438
|
+
: {}
|
|
3439
|
+
}
|
|
3440
|
+
>
|
|
3246
3441
|
{stage.label}
|
|
3247
3442
|
{isStageComplete && (
|
|
3248
3443
|
<CircleCheck
|
|
3249
3444
|
strokeWidth={2}
|
|
3250
3445
|
size={16}
|
|
3251
|
-
color={
|
|
3446
|
+
color={
|
|
3447
|
+
stageColor
|
|
3448
|
+
? '#fff'
|
|
3449
|
+
: undefined
|
|
3450
|
+
}
|
|
3252
3451
|
/>
|
|
3253
3452
|
)}
|
|
3254
3453
|
</a>
|
|
@@ -2634,8 +2634,8 @@ const GenericGrid = ({
|
|
|
2634
2634
|
);
|
|
2635
2635
|
return (
|
|
2636
2636
|
matchingOption || {
|
|
2637
|
-
value: val,
|
|
2638
|
-
label: val,
|
|
2637
|
+
value: typeof val === 'object' ? (val.id || val.value || val.key || val.code || val.slug || val.uuid || Object.keys(val)[0]) : val,
|
|
2638
|
+
label: typeof val === 'object' ? (val.name || val.label || val.title || val.text || val.description || val.display_name || val.displayName || val.full_name || val.fullName || val.username || val.email || val.code || val.slug || val.id || val.value || val.key || val.uuid || JSON.stringify(val)) : val,
|
|
2639
2639
|
}
|
|
2640
2640
|
);
|
|
2641
2641
|
})
|