@visns-studio/visns-components 5.2.7 → 5.2.8

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.7",
81
+ "version": "5.2.8",
82
82
  "description": "Various packages to assist in the development of our Custom Applications.",
83
83
  "main": "src/index.js",
84
84
  "files": [
@@ -8,7 +8,9 @@ import Popup from 'reactjs-popup';
8
8
  import dayjs from 'dayjs';
9
9
  import parse from 'html-react-parser';
10
10
  import { createSwapy } from 'swapy';
11
- import { CircleX, Minus, Plus, Star } from 'akar-icons';
11
+ import { CircleX, Minus, Plus, Star, TrashCan } from 'akar-icons';
12
+ import { confirmAlert } from 'react-confirm-alert';
13
+ import 'react-confirm-alert/src/react-confirm-alert.css';
12
14
 
13
15
  import CustomFetch from '../../crm/Fetch';
14
16
  import MultiSelect from '../../crm/MultiSelect';
@@ -30,6 +32,18 @@ function GenericDashboard({ setting, userProfile }) {
30
32
  const [modalShow, setModalShow] = useState(false);
31
33
  const [modalContent, setModalContent] = useState(null);
32
34
  const [editMode, setEditMode] = useState(false);
35
+ const [showAddWidgetModal, setShowAddWidgetModal] = useState(false);
36
+ const [selectedAddWidgetId, setSelectedAddWidgetId] = useState('');
37
+ // Flag used only for triggering reinitialization after widget addition.
38
+ const [widgetAdded, setWidgetAdded] = useState(false);
39
+
40
+ // Compute available widgets from original settings that are not yet added.
41
+ const availableWidgets = setting.widgets.filter(
42
+ (widget) =>
43
+ !dashboardSetting.widgets.find(
44
+ (dashWidget) => dashWidget.id === widget.id
45
+ )
46
+ );
33
47
 
34
48
  const getDefaultDate = (defaultValue) => {
35
49
  switch (defaultValue) {
@@ -42,7 +56,7 @@ function GenericDashboard({ setting, userProfile }) {
42
56
  }
43
57
  };
44
58
 
45
- // Initialize filters based on each widget’s filters
59
+ // Initialize filters based on each widget’s filters.
46
60
  useEffect(() => {
47
61
  const initialFilters = {};
48
62
  dashboardSetting.widgets.forEach((widget) => {
@@ -70,7 +84,6 @@ function GenericDashboard({ setting, userProfile }) {
70
84
  try {
71
85
  const promises = dashboardSetting.widgets.map(async (widget) => {
72
86
  const widgetFilters = appliedFilters[widget.id] || {};
73
-
74
87
  if (widget.filters) {
75
88
  for (const filter of widget.filters) {
76
89
  if (filter.url) {
@@ -97,7 +110,6 @@ function GenericDashboard({ setting, userProfile }) {
97
110
  }
98
111
  }
99
112
  }
100
-
101
113
  if (widget.api?.url && widget.api?.method) {
102
114
  return CustomFetch(
103
115
  widget.api.url,
@@ -107,7 +119,6 @@ function GenericDashboard({ setting, userProfile }) {
107
119
  }
108
120
  return null;
109
121
  });
110
-
111
122
  const results = await Promise.all(promises);
112
123
  const fetchedData = dashboardSetting.widgets.reduce(
113
124
  (acc, widget, index) => {
@@ -150,7 +161,6 @@ function GenericDashboard({ setting, userProfile }) {
150
161
  const handleFilterSelectChange = (value, action, id) => {
151
162
  const widgetId = id.split('.')[0];
152
163
  const filterId = id.split('.')[1];
153
-
154
164
  if (action.action === 'select-option') {
155
165
  setFilters((prevFilters) => ({
156
166
  ...prevFilters,
@@ -207,7 +217,6 @@ function GenericDashboard({ setting, userProfile }) {
207
217
  } else if (direction === 'decrease' && currentIndex > 0) {
208
218
  newSize = order[currentIndex - 1];
209
219
  }
210
-
211
220
  return { ...widget, size: newSize };
212
221
  }
213
222
  return widget;
@@ -218,7 +227,7 @@ function GenericDashboard({ setting, userProfile }) {
218
227
  });
219
228
  };
220
229
 
221
- // Toggle the highlight state of a widget
230
+ // Toggle the highlight state of a widget.
222
231
  const toggleHighlight = (widgetId) => {
223
232
  setDashboardSetting((prevSetting) => {
224
233
  const updatedWidgets = prevSetting.widgets.map((widget) => {
@@ -233,7 +242,48 @@ function GenericDashboard({ setting, userProfile }) {
233
242
  });
234
243
  };
235
244
 
236
- // Map widget size to a CSS class
245
+ // Delete a widget from the dashboard after confirming.
246
+ const handleDeleteWidget = (widgetId) => {
247
+ setDashboardSetting((prevSetting) => {
248
+ const newWidgets = prevSetting.widgets.filter(
249
+ (widget) => widget.id !== widgetId
250
+ );
251
+ const newSetting = { ...prevSetting, widgets: newWidgets };
252
+ updateDashboardSetting(newSetting);
253
+ return newSetting;
254
+ });
255
+ };
256
+
257
+ const confirmDelete = (widgetId) => {
258
+ confirmAlert({
259
+ title: 'Confirm to delete',
260
+ message: 'Are you sure you want to delete this widget?',
261
+ buttons: [
262
+ { label: 'Yes', onClick: () => handleDeleteWidget(widgetId) },
263
+ { label: 'No' },
264
+ ],
265
+ });
266
+ };
267
+
268
+ // Add a widget to the dashboard (append to end). After adding, set widgetAdded flag.
269
+ const handleAddWidget = () => {
270
+ const widgetToAdd = setting.widgets.find(
271
+ (widget) => widget.id === selectedAddWidgetId
272
+ );
273
+ if (widgetToAdd) {
274
+ setDashboardSetting((prevSetting) => {
275
+ const newWidgets = [...prevSetting.widgets, widgetToAdd];
276
+ const newSetting = { ...prevSetting, widgets: newWidgets };
277
+ updateDashboardSetting(newSetting);
278
+ return newSetting;
279
+ });
280
+ setSelectedAddWidgetId('');
281
+ setShowAddWidgetModal(false);
282
+ setWidgetAdded(true); // Trigger reinitialization after addition.
283
+ }
284
+ };
285
+
286
+ // Map widget size to a CSS class.
237
287
  const getGridClass = (size) => {
238
288
  switch (size) {
239
289
  case 'full':
@@ -465,7 +515,6 @@ function GenericDashboard({ setting, userProfile }) {
465
515
  }, {});
466
516
  widget.props.legendLabel = (e) =>
467
517
  legendMapping[e.id] || e.id;
468
-
469
518
  const CustomTooltip = ({ id, value, color }) => {
470
519
  const label = legendMapping[id] || id;
471
520
  return (
@@ -482,12 +531,10 @@ function GenericDashboard({ setting, userProfile }) {
482
531
  </div>
483
532
  );
484
533
  };
485
-
486
534
  widget.props.tooltip = CustomTooltip;
487
535
  }
488
536
  const barData =
489
537
  widgetData.bar?.data || widgetData.data || widgetData;
490
-
491
538
  if (barData?.length > 0) {
492
539
  return (
493
540
  <div style={{ height: widget.height || '600px' }}>
@@ -784,17 +831,11 @@ function GenericDashboard({ setting, userProfile }) {
784
831
  }
785
832
  };
786
833
 
787
- // Drag-and-drop swap handling. Each widget is given a slot based on its index.
834
+ // Swap handler.
788
835
  const handleWidgetSwap = (event) => {
789
- const parseSlot = (slot) => {
790
- // Slot format: "widget-slot-INDEX"
791
- const parts = slot.split('-');
792
- return parseInt(parts[2], 10);
793
- };
794
-
836
+ const parseSlot = (slot) => parseInt(slot.split('-')[2], 10);
795
837
  const fromIndex = parseSlot(event.fromSlot);
796
838
  const toIndex = parseSlot(event.toSlot);
797
-
798
839
  setDashboardSetting((prevSetting) => {
799
840
  const newWidgets = [...prevSetting.widgets];
800
841
  [newWidgets[fromIndex], newWidgets[toIndex]] = [
@@ -807,120 +848,138 @@ function GenericDashboard({ setting, userProfile }) {
807
848
  });
808
849
  };
809
850
 
851
+ // Effect to reinitialize Swapy when editMode changes.
810
852
  useEffect(() => {
811
853
  if (editMode && container.current) {
854
+ swapy.current?.destroy();
812
855
  swapy.current = createSwapy(container.current, {
813
856
  manualSwap: true,
814
857
  });
815
858
  swapy.current.onSwap((event) => {
816
- console.log('swap event', event);
817
859
  handleWidgetSwap(event);
818
860
  });
819
- }
820
- return () => {
861
+ } else {
821
862
  swapy.current?.destroy();
822
863
  swapy.current = null;
823
- };
864
+ }
865
+ // This effect depends solely on editMode.
824
866
  }, [editMode]);
825
867
 
868
+ // Separate effect to reinitialize Swapy after a widget is added.
869
+ useEffect(() => {
870
+ if (widgetAdded && editMode && container.current) {
871
+ swapy.current?.destroy();
872
+ swapy.current = createSwapy(container.current, {
873
+ manualSwap: true,
874
+ });
875
+ swapy.current.onSwap((event) => {
876
+ handleWidgetSwap(event);
877
+ });
878
+ setWidgetAdded(false);
879
+ }
880
+ // This effect only triggers when widgetAdded changes.
881
+ }, [widgetAdded, editMode]);
882
+
826
883
  const renderWidgets = () => {
827
- return dashboardSetting.widgets.map((widget, index) => {
828
- return (
884
+ return dashboardSetting.widgets.map((widget, index) => (
885
+ <div
886
+ key={widget.id}
887
+ className={`${getGridClass(widget.size)} ${
888
+ widget.type !== 'list'
889
+ ? widget.highlight
890
+ ? styles['dashwidget--highlight']
891
+ : styles.dashwidget
892
+ : ''
893
+ } ${
894
+ editMode
895
+ ? widget.highlight
896
+ ? styles.editModeWidgetHighlight
897
+ : styles.editModeWidgetNormal
898
+ : ''
899
+ }`}
900
+ style={editMode ? { cursor: 'grab' } : {}}
901
+ {...(editMode && { 'data-swapy-slot': `widget-slot-${index}` })}
902
+ >
829
903
  <div
830
- key={widget.id}
831
- className={`${getGridClass(widget.size)} ${
832
- widget.type !== 'list'
833
- ? widget.highlight
834
- ? styles['dashwidget--highlight']
835
- : styles.dashwidget
836
- : ''
837
- } ${
838
- editMode
839
- ? widget.highlight
840
- ? styles.editModeWidgetHighlight
841
- : styles.editModeWidgetNormal
842
- : ''
843
- }`}
844
- style={editMode ? { cursor: 'grab' } : {}}
845
904
  {...(editMode && {
846
- 'data-swapy-slot': `widget-slot-${index}`,
905
+ 'data-swapy-item': `widget-${widget.id}`,
847
906
  })}
907
+ className={`${styles.widgetItem}`}
848
908
  >
849
- <div
850
- {...(editMode && {
851
- 'data-swapy-item': `widget-${widget.id}`,
852
- })}
853
- className={`${styles.widgetItem}`}
854
- >
855
- <div className={styles.widgetTitle}>
856
- <h2>{widget.title}</h2>
909
+ <div className={styles.widgetTitle}>
910
+ <h2>{widget.title}</h2>
911
+ </div>
912
+ {renderFilters(widget)}
913
+ <div>{renderWidget(widget)}</div>
914
+ {widget.modal && (
915
+ <div className={styles.widgetAction}>
916
+ <button
917
+ className={`${styles.btn} ${styles.dmore}`}
918
+ onClick={() => openModal(widget.id)}
919
+ >
920
+ View all{' '}
921
+ <span>({data[widget.id]?.length || 0})</span>
922
+ </button>
857
923
  </div>
858
- {renderFilters(widget)}
859
- <div>{renderWidget(widget)}</div>
860
- {widget.modal && (
861
- <div className={styles.widgetAction}>
924
+ )}
925
+ {editMode && (
926
+ <div className={styles.widgetTools}>
927
+ <div className={styles.resizeTools}>
862
928
  <button
863
- className={`${styles.btn} ${styles.dmore}`}
864
- onClick={() => openModal(widget.id)}
929
+ onClick={() => toggleHighlight(widget.id)}
865
930
  >
866
- View all{' '}
867
- <span>
868
- ({data[widget.id]?.length || 0})
869
- </span>
931
+ <Star strokeWidth={2} size={12} />
870
932
  </button>
871
- </div>
872
- )}
873
- {editMode && (
874
- <div className={styles.widgetTools}>
875
- <div className={styles.resizeTools}>
933
+ {(widget.size || '1/3') !== '1/3' && (
876
934
  <button
877
935
  onClick={() =>
878
- toggleHighlight(widget.id)
936
+ changeSize(widget.id, 'decrease')
879
937
  }
880
938
  >
881
- <Star strokeWidth={2} size={12} />
939
+ <Minus strokeWidth={2} size={12} />
882
940
  </button>
883
- {(widget.size || '1/3') !== '1/3' && (
884
- <button
885
- onClick={() =>
886
- changeSize(
887
- widget.id,
888
- 'decrease'
889
- )
890
- }
891
- >
892
- <Minus strokeWidth={2} size={12} />
893
- </button>
894
- )}
895
- {(widget.size || '1/3') !== 'full' && (
896
- <button
897
- onClick={() =>
898
- changeSize(
899
- widget.id,
900
- 'increase'
901
- )
902
- }
903
- >
904
- <Plus strokeWidth={2} size={12} />
905
- </button>
906
- )}
907
- </div>
941
+ )}
942
+ {(widget.size || '1/3') !== 'full' && (
943
+ <button
944
+ onClick={() =>
945
+ changeSize(widget.id, 'increase')
946
+ }
947
+ >
948
+ <Plus strokeWidth={2} size={12} />
949
+ </button>
950
+ )}
951
+ <button
952
+ onClick={() => confirmDelete(widget.id)}
953
+ >
954
+ <TrashCan strokeWidth={2} size={12} />
955
+ </button>
908
956
  </div>
909
- )}
910
- </div>
957
+ </div>
958
+ )}
911
959
  </div>
912
- );
913
- });
960
+ </div>
961
+ ));
914
962
  };
915
963
 
916
964
  return (
917
965
  <>
918
966
  <div className={styles.editButtonContainer}>
967
+ {editMode && (
968
+ <>
969
+ <button
970
+ className={styles.btn}
971
+ style={{ marginRight: '10px' }}
972
+ onClick={() => setShowAddWidgetModal(true)}
973
+ >
974
+ Add Widget
975
+ </button>
976
+ </>
977
+ )}
919
978
  <button
920
979
  className={styles.btn}
921
980
  onClick={() => setEditMode((prev) => !prev)}
922
981
  >
923
- {editMode ? 'Done' : 'Edit'}
982
+ {editMode ? 'Done' : 'Edit Dashboard'}
924
983
  </button>
925
984
  </div>
926
985
  <div className={styles.grid} ref={container}>
@@ -954,6 +1013,63 @@ function GenericDashboard({ setting, userProfile }) {
954
1013
  </div>
955
1014
  </div>
956
1015
  </Popup>
1016
+ <Popup
1017
+ open={showAddWidgetModal}
1018
+ onClose={() => setShowAddWidgetModal(false)}
1019
+ modal
1020
+ >
1021
+ <div className="modalwrap">
1022
+ <div className={`${styles.addWidgetPopup} modal`}>
1023
+ <div className="modal__header">
1024
+ <h1>Add a Widget</h1>
1025
+ <button
1026
+ className="modal__close"
1027
+ onClick={() => setShowAddWidgetModal(false)}
1028
+ >
1029
+ <CircleX strokeWidth={1} size={24} />
1030
+ </button>
1031
+ </div>
1032
+ <div className="modal__content">
1033
+ {availableWidgets.length > 0 ? (
1034
+ <div>
1035
+ <select
1036
+ value={selectedAddWidgetId}
1037
+ onChange={(e) =>
1038
+ setSelectedAddWidgetId(
1039
+ e.target.value
1040
+ )
1041
+ }
1042
+ className={styles.addWidgetSelect}
1043
+ >
1044
+ <option value="">
1045
+ -- Select a widget --
1046
+ </option>
1047
+ {availableWidgets.map((widget) => (
1048
+ <option
1049
+ key={widget.id}
1050
+ value={widget.id}
1051
+ >
1052
+ {widget.title}
1053
+ </option>
1054
+ ))}
1055
+ </select>
1056
+ <button
1057
+ className={styles.addWidgetButton}
1058
+ onClick={handleAddWidget}
1059
+ disabled={!selectedAddWidgetId}
1060
+ >
1061
+ Add
1062
+ </button>
1063
+ </div>
1064
+ ) : (
1065
+ <div className={styles.noWidgetsMessage}>
1066
+ No more widgets available
1067
+ </div>
1068
+ )}
1069
+ </div>
1070
+ </div>
1071
+ </div>
1072
+ </Popup>
957
1073
  </div>
958
1074
  </>
959
1075
  );
@@ -536,3 +536,38 @@
536
536
  .widgetItem {
537
537
  position: relative;
538
538
  }
539
+
540
+ /* Styles for the Add Widget Popup */
541
+ .addWidgetSelect {
542
+ width: 100%;
543
+ padding: 10px;
544
+ margin-bottom: 1rem;
545
+ border: 1px solid #dadce0;
546
+ border-radius: 5px;
547
+ font-size: 1rem;
548
+ }
549
+
550
+ .addWidgetButton {
551
+ width: 100%;
552
+ padding: 10px;
553
+ background: var(--primary-color);
554
+ color: var(--tertiary-color);
555
+ border: none;
556
+ border-radius: 5px;
557
+ font-size: 1rem;
558
+ font-weight: 600;
559
+ text-transform: uppercase;
560
+ cursor: pointer;
561
+ transition: background-color 0.25s ease;
562
+ }
563
+
564
+ .addWidgetButton:hover {
565
+ background: var(--secondary-color);
566
+ }
567
+
568
+ .noWidgetsMessage {
569
+ text-align: center;
570
+ font-size: 1rem;
571
+ color: #888;
572
+ padding: 20px;
573
+ }