@visns-studio/visns-components 5.0.19 → 5.0.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.
Files changed (38) hide show
  1. package/package.json +6 -6
  2. package/src/components/crm/MultiSelect.jsx +46 -67
  3. package/src/components/crm/auth/styles/Profile.module.scss +6 -7
  4. package/src/components/crm/generic/GenericDashboard.jsx +467 -29
  5. package/src/components/crm/generic/GenericFormBuilder.jsx +96 -32
  6. package/src/components/crm/generic/styles/AuditLog.module.scss +3 -3
  7. package/src/components/crm/generic/styles/AuditLogs.module.scss +1 -1
  8. package/src/components/crm/generic/styles/GenericDashboard.module.scss +364 -0
  9. package/src/components/crm/generic/styles/GenericDynamic.module.scss +11 -11
  10. package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +327 -317
  11. package/src/components/crm/generic/styles/GenericIndex.module.scss +8 -8
  12. package/src/components/crm/generic/styles/GenericSort.module.scss +3 -3
  13. package/src/components/crm/generic/styles/NotificationList.module.scss +2 -2
  14. package/src/components/crm/sketch/SketchField.jsx +395 -0
  15. package/src/components/crm/sketch/arrow.jsx +108 -0
  16. package/src/components/crm/sketch/circle.jsx +75 -0
  17. package/src/components/crm/sketch/default-tool.jsx +16 -0
  18. package/src/components/crm/sketch/fabrictool.jsx +22 -0
  19. package/src/components/crm/sketch/history.jsx +144 -0
  20. package/src/components/crm/sketch/json/config.json +14 -0
  21. package/src/components/crm/sketch/line.jsx +64 -0
  22. package/src/components/crm/sketch/pan.jsx +48 -0
  23. package/src/components/crm/sketch/pencil.jsx +36 -0
  24. package/src/components/crm/sketch/rectangle-label-object.jsx +69 -0
  25. package/src/components/crm/sketch/rectangle-label.jsx +93 -0
  26. package/src/components/crm/sketch/rectangle.jsx +76 -0
  27. package/src/components/crm/sketch/select.jsx +16 -0
  28. package/src/components/crm/sketch/tools.jsx +11 -0
  29. package/src/components/crm/sketch/utils.jsx +67 -0
  30. package/src/components/crm/sorting/Item.jsx +2 -2
  31. package/src/components/crm/sorting/styles/Item.module.scss +12 -65
  32. package/src/components/crm/sorting/styles/List.module.scss +6 -20
  33. package/src/components/crm/styles/Form.module.scss +4 -6
  34. package/src/components/crm/styles/MultiSelect.module.scss +4 -0
  35. package/src/components/crm/styles/Navigation.module.scss +13 -6
  36. package/src/components/crm/styles/QrCode.module.scss +2 -2
  37. package/src/components/styles/global.css +8 -0
  38. package/src/index.js +2 -0
@@ -1,42 +1,480 @@
1
+ import '../../styles/global.css';
2
+
1
3
  import React, { useState, useEffect } from 'react';
2
- import { toast } from 'react-toastify';
4
+ import { Link } from 'react-router-dom';
5
+ import { ResponsiveBar } from '@nivo/bar';
6
+ import { ResponsiveLine } from '@nivo/line';
7
+ import { ResponsivePie } from '@nivo/pie';
8
+ import Popup from 'reactjs-popup';
9
+ import { CircleX } from 'akar-icons';
10
+
3
11
  import CustomFetch from '../../crm/Fetch';
12
+ import MultiSelect from '../../crm/MultiSelect';
4
13
 
5
- const GenericDashboard = ({ userProfile }) => {
6
- const [data, setData] = useState([]);
14
+ import styles from './styles/GenericDashboard.module.scss';
15
+ import { drop } from 'lodash';
7
16
 
8
- return (
9
- <div className="grid">
10
- <div className="grid__row">
11
- <div className="grid__three dashtopwidge">
12
- <div className="dashtopwidge__left">
13
- <h2>
14
- Current
15
- <span>
16
- <strong>Enquiries</strong>
17
- </span>
18
- </h2>
19
- </div>
20
- <div className="dashtopwidge__right">
21
- <span>0</span>
17
+ const toSnakeCase = (str) => str.replace(/[\s-]+/g, '_').toLowerCase();
18
+
19
+ function GenericDashboard({ setting }) {
20
+ const [data, setData] = useState({});
21
+ const [dropdowns, setDropdowns] = useState({});
22
+ const [filters, setFilters] = useState({}); // Track filter values for each widget
23
+ const [modalShow, setModalShow] = useState(false);
24
+ const [modalContent, setModalContent] = useState(null);
25
+
26
+ useEffect(() => {
27
+ const fetchData = async () => {
28
+ try {
29
+ const promises = setting.widgets.map(async (widget) => {
30
+ // If filters have a URL key, fetch initial data
31
+ if (widget.filters) {
32
+ for (const filter of widget.filters) {
33
+ if (filter.url) {
34
+ const initialFilterData = await CustomFetch(
35
+ filter.url,
36
+ 'POST',
37
+ filter.defaultParams || {}
38
+ );
39
+
40
+ setDropdowns((prevFilters) => ({
41
+ ...prevFilters,
42
+ [widget.id]: {
43
+ ...prevFilters[widget.id],
44
+ [filter.id]:
45
+ initialFilterData?.data?.data || [],
46
+ },
47
+ }));
48
+ }
49
+ }
50
+ }
51
+
52
+ // Fetch widget data
53
+ return CustomFetch(
54
+ widget.api.url,
55
+ widget.api.method,
56
+ widget.api.params || {}
57
+ );
58
+ });
59
+
60
+ const results = await Promise.all(promises);
61
+ const fetchedData = setting.widgets.reduce(
62
+ (acc, widget, index) => {
63
+ acc[widget.id] = results[index].data;
64
+ return acc;
65
+ },
66
+ {}
67
+ );
68
+
69
+ setData(fetchedData);
70
+ } catch (error) {
71
+ console.error('Error fetching dashboard data:', error);
72
+ }
73
+ };
74
+
75
+ fetchData();
76
+ }, [setting]);
77
+
78
+ const openModal = (widgetId) => {
79
+ setModalContent(widgetId);
80
+ setModalShow(true);
81
+ };
82
+
83
+ const closeModal = () => {
84
+ setModalShow(false);
85
+ };
86
+
87
+ const handleFilterChange = (widgetId, key, value) => {
88
+ setFilters((prevFilters) => ({
89
+ ...prevFilters,
90
+ [widgetId]: {
91
+ ...prevFilters[widgetId],
92
+ [key]: value,
93
+ },
94
+ }));
95
+ };
96
+
97
+ const handleFilterSelectChange = (id, action, value) => {};
98
+
99
+ const renderFilters = (widget) => {
100
+ if (!widget.filters || widget.filters.length === 0) return null;
101
+
102
+ return (
103
+ <div className={styles['filter-container']}>
104
+ <div className={styles['filter-header']}>
105
+ <h3>Filters</h3>
106
+ </div>
107
+ <div className={styles['filter-fields']}>
108
+ {widget.filters.map((filter) => {
109
+ switch (filter.type) {
110
+ case 'date':
111
+ return (
112
+ <div
113
+ className={styles['filter-field']}
114
+ key={filter.id}
115
+ >
116
+ <label htmlFor={filter.id}>
117
+ {filter.label}
118
+ </label>
119
+ <input
120
+ id={filter.id}
121
+ type="date"
122
+ value={
123
+ filters[widget.id]?.[
124
+ filter.id
125
+ ] || ''
126
+ }
127
+ onChange={(e) =>
128
+ handleFilterChange(
129
+ widget.id,
130
+ filter.id,
131
+ e.target.value
132
+ )
133
+ }
134
+ />
135
+ </div>
136
+ );
137
+ case 'multi-dropdown-ajax':
138
+ return (
139
+ <div
140
+ className={styles['filter-field']}
141
+ key={filter.id}
142
+ >
143
+ <label htmlFor={filter.id}>
144
+ {filter.label}
145
+ </label>
146
+ <MultiSelect
147
+ id={filter.id}
148
+ isMulti
149
+ options={
150
+ dropdowns[widget.id]?.[
151
+ filter.id
152
+ ] || []
153
+ }
154
+ value={
155
+ filters[widget.id]?.[
156
+ filter.id
157
+ ] || []
158
+ }
159
+ onChange={handleFilterChange}
160
+ style={{
161
+ multi_select_height: '52px',
162
+ }}
163
+ />
164
+ </div>
165
+ );
166
+ default:
167
+ return null;
168
+ }
169
+ })}
170
+ <div className={styles['filter-actions']}>
171
+ <button
172
+ className={`${styles.btn} ${styles['btn-primary']}`}
173
+ onClick={() => {
174
+ console.log(
175
+ 'Filters applied:',
176
+ filters[widget.id]
177
+ );
178
+ // Logic to apply filters can be added here
179
+ }}
180
+ >
181
+ Apply Filters
182
+ </button>
183
+ <button
184
+ className={`${styles.btn} ${styles['btn-secondary']}`}
185
+ onClick={() => {
186
+ setFilters((prevFilters) => ({
187
+ ...prevFilters,
188
+ [widget.id]: {},
189
+ }));
190
+ }}
191
+ >
192
+ Clear Filters
193
+ </button>
22
194
  </div>
23
195
  </div>
24
- <div className="grid__three dashtopwidge">
25
- <div className="dashtopwidge__left">
26
- <h2>
27
- Current{' '}
28
- <span>
29
- <strong>Enquiries</strong>
30
- </span>
31
- </h2>
196
+ </div>
197
+ );
198
+ };
199
+
200
+ const renderWidget = (widget) => {
201
+ const widgetData = data[widget.id] || [];
202
+
203
+ if (widgetData.length > 0 || widgetData?.data?.length > 0) {
204
+ switch (widget.type) {
205
+ case 'pie':
206
+ return (
207
+ <ResponsivePie data={widgetData} {...widget.props} />
208
+ );
209
+ case 'bar':
210
+ if (widgetData.keys && widget.props.keys) {
211
+ widget.props.keys = widgetData.keys;
212
+ }
213
+
214
+ const barData = widgetData.bar.data || widgetData;
215
+
216
+ return (
217
+ <div style={{ height: '600px' }}>
218
+ <ResponsiveBar data={barData} {...widget.props} />
219
+ </div>
220
+ );
221
+ case 'line':
222
+ return (
223
+ <ResponsiveLine data={widgetData} {...widget.props} />
224
+ );
225
+ case 'list':
226
+ return (
227
+ <ul className={styles.dashList}>
228
+ {widgetData.length > 0 ? (
229
+ widgetData
230
+ .slice(0, widget.limit || 4)
231
+ .map((item) => (
232
+ <li key={`${widget.id}-${item.id}`}>
233
+ <Link
234
+ to={`${widget.link.base}/$${
235
+ item[widget.link.key]
236
+ }`}
237
+ >
238
+ <button>
239
+ <strong>
240
+ {widget?.item?.label &&
241
+ item[widget.item.label]
242
+ ? item[
243
+ widget.item
244
+ .label
245
+ ]
246
+ : ''}
247
+ </strong>
248
+ {widget.showDate && (
249
+ <span>{item.date}</span>
250
+ )}
251
+ </button>
252
+ </Link>
253
+ </li>
254
+ ))
255
+ ) : (
256
+ <li>
257
+ <button>
258
+ <strong>{widget.emptyMessage}</strong>
259
+ </button>
260
+ </li>
261
+ )}
262
+ </ul>
263
+ );
264
+ case 'table':
265
+ const tableData = widgetData.data || widgetData;
266
+ const tableKeys = widgetData.keys || [];
267
+
268
+ return (
269
+ <div className={styles['table-container']}>
270
+ {tableData.length > 0 ? (
271
+ <div className={styles['table-scroll']}>
272
+ <table>
273
+ <thead>
274
+ <tr>
275
+ <th>Form</th>
276
+ {tableData.map((row, index) => (
277
+ <th
278
+ key={`${toSnakeCase(
279
+ row.supervisor_name
280
+ )}-${index}`}
281
+ >
282
+ {row.supervisor_name}
283
+ </th>
284
+ ))}
285
+ <th>Total</th>
286
+ </tr>
287
+ </thead>
288
+ <tbody>
289
+ {tableKeys.map(
290
+ (type, typeIndex) => {
291
+ let total = 0; // Track total for the current form type
292
+ return (
293
+ <tr
294
+ key={`form-type-${toSnakeCase(
295
+ type
296
+ )}`}
297
+ >
298
+ <td>{type}</td>
299
+ {tableData.map(
300
+ (
301
+ row,
302
+ rowIndex
303
+ ) => {
304
+ const form =
305
+ row.forms.find(
306
+ (
307
+ form
308
+ ) =>
309
+ form.form_template_label ===
310
+ type
311
+ );
312
+
313
+ const count =
314
+ form
315
+ ? form.form_count
316
+ : 0;
317
+
318
+ total +=
319
+ count;
320
+
321
+ return (
322
+ <td
323
+ key={`${toSnakeCase(
324
+ row.supervisor_name
325
+ )}-${typeIndex}-${rowIndex}`}
326
+ >
327
+ {
328
+ count
329
+ }
330
+ </td>
331
+ );
332
+ }
333
+ )}
334
+ <td>
335
+ <strong>
336
+ {total}
337
+ </strong>
338
+ </td>
339
+ </tr>
340
+ );
341
+ }
342
+ )}
343
+ {/* Add a total row for supervisors */}
344
+ <tr>
345
+ <td>
346
+ <strong>Total</strong>
347
+ </td>
348
+ {tableData.map(
349
+ (row, rowIndex) => {
350
+ const supervisorTotal =
351
+ tableKeys.reduce(
352
+ (sum, type) => {
353
+ const form =
354
+ row.forms.find(
355
+ (
356
+ form
357
+ ) =>
358
+ form.form_template_label ===
359
+ type
360
+ );
361
+
362
+ return (
363
+ sum +
364
+ (form
365
+ ? form.form_count
366
+ : 0)
367
+ );
368
+ },
369
+ 0
370
+ );
371
+
372
+ return (
373
+ <td
374
+ key={`${toSnakeCase(
375
+ row.supervisor_name
376
+ )}-total-${rowIndex}`}
377
+ >
378
+ <strong>
379
+ {
380
+ supervisorTotal
381
+ }
382
+ </strong>
383
+ </td>
384
+ );
385
+ }
386
+ )}
387
+ <td />
388
+ </tr>
389
+ </tbody>
390
+ </table>
391
+ </div>
392
+ ) : (
393
+ <div>No data available</div>
394
+ )}
395
+ </div>
396
+ );
397
+ default:
398
+ return null;
399
+ }
400
+ } else {
401
+ return null;
402
+ }
403
+ };
404
+
405
+ const renderRows = () => {
406
+ const columns = setting.columns || 3; // Default to 3 columns if not specified
407
+ const widgetChunks = [];
408
+
409
+ for (let i = 0; i < setting.widgets.length; i += columns) {
410
+ widgetChunks.push(setting.widgets.slice(i, i + columns));
411
+ }
412
+
413
+ return widgetChunks.map((row, rowIndex) => (
414
+ <div className={styles.grid__row} key={`row-${rowIndex}`}>
415
+ {row.map((widget) => (
416
+ <div
417
+ key={widget.id}
418
+ className={`${styles.grid__full} ${
419
+ widget.type !== 'list'
420
+ ? widget.highlight
421
+ ? styles['dashwidget--highlight']
422
+ : styles.dashwidget
423
+ : null
424
+ }`}
425
+ >
426
+ <div className={styles.widgetTitle}>
427
+ <h2>{widget.title}</h2>
428
+ </div>
429
+ {renderFilters(widget)}
430
+ <div>{renderWidget(widget)}</div>
431
+ {widget.modal && (
432
+ <div className={styles.widgetAction}>
433
+ <button
434
+ className={`${styles.btn} ${styles.dmore}`}
435
+ onClick={() => openModal(widget.id)}
436
+ >
437
+ View all{' '}
438
+ <span>
439
+ ({data[widget.id]?.length || 0})
440
+ </span>
441
+ </button>
442
+ </div>
443
+ )}
32
444
  </div>
33
- <div className="dashtopwidge__right">
34
- <span>0</span>
445
+ ))}
446
+ </div>
447
+ ));
448
+ };
449
+
450
+ return (
451
+ <div className={styles.grid}>
452
+ {renderRows()}
453
+ <Popup open={modalShow} onClose={closeModal}>
454
+ <div className="modalwrap top--modal modalWide">
455
+ <div className="modal">
456
+ <div className="modal__header">
457
+ <h1>{setting.modalTitles?.[modalContent]}</h1>
458
+ <button
459
+ className="modal__close"
460
+ onClick={closeModal}
461
+ >
462
+ <CircleX strokeWidth={1} size={24} />
463
+ </button>
464
+ </div>
465
+ <div className="modal__content">
466
+ {modalContent &&
467
+ renderWidget(
468
+ setting.widgets.find(
469
+ (w) => w.id === modalContent
470
+ )
471
+ )}
472
+ </div>
35
473
  </div>
36
474
  </div>
37
- </div>
475
+ </Popup>
38
476
  </div>
39
477
  );
40
- };
478
+ }
41
479
 
42
480
  export default GenericDashboard;