@visns-studio/visns-components 5.2.3 → 5.2.5
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
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
79
79
|
},
|
|
80
80
|
"name": "@visns-studio/visns-components",
|
|
81
|
-
"version": "5.2.
|
|
81
|
+
"version": "5.2.5",
|
|
82
82
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
83
83
|
"main": "src/index.js",
|
|
84
84
|
"files": [
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
|
-
|
|
3
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
4
3
|
import { Link, useNavigate } from 'react-router-dom';
|
|
5
4
|
import { ResponsiveBar } from '@nivo/bar';
|
|
@@ -9,7 +8,7 @@ import Popup from 'reactjs-popup';
|
|
|
9
8
|
import dayjs from 'dayjs';
|
|
10
9
|
import parse from 'html-react-parser';
|
|
11
10
|
import { createSwapy } from 'swapy';
|
|
12
|
-
import { CircleX } from 'akar-icons';
|
|
11
|
+
import { CircleX, Minus, Plus, Star } from 'akar-icons';
|
|
13
12
|
|
|
14
13
|
import CustomFetch from '../../crm/Fetch';
|
|
15
14
|
import MultiSelect from '../../crm/MultiSelect';
|
|
@@ -18,29 +17,60 @@ import styles from './styles/GenericDashboard.module.scss';
|
|
|
18
17
|
|
|
19
18
|
const toSnakeCase = (str) => str.replace(/[\s-]+/g, '_').toLowerCase();
|
|
20
19
|
|
|
21
|
-
function GenericDashboard({ setting }) {
|
|
20
|
+
function GenericDashboard({ setting, userProfile }) {
|
|
22
21
|
const swapy = useRef(null);
|
|
23
22
|
const container = useRef(null);
|
|
24
23
|
const navigate = useNavigate();
|
|
24
|
+
const [dashboardSetting, setDashboardSetting] = useState(
|
|
25
|
+
userProfile.dashboard_setting || setting
|
|
26
|
+
);
|
|
25
27
|
const [data, setData] = useState({});
|
|
26
28
|
const [dropdowns, setDropdowns] = useState({});
|
|
27
|
-
const [filters, setFilters] = useState({});
|
|
29
|
+
const [filters, setFilters] = useState({});
|
|
28
30
|
const [modalShow, setModalShow] = useState(false);
|
|
29
31
|
const [modalContent, setModalContent] = useState(null);
|
|
32
|
+
const [editMode, setEditMode] = useState(false);
|
|
33
|
+
|
|
34
|
+
const getDefaultDate = (defaultValue) => {
|
|
35
|
+
switch (defaultValue) {
|
|
36
|
+
case 'startOfMonth':
|
|
37
|
+
return dayjs().startOf('month').format('YYYY-MM-DD');
|
|
38
|
+
case 'endOfMonth':
|
|
39
|
+
return dayjs().endOf('month').format('YYYY-MM-DD');
|
|
40
|
+
default:
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Initialize filters based on each widget’s filters
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const initialFilters = {};
|
|
48
|
+
dashboardSetting.widgets.forEach((widget) => {
|
|
49
|
+
if (widget.filters) {
|
|
50
|
+
initialFilters[widget.id] = widget.filters.reduce(
|
|
51
|
+
(acc, filter) => {
|
|
52
|
+
if (filter.type === 'date' && filter.default) {
|
|
53
|
+
acc[filter.id] = getDefaultDate(filter.default);
|
|
54
|
+
} else if (filter.type === 'multi-dropdown-ajax') {
|
|
55
|
+
acc[filter.id] = [];
|
|
56
|
+
} else if (filter.type === 'years') {
|
|
57
|
+
acc[filter.id] = dayjs().year();
|
|
58
|
+
}
|
|
59
|
+
return acc;
|
|
60
|
+
},
|
|
61
|
+
{}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
setFilters(initialFilters);
|
|
66
|
+
updateDashboardSetting(dashboardSetting);
|
|
67
|
+
}, [dashboardSetting]);
|
|
30
68
|
|
|
31
69
|
const fetchData = async (appliedFilters = {}) => {
|
|
32
70
|
try {
|
|
33
|
-
|
|
34
|
-
const flattenedWidgets = setting.widgets.reduce(
|
|
35
|
-
(acc, row) => [...acc, ...row],
|
|
36
|
-
[]
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Map through the flattened array of widgets
|
|
40
|
-
const promises = flattenedWidgets.map(async (widget) => {
|
|
71
|
+
const promises = dashboardSetting.widgets.map(async (widget) => {
|
|
41
72
|
const widgetFilters = appliedFilters[widget.id] || {};
|
|
42
73
|
|
|
43
|
-
// Fetch dropdown data if required
|
|
44
74
|
if (widget.filters) {
|
|
45
75
|
for (const filter of widget.filters) {
|
|
46
76
|
if (filter.url) {
|
|
@@ -68,7 +98,6 @@ function GenericDashboard({ setting }) {
|
|
|
68
98
|
}
|
|
69
99
|
}
|
|
70
100
|
|
|
71
|
-
// Check if widget.api exists before making a request
|
|
72
101
|
if (widget.api?.url && widget.api?.method) {
|
|
73
102
|
return CustomFetch(
|
|
74
103
|
widget.api.url,
|
|
@@ -76,15 +105,11 @@ function GenericDashboard({ setting }) {
|
|
|
76
105
|
widgetFilters
|
|
77
106
|
);
|
|
78
107
|
}
|
|
79
|
-
|
|
80
|
-
return null; // Return null if no API request is needed
|
|
108
|
+
return null;
|
|
81
109
|
});
|
|
82
110
|
|
|
83
|
-
// Execute all API requests
|
|
84
111
|
const results = await Promise.all(promises);
|
|
85
|
-
|
|
86
|
-
// Build a data object keyed by widget id
|
|
87
|
-
const fetchedData = flattenedWidgets.reduce(
|
|
112
|
+
const fetchedData = dashboardSetting.widgets.reduce(
|
|
88
113
|
(acc, widget, index) => {
|
|
89
114
|
if (results[index]) {
|
|
90
115
|
acc[widget.id] = results[index]?.data;
|
|
@@ -93,13 +118,16 @@ function GenericDashboard({ setting }) {
|
|
|
93
118
|
},
|
|
94
119
|
{}
|
|
95
120
|
);
|
|
96
|
-
|
|
97
121
|
setData(fetchedData);
|
|
98
122
|
} catch (error) {
|
|
99
123
|
console.error('Error fetching dashboard data:', error);
|
|
100
124
|
}
|
|
101
125
|
};
|
|
102
126
|
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
fetchData(filters);
|
|
129
|
+
}, [filters]);
|
|
130
|
+
|
|
103
131
|
const openModal = (widgetId) => {
|
|
104
132
|
setModalContent(widgetId);
|
|
105
133
|
setModalShow(true);
|
|
@@ -150,81 +178,80 @@ function GenericDashboard({ setting }) {
|
|
|
150
178
|
}
|
|
151
179
|
};
|
|
152
180
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
const updateDashboardSetting = async (newSetting) => {
|
|
182
|
+
try {
|
|
183
|
+
if (userProfile.id && newSetting) {
|
|
184
|
+
await CustomFetch(`/ajax/users/${userProfile.id}/`, 'PUT', {
|
|
185
|
+
dashboard_setting: newSetting,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.info(err);
|
|
161
190
|
}
|
|
162
191
|
};
|
|
163
192
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
) {
|
|
181
|
-
acc[filter.id] = [];
|
|
182
|
-
} else if (filter.type === 'years') {
|
|
183
|
-
acc[filter.id] = dayjs().year();
|
|
184
|
-
}
|
|
185
|
-
return acc;
|
|
186
|
-
},
|
|
187
|
-
{}
|
|
188
|
-
);
|
|
193
|
+
// Cycle widget size: "1/3" → "half" → "2/3" → "full" (and reverse)
|
|
194
|
+
const changeSize = (widgetId, direction) => {
|
|
195
|
+
setDashboardSetting((prevSetting) => {
|
|
196
|
+
const updatedWidgets = prevSetting.widgets.map((widget) => {
|
|
197
|
+
if (widget.id === widgetId) {
|
|
198
|
+
const currentSize = widget.size || '1/3';
|
|
199
|
+
let newSize = currentSize;
|
|
200
|
+
const order = ['1/3', 'half', '2/3', 'full'];
|
|
201
|
+
const currentIndex = order.indexOf(currentSize);
|
|
202
|
+
if (
|
|
203
|
+
direction === 'increase' &&
|
|
204
|
+
currentIndex < order.length - 1
|
|
205
|
+
) {
|
|
206
|
+
newSize = order[currentIndex + 1];
|
|
207
|
+
} else if (direction === 'decrease' && currentIndex > 0) {
|
|
208
|
+
newSize = order[currentIndex - 1];
|
|
189
209
|
}
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
setFilters(initialFilters);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
initializeFilters();
|
|
197
|
-
}, [setting]);
|
|
198
210
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
return { ...widget, size: newSize };
|
|
212
|
+
}
|
|
213
|
+
return widget;
|
|
214
|
+
});
|
|
215
|
+
const newSetting = { ...prevSetting, widgets: updatedWidgets };
|
|
216
|
+
updateDashboardSetting(newSetting);
|
|
217
|
+
return newSetting;
|
|
218
|
+
});
|
|
219
|
+
};
|
|
207
220
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
// Toggle the highlight state of a widget
|
|
222
|
+
const toggleHighlight = (widgetId) => {
|
|
223
|
+
setDashboardSetting((prevSetting) => {
|
|
224
|
+
const updatedWidgets = prevSetting.widgets.map((widget) => {
|
|
225
|
+
if (widget.id === widgetId) {
|
|
226
|
+
return { ...widget, highlight: !widget.highlight };
|
|
227
|
+
}
|
|
228
|
+
return widget;
|
|
211
229
|
});
|
|
212
|
-
|
|
230
|
+
const newSetting = { ...prevSetting, widgets: updatedWidgets };
|
|
231
|
+
updateDashboardSetting(newSetting);
|
|
232
|
+
return newSetting;
|
|
233
|
+
});
|
|
234
|
+
};
|
|
213
235
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
// Map widget size to a CSS class
|
|
237
|
+
const getGridClass = (size) => {
|
|
238
|
+
switch (size) {
|
|
239
|
+
case 'full':
|
|
240
|
+
return styles.grid__full;
|
|
241
|
+
case 'half':
|
|
242
|
+
return styles.grid__half;
|
|
243
|
+
case '2/3':
|
|
244
|
+
return styles.grid__twothird;
|
|
245
|
+
case '1/3':
|
|
246
|
+
default:
|
|
247
|
+
return styles.grid__three;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
219
250
|
|
|
220
251
|
const renderFilters = (widget) => {
|
|
221
252
|
if (!widget.filters || widget.filters.length === 0) return null;
|
|
222
|
-
|
|
223
253
|
return (
|
|
224
254
|
<div className={styles['filter-container']}>
|
|
225
|
-
<div className={styles['filter-header']}>
|
|
226
|
-
<h3>Filters</h3>
|
|
227
|
-
</div>
|
|
228
255
|
<div className={styles['filter-fields']}>
|
|
229
256
|
{widget.filters.map((filter) => {
|
|
230
257
|
switch (filter.type) {
|
|
@@ -257,7 +284,7 @@ function GenericDashboard({ setting }) {
|
|
|
257
284
|
filter.id,
|
|
258
285
|
e.target.value
|
|
259
286
|
);
|
|
260
|
-
e.target.blur();
|
|
287
|
+
e.target.blur();
|
|
261
288
|
}}
|
|
262
289
|
/>
|
|
263
290
|
</div>
|
|
@@ -328,7 +355,7 @@ function GenericDashboard({ setting }) {
|
|
|
328
355
|
filter.id,
|
|
329
356
|
e.target.value
|
|
330
357
|
);
|
|
331
|
-
e.target.blur();
|
|
358
|
+
e.target.blur();
|
|
332
359
|
}}
|
|
333
360
|
>
|
|
334
361
|
{Array.from(
|
|
@@ -365,8 +392,7 @@ function GenericDashboard({ setting }) {
|
|
|
365
392
|
...prevFilters,
|
|
366
393
|
[widget.id]: {},
|
|
367
394
|
}));
|
|
368
|
-
|
|
369
|
-
fetchData(); // Reset filters and fetch data
|
|
395
|
+
fetchData();
|
|
370
396
|
}}
|
|
371
397
|
>
|
|
372
398
|
Clear Filters
|
|
@@ -379,7 +405,6 @@ function GenericDashboard({ setting }) {
|
|
|
379
405
|
|
|
380
406
|
const renderWidget = (widget) => {
|
|
381
407
|
const widgetData = data[widget.id] || [];
|
|
382
|
-
|
|
383
408
|
if (
|
|
384
409
|
widgetData.length > 0 ||
|
|
385
410
|
widgetData?.data?.length > 0 ||
|
|
@@ -389,7 +414,13 @@ function GenericDashboard({ setting }) {
|
|
|
389
414
|
case 'counter':
|
|
390
415
|
return (
|
|
391
416
|
<>
|
|
392
|
-
<div
|
|
417
|
+
<div
|
|
418
|
+
className={`${
|
|
419
|
+
widget.highlight
|
|
420
|
+
? styles['widgetNo-alt']
|
|
421
|
+
: styles.widgetNo
|
|
422
|
+
}`}
|
|
423
|
+
>
|
|
393
424
|
<span>
|
|
394
425
|
{(Array.isArray(widgetData) &&
|
|
395
426
|
widgetData.length === 0) ||
|
|
@@ -422,12 +453,10 @@ function GenericDashboard({ setting }) {
|
|
|
422
453
|
return (
|
|
423
454
|
<ResponsivePie data={widgetData} {...widget.props} />
|
|
424
455
|
);
|
|
425
|
-
case 'bar':
|
|
456
|
+
case 'bar': {
|
|
426
457
|
if (widgetData.keys && widget.props.keys) {
|
|
427
458
|
widget.props.keys = widgetData.keys;
|
|
428
459
|
}
|
|
429
|
-
|
|
430
|
-
// Build legendMapping and attach legendLabel and a custom tooltip if a legend is provided.
|
|
431
460
|
let legendMapping = {};
|
|
432
461
|
if (widget.legend) {
|
|
433
462
|
legendMapping = widget.legend.reduce((acc, curr) => {
|
|
@@ -437,7 +466,6 @@ function GenericDashboard({ setting }) {
|
|
|
437
466
|
widget.props.legendLabel = (e) =>
|
|
438
467
|
legendMapping[e.id] || e.id;
|
|
439
468
|
|
|
440
|
-
// Define a custom tooltip that uses the legendMapping
|
|
441
469
|
const CustomTooltip = ({ id, value, color }) => {
|
|
442
470
|
const label = legendMapping[id] || id;
|
|
443
471
|
return (
|
|
@@ -457,14 +485,13 @@ function GenericDashboard({ setting }) {
|
|
|
457
485
|
|
|
458
486
|
widget.props.tooltip = CustomTooltip;
|
|
459
487
|
}
|
|
460
|
-
|
|
461
488
|
const barData =
|
|
462
489
|
widgetData.bar?.data || widgetData.data || widgetData;
|
|
463
|
-
|
|
464
490
|
if (barData?.length > 0) {
|
|
465
491
|
return (
|
|
466
492
|
<div style={{ height: widget.height || '600px' }}>
|
|
467
493
|
<ResponsiveBar
|
|
494
|
+
key={`${widget.id}-${widget.size}`}
|
|
468
495
|
data={barData}
|
|
469
496
|
{...widget.props}
|
|
470
497
|
/>
|
|
@@ -472,15 +499,18 @@ function GenericDashboard({ setting }) {
|
|
|
472
499
|
);
|
|
473
500
|
}
|
|
474
501
|
break;
|
|
502
|
+
}
|
|
475
503
|
case 'line':
|
|
476
504
|
if (widgetData.length > 0) {
|
|
477
505
|
return (
|
|
478
506
|
<ResponsiveLine
|
|
507
|
+
key={`${widget.id}-${widget.size}`}
|
|
479
508
|
data={widgetData}
|
|
480
509
|
{...widget.props}
|
|
481
510
|
/>
|
|
482
511
|
);
|
|
483
512
|
}
|
|
513
|
+
break;
|
|
484
514
|
case 'list':
|
|
485
515
|
return (
|
|
486
516
|
<ul className={styles.dashList}>
|
|
@@ -520,10 +550,9 @@ function GenericDashboard({ setting }) {
|
|
|
520
550
|
)}
|
|
521
551
|
</ul>
|
|
522
552
|
);
|
|
523
|
-
case 'table':
|
|
553
|
+
case 'table': {
|
|
524
554
|
const tableData = widgetData.data || widgetData;
|
|
525
555
|
const tableKeys = widgetData.keys || [];
|
|
526
|
-
|
|
527
556
|
return (
|
|
528
557
|
<div className={styles['table-container']}>
|
|
529
558
|
{tableData.length > 0 ? (
|
|
@@ -581,14 +610,13 @@ function GenericDashboard({ setting }) {
|
|
|
581
610
|
{tableKeys && tableKeys.length > 0
|
|
582
611
|
? tableKeys.map(
|
|
583
612
|
(type, typeIndex) => {
|
|
584
|
-
let total = 0;
|
|
613
|
+
let total = 0;
|
|
585
614
|
return (
|
|
586
615
|
<tr
|
|
587
616
|
key={`form-type-${toSnakeCase(
|
|
588
617
|
type
|
|
589
618
|
)}`}
|
|
590
619
|
>
|
|
591
|
-
{/* <td>{type}</td> */}
|
|
592
620
|
{widget.total
|
|
593
621
|
.column &&
|
|
594
622
|
tableData.map(
|
|
@@ -608,7 +636,6 @@ function GenericDashboard({ setting }) {
|
|
|
608
636
|
widget
|
|
609
637
|
.axisKeys
|
|
610
638
|
.xAxis;
|
|
611
|
-
|
|
612
639
|
const column =
|
|
613
640
|
row[
|
|
614
641
|
columnKey
|
|
@@ -627,7 +654,6 @@ function GenericDashboard({ setting }) {
|
|
|
627
654
|
: 0;
|
|
628
655
|
total +=
|
|
629
656
|
count;
|
|
630
|
-
|
|
631
657
|
return (
|
|
632
658
|
<td
|
|
633
659
|
key={`${toSnakeCase(
|
|
@@ -680,8 +706,6 @@ function GenericDashboard({ setting }) {
|
|
|
680
706
|
</tr>
|
|
681
707
|
)
|
|
682
708
|
)}
|
|
683
|
-
|
|
684
|
-
{/* Add a total row for supervisors */}
|
|
685
709
|
{widget.total.column && (
|
|
686
710
|
<tr>
|
|
687
711
|
<td>
|
|
@@ -707,7 +731,6 @@ function GenericDashboard({ setting }) {
|
|
|
707
731
|
column.form_template_label ===
|
|
708
732
|
type
|
|
709
733
|
);
|
|
710
|
-
|
|
711
734
|
return (
|
|
712
735
|
sum +
|
|
713
736
|
(column
|
|
@@ -721,7 +744,6 @@ function GenericDashboard({ setting }) {
|
|
|
721
744
|
},
|
|
722
745
|
0
|
|
723
746
|
);
|
|
724
|
-
|
|
725
747
|
return (
|
|
726
748
|
<td
|
|
727
749
|
key={`${toSnakeCase(
|
|
@@ -752,6 +774,7 @@ function GenericDashboard({ setting }) {
|
|
|
752
774
|
)}
|
|
753
775
|
</div>
|
|
754
776
|
);
|
|
777
|
+
}
|
|
755
778
|
default:
|
|
756
779
|
return null;
|
|
757
780
|
}
|
|
@@ -760,87 +783,178 @@ function GenericDashboard({ setting }) {
|
|
|
760
783
|
}
|
|
761
784
|
};
|
|
762
785
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
786
|
+
// Drag-and-drop swap handling. Each widget is given a slot based on its index.
|
|
787
|
+
const handleWidgetSwap = (event) => {
|
|
788
|
+
const parseSlot = (slot) => {
|
|
789
|
+
// Slot format: "widget-slot-INDEX"
|
|
790
|
+
const parts = slot.split('-');
|
|
791
|
+
return parseInt(parts[2], 10);
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
const fromIndex = parseSlot(event.fromSlot);
|
|
795
|
+
const toIndex = parseSlot(event.toSlot);
|
|
796
|
+
|
|
797
|
+
setDashboardSetting((prevSetting) => {
|
|
798
|
+
const newWidgets = [...prevSetting.widgets];
|
|
799
|
+
[newWidgets[fromIndex], newWidgets[toIndex]] = [
|
|
800
|
+
newWidgets[toIndex],
|
|
801
|
+
newWidgets[fromIndex],
|
|
802
|
+
];
|
|
803
|
+
const newSetting = { ...prevSetting, widgets: newWidgets };
|
|
804
|
+
updateDashboardSetting(newSetting);
|
|
805
|
+
return newSetting;
|
|
806
|
+
});
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
useEffect(() => {
|
|
810
|
+
if (editMode && container.current) {
|
|
811
|
+
swapy.current = createSwapy(container.current, {
|
|
812
|
+
manualSwap: true,
|
|
813
|
+
});
|
|
814
|
+
swapy.current.onSwap((event) => {
|
|
815
|
+
console.log('swap event', event);
|
|
816
|
+
handleWidgetSwap(event);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return () => {
|
|
820
|
+
swapy.current?.destroy();
|
|
821
|
+
swapy.current = null;
|
|
822
|
+
};
|
|
823
|
+
}, [editMode]);
|
|
775
824
|
|
|
825
|
+
const renderWidgets = () => {
|
|
826
|
+
return dashboardSetting.widgets.map((widget, index) => {
|
|
776
827
|
return (
|
|
777
|
-
<div
|
|
778
|
-
{
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
828
|
+
<div
|
|
829
|
+
key={widget.id}
|
|
830
|
+
className={`${getGridClass(widget.size)} ${
|
|
831
|
+
widget.type !== 'list'
|
|
832
|
+
? widget.highlight
|
|
833
|
+
? styles['dashwidget--highlight']
|
|
834
|
+
: styles.dashwidget
|
|
835
|
+
: ''
|
|
836
|
+
} ${
|
|
837
|
+
editMode
|
|
838
|
+
? widget.highlight
|
|
839
|
+
? styles.editModeWidgetHighlight
|
|
840
|
+
: styles.editModeWidgetNormal
|
|
841
|
+
: ''
|
|
842
|
+
}`}
|
|
843
|
+
style={editMode ? { cursor: 'grab' } : {}}
|
|
844
|
+
{...(editMode && {
|
|
845
|
+
'data-swapy-slot': `widget-slot-${index}`,
|
|
846
|
+
})}
|
|
847
|
+
>
|
|
848
|
+
<div
|
|
849
|
+
{...(editMode && {
|
|
850
|
+
'data-swapy-item': `widget-${widget.id}`,
|
|
851
|
+
})}
|
|
852
|
+
className={`${styles.widgetItem}`}
|
|
853
|
+
>
|
|
854
|
+
<div className={styles.widgetTitle}>
|
|
855
|
+
<h2>{widget.title}</h2>
|
|
856
|
+
</div>
|
|
857
|
+
{renderFilters(widget)}
|
|
858
|
+
<div>{renderWidget(widget)}</div>
|
|
859
|
+
{widget.modal && (
|
|
860
|
+
<div className={styles.widgetAction}>
|
|
861
|
+
<button
|
|
862
|
+
className={`${styles.btn} ${styles.dmore}`}
|
|
863
|
+
onClick={() => openModal(widget.id)}
|
|
864
|
+
>
|
|
865
|
+
View all{' '}
|
|
866
|
+
<span>
|
|
867
|
+
({data[widget.id]?.length || 0})
|
|
868
|
+
</span>
|
|
869
|
+
</button>
|
|
870
|
+
</div>
|
|
871
|
+
)}
|
|
872
|
+
{editMode && (
|
|
873
|
+
<div className={styles.widgetTools}>
|
|
874
|
+
<div className={styles.resizeTools}>
|
|
875
|
+
<button
|
|
876
|
+
onClick={() =>
|
|
877
|
+
toggleHighlight(widget.id)
|
|
878
|
+
}
|
|
879
|
+
>
|
|
880
|
+
<Star strokeWidth={2} size={12} />
|
|
881
|
+
</button>
|
|
882
|
+
{(widget.size || '1/3') !== '1/3' && (
|
|
798
883
|
<button
|
|
799
|
-
|
|
800
|
-
|
|
884
|
+
onClick={() =>
|
|
885
|
+
changeSize(
|
|
886
|
+
widget.id,
|
|
887
|
+
'decrease'
|
|
888
|
+
)
|
|
889
|
+
}
|
|
801
890
|
>
|
|
802
|
-
|
|
803
|
-
<span>
|
|
804
|
-
({data[widget.id]?.length || 0})
|
|
805
|
-
</span>
|
|
891
|
+
<Minus strokeWidth={2} size={12} />
|
|
806
892
|
</button>
|
|
807
|
-
|
|
808
|
-
|
|
893
|
+
)}
|
|
894
|
+
{(widget.size || '1/3') !== 'full' && (
|
|
895
|
+
<button
|
|
896
|
+
onClick={() =>
|
|
897
|
+
changeSize(
|
|
898
|
+
widget.id,
|
|
899
|
+
'increase'
|
|
900
|
+
)
|
|
901
|
+
}
|
|
902
|
+
>
|
|
903
|
+
<Plus strokeWidth={2} size={12} />
|
|
904
|
+
</button>
|
|
905
|
+
)}
|
|
906
|
+
</div>
|
|
809
907
|
</div>
|
|
810
|
-
|
|
811
|
-
|
|
908
|
+
)}
|
|
909
|
+
</div>
|
|
812
910
|
</div>
|
|
813
911
|
);
|
|
814
912
|
});
|
|
815
913
|
};
|
|
816
914
|
|
|
817
915
|
return (
|
|
818
|
-
|
|
819
|
-
{
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
916
|
+
<>
|
|
917
|
+
<div className={styles.editButtonContainer}>
|
|
918
|
+
<button
|
|
919
|
+
className={styles.btn}
|
|
920
|
+
onClick={() => setEditMode((prev) => !prev)}
|
|
921
|
+
>
|
|
922
|
+
{editMode ? 'Done' : 'Edit'}
|
|
923
|
+
</button>
|
|
924
|
+
</div>
|
|
925
|
+
<div className={styles.grid} ref={container}>
|
|
926
|
+
{renderWidgets()}
|
|
927
|
+
<Popup open={modalShow} onClose={closeModal}>
|
|
928
|
+
<div className="modalwrap top--modal modalWide">
|
|
929
|
+
<div className="modal">
|
|
930
|
+
<div className="modal__header">
|
|
931
|
+
<h1>
|
|
932
|
+
{
|
|
933
|
+
dashboardSetting.modalTitles?.[
|
|
934
|
+
modalContent
|
|
935
|
+
]
|
|
936
|
+
}
|
|
937
|
+
</h1>
|
|
938
|
+
<button
|
|
939
|
+
className="modal__close"
|
|
940
|
+
onClick={closeModal}
|
|
941
|
+
>
|
|
942
|
+
<CircleX strokeWidth={1} size={24} />
|
|
943
|
+
</button>
|
|
944
|
+
</div>
|
|
945
|
+
<div className="modal__content">
|
|
946
|
+
{modalContent &&
|
|
947
|
+
renderWidget(
|
|
948
|
+
dashboardSetting.widgets.find(
|
|
949
|
+
(w) => w.id === modalContent
|
|
950
|
+
)
|
|
951
|
+
)}
|
|
952
|
+
</div>
|
|
839
953
|
</div>
|
|
840
954
|
</div>
|
|
841
|
-
</
|
|
842
|
-
</
|
|
843
|
-
|
|
955
|
+
</Popup>
|
|
956
|
+
</div>
|
|
957
|
+
</>
|
|
844
958
|
);
|
|
845
959
|
}
|
|
846
960
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* New grid container */
|
|
2
|
+
.grid {
|
|
3
3
|
display: flex;
|
|
4
|
-
|
|
5
|
-
flex-wrap: nowrap;
|
|
6
|
-
height: auto;
|
|
4
|
+
flex-wrap: wrap;
|
|
7
5
|
gap: 1rem;
|
|
8
6
|
}
|
|
9
7
|
|
|
10
8
|
.grid__three {
|
|
11
|
-
|
|
9
|
+
flex: 0 0 calc(33.33% - 0.67rem);
|
|
12
10
|
background: var(--item-color);
|
|
13
11
|
border-radius: var(--br);
|
|
14
12
|
border: 1px solid rgba(var(--item-color-rgb), 1.15);
|
|
@@ -21,10 +19,10 @@
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
.grid__half {
|
|
24
|
-
|
|
22
|
+
flex: 0 0 calc(50% - 0.5rem);
|
|
25
23
|
background: var(--item-color);
|
|
26
|
-
border-radius:
|
|
27
|
-
border: 1px solid
|
|
24
|
+
border-radius: var(--br);
|
|
25
|
+
border: 1px solid rgba(var(--item-color-rgb), 1.15);
|
|
28
26
|
box-sizing: border-box;
|
|
29
27
|
overflow: visible;
|
|
30
28
|
position: relative;
|
|
@@ -34,23 +32,23 @@
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
.grid__full {
|
|
37
|
-
|
|
35
|
+
flex: 0 0 100%;
|
|
38
36
|
background: var(--item-color);
|
|
39
37
|
border-radius: var(--br);
|
|
40
38
|
border: 1px solid rgba(var(--item-color-rgb), 1.15);
|
|
41
39
|
box-sizing: border-box;
|
|
42
40
|
overflow: visible;
|
|
43
41
|
position: relative;
|
|
44
|
-
padding:
|
|
42
|
+
padding: 20px;
|
|
45
43
|
margin: 8px 0;
|
|
46
44
|
box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
.
|
|
50
|
-
|
|
47
|
+
.grid__twothird {
|
|
48
|
+
flex: 0 0 calc(66.66% - 0.67rem);
|
|
51
49
|
background: var(--item-color);
|
|
52
|
-
border-radius:
|
|
53
|
-
border: 1px solid
|
|
50
|
+
border-radius: var(--br);
|
|
51
|
+
border: 1px solid rgba(var(--item-color-rgb), 1.15);
|
|
54
52
|
box-sizing: border-box;
|
|
55
53
|
overflow: visible;
|
|
56
54
|
position: relative;
|
|
@@ -59,21 +57,21 @@
|
|
|
59
57
|
box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
|
|
60
58
|
}
|
|
61
59
|
|
|
60
|
+
/* The remaining CSS remains largely unchanged */
|
|
62
61
|
.widgetTitle {
|
|
63
62
|
width: 100%;
|
|
64
63
|
height: auto;
|
|
65
64
|
padding: 0;
|
|
66
65
|
margin-bottom: 0.85rem;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
66
|
+
}
|
|
67
|
+
.widgetTitle h2 {
|
|
68
|
+
font-size: 1rem;
|
|
69
|
+
margin: 0;
|
|
70
|
+
padding: 0;
|
|
71
|
+
line-height: 1;
|
|
72
|
+
text-transform: capitalize;
|
|
73
|
+
text-align: left;
|
|
74
|
+
color: var(--primary-color);
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
.dashList {
|
|
@@ -192,6 +190,22 @@
|
|
|
192
190
|
padding: 0 0 2rem 0;
|
|
193
191
|
box-sizing: border-box;
|
|
194
192
|
|
|
193
|
+
span {
|
|
194
|
+
width: 100%;
|
|
195
|
+
display: block;
|
|
196
|
+
text-align: center;
|
|
197
|
+
font-weight: 400;
|
|
198
|
+
font-size: 3em;
|
|
199
|
+
color: var(--primary-color);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.widgetNo-alt {
|
|
204
|
+
width: 100%;
|
|
205
|
+
height: auto;
|
|
206
|
+
padding: 0 0 2rem 0;
|
|
207
|
+
box-sizing: border-box;
|
|
208
|
+
|
|
195
209
|
span {
|
|
196
210
|
width: 100%;
|
|
197
211
|
display: block;
|
|
@@ -212,11 +226,12 @@
|
|
|
212
226
|
display: block;
|
|
213
227
|
padding: 0.25em 3em;
|
|
214
228
|
background: none;
|
|
215
|
-
border-color: rgba(var(--
|
|
229
|
+
border-color: rgba(var(--primary-rgb), 0.05);
|
|
230
|
+
border-radius: 5px;
|
|
216
231
|
|
|
217
232
|
&:hover {
|
|
218
233
|
background: none;
|
|
219
|
-
color: var(--
|
|
234
|
+
border-color: rgba(var(--primary-rgb), 0.5);
|
|
220
235
|
}
|
|
221
236
|
}
|
|
222
237
|
|
|
@@ -430,3 +445,94 @@
|
|
|
430
445
|
.table-container th:last-child {
|
|
431
446
|
border-right: none;
|
|
432
447
|
}
|
|
448
|
+
|
|
449
|
+
// Indicates that the widget is in edit mode with a glowing dashed border.
|
|
450
|
+
.editModeWidget {
|
|
451
|
+
border: 2px dashed var(--primary-color);
|
|
452
|
+
box-shadow: 0 0 10px var(--primary-color);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Container for the edit mode toggle button.
|
|
456
|
+
.editButtonContainer {
|
|
457
|
+
position: fixed;
|
|
458
|
+
bottom: 1rem;
|
|
459
|
+
right: 1rem;
|
|
460
|
+
z-index: 1000; // Ensures the button stays on top of other elements
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.editButtonContainer .btn {
|
|
464
|
+
padding: 0.5rem 1rem;
|
|
465
|
+
background: var(--primary-color);
|
|
466
|
+
color: var(--tertiary-color);
|
|
467
|
+
border: none;
|
|
468
|
+
border-radius: var(--br);
|
|
469
|
+
font-size: 0.875rem;
|
|
470
|
+
font-weight: 600;
|
|
471
|
+
text-transform: uppercase;
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
transition: background-color 0.25s ease;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.editButtonContainer .btn:hover {
|
|
477
|
+
background-color: var(--secondary-color);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Normal edit mode style for non-highlighted widgets
|
|
481
|
+
.editModeWidgetNormal {
|
|
482
|
+
border: 2px dashed var(--primary-color);
|
|
483
|
+
box-shadow: 0 0 10px var(--primary-color);
|
|
484
|
+
|
|
485
|
+
// Use the default resize tools positioning for normal widgets.
|
|
486
|
+
.resizeTools {
|
|
487
|
+
top: -5px;
|
|
488
|
+
right: 0px;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Edit mode style for highlighted widgets (using a contrasting color)
|
|
493
|
+
.editModeWidgetHighlight {
|
|
494
|
+
border: 2px dashed var(--secondary-color);
|
|
495
|
+
box-shadow: 0 0 10px var(--secondary-color);
|
|
496
|
+
|
|
497
|
+
// Position resize tools further up to avoid blending with the background.
|
|
498
|
+
.resizeTools {
|
|
499
|
+
top: -5px; // Adjust this value as needed
|
|
500
|
+
right: 0px;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Resizing tool container (common styling)
|
|
505
|
+
.resizeTools {
|
|
506
|
+
position: absolute;
|
|
507
|
+
display: flex;
|
|
508
|
+
gap: 5px;
|
|
509
|
+
|
|
510
|
+
button {
|
|
511
|
+
background: var(--primary-color);
|
|
512
|
+
color: var(--tertiary-color);
|
|
513
|
+
border: none;
|
|
514
|
+
border-radius: 3px;
|
|
515
|
+
padding: 0.25rem 0.5rem;
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
font-size: 0.75rem;
|
|
518
|
+
transition: background-color 0.2s;
|
|
519
|
+
|
|
520
|
+
&:hover {
|
|
521
|
+
background-color: rgba(var(--primary-rgb), 0.8);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Override the resize button for highlighted widgets
|
|
527
|
+
.editModeWidgetHighlight .resizeTools button {
|
|
528
|
+
background: var(--tertiary-color); // Use a contrasting background
|
|
529
|
+
color: var(--primary-color); // Ensure the text is legible
|
|
530
|
+
|
|
531
|
+
&:hover {
|
|
532
|
+
background-color: rgba(var(--tertiary-color-rgb), 0.8);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.widgetItem {
|
|
537
|
+
position: relative;
|
|
538
|
+
}
|