@visns-studio/visns-components 1.0.4 → 1.0.5

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.
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { SortableElement } from 'react-sortable-hoc';
3
+ import { ChevronVertical } from 'akar-icons';
4
+
5
+ const SortableItem = (props) => {
6
+ const { value, index } = props;
7
+
8
+ return (
9
+ <li>
10
+ <ChevronVertical strokeWidth={1} size={26} />
11
+ {value.label}
12
+ {value.hasOwnProperty('image_url') && value.image_url !== '' ? (
13
+ <div className="sortimg">
14
+ <img src={value.image_url} />
15
+ </div>
16
+ ) : null}
17
+ </li>
18
+ );
19
+ };
20
+
21
+ export default SortableElement(SortableItem);
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import VisnsSortableItem from './item';
3
+ import { SortableContainer } from 'react-sortable-hoc';
4
+
5
+ const SortableList = (props) => {
6
+ const { items } = props;
7
+
8
+ return (
9
+ <ul className="sortlist">
10
+ {items.map((value, index) => (
11
+ <VisnsSortableItem
12
+ key={`item-${index}`}
13
+ index={index}
14
+ value={value}
15
+ />
16
+ ))}
17
+ </ul>
18
+ );
19
+ };
20
+
21
+ export default SortableContainer(SortableList);
@@ -0,0 +1,597 @@
1
+ import React, { useCallback, useEffect, useState } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+ import axios from "axios";
4
+ import debounce from "lodash.debounce";
5
+ import moment from "moment";
6
+ import numeral from "numeral";
7
+ import Popup from "reactjs-popup";
8
+ import ReactDataGrid from "@inovua/reactdatagrid-community";
9
+ import { confirmAlert } from "react-confirm-alert";
10
+ import { toast } from "react-toastify";
11
+ import {
12
+ Backspace,
13
+ Check,
14
+ Clock,
15
+ CloudDownload,
16
+ Edit,
17
+ Ribbon,
18
+ Search,
19
+ } from "akar-icons";
20
+
21
+ import "@inovua/reactdatagrid-community/index.css";
22
+ import "react-confirm-alert/src/react-confirm-alert.css";
23
+
24
+ import CustomFetch from "../crm/visns-fetch";
25
+ import Form from "./visns-form";
26
+
27
+ const loadData = async ({ skip, limit, sortInfo }, search, ajaxSetting) => {
28
+ const url = ajaxSetting.url;
29
+
30
+ let page = Math.floor(skip / limit) + 1;
31
+ let params = { page, sortInfo, take: limit, search: search };
32
+
33
+ if (sortInfo) {
34
+ params.sortBy = sortInfo.name;
35
+ params.sort = sortInfo.dir === 1 ? "asc" : "desc";
36
+ }
37
+
38
+ if (ajaxSetting && ajaxSetting.where) {
39
+ params.where = ajaxSetting.where;
40
+ }
41
+
42
+ try {
43
+ const response = await axios.post(url, params);
44
+ const { data, total } = response.data;
45
+ return { data, count: total };
46
+ } catch (error) {
47
+ console.error("Error fetching data:", error);
48
+ return { data: [], count: 0 };
49
+ }
50
+ };
51
+
52
+ const DataGrid = (props) => {
53
+ const { ajaxSetting, form, columns, settings } = props;
54
+ const navigate = useNavigate();
55
+
56
+ /** Modal States */
57
+ const [formData, setFormData] = useState(form);
58
+ const [formType, setFormType] = useState("");
59
+ const [formId, setFormId] = useState(0);
60
+ const [modalShow, setModalShow] = useState(false);
61
+
62
+ /** Modal Functions */
63
+ const childDropdownCallback = (data) => {
64
+ let _form = { ...formData };
65
+
66
+ if (formData.fields.length > 0) {
67
+ formData.fields.forEach((a, b) => {
68
+ if (a.id === data.id) {
69
+ _form.fields[b].options = data.data;
70
+ }
71
+ });
72
+ }
73
+
74
+ setFormData(_form);
75
+ };
76
+
77
+ const modalOpen = (formType, formId) => {
78
+ setModalShow(true);
79
+ setFormType(formType);
80
+ setFormId(formId);
81
+ };
82
+
83
+ const modalClose = () => {
84
+ setModalShow(false);
85
+ };
86
+
87
+ /** Table States */
88
+ const [dataReload, setDataReload] = useState(0);
89
+ const [dSearch, setDSearch] = useState("");
90
+ const data = useCallback(
91
+ (params) => loadData(params, dSearch, ajaxSetting),
92
+ [ajaxSetting, dataReload, dSearch]
93
+ );
94
+ const [gridColumns, setGridColumns] = useState([]);
95
+ const [gridHeight, setGridHeight] = useState(550);
96
+ const limit = ajaxSetting.take || 10;
97
+ const [search, setSearch] = useState("");
98
+ const sortInfo =
99
+ ajaxSetting.sortBy && ajaxSetting.sort
100
+ ? {
101
+ name: ajaxSetting.sortBy,
102
+ dir: ajaxSetting.sort === "asc" ? 1 : -1,
103
+ }
104
+ : null;
105
+
106
+ /** Table Functions */
107
+ const handleChangeSearch = (e) => {
108
+ const { value } = e.target;
109
+ setSearch(value);
110
+ };
111
+
112
+ const handleSettingClick = (s, d) => {
113
+ switch (s.id) {
114
+ case "update":
115
+ modalOpen("update", d.id);
116
+ break;
117
+ case "delete":
118
+ confirmAlert({
119
+ title: "Delete Item",
120
+ message: `Are you sure you want to delete ${
121
+ d.name ? d.name : d.label ? d.label : "this item"
122
+ }?`,
123
+ buttons: [
124
+ {
125
+ label: "Yes",
126
+ onClick: () =>
127
+ CustomFetch(
128
+ form.url + "/" + d[form.primaryKey],
129
+ "DELETE",
130
+ {},
131
+ (result) => {
132
+ if (result.error === "") {
133
+ const min = 0;
134
+ const max = 999999999;
135
+ const randomNumber = Math.floor(
136
+ Math.random() *
137
+ (max - min + 1) +
138
+ min
139
+ );
140
+
141
+ setDataReload(randomNumber);
142
+
143
+ toast.success(
144
+ `The selected item has been deleted.`
145
+ );
146
+ } else {
147
+ toast.error(String(result.error));
148
+ }
149
+ }
150
+ ),
151
+ },
152
+ {
153
+ label: "No",
154
+ onClick: () => close(),
155
+ },
156
+ ],
157
+ });
158
+ break;
159
+ default:
160
+ confirmAlert({
161
+ title: s.title,
162
+ message: "",
163
+ buttons: [
164
+ {
165
+ label: "Yes",
166
+ onClick: () =>
167
+ CustomFetch(
168
+ s.url + "/" + d[form.primaryKey],
169
+ s.method,
170
+ {},
171
+ function (result) {
172
+ if (result.error === "") {
173
+ const min = 0;
174
+ const max = 999999999;
175
+ const randomNumber = Math.floor(
176
+ Math.random() *
177
+ (max - min + 1) +
178
+ min
179
+ );
180
+
181
+ setDataReload(randomNumber);
182
+
183
+ toast.success(String(s.title));
184
+ } else {
185
+ toast.error(String(result.error));
186
+ }
187
+ }
188
+ ),
189
+ },
190
+ {
191
+ label: "No",
192
+ onClick: () => close(),
193
+ },
194
+ ],
195
+ });
196
+ break;
197
+ }
198
+ };
199
+
200
+ const handleReload = () => {
201
+ setDataReload(dataReload + 1);
202
+ };
203
+
204
+ const onRowClick = useCallback((rowProps, event) => {
205
+ const cellElement = event.target.closest(".InovuaReactDataGrid__cell");
206
+ const columnIndex = Array.from(cellElement.parentNode.children).indexOf(
207
+ cellElement
208
+ );
209
+
210
+ if (columnIndex === rowProps.columns.length - 1) {
211
+ } else {
212
+ if (
213
+ rowProps.columns[columnIndex].link &&
214
+ rowProps.columns[columnIndex].link.url
215
+ ) {
216
+ if (rowProps.columns[columnIndex].link.folder) {
217
+ window.location.href =
218
+ rowProps.columns[columnIndex].link.url +
219
+ rowProps.data[rowProps.columns[columnIndex].link.key] +
220
+ "/" +
221
+ rowProps.columns[columnIndex].link.folder;
222
+ } else {
223
+ navigate(
224
+ `${rowProps.columns[columnIndex].link.url}${
225
+ rowProps.data[
226
+ rowProps.columns[columnIndex].link.key
227
+ ]
228
+ }`,
229
+ {
230
+ state: {
231
+ form: form,
232
+ },
233
+ }
234
+ );
235
+ }
236
+ } else {
237
+ modalOpen("update", rowProps.data[form.primaryKey]);
238
+ }
239
+ }
240
+ }, []);
241
+
242
+ const onRenderRow = useCallback((rowProps) => {
243
+ // save the original handlers to be called later
244
+ const { onClick } = rowProps;
245
+
246
+ rowProps.onClick = (event) => {
247
+ onRowClick(rowProps, event);
248
+ if (onClick) {
249
+ onClick(event);
250
+ }
251
+ };
252
+
253
+ // add a CSS class to rowProps
254
+ rowProps.className = "vs-datagrid--row";
255
+ }, []);
256
+
257
+ /** Table Hooks */
258
+ useEffect(() => {
259
+ const delayedSetDSearch = debounce(() => {
260
+ setDSearch(search);
261
+ }, 300); // Adjust the delay (in milliseconds) according to your needs
262
+
263
+ delayedSetDSearch();
264
+
265
+ return delayedSetDSearch.cancel; // Cleanup the debounce timer on unmount
266
+ }, [search, setDSearch]);
267
+
268
+ const renderSetting = (s, d) => {
269
+ let iconStyle = {};
270
+
271
+ if (s.active) {
272
+ if (
273
+ d.hasOwnProperty(s.active.id) &&
274
+ !s.active.hasOwnProperty("type") &&
275
+ d[s.active.id] === s.active.value
276
+ ) {
277
+ iconStyle = {
278
+ color: s.active.colour,
279
+ };
280
+ } else if (s.active.hasOwnProperty("type")) {
281
+ switch (s.active.type) {
282
+ case "greater":
283
+ if (d[s.active.id] > s.active.value) {
284
+ iconStyle = {
285
+ color: s.active.colour,
286
+ };
287
+ }
288
+ break;
289
+ case "not null":
290
+ if (d[s.active.id] !== null) {
291
+ iconStyle = {
292
+ color: s.active.colour,
293
+ };
294
+ }
295
+ break;
296
+ default:
297
+ break;
298
+ }
299
+ }
300
+ }
301
+
302
+ const getIconComponent = (IconComponent) => (
303
+ <IconComponent
304
+ data-tooltip-id="system-tooltip"
305
+ data-tooltip-content={
306
+ iconStyle && iconStyle.color ? s.active.title : s.title
307
+ }
308
+ strokeWidth={2}
309
+ size={18}
310
+ className="tdaction"
311
+ style={iconStyle}
312
+ />
313
+ );
314
+
315
+ switch (s.id) {
316
+ case "complete":
317
+ return getIconComponent(Check);
318
+ case "delete":
319
+ return getIconComponent(Backspace);
320
+ case "primary":
321
+ return getIconComponent(Ribbon);
322
+ case "ssa":
323
+ return getIconComponent(Clock);
324
+ case "update":
325
+ return getIconComponent(Edit);
326
+ default:
327
+ return null;
328
+ }
329
+ };
330
+
331
+ const renderColumnContextMenu = useCallback((menuProps, { cellProps }) => {
332
+ const filteredItems = menuProps.items
333
+ .map((item, key) => {
334
+ if (key >= 6 && key <= 9) {
335
+ return null; // Skip items with keys 6-9 [lock columns setting]
336
+ }
337
+ return item;
338
+ })
339
+ .filter(Boolean); // Filter out any null items
340
+
341
+ menuProps.items = filteredItems;
342
+ }, []);
343
+
344
+ useEffect(() => {
345
+ const renderColumn = (column) => {
346
+ switch (column.type) {
347
+ case "currency":
348
+ return {
349
+ name: column.id,
350
+ header: column.label,
351
+ defaultFlex: 1,
352
+ link: column.link ? column.link : {},
353
+ render: ({ data }) => {
354
+ if (data) {
355
+ data = numeral(data[column.id]).format(
356
+ "$0,0.00"
357
+ );
358
+ }
359
+ return <span>{data}</span>;
360
+ },
361
+ };
362
+ case "date":
363
+ return {
364
+ name: column.id,
365
+ header: column.label,
366
+ defaultFlex: 1,
367
+ link: column.link ? column.link : {},
368
+ render: ({ data }) => {
369
+ if (data && data[column.id]) {
370
+ data = moment(data[column.id]).format(
371
+ column.format ? column.format : "DD-MM-YYYY"
372
+ );
373
+ }
374
+ return <span>{data}</span>;
375
+ },
376
+ };
377
+ case "datetime":
378
+ return {
379
+ name: column.id,
380
+ header: column.label,
381
+ defaultFlex: 1,
382
+ link: column.link ? column.link : {},
383
+ render: ({ data }) => {
384
+ if (data && data[column.id]) {
385
+ data = moment(data[column.id]).format(
386
+ column.format
387
+ ? column.format
388
+ : "DD-MM-YYYY hh:mm A"
389
+ );
390
+ }
391
+ return <span>{data}</span>;
392
+ },
393
+ };
394
+ case "file":
395
+ return {
396
+ name: column.id,
397
+ header: column.label,
398
+ defaultFlex: 1,
399
+ link: column.link ? column.link : {},
400
+ render: ({ data }) => {
401
+ if (data) {
402
+ return (
403
+ <span>
404
+ <CloudDownload
405
+ data-tooltip-id="system-tooltip"
406
+ data-tooltip-content="Download File"
407
+ strokeWidth={2}
408
+ size={18}
409
+ className="tdaction"
410
+ />
411
+ </span>
412
+ );
413
+ } else {
414
+ return null;
415
+ }
416
+ },
417
+ };
418
+ case "option":
419
+ return {
420
+ name: column.id,
421
+ header: column.label,
422
+ defaultFlex: 1,
423
+ link: column.link ? column.link : {},
424
+ render: ({ data }) => {
425
+ if (data) {
426
+ data = column.options[data[column.id]];
427
+ }
428
+ return <span>{data}</span>;
429
+ },
430
+ };
431
+ case "relation":
432
+ let relationName = column.id.reduce(
433
+ (acc, id) => acc + `${id}.`,
434
+ ""
435
+ );
436
+ relationName += column.nameFrom;
437
+
438
+ return {
439
+ name: relationName,
440
+ header: column.label,
441
+ defaultFlex: 1,
442
+ link: column.link ? column.link : {},
443
+ render: ({ data }) => {
444
+ let value = column.id.reduce((acc, id) => {
445
+ if (acc === "") {
446
+ return data[id];
447
+ } else {
448
+ return acc[id];
449
+ }
450
+ }, "");
451
+
452
+ if (value) {
453
+ value = value[column.nameFrom];
454
+ }
455
+
456
+ return <span>{value}</span>;
457
+ },
458
+ };
459
+ case "stage":
460
+ return {
461
+ name: column.id,
462
+ header: column.label,
463
+ defaultFlex: 1,
464
+ link: column.link ? column.link : {},
465
+ render: ({ data }) => {
466
+ let stage = "";
467
+
468
+ column.keyIds.forEach((a, b) => {
469
+ if (data[a] === 1) {
470
+ stage = column.valueIds[b];
471
+ }
472
+ });
473
+
474
+ return <span>{stage}</span>;
475
+ },
476
+ };
477
+ case "time":
478
+ return {
479
+ name: column.id,
480
+ header: column.label,
481
+ defaultFlex: 1,
482
+ link: column.link ? column.link : {},
483
+ render: ({ data }) => {
484
+ if (data && data[column.id]) {
485
+ data = moment(data[column.id]).format(
486
+ column.format ? column.format : "hh:mm A"
487
+ );
488
+ }
489
+ return <span>{data}</span>;
490
+ },
491
+ };
492
+ default:
493
+ return {
494
+ name: column.id,
495
+ header: column.label,
496
+ defaultFlex: 1,
497
+ link: column.link ? column.link : {},
498
+ };
499
+ }
500
+ };
501
+
502
+ const newColumns = columns.map(renderColumn);
503
+
504
+ if (settings.length > 0) {
505
+ newColumns.push({
506
+ name: "setting",
507
+ header: "",
508
+ defaultWidth: 100,
509
+ textAlign: "center",
510
+ render: ({ data }) => {
511
+ return (
512
+ <div className="tdactions">
513
+ {settings.map((setting) => (
514
+ <span
515
+ key={`setting-${setting.id}`}
516
+ onClick={(e) => {
517
+ e.preventDefault();
518
+ e.stopPropagation();
519
+ handleSettingClick(setting, data);
520
+ }}
521
+ >
522
+ {renderSetting(setting, data)}
523
+ </span>
524
+ ))}
525
+ </div>
526
+ );
527
+ },
528
+ });
529
+ }
530
+
531
+ setGridColumns(newColumns);
532
+ }, [columns]);
533
+
534
+ // Data Grid Styling
535
+ const gridStyle = { minHeight: gridHeight };
536
+
537
+ return (
538
+ <>
539
+ <div className="filterInput--alt">
540
+ <div className="icon-search">
541
+ <Search strokeWidth={2} size={18} />
542
+ </div>
543
+ <input
544
+ type="text"
545
+ placeholder="Search"
546
+ value={search}
547
+ onChange={handleChangeSearch}
548
+ />
549
+ </div>
550
+ <div className="filterAction--alt">
551
+ <button
552
+ className="btn"
553
+ onClick={() => {
554
+ let _url = form.url.replace("/ajax", "");
555
+
556
+ navigate(_url + "/create", {
557
+ state: {
558
+ form: form,
559
+ },
560
+ });
561
+ }}
562
+ >
563
+ <div className="icon-plus"></div> Create
564
+ </button>
565
+ </div>
566
+ <ReactDataGrid
567
+ columns={gridColumns}
568
+ dataSource={data}
569
+ defaultLimit={limit}
570
+ defaultSortInfo={sortInfo}
571
+ enableColumnAutosize={false}
572
+ idProperty="uniqueId"
573
+ style={gridStyle}
574
+ onRenderRow={onRenderRow}
575
+ pagination
576
+ renderColumnContextMenu={renderColumnContextMenu}
577
+ />
578
+ <Popup
579
+ open={modalShow}
580
+ onClose={modalClose}
581
+ closeOnDocumentClick={false}
582
+ >
583
+ <Form
584
+ closeModal={modalClose}
585
+ fetchTable={handleReload}
586
+ formSettings={formData}
587
+ formType={formType}
588
+ updateForm={setFormData}
589
+ columnId={formId}
590
+ childDropdownCallback={childDropdownCallback}
591
+ />
592
+ </Popup>
593
+ </>
594
+ );
595
+ };
596
+
597
+ export default DataGrid;