@visns-studio/visns-components 5.1.25 → 5.2.0

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
@@ -51,6 +51,7 @@
51
51
  "react-window": "^1.8.11",
52
52
  "reactjs-popup": "^2.0.6",
53
53
  "style-loader": "^4.0.0",
54
+ "swapy": "^1.0.5",
54
55
  "truncate": "^3.0.0",
55
56
  "uuid": "^10.0.0",
56
57
  "validator": "^13.12.0",
@@ -77,7 +78,7 @@
77
78
  "react-dom": "^17.0.0 || ^18.0.0"
78
79
  },
79
80
  "name": "@visns-studio/visns-components",
80
- "version": "5.1.25",
81
+ "version": "5.2.0",
81
82
  "description": "Various packages to assist in the development of our Custom Applications.",
82
83
  "main": "src/index.js",
83
84
  "files": [
@@ -1,6 +1,6 @@
1
1
  import '../../styles/global.css';
2
2
 
3
- import React, { useState, useEffect } from 'react';
3
+ import React, { useState, useEffect, useRef } from 'react';
4
4
  import { Link, useNavigate } from 'react-router-dom';
5
5
  import { ResponsiveBar } from '@nivo/bar';
6
6
  import { ResponsiveLine } from '@nivo/line';
@@ -8,6 +8,7 @@ import { ResponsivePie } from '@nivo/pie';
8
8
  import Popup from 'reactjs-popup';
9
9
  import dayjs from 'dayjs';
10
10
  import parse from 'html-react-parser';
11
+ import { createSwapy } from 'swapy';
11
12
  import { CircleX } from 'akar-icons';
12
13
 
13
14
  import CustomFetch from '../../crm/Fetch';
@@ -18,6 +19,8 @@ import styles from './styles/GenericDashboard.module.scss';
18
19
  const toSnakeCase = (str) => str.replace(/[\s-]+/g, '_').toLowerCase();
19
20
 
20
21
  function GenericDashboard({ setting }) {
22
+ const swapy = useRef(null);
23
+ const container = useRef(null);
21
24
  const navigate = useNavigate();
22
25
  const [data, setData] = useState({});
23
26
  const [dropdowns, setDropdowns] = useState({});
@@ -197,6 +200,23 @@ function GenericDashboard({ setting }) {
197
200
  fetchData(filters);
198
201
  }, [filters]);
199
202
 
203
+ useEffect(() => {
204
+ // If container element is loaded
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
+ });
212
+ }
213
+
214
+ return () => {
215
+ // Destroy the swapy instance on component destroy
216
+ swapy.current?.destroy();
217
+ };
218
+ }, []);
219
+
200
220
  const renderFilters = (widget) => {
201
221
  if (!widget.filters || widget.filters.length === 0) return null;
202
222
 
@@ -407,6 +427,37 @@ function GenericDashboard({ setting }) {
407
427
  widget.props.keys = widgetData.keys;
408
428
  }
409
429
 
430
+ // Build legendMapping and attach legendLabel and a custom tooltip if a legend is provided.
431
+ let legendMapping = {};
432
+ if (widget.legend) {
433
+ legendMapping = widget.legend.reduce((acc, curr) => {
434
+ acc[curr.id] = curr.label;
435
+ return acc;
436
+ }, {});
437
+ widget.props.legendLabel = (e) =>
438
+ legendMapping[e.id] || e.id;
439
+
440
+ // Define a custom tooltip that uses the legendMapping
441
+ const CustomTooltip = ({ id, value, color }) => {
442
+ const label = legendMapping[id] || id;
443
+ return (
444
+ <div
445
+ style={{
446
+ padding: '6px 9px',
447
+ background: 'white',
448
+ border: `1px solid ${color}`,
449
+ borderRadius: '3px',
450
+ }}
451
+ >
452
+ <strong style={{ color }}>{label}</strong>:{' '}
453
+ {value}
454
+ </div>
455
+ );
456
+ };
457
+
458
+ widget.props.tooltip = CustomTooltip;
459
+ }
460
+
410
461
  const barData =
411
462
  widgetData.bar?.data || widgetData.data || widgetData;
412
463
 
@@ -420,6 +471,7 @@ function GenericDashboard({ setting }) {
420
471
  </div>
421
472
  );
422
473
  }
474
+ break;
423
475
  case 'line':
424
476
  if (widgetData.length > 0) {
425
477
  return (
@@ -733,25 +785,28 @@ function GenericDashboard({ setting }) {
733
785
  : styles.dashwidget
734
786
  : ''
735
787
  }`}
788
+ data-swapy-slot={`row-slot-${widget.id}-${rowIndex}`}
736
789
  >
737
- <div className={styles.widgetTitle}>
738
- <h2>{widget.title}</h2>
739
- </div>
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>
790
+ <div data-swapy-item={`widget-${widget.id}`}>
791
+ <div className={styles.widgetTitle}>
792
+ <h2>{widget.title}</h2>
753
793
  </div>
754
- )}
794
+ {renderFilters(widget)}
795
+ <div>{renderWidget(widget)}</div>
796
+ {widget.modal && (
797
+ <div className={styles.widgetAction}>
798
+ <button
799
+ className={`${styles.btn} ${styles.dmore}`}
800
+ onClick={() => openModal(widget.id)}
801
+ >
802
+ View all{' '}
803
+ <span>
804
+ ({data[widget.id]?.length || 0})
805
+ </span>
806
+ </button>
807
+ </div>
808
+ )}
809
+ </div>
755
810
  </div>
756
811
  ))}
757
812
  </div>
@@ -760,7 +815,7 @@ function GenericDashboard({ setting }) {
760
815
  };
761
816
 
762
817
  return (
763
- <div className={styles.grid}>
818
+ <div className={styles.grid} ref={container}>
764
819
  {renderRows()}
765
820
  <Popup open={modalShow} onClose={closeModal}>
766
821
  <div className="modalwrap top--modal modalWide">
@@ -226,18 +226,27 @@ const GenericEditableTable = ({
226
226
 
227
227
  dataArray.forEach((entry, index) => {
228
228
  const categoryInfo = entry[rows.categoryKey.id];
229
- const categoryType = categoryInfo
229
+ const categoryLabel = categoryInfo
230
230
  ? categoryInfo[rows.categoryKey.label]
231
231
  : null;
232
232
  const categoryId = categoryInfo ? categoryInfo.id : null;
233
233
  const groupKey = categoryId || 'no_category';
234
+ // Use the category's sort_order if provided; otherwise, default to a high number (e.g. 9999)
235
+
236
+ console.info(categoryInfo);
237
+
238
+ const catSortOrder =
239
+ categoryInfo && typeof categoryInfo.sort_order !== 'undefined'
240
+ ? categoryInfo.sort_order
241
+ : 9999;
234
242
 
235
243
  if (!grouped[groupKey]) {
236
244
  grouped[groupKey] = {
237
245
  id: groupKey,
238
246
  category:
239
- categoryType ||
247
+ categoryLabel ||
240
248
  (groupKey === 'no_category' ? 'No Category' : ''),
249
+ catSortOrder, // assign the sort order (or fallback)
241
250
  entries: [],
242
251
  };
243
252
  }
@@ -247,15 +256,23 @@ const GenericEditableTable = ({
247
256
  });
248
257
  });
249
258
 
259
+ // Sort entries within each group by their universal sort_order
250
260
  Object.values(grouped).forEach((group) => {
251
261
  group.entries.sort((a, b) => a.sort_order - b.sort_order);
252
262
  });
253
263
 
254
- // Ensure 'no_category' group is at the top
264
+ // Now sort the groups by the cost category's sort_order (catSortOrder)
255
265
  const groupedArray = Object.values(grouped);
256
- return groupedArray.sort((a, b) =>
257
- a.id === 'no_category' ? -1 : b.id === 'no_category' ? 1 : 0
258
- );
266
+ groupedArray.sort((a, b) => {
267
+ // Optionally, place 'no_category' first (or adjust as needed)
268
+ if (a.id === 'no_category') return -1;
269
+ if (b.id === 'no_category') return 1;
270
+ return a.catSortOrder - b.catSortOrder;
271
+ });
272
+
273
+ console.info(groupedArray);
274
+
275
+ return groupedArray;
259
276
  };
260
277
 
261
278
  const groupedCategories = useMemo(() => {