@visns-studio/visns-components 5.2.3 → 5.2.4
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.4",
|
|
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';
|
|
@@ -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,64 @@ 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
|
-
|
|
210
|
+
return { ...widget, size: newSize };
|
|
211
|
+
}
|
|
212
|
+
return widget;
|
|
191
213
|
});
|
|
214
|
+
const newSetting = { ...prevSetting, widgets: updatedWidgets };
|
|
215
|
+
updateDashboardSetting(newSetting);
|
|
216
|
+
return newSetting;
|
|
217
|
+
});
|
|
218
|
+
};
|
|
192
219
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (container.current) {
|
|
206
|
-
swapy.current = createSwapy(container.current);
|
|
207
|
-
|
|
208
|
-
// Your event listeners
|
|
209
|
-
swapy.current.onSwap((event) => {
|
|
210
|
-
console.log('swap', event);
|
|
211
|
-
});
|
|
220
|
+
// Map widget size to a CSS class
|
|
221
|
+
const getGridClass = (size) => {
|
|
222
|
+
switch (size) {
|
|
223
|
+
case 'full':
|
|
224
|
+
return styles.grid__full;
|
|
225
|
+
case 'half':
|
|
226
|
+
return styles.grid__half;
|
|
227
|
+
case '2/3':
|
|
228
|
+
return styles.grid__twothird;
|
|
229
|
+
case '1/3':
|
|
230
|
+
default:
|
|
231
|
+
return styles.grid__three;
|
|
212
232
|
}
|
|
213
|
-
|
|
214
|
-
return () => {
|
|
215
|
-
// Destroy the swapy instance on component destroy
|
|
216
|
-
swapy.current?.destroy();
|
|
217
|
-
};
|
|
218
|
-
}, []);
|
|
233
|
+
};
|
|
219
234
|
|
|
220
235
|
const renderFilters = (widget) => {
|
|
221
236
|
if (!widget.filters || widget.filters.length === 0) return null;
|
|
222
|
-
|
|
223
237
|
return (
|
|
224
238
|
<div className={styles['filter-container']}>
|
|
225
|
-
<div className={styles['filter-header']}>
|
|
226
|
-
<h3>Filters</h3>
|
|
227
|
-
</div>
|
|
228
239
|
<div className={styles['filter-fields']}>
|
|
229
240
|
{widget.filters.map((filter) => {
|
|
230
241
|
switch (filter.type) {
|
|
@@ -257,7 +268,7 @@ function GenericDashboard({ setting }) {
|
|
|
257
268
|
filter.id,
|
|
258
269
|
e.target.value
|
|
259
270
|
);
|
|
260
|
-
e.target.blur();
|
|
271
|
+
e.target.blur();
|
|
261
272
|
}}
|
|
262
273
|
/>
|
|
263
274
|
</div>
|
|
@@ -328,7 +339,7 @@ function GenericDashboard({ setting }) {
|
|
|
328
339
|
filter.id,
|
|
329
340
|
e.target.value
|
|
330
341
|
);
|
|
331
|
-
e.target.blur();
|
|
342
|
+
e.target.blur();
|
|
332
343
|
}}
|
|
333
344
|
>
|
|
334
345
|
{Array.from(
|
|
@@ -365,8 +376,7 @@ function GenericDashboard({ setting }) {
|
|
|
365
376
|
...prevFilters,
|
|
366
377
|
[widget.id]: {},
|
|
367
378
|
}));
|
|
368
|
-
|
|
369
|
-
fetchData(); // Reset filters and fetch data
|
|
379
|
+
fetchData();
|
|
370
380
|
}}
|
|
371
381
|
>
|
|
372
382
|
Clear Filters
|
|
@@ -379,7 +389,6 @@ function GenericDashboard({ setting }) {
|
|
|
379
389
|
|
|
380
390
|
const renderWidget = (widget) => {
|
|
381
391
|
const widgetData = data[widget.id] || [];
|
|
382
|
-
|
|
383
392
|
if (
|
|
384
393
|
widgetData.length > 0 ||
|
|
385
394
|
widgetData?.data?.length > 0 ||
|
|
@@ -422,12 +431,10 @@ function GenericDashboard({ setting }) {
|
|
|
422
431
|
return (
|
|
423
432
|
<ResponsivePie data={widgetData} {...widget.props} />
|
|
424
433
|
);
|
|
425
|
-
case 'bar':
|
|
434
|
+
case 'bar': {
|
|
426
435
|
if (widgetData.keys && widget.props.keys) {
|
|
427
436
|
widget.props.keys = widgetData.keys;
|
|
428
437
|
}
|
|
429
|
-
|
|
430
|
-
// Build legendMapping and attach legendLabel and a custom tooltip if a legend is provided.
|
|
431
438
|
let legendMapping = {};
|
|
432
439
|
if (widget.legend) {
|
|
433
440
|
legendMapping = widget.legend.reduce((acc, curr) => {
|
|
@@ -437,7 +444,6 @@ function GenericDashboard({ setting }) {
|
|
|
437
444
|
widget.props.legendLabel = (e) =>
|
|
438
445
|
legendMapping[e.id] || e.id;
|
|
439
446
|
|
|
440
|
-
// Define a custom tooltip that uses the legendMapping
|
|
441
447
|
const CustomTooltip = ({ id, value, color }) => {
|
|
442
448
|
const label = legendMapping[id] || id;
|
|
443
449
|
return (
|
|
@@ -457,10 +463,8 @@ function GenericDashboard({ setting }) {
|
|
|
457
463
|
|
|
458
464
|
widget.props.tooltip = CustomTooltip;
|
|
459
465
|
}
|
|
460
|
-
|
|
461
466
|
const barData =
|
|
462
467
|
widgetData.bar?.data || widgetData.data || widgetData;
|
|
463
|
-
|
|
464
468
|
if (barData?.length > 0) {
|
|
465
469
|
return (
|
|
466
470
|
<div style={{ height: widget.height || '600px' }}>
|
|
@@ -472,6 +476,7 @@ function GenericDashboard({ setting }) {
|
|
|
472
476
|
);
|
|
473
477
|
}
|
|
474
478
|
break;
|
|
479
|
+
}
|
|
475
480
|
case 'line':
|
|
476
481
|
if (widgetData.length > 0) {
|
|
477
482
|
return (
|
|
@@ -481,6 +486,7 @@ function GenericDashboard({ setting }) {
|
|
|
481
486
|
/>
|
|
482
487
|
);
|
|
483
488
|
}
|
|
489
|
+
break;
|
|
484
490
|
case 'list':
|
|
485
491
|
return (
|
|
486
492
|
<ul className={styles.dashList}>
|
|
@@ -520,10 +526,9 @@ function GenericDashboard({ setting }) {
|
|
|
520
526
|
)}
|
|
521
527
|
</ul>
|
|
522
528
|
);
|
|
523
|
-
case 'table':
|
|
529
|
+
case 'table': {
|
|
524
530
|
const tableData = widgetData.data || widgetData;
|
|
525
531
|
const tableKeys = widgetData.keys || [];
|
|
526
|
-
|
|
527
532
|
return (
|
|
528
533
|
<div className={styles['table-container']}>
|
|
529
534
|
{tableData.length > 0 ? (
|
|
@@ -581,14 +586,13 @@ function GenericDashboard({ setting }) {
|
|
|
581
586
|
{tableKeys && tableKeys.length > 0
|
|
582
587
|
? tableKeys.map(
|
|
583
588
|
(type, typeIndex) => {
|
|
584
|
-
let total = 0;
|
|
589
|
+
let total = 0;
|
|
585
590
|
return (
|
|
586
591
|
<tr
|
|
587
592
|
key={`form-type-${toSnakeCase(
|
|
588
593
|
type
|
|
589
594
|
)}`}
|
|
590
595
|
>
|
|
591
|
-
{/* <td>{type}</td> */}
|
|
592
596
|
{widget.total
|
|
593
597
|
.column &&
|
|
594
598
|
tableData.map(
|
|
@@ -608,7 +612,6 @@ function GenericDashboard({ setting }) {
|
|
|
608
612
|
widget
|
|
609
613
|
.axisKeys
|
|
610
614
|
.xAxis;
|
|
611
|
-
|
|
612
615
|
const column =
|
|
613
616
|
row[
|
|
614
617
|
columnKey
|
|
@@ -627,7 +630,6 @@ function GenericDashboard({ setting }) {
|
|
|
627
630
|
: 0;
|
|
628
631
|
total +=
|
|
629
632
|
count;
|
|
630
|
-
|
|
631
633
|
return (
|
|
632
634
|
<td
|
|
633
635
|
key={`${toSnakeCase(
|
|
@@ -680,8 +682,6 @@ function GenericDashboard({ setting }) {
|
|
|
680
682
|
</tr>
|
|
681
683
|
)
|
|
682
684
|
)}
|
|
683
|
-
|
|
684
|
-
{/* Add a total row for supervisors */}
|
|
685
685
|
{widget.total.column && (
|
|
686
686
|
<tr>
|
|
687
687
|
<td>
|
|
@@ -707,7 +707,6 @@ function GenericDashboard({ setting }) {
|
|
|
707
707
|
column.form_template_label ===
|
|
708
708
|
type
|
|
709
709
|
);
|
|
710
|
-
|
|
711
710
|
return (
|
|
712
711
|
sum +
|
|
713
712
|
(column
|
|
@@ -721,7 +720,6 @@ function GenericDashboard({ setting }) {
|
|
|
721
720
|
},
|
|
722
721
|
0
|
|
723
722
|
);
|
|
724
|
-
|
|
725
723
|
return (
|
|
726
724
|
<td
|
|
727
725
|
key={`${toSnakeCase(
|
|
@@ -752,6 +750,7 @@ function GenericDashboard({ setting }) {
|
|
|
752
750
|
)}
|
|
753
751
|
</div>
|
|
754
752
|
);
|
|
753
|
+
}
|
|
755
754
|
default:
|
|
756
755
|
return null;
|
|
757
756
|
}
|
|
@@ -760,87 +759,162 @@ function GenericDashboard({ setting }) {
|
|
|
760
759
|
}
|
|
761
760
|
};
|
|
762
761
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
762
|
+
// Drag-and-drop swap handling. Each widget is given a slot based on its index.
|
|
763
|
+
const handleWidgetSwap = (event) => {
|
|
764
|
+
const parseSlot = (slot) => {
|
|
765
|
+
// Slot format: "widget-slot-INDEX"
|
|
766
|
+
const parts = slot.split('-');
|
|
767
|
+
return parseInt(parts[2], 10);
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const fromIndex = parseSlot(event.fromSlot);
|
|
771
|
+
const toIndex = parseSlot(event.toSlot);
|
|
772
|
+
|
|
773
|
+
setDashboardSetting((prevSetting) => {
|
|
774
|
+
const newWidgets = [...prevSetting.widgets];
|
|
775
|
+
[newWidgets[fromIndex], newWidgets[toIndex]] = [
|
|
776
|
+
newWidgets[toIndex],
|
|
777
|
+
newWidgets[fromIndex],
|
|
778
|
+
];
|
|
779
|
+
const newSetting = { ...prevSetting, widgets: newWidgets };
|
|
780
|
+
updateDashboardSetting(newSetting);
|
|
781
|
+
return newSetting;
|
|
782
|
+
});
|
|
783
|
+
};
|
|
775
784
|
|
|
785
|
+
useEffect(() => {
|
|
786
|
+
if (editMode && container.current) {
|
|
787
|
+
swapy.current = createSwapy(container.current, {
|
|
788
|
+
manualSwap: true,
|
|
789
|
+
});
|
|
790
|
+
swapy.current.onSwap((event) => {
|
|
791
|
+
console.log('swap event', event);
|
|
792
|
+
handleWidgetSwap(event);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
return () => {
|
|
796
|
+
swapy.current?.destroy();
|
|
797
|
+
swapy.current = null;
|
|
798
|
+
};
|
|
799
|
+
}, [editMode]);
|
|
800
|
+
|
|
801
|
+
const renderWidgets = () => {
|
|
802
|
+
return dashboardSetting.widgets.map((widget, index) => {
|
|
776
803
|
return (
|
|
777
|
-
<div
|
|
778
|
-
{
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
804
|
+
<div
|
|
805
|
+
key={widget.id}
|
|
806
|
+
className={`${getGridClass(widget.size)} ${
|
|
807
|
+
widget.type !== 'list'
|
|
808
|
+
? widget.highlight
|
|
809
|
+
? styles['dashwidget--highlight']
|
|
810
|
+
: styles.dashwidget
|
|
811
|
+
: ''
|
|
812
|
+
} ${
|
|
813
|
+
editMode
|
|
814
|
+
? widget.highlight
|
|
815
|
+
? styles.editModeWidgetHighlight
|
|
816
|
+
: styles.editModeWidgetNormal
|
|
817
|
+
: ''
|
|
818
|
+
}`}
|
|
819
|
+
{...(editMode && {
|
|
820
|
+
'data-swapy-slot': `widget-slot-${index}`,
|
|
821
|
+
})}
|
|
822
|
+
>
|
|
823
|
+
<div
|
|
824
|
+
{...(editMode && {
|
|
825
|
+
'data-swapy-item': `widget-${widget.id}`,
|
|
826
|
+
})}
|
|
827
|
+
className={`${styles.widgetItem}`}
|
|
828
|
+
>
|
|
829
|
+
<div className={styles.widgetTitle}>
|
|
830
|
+
<h2>{widget.title}</h2>
|
|
831
|
+
</div>
|
|
832
|
+
{renderFilters(widget)}
|
|
833
|
+
<div>{renderWidget(widget)}</div>
|
|
834
|
+
{widget.modal && (
|
|
835
|
+
<div className={styles.widgetAction}>
|
|
836
|
+
<button
|
|
837
|
+
className={`${styles.btn} ${styles.dmore}`}
|
|
838
|
+
onClick={() => openModal(widget.id)}
|
|
839
|
+
>
|
|
840
|
+
View all{' '}
|
|
841
|
+
<span>
|
|
842
|
+
({data[widget.id]?.length || 0})
|
|
843
|
+
</span>
|
|
844
|
+
</button>
|
|
845
|
+
</div>
|
|
846
|
+
)}
|
|
847
|
+
{editMode && (
|
|
848
|
+
<div className={styles.resizeTools}>
|
|
849
|
+
{(widget.size || '1/3') !== '1/3' && (
|
|
850
|
+
<button
|
|
851
|
+
onClick={() =>
|
|
852
|
+
changeSize(widget.id, 'decrease')
|
|
853
|
+
}
|
|
854
|
+
>
|
|
855
|
+
–
|
|
856
|
+
</button>
|
|
857
|
+
)}
|
|
858
|
+
{(widget.size || '1/3') !== 'full' && (
|
|
859
|
+
<button
|
|
860
|
+
onClick={() =>
|
|
861
|
+
changeSize(widget.id, 'increase')
|
|
862
|
+
}
|
|
863
|
+
>
|
|
864
|
+
+
|
|
865
|
+
</button>
|
|
808
866
|
)}
|
|
809
867
|
</div>
|
|
810
|
-
|
|
811
|
-
|
|
868
|
+
)}
|
|
869
|
+
</div>
|
|
812
870
|
</div>
|
|
813
871
|
);
|
|
814
872
|
});
|
|
815
873
|
};
|
|
816
874
|
|
|
817
875
|
return (
|
|
818
|
-
|
|
819
|
-
{
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
876
|
+
<>
|
|
877
|
+
<div className={styles.editButtonContainer}>
|
|
878
|
+
<button
|
|
879
|
+
className={styles.btn}
|
|
880
|
+
onClick={() => setEditMode((prev) => !prev)}
|
|
881
|
+
>
|
|
882
|
+
{editMode ? 'Done' : 'Edit'}
|
|
883
|
+
</button>
|
|
884
|
+
</div>
|
|
885
|
+
<div className={styles.grid} ref={container}>
|
|
886
|
+
{renderWidgets()}
|
|
887
|
+
<Popup open={modalShow} onClose={closeModal}>
|
|
888
|
+
<div className="modalwrap top--modal modalWide">
|
|
889
|
+
<div className="modal">
|
|
890
|
+
<div className="modal__header">
|
|
891
|
+
<h1>
|
|
892
|
+
{
|
|
893
|
+
dashboardSetting.modalTitles?.[
|
|
894
|
+
modalContent
|
|
895
|
+
]
|
|
896
|
+
}
|
|
897
|
+
</h1>
|
|
898
|
+
<button
|
|
899
|
+
className="modal__close"
|
|
900
|
+
onClick={closeModal}
|
|
901
|
+
>
|
|
902
|
+
<CircleX strokeWidth={1} size={24} />
|
|
903
|
+
</button>
|
|
904
|
+
</div>
|
|
905
|
+
<div className="modal__content">
|
|
906
|
+
{modalContent &&
|
|
907
|
+
renderWidget(
|
|
908
|
+
dashboardSetting.widgets.find(
|
|
909
|
+
(w) => w.id === modalContent
|
|
910
|
+
)
|
|
911
|
+
)}
|
|
912
|
+
</div>
|
|
839
913
|
</div>
|
|
840
914
|
</div>
|
|
841
|
-
</
|
|
842
|
-
</
|
|
843
|
-
|
|
915
|
+
</Popup>
|
|
916
|
+
</div>
|
|
917
|
+
</>
|
|
844
918
|
);
|
|
845
919
|
}
|
|
846
920
|
|
|
@@ -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 {
|
|
@@ -430,3 +428,93 @@
|
|
|
430
428
|
.table-container th:last-child {
|
|
431
429
|
border-right: none;
|
|
432
430
|
}
|
|
431
|
+
|
|
432
|
+
// Indicates that the widget is in edit mode with a glowing dashed border.
|
|
433
|
+
.editModeWidget {
|
|
434
|
+
border: 2px dashed var(--primary-color);
|
|
435
|
+
box-shadow: 0 0 10px var(--primary-color);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Container for the edit mode toggle button.
|
|
439
|
+
.editButtonContainer {
|
|
440
|
+
display: flex;
|
|
441
|
+
justify-content: flex-end;
|
|
442
|
+
margin-bottom: 1rem;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.editButtonContainer .btn {
|
|
446
|
+
padding: 0.5rem 1rem;
|
|
447
|
+
background: var(--primary-color);
|
|
448
|
+
color: var(--tertiary-color);
|
|
449
|
+
border: none;
|
|
450
|
+
border-radius: var(--br);
|
|
451
|
+
font-size: 0.875rem;
|
|
452
|
+
font-weight: 600;
|
|
453
|
+
text-transform: uppercase;
|
|
454
|
+
cursor: pointer;
|
|
455
|
+
transition: background-color 0.25s ease;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.editButtonContainer .btn:hover {
|
|
459
|
+
background-color: var(--secondary-color);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Normal edit mode style for non-highlighted widgets
|
|
463
|
+
.editModeWidgetNormal {
|
|
464
|
+
border: 2px dashed var(--primary-color);
|
|
465
|
+
box-shadow: 0 0 10px var(--primary-color);
|
|
466
|
+
|
|
467
|
+
// Use the default resize tools positioning for normal widgets.
|
|
468
|
+
.resizeTools {
|
|
469
|
+
top: -5px;
|
|
470
|
+
right: 0px;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Edit mode style for highlighted widgets (using a contrasting color)
|
|
475
|
+
.editModeWidgetHighlight {
|
|
476
|
+
border: 2px dashed var(--secondary-color);
|
|
477
|
+
box-shadow: 0 0 10px var(--secondary-color);
|
|
478
|
+
|
|
479
|
+
// Position resize tools further up to avoid blending with the background.
|
|
480
|
+
.resizeTools {
|
|
481
|
+
top: -5px; // Adjust this value as needed
|
|
482
|
+
right: 0px;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Resizing tool container (common styling)
|
|
487
|
+
.resizeTools {
|
|
488
|
+
position: absolute;
|
|
489
|
+
display: flex;
|
|
490
|
+
gap: 5px;
|
|
491
|
+
|
|
492
|
+
button {
|
|
493
|
+
background: var(--primary-color);
|
|
494
|
+
color: var(--tertiary-color);
|
|
495
|
+
border: none;
|
|
496
|
+
border-radius: 3px;
|
|
497
|
+
padding: 0.25rem 0.5rem;
|
|
498
|
+
cursor: pointer;
|
|
499
|
+
font-size: 0.75rem;
|
|
500
|
+
transition: background-color 0.2s;
|
|
501
|
+
|
|
502
|
+
&:hover {
|
|
503
|
+
background-color: rgba(var(--primary-rgb), 0.8);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Override the resize button for highlighted widgets
|
|
509
|
+
.editModeWidgetHighlight .resizeTools button {
|
|
510
|
+
background: var(--tertiary-color); // Use a contrasting background
|
|
511
|
+
color: var(--primary-color); // Ensure the text is legible
|
|
512
|
+
|
|
513
|
+
&:hover {
|
|
514
|
+
background-color: rgba(var(--tertiary-color-rgb), 0.8);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.widgetItem {
|
|
519
|
+
position: relative;
|
|
520
|
+
}
|