@visns-studio/visns-components 5.1.18 → 5.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/crm/generic/GenericDashboard.jsx +232 -105
- package/src/components/crm/generic/GenericEditableTable.jsx +398 -174
- package/src/components/crm/generic/styles/GenericDashboard.module.scss +33 -1
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +37 -43
package/package.json
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
78
78
|
},
|
|
79
79
|
"name": "@visns-studio/visns-components",
|
|
80
|
-
"version": "5.1.
|
|
80
|
+
"version": "5.1.20",
|
|
81
81
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
82
82
|
"main": "src/index.js",
|
|
83
83
|
"files": [
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
|
-
import { Link } from 'react-router-dom';
|
|
4
|
+
import { Link, useNavigate } from 'react-router-dom';
|
|
5
5
|
import { ResponsiveBar } from '@nivo/bar';
|
|
6
6
|
import { ResponsiveLine } from '@nivo/line';
|
|
7
7
|
import { ResponsivePie } from '@nivo/pie';
|
|
8
8
|
import Popup from 'reactjs-popup';
|
|
9
9
|
import dayjs from 'dayjs';
|
|
10
|
+
import parse from 'html-react-parser';
|
|
10
11
|
import { CircleX } from 'akar-icons';
|
|
11
12
|
|
|
12
13
|
import CustomFetch from '../../crm/Fetch';
|
|
@@ -17,6 +18,7 @@ import styles from './styles/GenericDashboard.module.scss';
|
|
|
17
18
|
const toSnakeCase = (str) => str.replace(/[\s-]+/g, '_').toLowerCase();
|
|
18
19
|
|
|
19
20
|
function GenericDashboard({ setting }) {
|
|
21
|
+
const navigate = useNavigate();
|
|
20
22
|
const [data, setData] = useState({});
|
|
21
23
|
const [dropdowns, setDropdowns] = useState({});
|
|
22
24
|
const [filters, setFilters] = useState({}); // Track filter values for each widget
|
|
@@ -25,44 +27,69 @@ function GenericDashboard({ setting }) {
|
|
|
25
27
|
|
|
26
28
|
const fetchData = async (appliedFilters = {}) => {
|
|
27
29
|
try {
|
|
28
|
-
|
|
30
|
+
// Flatten the nested widgets array into a single array
|
|
31
|
+
const flattenedWidgets = setting.widgets.reduce(
|
|
32
|
+
(acc, row) => [...acc, ...row],
|
|
33
|
+
[]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Map through the flattened array of widgets
|
|
37
|
+
const promises = flattenedWidgets.map(async (widget) => {
|
|
29
38
|
const widgetFilters = appliedFilters[widget.id] || {};
|
|
30
39
|
|
|
31
40
|
// Fetch dropdown data if required
|
|
32
41
|
if (widget.filters) {
|
|
33
42
|
for (const filter of widget.filters) {
|
|
34
43
|
if (filter.url) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
try {
|
|
45
|
+
const initialFilterData = await CustomFetch(
|
|
46
|
+
filter.url,
|
|
47
|
+
'POST',
|
|
48
|
+
filter.params || {}
|
|
49
|
+
);
|
|
50
|
+
setDropdowns((prevFilters) => ({
|
|
51
|
+
...prevFilters,
|
|
52
|
+
[widget.id]: {
|
|
53
|
+
...prevFilters[widget.id],
|
|
54
|
+
[filter.id]:
|
|
55
|
+
initialFilterData?.data?.data || [],
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(
|
|
60
|
+
`Error fetching dropdown for widget ${widget.id}:`,
|
|
61
|
+
error
|
|
62
|
+
);
|
|
63
|
+
}
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
// Check if widget.api exists before making a request
|
|
69
|
+
if (widget.api?.url && widget.api?.method) {
|
|
70
|
+
return CustomFetch(
|
|
71
|
+
widget.api.url,
|
|
72
|
+
widget.api.method,
|
|
73
|
+
widgetFilters
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null; // Return null if no API request is needed
|
|
58
78
|
});
|
|
59
79
|
|
|
80
|
+
// Execute all API requests
|
|
60
81
|
const results = await Promise.all(promises);
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
// Build a data object keyed by widget id
|
|
84
|
+
const fetchedData = flattenedWidgets.reduce(
|
|
85
|
+
(acc, widget, index) => {
|
|
86
|
+
if (results[index]) {
|
|
87
|
+
acc[widget.id] = results[index]?.data;
|
|
88
|
+
}
|
|
89
|
+
return acc;
|
|
90
|
+
},
|
|
91
|
+
{}
|
|
92
|
+
);
|
|
66
93
|
|
|
67
94
|
setData(fetchedData);
|
|
68
95
|
} catch (error) {
|
|
@@ -136,20 +163,28 @@ function GenericDashboard({ setting }) {
|
|
|
136
163
|
const initializeFilters = () => {
|
|
137
164
|
const initialFilters = {};
|
|
138
165
|
|
|
139
|
-
setting.widgets.forEach((
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
setting.widgets.forEach((row) => {
|
|
167
|
+
row.forEach((widget) => {
|
|
168
|
+
if (widget.filters) {
|
|
169
|
+
initialFilters[widget.id] = widget.filters.reduce(
|
|
170
|
+
(acc, filter) => {
|
|
171
|
+
if (filter.type === 'date' && filter.default) {
|
|
172
|
+
acc[filter.id] = getDefaultDate(
|
|
173
|
+
filter.default
|
|
174
|
+
);
|
|
175
|
+
} else if (
|
|
176
|
+
filter.type === 'multi-dropdown-ajax'
|
|
177
|
+
) {
|
|
178
|
+
acc[filter.id] = [];
|
|
179
|
+
} else if (filter.type === 'years') {
|
|
180
|
+
acc[filter.id] = dayjs().year();
|
|
181
|
+
}
|
|
182
|
+
return acc;
|
|
183
|
+
},
|
|
184
|
+
{}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
153
188
|
});
|
|
154
189
|
|
|
155
190
|
setFilters(initialFilters);
|
|
@@ -176,7 +211,13 @@ function GenericDashboard({ setting }) {
|
|
|
176
211
|
case 'date':
|
|
177
212
|
return (
|
|
178
213
|
<div
|
|
179
|
-
className={
|
|
214
|
+
className={
|
|
215
|
+
widget.highlight
|
|
216
|
+
? styles[
|
|
217
|
+
'filter-field-highlight'
|
|
218
|
+
]
|
|
219
|
+
: styles['filter-field']
|
|
220
|
+
}
|
|
180
221
|
key={filter.id}
|
|
181
222
|
>
|
|
182
223
|
<label htmlFor={filter.id}>
|
|
@@ -204,7 +245,13 @@ function GenericDashboard({ setting }) {
|
|
|
204
245
|
case 'multi-dropdown-ajax':
|
|
205
246
|
return (
|
|
206
247
|
<div
|
|
207
|
-
className={
|
|
248
|
+
className={
|
|
249
|
+
widget.highlight
|
|
250
|
+
? styles[
|
|
251
|
+
'filter-field-highlight'
|
|
252
|
+
]
|
|
253
|
+
: styles['filter-field']
|
|
254
|
+
}
|
|
208
255
|
key={filter.id}
|
|
209
256
|
>
|
|
210
257
|
<label htmlFor={filter.id}>
|
|
@@ -232,6 +279,60 @@ function GenericDashboard({ setting }) {
|
|
|
232
279
|
/>
|
|
233
280
|
</div>
|
|
234
281
|
);
|
|
282
|
+
case 'years':
|
|
283
|
+
return (
|
|
284
|
+
<div
|
|
285
|
+
className={
|
|
286
|
+
widget.highlight
|
|
287
|
+
? styles[
|
|
288
|
+
'filter-field-highlight'
|
|
289
|
+
]
|
|
290
|
+
: styles['filter-field']
|
|
291
|
+
}
|
|
292
|
+
key={filter.id}
|
|
293
|
+
>
|
|
294
|
+
<label htmlFor={filter.id}>
|
|
295
|
+
{filter.label}
|
|
296
|
+
</label>
|
|
297
|
+
<select
|
|
298
|
+
name="year"
|
|
299
|
+
style={{ height: '51px' }}
|
|
300
|
+
value={
|
|
301
|
+
filters[widget.id]?.[
|
|
302
|
+
filter.id
|
|
303
|
+
] || ''
|
|
304
|
+
}
|
|
305
|
+
onChange={(e) => {
|
|
306
|
+
handleFilterChange(
|
|
307
|
+
widget.id,
|
|
308
|
+
filter.id,
|
|
309
|
+
e.target.value
|
|
310
|
+
);
|
|
311
|
+
e.target.blur(); // Closes the picker
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
{Array.from(
|
|
315
|
+
{
|
|
316
|
+
length:
|
|
317
|
+
new Date().getFullYear() -
|
|
318
|
+
2021 +
|
|
319
|
+
3,
|
|
320
|
+
},
|
|
321
|
+
(_, i) => {
|
|
322
|
+
const yearOption = 2022 + i;
|
|
323
|
+
return (
|
|
324
|
+
<option
|
|
325
|
+
key={yearOption}
|
|
326
|
+
value={yearOption}
|
|
327
|
+
>
|
|
328
|
+
{yearOption}
|
|
329
|
+
</option>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
)}
|
|
333
|
+
</select>
|
|
334
|
+
</div>
|
|
335
|
+
);
|
|
235
336
|
default:
|
|
236
337
|
return null;
|
|
237
338
|
}
|
|
@@ -269,22 +370,32 @@ function GenericDashboard({ setting }) {
|
|
|
269
370
|
return (
|
|
270
371
|
<>
|
|
271
372
|
<div className={styles.widgetNo}>
|
|
272
|
-
<span>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
: styles.btn
|
|
280
|
-
}
|
|
281
|
-
onClick={() => {
|
|
282
|
-
navigate('/leads/follow-ups');
|
|
283
|
-
}}
|
|
284
|
-
>
|
|
285
|
-
View All
|
|
286
|
-
</button>
|
|
373
|
+
<span>
|
|
374
|
+
{(Array.isArray(widgetData) &&
|
|
375
|
+
widgetData.length === 0) ||
|
|
376
|
+
widgetData == null
|
|
377
|
+
? parse(` `)
|
|
378
|
+
: widgetData}
|
|
379
|
+
</span>
|
|
287
380
|
</div>
|
|
381
|
+
{widget.button && (
|
|
382
|
+
<div className={styles.widgetLink}>
|
|
383
|
+
<button
|
|
384
|
+
className={
|
|
385
|
+
widget.highlight
|
|
386
|
+
? styles['btn-alt']
|
|
387
|
+
: styles.btn
|
|
388
|
+
}
|
|
389
|
+
onClick={() => {
|
|
390
|
+
if (widget.button.url) {
|
|
391
|
+
navigate(widget.button.url);
|
|
392
|
+
}
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
{widget.button.title}
|
|
396
|
+
</button>
|
|
397
|
+
</div>
|
|
398
|
+
)}
|
|
288
399
|
</>
|
|
289
400
|
);
|
|
290
401
|
case 'pie':
|
|
@@ -299,15 +410,25 @@ function GenericDashboard({ setting }) {
|
|
|
299
410
|
const barData =
|
|
300
411
|
widgetData.bar?.data || widgetData.data || widgetData;
|
|
301
412
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
<
|
|
305
|
-
|
|
306
|
-
|
|
413
|
+
if (barData?.length > 0) {
|
|
414
|
+
return (
|
|
415
|
+
<div style={{ height: widget.height || '600px' }}>
|
|
416
|
+
<ResponsiveBar
|
|
417
|
+
data={barData}
|
|
418
|
+
{...widget.props}
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
307
423
|
case 'line':
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
424
|
+
if (widgetData.length > 0) {
|
|
425
|
+
return (
|
|
426
|
+
<ResponsiveLine
|
|
427
|
+
data={widgetData}
|
|
428
|
+
{...widget.props}
|
|
429
|
+
/>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
311
432
|
case 'list':
|
|
312
433
|
return (
|
|
313
434
|
<ul className={styles.dashList}>
|
|
@@ -588,48 +709,54 @@ function GenericDashboard({ setting }) {
|
|
|
588
709
|
};
|
|
589
710
|
|
|
590
711
|
const renderRows = () => {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
<button
|
|
619
|
-
className={`${styles.btn} ${styles.dmore}`}
|
|
620
|
-
onClick={() => openModal(widget.id)}
|
|
621
|
-
>
|
|
622
|
-
View all{' '}
|
|
623
|
-
<span>
|
|
624
|
-
({data[widget.id]?.length || 0})
|
|
625
|
-
</span>
|
|
626
|
-
</button>
|
|
712
|
+
return setting.widgets.map((row, rowIndex) => {
|
|
713
|
+
// Use the number of widgets in the row to determine the grid class
|
|
714
|
+
const columns = row.length;
|
|
715
|
+
const gridClass =
|
|
716
|
+
columns === 1
|
|
717
|
+
? styles.grid__full
|
|
718
|
+
: columns === 2
|
|
719
|
+
? styles.grid__half
|
|
720
|
+
: columns === 3
|
|
721
|
+
? styles.grid__three
|
|
722
|
+
: styles.grid__three; // fallback if more than 3 columns
|
|
723
|
+
|
|
724
|
+
return (
|
|
725
|
+
<div className={styles.grid__row} key={`row-${rowIndex}`}>
|
|
726
|
+
{row.map((widget) => (
|
|
727
|
+
<div
|
|
728
|
+
key={widget.id}
|
|
729
|
+
className={`${gridClass} ${
|
|
730
|
+
widget.type !== 'list'
|
|
731
|
+
? widget.highlight
|
|
732
|
+
? styles['dashwidget--highlight']
|
|
733
|
+
: styles.dashwidget
|
|
734
|
+
: ''
|
|
735
|
+
}`}
|
|
736
|
+
>
|
|
737
|
+
<div className={styles.widgetTitle}>
|
|
738
|
+
<h2>{widget.title}</h2>
|
|
627
739
|
</div>
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
740
|
+
{renderFilters(widget)}
|
|
741
|
+
<div>{renderWidget(widget)}</div>
|
|
742
|
+
{widget.modal && (
|
|
743
|
+
<div className={styles.widgetAction}>
|
|
744
|
+
<button
|
|
745
|
+
className={`${styles.btn} ${styles.dmore}`}
|
|
746
|
+
onClick={() => openModal(widget.id)}
|
|
747
|
+
>
|
|
748
|
+
View all{' '}
|
|
749
|
+
<span>
|
|
750
|
+
({data[widget.id]?.length || 0})
|
|
751
|
+
</span>
|
|
752
|
+
</button>
|
|
753
|
+
</div>
|
|
754
|
+
)}
|
|
755
|
+
</div>
|
|
756
|
+
))}
|
|
757
|
+
</div>
|
|
758
|
+
);
|
|
759
|
+
});
|
|
633
760
|
};
|
|
634
761
|
|
|
635
762
|
return (
|