@visns-studio/visns-components 1.2.1 → 1.2.2

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.
@@ -9,14 +9,14 @@ import ReactDataGrid from "@inovua/reactdatagrid-community";
9
9
  import { confirmAlert } from "react-confirm-alert";
10
10
  import { toast } from "react-toastify";
11
11
  import {
12
- Backspace,
13
- Check,
14
- Clock,
15
- CloudDownload,
16
- Copy,
17
- Edit,
18
- Ribbon,
19
- Search,
12
+ Backspace,
13
+ Check,
14
+ Clock,
15
+ CloudDownload,
16
+ Copy,
17
+ Edit,
18
+ Ribbon,
19
+ Search,
20
20
  } from "akar-icons";
21
21
 
22
22
  import "@inovua/reactdatagrid-community/index.css";
@@ -26,825 +26,889 @@ import CustomFetch from "./visns-fetch";
26
26
  import Form from "./visns-form";
27
27
 
28
28
  const loadData = async (
29
- { skip, limit, sortInfo, currentData, filterValue, groupBy },
30
- search,
31
- ajaxSetting
29
+ { skip, limit, sortInfo, currentData, filterValue, groupBy },
30
+ search,
31
+ ajaxSetting
32
32
  ) => {
33
- const url = ajaxSetting.url;
34
-
35
- let page = Math.floor(skip / limit) + 1;
36
- let params = { page, sortInfo, take: limit, search: search };
37
-
38
- if (sortInfo) {
39
- params.sortBy = sortInfo.name;
40
- params.sort = sortInfo.dir === 1 ? "asc" : "desc";
41
- }
42
-
43
- if (ajaxSetting && ajaxSetting.where) {
44
- params.where = ajaxSetting.where;
45
- }
46
-
47
- try {
48
- const response = await axios.post(url, params);
49
- const { data, total } = response.data;
50
- return { data, count: total };
51
- } catch (error) {
52
- console.error("Error fetching data:", error);
53
- return { data: [], count: 0 };
54
- }
33
+ const url = ajaxSetting.url;
34
+
35
+ let page = Math.floor(skip / limit) + 1;
36
+ let params = { page, sortInfo, take: limit, search: search };
37
+
38
+ if (sortInfo) {
39
+ params.sortBy = sortInfo.name;
40
+ params.sort = sortInfo.dir === 1 ? "asc" : "desc";
41
+ }
42
+
43
+ if (ajaxSetting && ajaxSetting.where) {
44
+ params.where = ajaxSetting.where;
45
+ }
46
+
47
+ try {
48
+ const response = await axios.post(url, params);
49
+ const { data, total } = response.data;
50
+ return { data, count: total };
51
+ } catch (error) {
52
+ console.error("Error fetching data:", error);
53
+ return { data: [], count: 0 };
54
+ }
55
55
  };
56
56
 
57
57
  const DataGrid = (props) => {
58
- const {
59
- ajaxSetting,
60
- form,
61
- columns,
62
- settings,
63
- style,
64
- tableFilters,
65
- historySearch,
66
- historyUrl,
67
- } = props;
68
- const navigate = useNavigate();
69
-
70
- /** Modal States */
71
- const [formData, setFormData] = useState(form);
72
- const [formType, setFormType] = useState("");
73
- const [formId, setFormId] = useState(0);
74
- const [modalShow, setModalShow] = useState(false);
75
-
76
- /** Modal Functions */
77
- const childDropdownCallback = (data) => {
78
- let _form = { ...formData };
79
-
80
- if (formData.fields.length > 0) {
81
- formData.fields.forEach((a, b) => {
82
- if (a.id === data.id) {
83
- _form.fields[b].options = data.data;
84
- }
85
- });
86
- }
87
-
88
- setFormData(_form);
89
- };
90
-
91
- const modalOpen = (formType, formId) => {
92
- setModalShow(true);
93
- setFormType(formType);
94
- setFormId(formId);
95
- };
96
-
97
- const modalClose = () => {
98
- setModalShow(false);
99
- };
100
-
101
- /** Table States */
102
- const [dataReload, setDataReload] = useState(0);
103
- const [dSearch, setDSearch] = useState("");
104
- const data = useCallback(
105
- (params) => loadData(params, dSearch, ajaxSetting),
106
- [ajaxSetting, dataReload, dSearch]
107
- );
108
- const [gridColumns, setGridColumns] = useState([]);
109
- const limit = ajaxSetting.take || 10;
110
- const [search, setSearch] = useState("");
111
- const sortInfo =
112
- ajaxSetting.sortBy && ajaxSetting.sort
113
- ? {
114
- name: ajaxSetting.sortBy,
115
- dir: ajaxSetting.sort === "asc" ? 1 : -1,
116
- }
117
- : null;
118
-
119
- /** Table Functions */
120
- const handleChangeSearch = (e) => {
121
- const { value } = e.target;
122
- setSearch(value);
123
- };
124
-
125
- const handleSettingClick = (s, d) => {
126
- switch (s.id) {
127
- case "update":
128
- modalOpen("update", d.id);
129
- break;
130
- case "delete":
131
- confirmAlert({
132
- title: "Delete Item",
133
- message: `Are you sure you want to delete ${
134
- d.name ? d.name : d.label ? d.label : "this item"
135
- }?`,
136
- buttons: [
137
- {
138
- label: "Yes",
139
- onClick: () =>
140
- CustomFetch(
141
- form.url + "/" + d[form.primaryKey],
142
- "DELETE",
143
- {},
144
- (result) => {
145
- if (result.error === "") {
146
- const min = 0;
147
- const max = 999999999;
148
- const randomNumber = Math.floor(
149
- Math.random() *
150
- (max - min + 1) +
151
- min
152
- );
153
-
154
- setDataReload(randomNumber);
155
-
156
- toast.success(
157
- `The selected item has been deleted.`
158
- );
159
- } else {
160
- toast.error(String(result.error));
161
- }
162
- }
163
- ),
164
- },
165
- {
166
- label: "No",
167
- onClick: () => close(),
168
- },
169
- ],
170
- });
171
- break;
172
- default:
173
- confirmAlert({
174
- title: s.title,
175
- message: "",
176
- buttons: [
177
- {
178
- label: "Yes",
179
- onClick: () =>
180
- CustomFetch(
181
- s.url + "/" + d[form.primaryKey],
182
- s.method,
183
- {},
184
- function (result) {
185
- if (result.error === "") {
186
- const min = 0;
187
- const max = 999999999;
188
- const randomNumber = Math.floor(
189
- Math.random() *
190
- (max - min + 1) +
191
- min
192
- );
193
-
194
- setDataReload(randomNumber);
195
-
196
- toast.success(String(s.title));
197
- } else {
198
- toast.error(String(result.error));
199
- }
200
- }
201
- ),
202
- },
203
- {
204
- label: "No",
205
- onClick: () => close(),
206
- },
207
- ],
208
- });
209
- break;
210
- }
211
- };
212
-
213
- const handleReload = () => {
214
- setDataReload(dataReload + 1);
215
- };
216
-
217
- const onRowClick = useCallback((rowProps, event) => {
218
- const cellElement = event.target.closest(".InovuaReactDataGrid__cell");
219
- const columnIndex = Array.from(cellElement.parentNode.children).indexOf(
220
- cellElement
221
- );
222
-
223
- if (columnIndex === rowProps.columns.length - 1) {
224
- } else {
225
- if (
226
- rowProps.columns[columnIndex].link &&
227
- rowProps.columns[columnIndex].link.url
228
- ) {
229
- if (rowProps.columns[columnIndex].link.folder) {
230
- window.location.href =
231
- rowProps.columns[columnIndex].link.url +
232
- rowProps.data[rowProps.columns[columnIndex].link.key] +
233
- "/" +
234
- rowProps.columns[columnIndex].link.folder;
235
- } else {
236
- navigate(
237
- `${rowProps.columns[columnIndex].link.url}${
238
- rowProps.data[
239
- rowProps.columns[columnIndex].link.key
240
- ]
241
- }`
242
- );
243
- }
244
- } else {
245
- modalOpen("update", rowProps.data[form.primaryKey]);
246
- }
247
- }
248
- }, []);
249
-
250
- const onRenderRow = useCallback((rowProps) => {
251
- // save the original handlers to be called later
252
- const { onClick } = rowProps;
253
-
254
- rowProps.onClick = (event) => {
255
- onRowClick(rowProps, event);
256
- if (onClick) {
257
- onClick(event);
258
- }
259
- };
260
- }, []);
261
-
262
- /** Table Hooks */
263
- useEffect(() => {
264
- const delayedSetDSearch = debounce(() => {
265
- setDSearch(search);
266
- }, 300); // Adjust the delay (in milliseconds) according to your needs
267
-
268
- delayedSetDSearch();
269
-
270
- return delayedSetDSearch.cancel; // Cleanup the debounce timer on unmount
271
- }, [search, setDSearch]);
272
-
273
- const renderSetting = (s, d) => {
274
- let iconStyle = {};
275
-
276
- if (s.active) {
277
- if (
278
- d.hasOwnProperty(s.active.id) &&
279
- !s.active.hasOwnProperty("type") &&
280
- d[s.active.id] === s.active.value
281
- ) {
282
- iconStyle = {
283
- color: s.active.colour,
284
- };
285
- } else if (s.active.hasOwnProperty("type")) {
286
- switch (s.active.type) {
287
- case "greater":
288
- if (d[s.active.id] > s.active.value) {
289
- iconStyle = {
290
- color: s.active.colour,
291
- };
292
- }
293
- break;
294
- case "not null":
295
- if (d[s.active.id] !== null) {
296
- iconStyle = {
297
- color: s.active.colour,
298
- };
299
- }
300
- break;
301
- default:
302
- break;
303
- }
304
- }
305
- }
306
-
307
- const getIconComponent = (IconComponent) => (
308
- <IconComponent
309
- data-tooltip-id="system-tooltip"
310
- data-tooltip-content={
311
- iconStyle && iconStyle.color ? s.active.title : s.title
312
- }
313
- strokeWidth={2}
314
- size={18}
315
- className="tdaction"
316
- style={iconStyle}
317
- />
318
- );
319
-
320
- switch (s.id) {
321
- case "complete":
322
- return getIconComponent(Check);
323
- case "copy":
324
- return getIconComponent(Copy);
325
- case "delete":
326
- return getIconComponent(Backspace);
327
- case "primary":
328
- return getIconComponent(Ribbon);
329
- case "ssa":
330
- return getIconComponent(Clock);
331
- case "update":
332
- return getIconComponent(Edit);
333
- default:
334
- return null;
335
- }
336
- };
337
-
338
- const renderColumnContextMenu = useCallback((menuProps, { cellProps }) => {
339
- const filteredItems = menuProps.items
340
- .map((item, key) => {
341
- if (key >= 6 && key <= 9) {
342
- return null; // Skip items with keys 6-9 [lock columns setting]
343
- }
344
- return item;
345
- })
346
- .filter(Boolean); // Filter out any null items
347
-
348
- menuProps.items = filteredItems;
349
- }, []);
350
-
351
- useEffect(() => {
352
- const renderColumn = (column) => {
353
- switch (column.type) {
354
- case "created_by":
355
- return {
356
- name: "audits.name",
357
- header: column.label,
358
- defaultFlex: 1,
359
- link: column.link ? column.link : {},
360
- minWidth:
361
- column.width && column.width > 0
362
- ? column.width
363
- : undefined,
364
- render: ({ data }) => {
365
- if (
366
- data.audits[0] &&
367
- data.audits[0].user &&
368
- data.audits[0].user.name
369
- ) {
370
- let value = data.audits[0].user.name;
371
-
372
- return <span>{value}</span>;
373
- } else {
374
- return null;
375
- }
376
- },
377
- };
378
- case "currency":
379
- return {
380
- name: column.id,
381
- header: column.label,
382
- defaultFlex: 1,
383
- minWidth:
384
- column.width && column.width > 0
385
- ? column.width
386
- : undefined,
387
- link: column.link ? column.link : {},
388
- render: ({ data }) => {
389
- if (data) {
390
- data = numeral(data[column.id]).format(
391
- "$0,0.00"
392
- );
393
-
394
- return <span>{data}</span>;
395
- } else {
396
- return null;
397
- }
398
- },
399
- };
400
- case "date":
401
- return {
402
- name: column.id,
403
- header: column.label,
404
- defaultFlex: 1,
405
- minWidth:
406
- column.width && column.width > 0
407
- ? column.width
408
- : undefined,
409
- link: column.link ? column.link : {},
410
- render: ({ data }) => {
411
- if (data && data[column.id]) {
412
- data = moment(data[column.id]).format(
413
- column.format ? column.format : "DD-MM-YYYY"
414
- );
415
-
416
- return <span>{data}</span>;
417
- } else {
418
- return null;
419
- }
420
- },
421
- };
422
- case "datetime":
423
- return {
424
- name: column.id,
425
- header: column.label,
426
- defaultFlex: 1,
427
- minWidth:
428
- column.width && column.width > 0
429
- ? column.width
430
- : undefined,
431
- link: column.link ? column.link : {},
432
- render: ({ data }) => {
433
- if (data && data[column.id]) {
434
- data = moment(data[column.id]).format(
435
- column.format
436
- ? column.format
437
- : "DD-MM-YYYY hh:mm A"
438
- );
439
-
440
- return <span>{data}</span>;
441
- } else {
442
- return null;
443
- }
444
- },
445
- };
446
- case "file":
447
- return {
448
- name: column.id,
449
- header: column.label,
450
- defaultFlex: 1,
451
- minWidth:
452
- column.width && column.width > 0
453
- ? column.width
454
- : undefined,
455
- link: column.link ? column.link : {},
456
- render: ({ data }) => {
457
- if (data) {
458
- return (
459
- <span>
460
- <CloudDownload
461
- data-tooltip-id="system-tooltip"
462
- data-tooltip-content="Download File"
463
- strokeWidth={2}
464
- size={18}
465
- className="tdaction"
466
- />
467
- </span>
468
- );
469
- } else {
470
- return null;
471
- }
472
- },
473
- };
474
- case "icons":
475
- return {
476
- name: column.id,
477
- header: column.label,
478
- defaultFlex: 1,
479
- minWidth:
480
- column.width && column.width > 0
481
- ? column.width
482
- : undefined,
483
- link: column.link ? column.link : {},
484
- render: ({ data }) => {
485
- let icons = [];
486
-
487
- if (column.icons && column.icons.length > 0) {
488
- column.icons.forEach((icon) => {
489
- icons.push(
490
- <img
491
- src={icon.url}
492
- alt={icon.name}
493
- data-tooltip-id="system-tooltip"
494
- data-tooltip-content={icon.name}
495
- />
496
- );
497
- });
498
- }
499
-
500
- return <span>{icons}</span>;
501
- },
502
- };
503
- case "json":
504
- return {
505
- name: column.id,
506
- header: column.label,
507
- defaultFlex: 1,
508
- minWidth:
509
- column.width && column.width > 0
510
- ? column.width
511
- : undefined,
512
- link: column.link ? column.link : {},
513
- render: ({ data }) => {
514
- if (
515
- data &&
516
- data[column.id] &&
517
- data[column.id][column.jsonData]
518
- ) {
519
- data = data[column.id][column.jsonData];
520
-
521
- return <span>{data}</span>;
522
- } else {
523
- return null;
524
- }
525
- },
526
- };
527
- case "number":
528
- return {
529
- name: column.id,
530
- header: column.label,
531
- defaultFlex: 1,
532
- minWidth:
533
- column.width && column.width > 0
534
- ? column.width
535
- : undefined,
536
- link: column.link ? column.link : {},
537
- type: "number",
538
- };
539
- case "option":
540
- return {
541
- name: column.id,
542
- header: column.label,
543
- defaultFlex: 1,
544
- minWidth:
545
- column.width && column.width > 0
546
- ? column.width
547
- : undefined,
548
- link: column.link ? column.link : {},
549
- render: ({ data }) => {
550
- if (
551
- data &&
552
- column.options &&
553
- column.options[data[column.id]]
554
- ) {
555
- data = column.options[data[column.id]];
556
-
557
- return <span>{data}</span>;
558
- } else {
559
- return null;
560
- }
561
- },
562
- };
563
- case "placeholder":
564
- return {
565
- name: column.id,
566
- header: column.label,
567
- defaultFlex: 1,
568
- minWidth:
569
- column.width && column.width > 0
570
- ? column.width
571
- : undefined,
572
- link: column.link ? column.link : {},
573
- render: ({ data }) => {
574
- return <span>{column.placeholder}</span>;
575
- },
576
- };
577
- case "relation":
578
- let relationName = column.id.reduce(
579
- (acc, id) => acc + `${id}.`,
580
- ""
581
- );
582
- relationName += column.nameFrom;
583
-
584
- return {
585
- name: relationName,
586
- header: column.label,
587
- defaultFlex: 1,
588
- minWidth:
589
- column.width && column.width > 0
590
- ? column.width
591
- : undefined,
592
- link: column.link ? column.link : {},
593
- render: ({ data }) => {
594
- let value = column.id.reduce((acc, id) => {
595
- if (acc === "") {
596
- return data[id];
597
- } else {
598
- return acc[id];
599
- }
600
- }, "");
601
-
602
- if (value) {
603
- if (Array.isArray(column.nameFrom)) {
604
- value = column.nameFrom
605
- .map((nf) => value[nf])
606
- .join(" ");
607
- } else {
608
- value = value[column.nameFrom];
609
- }
610
-
611
- if (
612
- column.placeholder &&
613
- column.placeholder !== ""
614
- ) {
615
- return <span>{column.placeholder}</span>;
616
- } else {
617
- return <span>{value}</span>;
618
- }
619
- } else {
620
- return null;
621
- }
622
- },
623
- };
624
- case "relationArray":
625
- const relationArrayName =
626
- column.id.reduce((acc, id) => acc + `${id}.`, "") +
627
- column.nameFrom;
628
-
629
- return {
630
- name: relationArrayName,
631
- header: column.label,
632
- defaultFlex: 1,
633
- minWidth:
634
- column.width && column.width > 0
635
- ? column.width
636
- : undefined,
637
- link: column.link ? column.link : {},
638
- render: ({ data }) => {
639
- const relationValue = column.id.reduce(
640
- (acc, id) => (acc === "" ? data[id] : acc[id]),
641
- ""
642
- );
643
-
644
- if (relationValue && Array.isArray(relationValue)) {
645
- const value = relationValue
646
- .map((rv) => rv[column.nameFrom])
647
- .join(", ");
648
- return <span>{value}</span>;
649
- } else {
650
- return null;
651
- }
652
- },
653
- };
654
-
655
- case "stage":
656
- return {
657
- name: column.id,
658
- header: column.label,
659
- defaultFlex: 1,
660
- minWidth:
661
- column.width && column.width > 0
662
- ? column.width
663
- : undefined,
664
- link: column.link ? column.link : {},
665
- render: ({ data }) => {
666
- let stage = "";
667
-
668
- column.keyIds.forEach((a, b) => {
669
- if (data[a] === 1) {
670
- stage = column.valueIds[b];
671
- }
672
- });
673
-
674
- return <span>{stage}</span>;
675
- },
676
- };
677
- case "time":
678
- return {
679
- name: column.id,
680
- header: column.label,
681
- defaultFlex: 1,
682
- minWidth:
683
- column.width && column.width > 0
684
- ? column.width
685
- : undefined,
686
- link: column.link ? column.link : {},
687
- render: ({ data }) => {
688
- if (data && data[column.id]) {
689
- data = moment(data[column.id]).format(
690
- column.format ? column.format : "hh:mm A"
691
- );
692
-
693
- return <span>{data}</span>;
694
- } else {
695
- return null;
696
- }
697
- },
698
- };
699
- case "url":
700
- return {
701
- name: column.id,
702
- header: column.label,
703
- defaultFlex: 1,
704
- minWidth:
705
- column.width && column.width > 0
706
- ? column.width
707
- : undefined,
708
- link: column.link ? column.link : {},
709
- render: ({ data }) => {
710
- if (data) {
711
- return (
712
- <span>
713
- <a href={data} target="_blank">
714
- Link
715
- </a>
716
- </span>
717
- );
718
- } else {
719
- return null;
720
- }
721
- },
722
- };
723
- default:
724
- return {
725
- name: column.id,
726
- header: column.label,
727
- defaultFlex: 1,
728
- minWidth:
729
- column.width && column.width > 0
730
- ? column.width
731
- : undefined,
732
- link: column.link ? column.link : {},
733
- };
734
- }
735
- };
736
-
737
- const newColumns = columns.map(renderColumn);
738
-
739
- if (settings.length > 0) {
740
- newColumns.push({
741
- name: "setting",
742
- header: "Action",
743
- defaultWidth: 100,
744
- textAlign: "center",
745
- render: ({ data }) => {
746
- return (
747
- <div className="tdactions">
748
- {settings.map((setting) => (
749
- <span
750
- key={`setting-${setting.id}`}
751
- onClick={(e) => {
752
- e.preventDefault();
753
- e.stopPropagation();
754
- handleSettingClick(setting, data);
755
- }}
756
- >
757
- {renderSetting(setting, data)}
758
- </span>
759
- ))}
760
- </div>
761
- );
762
- },
763
- });
764
- }
765
-
766
- setGridColumns(newColumns);
767
- }, [columns]);
768
-
769
- // Data Grid Styling
770
- const [windowHeight, setWindowHeight] = useState(window.innerHeight);
771
- const gridStyle = {
772
- minHeight: windowHeight * 0.6 > 550 ? windowHeight * 0.6 : 550,
773
- };
774
- const headerProps = {
775
- style: style ? style : {},
776
- };
777
-
778
- useEffect(() => {
779
- const handleResize = () => {
780
- setWindowHeight(window.innerHeight);
781
- };
782
-
783
- window.addEventListener("resize", handleResize);
784
-
785
- // Clean up the event listener
786
- return () => {
787
- window.removeEventListener("resize", handleResize);
788
- };
789
- }, []);
790
-
791
- return (
792
- <>
793
- <div className="filterInput--alt">
794
- <div className="icon-search">
795
- <Search strokeWidth={2} size={18} />
796
- </div>
797
- <input
798
- type="text"
799
- placeholder="Search"
800
- value={search}
801
- onChange={handleChangeSearch}
802
- />
803
- </div>
804
- <div className="filterAction--alt">
805
- <button
806
- className="btn"
807
- onClick={() => {
808
- modalOpen("create", 0);
809
- }}
810
- >
811
- <div className="icon-plus"></div> Create
812
- </button>
813
- </div>
814
- <ReactDataGrid
815
- columns={gridColumns}
816
- dataSource={data}
817
- defaultLimit={limit}
818
- defaultSortInfo={sortInfo}
819
- enableColumnAutosize={false}
820
- headerProps={headerProps}
821
- idProperty={`visns-datagrid-${ajaxSetting.url.replace(
822
- /\//g,
823
- "-"
824
- )}`}
825
- style={gridStyle}
826
- onRenderRow={onRenderRow}
827
- pagination
828
- renderColumnContextMenu={renderColumnContextMenu}
829
- showZebraRows={true}
830
- />
831
- <Popup
832
- open={modalShow}
833
- onClose={modalClose}
834
- closeOnDocumentClick={false}
835
- >
836
- <Form
837
- closeModal={modalClose}
838
- fetchTable={handleReload}
839
- formSettings={formData}
840
- formType={formType}
841
- updateForm={setFormData}
842
- columnId={formId}
843
- childDropdownCallback={childDropdownCallback}
844
- />
845
- </Popup>
846
- </>
847
- );
58
+ const {
59
+ ajaxSetting,
60
+ form,
61
+ columns,
62
+ settings,
63
+ style,
64
+ tableFilters,
65
+ historySearch,
66
+ historyUrl,
67
+ } = props;
68
+ const navigate = useNavigate();
69
+
70
+ /** Modal States */
71
+ const [formData, setFormData] = useState(form);
72
+ const [formType, setFormType] = useState("");
73
+ const [formId, setFormId] = useState(0);
74
+ const [modalShow, setModalShow] = useState(false);
75
+
76
+ /** Modal Functions */
77
+ const childDropdownCallback = (data) => {
78
+ let _form = { ...formData };
79
+
80
+ if (formData.fields.length > 0) {
81
+ formData.fields.forEach((a, b) => {
82
+ if (a.id === data.id) {
83
+ _form.fields[b].options = data.data;
84
+ }
85
+ });
86
+ }
87
+
88
+ setFormData(_form);
89
+ };
90
+
91
+ const modalOpen = (formType, formId) => {
92
+ setModalShow(true);
93
+ setFormType(formType);
94
+ setFormId(formId);
95
+ };
96
+
97
+ const modalClose = () => {
98
+ setModalShow(false);
99
+ };
100
+
101
+ /** Table States */
102
+ const [dataReload, setDataReload] = useState(0);
103
+ const [dSearch, setDSearch] = useState("");
104
+ const data = useCallback(
105
+ (params) => loadData(params, dSearch, ajaxSetting),
106
+ [ajaxSetting, dataReload, dSearch]
107
+ );
108
+ const [gridColumns, setGridColumns] = useState([]);
109
+ const limit = ajaxSetting.take || 10;
110
+ const [search, setSearch] = useState("");
111
+ const sortInfo =
112
+ ajaxSetting.sortBy && ajaxSetting.sort
113
+ ? {
114
+ name: ajaxSetting.sortBy,
115
+ dir: ajaxSetting.sort === "asc" ? 1 : -1,
116
+ }
117
+ : null;
118
+
119
+ /** Table Functions */
120
+ const handleChangeSearch = (e) => {
121
+ const { value } = e.target;
122
+ setSearch(value);
123
+ };
124
+
125
+ const handleSettingClick = (s, d) => {
126
+ switch (s.id) {
127
+ case "update":
128
+ modalOpen("update", d.id);
129
+ break;
130
+ case "delete":
131
+ confirmAlert({
132
+ title: "Delete Item",
133
+ message: `Are you sure you want to delete ${
134
+ d.name ? d.name : d.label ? d.label : "this item"
135
+ }?`,
136
+ buttons: [
137
+ {
138
+ label: "Yes",
139
+ onClick: () =>
140
+ CustomFetch(
141
+ form.url + "/" + d[form.primaryKey],
142
+ "DELETE",
143
+ {},
144
+ (result) => {
145
+ if (result.error === "") {
146
+ const min = 0;
147
+ const max = 999999999;
148
+ const randomNumber = Math.floor(
149
+ Math.random() *
150
+ (max - min + 1) +
151
+ min
152
+ );
153
+
154
+ setDataReload(randomNumber);
155
+
156
+ toast.success(
157
+ `The selected item has been deleted.`
158
+ );
159
+ } else {
160
+ toast.error(String(result.error));
161
+ }
162
+ }
163
+ ),
164
+ },
165
+ {
166
+ label: "No",
167
+ onClick: () => close(),
168
+ },
169
+ ],
170
+ });
171
+ break;
172
+ default:
173
+ confirmAlert({
174
+ title: s.title,
175
+ message: "",
176
+ buttons: [
177
+ {
178
+ label: "Yes",
179
+ onClick: () =>
180
+ CustomFetch(
181
+ s.url + "/" + d[form.primaryKey],
182
+ s.method,
183
+ {},
184
+ function (result) {
185
+ if (result.error === "") {
186
+ const min = 0;
187
+ const max = 999999999;
188
+ const randomNumber = Math.floor(
189
+ Math.random() *
190
+ (max - min + 1) +
191
+ min
192
+ );
193
+
194
+ setDataReload(randomNumber);
195
+
196
+ toast.success(String(s.title));
197
+ } else {
198
+ toast.error(String(result.error));
199
+ }
200
+ }
201
+ ),
202
+ },
203
+ {
204
+ label: "No",
205
+ onClick: () => close(),
206
+ },
207
+ ],
208
+ });
209
+ break;
210
+ }
211
+ };
212
+
213
+ const handleReload = () => {
214
+ setDataReload(dataReload + 1);
215
+ };
216
+
217
+ const onRowClick = useCallback((rowProps, event) => {
218
+ const cellElement = event.target.closest(".InovuaReactDataGrid__cell");
219
+ const columnIndex = Array.from(cellElement.parentNode.children).indexOf(
220
+ cellElement
221
+ );
222
+
223
+ if (columnIndex === rowProps.columns.length - 1) {
224
+ } else {
225
+ if (
226
+ rowProps.columns[columnIndex].link &&
227
+ rowProps.columns[columnIndex].link.url
228
+ ) {
229
+ if (rowProps.columns[columnIndex].link.folder) {
230
+ window.location.href =
231
+ rowProps.columns[columnIndex].link.url +
232
+ rowProps.data[rowProps.columns[columnIndex].link.key] +
233
+ "/" +
234
+ rowProps.columns[columnIndex].link.folder;
235
+ } else {
236
+ navigate(
237
+ `${rowProps.columns[columnIndex].link.url}${
238
+ rowProps.data[
239
+ rowProps.columns[columnIndex].link.key
240
+ ]
241
+ }`
242
+ );
243
+ }
244
+ } else {
245
+ modalOpen("update", rowProps.data[form.primaryKey]);
246
+ }
247
+ }
248
+ }, []);
249
+
250
+ const onRenderRow = useCallback((rowProps) => {
251
+ // save the original handlers to be called later
252
+ const { onClick } = rowProps;
253
+
254
+ rowProps.onClick = (event) => {
255
+ onRowClick(rowProps, event);
256
+ if (onClick) {
257
+ onClick(event);
258
+ }
259
+ };
260
+ }, []);
261
+
262
+ /** Table Hooks */
263
+ useEffect(() => {
264
+ const delayedSetDSearch = debounce(() => {
265
+ setDSearch(search);
266
+ }, 300); // Adjust the delay (in milliseconds) according to your needs
267
+
268
+ delayedSetDSearch();
269
+
270
+ return delayedSetDSearch.cancel; // Cleanup the debounce timer on unmount
271
+ }, [search, setDSearch]);
272
+
273
+ const renderSetting = (s, d) => {
274
+ let iconStyle = {};
275
+
276
+ if (s.active) {
277
+ if (
278
+ d.hasOwnProperty(s.active.id) &&
279
+ !s.active.hasOwnProperty("type") &&
280
+ d[s.active.id] === s.active.value
281
+ ) {
282
+ iconStyle = {
283
+ color: s.active.colour,
284
+ };
285
+ } else if (s.active.hasOwnProperty("type")) {
286
+ switch (s.active.type) {
287
+ case "greater":
288
+ if (d[s.active.id] > s.active.value) {
289
+ iconStyle = {
290
+ color: s.active.colour,
291
+ };
292
+ }
293
+ break;
294
+ case "not null":
295
+ if (d[s.active.id] !== null) {
296
+ iconStyle = {
297
+ color: s.active.colour,
298
+ };
299
+ }
300
+ break;
301
+ default:
302
+ break;
303
+ }
304
+ }
305
+ }
306
+
307
+ const getIconComponent = (IconComponent) => (
308
+ <IconComponent
309
+ data-tooltip-id="system-tooltip"
310
+ data-tooltip-content={
311
+ iconStyle && iconStyle.color ? s.active.title : s.title
312
+ }
313
+ strokeWidth={2}
314
+ size={18}
315
+ className="tdaction"
316
+ style={iconStyle}
317
+ />
318
+ );
319
+
320
+ switch (s.id) {
321
+ case "complete":
322
+ return getIconComponent(Check);
323
+ case "copy":
324
+ return getIconComponent(Copy);
325
+ case "delete":
326
+ return getIconComponent(Backspace);
327
+ case "primary":
328
+ return getIconComponent(Ribbon);
329
+ case "ssa":
330
+ return getIconComponent(Clock);
331
+ case "update":
332
+ return getIconComponent(Edit);
333
+ default:
334
+ return null;
335
+ }
336
+ };
337
+
338
+ const renderColumnContextMenu = useCallback((menuProps, { cellProps }) => {
339
+ const filteredItems = menuProps.items
340
+ .map((item, key) => {
341
+ if (key >= 6 && key <= 9) {
342
+ return null; // Skip items with keys 6-9 [lock columns setting]
343
+ }
344
+ return item;
345
+ })
346
+ .filter(Boolean); // Filter out any null items
347
+
348
+ menuProps.items = filteredItems;
349
+ }, []);
350
+
351
+ useEffect(() => {
352
+ const renderColumn = (column) => {
353
+ switch (column.type) {
354
+ case "created_by":
355
+ return {
356
+ name: "audits.name",
357
+ header: column.label,
358
+ defaultFlex: 1,
359
+ link: column.link ? column.link : {},
360
+ minWidth:
361
+ column.minWidth && column.minWidth > 0
362
+ ? column.minWidth
363
+ : undefined,
364
+ maxWidth:
365
+ column.maxWidth && column.maxWidth > 0
366
+ ? column.maxWidth
367
+ : undefined,
368
+ render: ({ data }) => {
369
+ if (
370
+ data.audits[0] &&
371
+ data.audits[0].user &&
372
+ data.audits[0].user.name
373
+ ) {
374
+ let value = data.audits[0].user.name;
375
+
376
+ return <span>{value}</span>;
377
+ } else {
378
+ return null;
379
+ }
380
+ },
381
+ };
382
+ case "currency":
383
+ return {
384
+ name: column.id,
385
+ header: column.label,
386
+ defaultFlex: 1,
387
+ minWidth:
388
+ column.minWidth && column.minWidth > 0
389
+ ? column.minWidth
390
+ : undefined,
391
+ maxWidth:
392
+ column.maxWidth && column.maxWidth > 0
393
+ ? column.maxWidth
394
+ : undefined,
395
+ link: column.link ? column.link : {},
396
+ render: ({ data }) => {
397
+ if (data) {
398
+ data = numeral(data[column.id]).format(
399
+ "$0,0.00"
400
+ );
401
+
402
+ return <span>{data}</span>;
403
+ } else {
404
+ return null;
405
+ }
406
+ },
407
+ };
408
+ case "date":
409
+ return {
410
+ name: column.id,
411
+ header: column.label,
412
+ defaultFlex: 1,
413
+ minWidth:
414
+ column.minWidth && column.minWidth > 0
415
+ ? column.minWidth
416
+ : undefined,
417
+ maxWidth:
418
+ column.maxWidth && column.maxWidth > 0
419
+ ? column.maxWidth
420
+ : undefined,
421
+ link: column.link ? column.link : {},
422
+ render: ({ data }) => {
423
+ if (data && data[column.id]) {
424
+ data = moment(data[column.id]).format(
425
+ column.format ? column.format : "DD-MM-YYYY"
426
+ );
427
+
428
+ return <span>{data}</span>;
429
+ } else {
430
+ return null;
431
+ }
432
+ },
433
+ };
434
+ case "datetime":
435
+ return {
436
+ name: column.id,
437
+ header: column.label,
438
+ defaultFlex: 1,
439
+ minWidth:
440
+ column.minWidth && column.minWidth > 0
441
+ ? column.minWidth
442
+ : undefined,
443
+ maxWidth:
444
+ column.maxWidth && column.maxWidth > 0
445
+ ? column.maxWidth
446
+ : undefined,
447
+ link: column.link ? column.link : {},
448
+ render: ({ data }) => {
449
+ if (data && data[column.id]) {
450
+ data = moment(data[column.id]).format(
451
+ column.format
452
+ ? column.format
453
+ : "DD-MM-YYYY hh:mm A"
454
+ );
455
+
456
+ return <span>{data}</span>;
457
+ } else {
458
+ return null;
459
+ }
460
+ },
461
+ };
462
+ case "file":
463
+ return {
464
+ name: column.id,
465
+ header: column.label,
466
+ defaultFlex: 1,
467
+ minWidth:
468
+ column.minWidth && column.minWidth > 0
469
+ ? column.minWidth
470
+ : undefined,
471
+ maxWidth:
472
+ column.maxWidth && column.maxWidth > 0
473
+ ? column.maxWidth
474
+ : undefined,
475
+ link: column.link ? column.link : {},
476
+ render: ({ data }) => {
477
+ if (data) {
478
+ return (
479
+ <span>
480
+ <CloudDownload
481
+ data-tooltip-id="system-tooltip"
482
+ data-tooltip-content="Download File"
483
+ strokeWidth={2}
484
+ size={18}
485
+ className="tdaction"
486
+ />
487
+ </span>
488
+ );
489
+ } else {
490
+ return null;
491
+ }
492
+ },
493
+ };
494
+ case "icons":
495
+ return {
496
+ name: column.id,
497
+ header: column.label,
498
+ defaultFlex: 1,
499
+ minWidth:
500
+ column.minWidth && column.minWidth > 0
501
+ ? column.minWidth
502
+ : undefined,
503
+ maxWidth:
504
+ column.maxWidth && column.maxWidth > 0
505
+ ? column.maxWidth
506
+ : undefined,
507
+ link: column.link ? column.link : {},
508
+ render: ({ data }) => {
509
+ let icons = [];
510
+
511
+ if (column.icons && column.icons.length > 0) {
512
+ column.icons.forEach((icon) => {
513
+ icons.push(
514
+ <img
515
+ src={icon.url}
516
+ alt={icon.name}
517
+ data-tooltip-id="system-tooltip"
518
+ data-tooltip-content={icon.name}
519
+ />
520
+ );
521
+ });
522
+ }
523
+
524
+ return <span>{icons}</span>;
525
+ },
526
+ };
527
+ case "json":
528
+ return {
529
+ name: column.id,
530
+ header: column.label,
531
+ defaultFlex: 1,
532
+ minWidth:
533
+ column.minWidth && column.minWidth > 0
534
+ ? column.minWidth
535
+ : undefined,
536
+ maxWidth:
537
+ column.maxWidth && column.maxWidth > 0
538
+ ? column.maxWidth
539
+ : undefined,
540
+ link: column.link ? column.link : {},
541
+ render: ({ data }) => {
542
+ if (
543
+ data &&
544
+ data[column.id] &&
545
+ data[column.id][column.jsonData]
546
+ ) {
547
+ data = data[column.id][column.jsonData];
548
+
549
+ return <span>{data}</span>;
550
+ } else {
551
+ return null;
552
+ }
553
+ },
554
+ };
555
+ case "number":
556
+ return {
557
+ name: column.id,
558
+ header: column.label,
559
+ defaultFlex: 1,
560
+ minWidth:
561
+ column.minWidth && column.minWidth > 0
562
+ ? column.minWidth
563
+ : undefined,
564
+ maxWidth:
565
+ column.maxWidth && column.maxWidth > 0
566
+ ? column.maxWidth
567
+ : undefined,
568
+ link: column.link ? column.link : {},
569
+ type: "number",
570
+ };
571
+ case "option":
572
+ return {
573
+ name: column.id,
574
+ header: column.label,
575
+ defaultFlex: 1,
576
+ minWidth:
577
+ column.minWidth && column.minWidth > 0
578
+ ? column.minWidth
579
+ : undefined,
580
+ maxWidth:
581
+ column.maxWidth && column.maxWidth > 0
582
+ ? column.maxWidth
583
+ : undefined,
584
+ link: column.link ? column.link : {},
585
+ render: ({ data }) => {
586
+ if (
587
+ data &&
588
+ column.options &&
589
+ column.options[data[column.id]]
590
+ ) {
591
+ data = column.options[data[column.id]];
592
+
593
+ return <span>{data}</span>;
594
+ } else {
595
+ return null;
596
+ }
597
+ },
598
+ };
599
+ case "placeholder":
600
+ return {
601
+ name: column.id,
602
+ header: column.label,
603
+ defaultFlex: 1,
604
+ minWidth:
605
+ column.minWidth && column.minWidth > 0
606
+ ? column.minWidth
607
+ : undefined,
608
+ maxWidth:
609
+ column.maxWidth && column.maxWidth > 0
610
+ ? column.maxWidth
611
+ : undefined,
612
+ link: column.link ? column.link : {},
613
+ render: ({ data }) => {
614
+ return <span>{column.placeholder}</span>;
615
+ },
616
+ };
617
+ case "relation":
618
+ let relationName = column.id.reduce(
619
+ (acc, id) => acc + `${id}.`,
620
+ ""
621
+ );
622
+ relationName += column.nameFrom;
623
+
624
+ return {
625
+ name: relationName,
626
+ header: column.label,
627
+ defaultFlex: 1,
628
+ minWidth:
629
+ column.minWidth && column.minWidth > 0
630
+ ? column.minWidth
631
+ : undefined,
632
+ maxWidth:
633
+ column.maxWidth && column.maxWidth > 0
634
+ ? column.maxWidth
635
+ : undefined,
636
+ link: column.link ? column.link : {},
637
+ render: ({ data }) => {
638
+ let value = column.id.reduce((acc, id) => {
639
+ if (acc === "") {
640
+ return data[id];
641
+ } else {
642
+ return acc[id];
643
+ }
644
+ }, "");
645
+
646
+ if (value) {
647
+ if (Array.isArray(column.nameFrom)) {
648
+ value = column.nameFrom
649
+ .map((nf) => value[nf])
650
+ .join(" ");
651
+ } else {
652
+ value = value[column.nameFrom];
653
+ }
654
+
655
+ if (
656
+ column.placeholder &&
657
+ column.placeholder !== ""
658
+ ) {
659
+ return <span>{column.placeholder}</span>;
660
+ } else {
661
+ return <span>{value}</span>;
662
+ }
663
+ } else {
664
+ return null;
665
+ }
666
+ },
667
+ };
668
+ case "relationArray":
669
+ const relationArrayName =
670
+ column.id.reduce((acc, id) => acc + `${id}.`, "") +
671
+ column.nameFrom;
672
+
673
+ return {
674
+ name: relationArrayName,
675
+ header: column.label,
676
+ defaultFlex: 1,
677
+ minWidth:
678
+ column.minWidth && column.minWidth > 0
679
+ ? column.minWidth
680
+ : undefined,
681
+ maxWidth:
682
+ column.maxWidth && column.maxWidth > 0
683
+ ? column.maxWidth
684
+ : undefined,
685
+ link: column.link ? column.link : {},
686
+ render: ({ data }) => {
687
+ const relationValue = column.id.reduce(
688
+ (acc, id) => (acc === "" ? data[id] : acc[id]),
689
+ ""
690
+ );
691
+
692
+ if (relationValue && Array.isArray(relationValue)) {
693
+ const value = relationValue
694
+ .map((rv) => rv[column.nameFrom])
695
+ .join(", ");
696
+ return <span>{value}</span>;
697
+ } else {
698
+ return null;
699
+ }
700
+ },
701
+ };
702
+
703
+ case "stage":
704
+ return {
705
+ name: column.id,
706
+ header: column.label,
707
+ defaultFlex: 1,
708
+ minWidth:
709
+ column.minWidth && column.minWidth > 0
710
+ ? column.minWidth
711
+ : undefined,
712
+ maxWidth:
713
+ column.maxWidth && column.maxWidth > 0
714
+ ? column.maxWidth
715
+ : undefined,
716
+ link: column.link ? column.link : {},
717
+ render: ({ data }) => {
718
+ let stage = "";
719
+
720
+ column.keyIds.forEach((a, b) => {
721
+ if (data[a] === 1) {
722
+ stage = column.valueIds[b];
723
+ }
724
+ });
725
+
726
+ return <span>{stage}</span>;
727
+ },
728
+ };
729
+ case "time":
730
+ return {
731
+ name: column.id,
732
+ header: column.label,
733
+ defaultFlex: 1,
734
+ minWidth:
735
+ column.minWidth && column.minWidth > 0
736
+ ? column.minWidth
737
+ : undefined,
738
+ maxWidth:
739
+ column.maxWidth && column.maxWidth > 0
740
+ ? column.maxWidth
741
+ : undefined,
742
+ link: column.link ? column.link : {},
743
+ render: ({ data }) => {
744
+ if (data && data[column.id]) {
745
+ data = moment(data[column.id]).format(
746
+ column.format ? column.format : "hh:mm A"
747
+ );
748
+
749
+ return <span>{data}</span>;
750
+ } else {
751
+ return null;
752
+ }
753
+ },
754
+ };
755
+ case "url":
756
+ return {
757
+ name: column.id,
758
+ header: column.label,
759
+ defaultFlex: 1,
760
+ minWidth:
761
+ column.minWidth && column.minWidth > 0
762
+ ? column.minWidth
763
+ : undefined,
764
+ maxWidth:
765
+ column.maxWidth && column.maxWidth > 0
766
+ ? column.maxWidth
767
+ : undefined,
768
+ link: column.link ? column.link : {},
769
+ render: ({ data }) => {
770
+ if (data) {
771
+ return (
772
+ <span>
773
+ <a href={data} target="_blank">
774
+ Link
775
+ </a>
776
+ </span>
777
+ );
778
+ } else {
779
+ return null;
780
+ }
781
+ },
782
+ };
783
+ default:
784
+ return {
785
+ name: column.id,
786
+ header: column.label,
787
+ defaultFlex: 1,
788
+ minWidth:
789
+ column.minWidth && column.minWidth > 0
790
+ ? column.minWidth
791
+ : undefined,
792
+ maxWidth:
793
+ column.maxWidth && column.maxWidth > 0
794
+ ? column.maxWidth
795
+ : undefined,
796
+ link: column.link ? column.link : {},
797
+ };
798
+ }
799
+ };
800
+
801
+ const newColumns = columns.map(renderColumn);
802
+
803
+ if (settings.length > 0) {
804
+ newColumns.push({
805
+ name: "setting",
806
+ header: "Action",
807
+ defaultWidth: 100,
808
+ textAlign: "center",
809
+ render: ({ data }) => {
810
+ return (
811
+ <div className="tdactions">
812
+ {settings.map((setting) => (
813
+ <span
814
+ key={`setting-${setting.id}`}
815
+ onClick={(e) => {
816
+ e.preventDefault();
817
+ e.stopPropagation();
818
+ handleSettingClick(setting, data);
819
+ }}
820
+ >
821
+ {renderSetting(setting, data)}
822
+ </span>
823
+ ))}
824
+ </div>
825
+ );
826
+ },
827
+ });
828
+ }
829
+
830
+ setGridColumns(newColumns);
831
+ }, [columns]);
832
+
833
+ // Data Grid Styling
834
+ const [windowHeight, setWindowHeight] = useState(window.innerHeight);
835
+ const gridStyle = {
836
+ minHeight: windowHeight * 0.6 > 550 ? windowHeight * 0.6 : 550,
837
+ };
838
+ const headerProps = {
839
+ style: style ? style : {},
840
+ };
841
+
842
+ useEffect(() => {
843
+ const handleResize = () => {
844
+ setWindowHeight(window.innerHeight);
845
+ };
846
+
847
+ window.addEventListener("resize", handleResize);
848
+
849
+ // Clean up the event listener
850
+ return () => {
851
+ window.removeEventListener("resize", handleResize);
852
+ };
853
+ }, []);
854
+
855
+ return (
856
+ <>
857
+ <div className="filterInput--alt">
858
+ <div className="icon-search">
859
+ <Search strokeWidth={2} size={18} />
860
+ </div>
861
+ <input
862
+ type="text"
863
+ placeholder="Search"
864
+ value={search}
865
+ onChange={handleChangeSearch}
866
+ />
867
+ </div>
868
+ <div className="filterAction--alt">
869
+ <button
870
+ className="btn"
871
+ onClick={() => {
872
+ modalOpen("create", 0);
873
+ }}
874
+ >
875
+ <div className="icon-plus"></div> Create
876
+ </button>
877
+ </div>
878
+ <ReactDataGrid
879
+ columns={gridColumns}
880
+ dataSource={data}
881
+ defaultLimit={limit}
882
+ defaultSortInfo={sortInfo}
883
+ enableColumnAutosize={false}
884
+ headerProps={headerProps}
885
+ idProperty={`visns-datagrid-${ajaxSetting.url.replace(
886
+ /\//g,
887
+ "-"
888
+ )}`}
889
+ style={gridStyle}
890
+ onRenderRow={onRenderRow}
891
+ pagination
892
+ renderColumnContextMenu={renderColumnContextMenu}
893
+ showZebraRows={true}
894
+ />
895
+ <Popup
896
+ open={modalShow}
897
+ onClose={modalClose}
898
+ closeOnDocumentClick={false}
899
+ >
900
+ <Form
901
+ closeModal={modalClose}
902
+ fetchTable={handleReload}
903
+ formSettings={formData}
904
+ formType={formType}
905
+ updateForm={setFormData}
906
+ columnId={formId}
907
+ childDropdownCallback={childDropdownCallback}
908
+ />
909
+ </Popup>
910
+ </>
911
+ );
848
912
  };
849
913
 
850
914
  export default DataGrid;