@visns-studio/visns-components 1.8.5 → 2.0.1

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 (36) hide show
  1. package/.prettierrc.json +7 -0
  2. package/components/cms/{visns-data-grid.jsx → DataGrid.jsx} +2 -2
  3. package/components/cms/DropZone.jsx +161 -0
  4. package/components/cms/Field.jsx +747 -0
  5. package/components/cms/Form.jsx +475 -0
  6. package/components/cms/sorting/{list.jsx → List.jsx} +3 -3
  7. package/components/crm/{visns-async-select.jsx → AsyncSelect.jsx} +9 -9
  8. package/components/crm/Call.jsx +220 -0
  9. package/components/crm/{visns-data-grid.jsx → DataGrid.jsx} +263 -277
  10. package/components/crm/{visns-field.jsx → Field.jsx} +5 -5
  11. package/components/crm/{visns-form.jsx → Form.jsx} +302 -205
  12. package/components/crm/{visns-multi-select.jsx → MultiSelect.jsx} +1 -1
  13. package/components/crm/{visns-navigation.jsx → Navigation.jsx} +22 -18
  14. package/components/crm/{visns-notification.jsx → Notification.jsx} +1 -1
  15. package/components/crm/TableFilter.jsx +125 -0
  16. package/components/crm/auth/Login.jsx +158 -0
  17. package/components/crm/auth/Profile.jsx +163 -0
  18. package/components/crm/auth/Reset.jsx +87 -0
  19. package/components/crm/auth/Verify.jsx +162 -0
  20. package/components/crm/generic/GenericDetail.jsx +563 -0
  21. package/components/crm/generic/GenericIndex.jsx +112 -0
  22. package/components/crm/generic/NotificationList.jsx +64 -0
  23. package/index.js +37 -19
  24. package/package.json +13 -7
  25. package/components/cms/visns-dropzone.jsx +0 -161
  26. package/components/cms/visns-field.jsx +0 -747
  27. package/components/cms/visns-form.jsx +0 -475
  28. package/components/crm/visns-call.jsx +0 -220
  29. package/components/crm/visns-table-filter.jsx +0 -153
  30. /package/components/cms/sorting/{item.jsx → Item.jsx} +0 -0
  31. /package/components/crm/{visns-autocomplete.jsx → Autocomplete.jsx} +0 -0
  32. /package/components/crm/{visns-download.jsx → Download.jsx} +0 -0
  33. /package/components/crm/{visns-fetch.jsx → Fetch.jsx} +0 -0
  34. /package/components/crm/{visns-loader.jsx → Loader.jsx} +0 -0
  35. /package/components/crm/{visns-select.jsx → Select.jsx} +0 -0
  36. /package/components/crm/{visns-select-list.jsx → SelectList.jsx} +0 -0
@@ -0,0 +1,563 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useParams, Link } from 'react-router-dom';
3
+ import moment from 'moment';
4
+ import parse from 'html-react-parser';
5
+ import Popup from 'reactjs-popup';
6
+ import { motion } from 'framer-motion';
7
+ import Dropzone from 'react-dropzone';
8
+ import { confirmAlert } from 'react-confirm-alert';
9
+ import { CircleChevronRight, File, TrashCan } from 'akar-icons';
10
+
11
+ import 'react-confirm-alert/src/react-confirm-alert.css';
12
+
13
+ import CustomFetch from '../Fetch';
14
+ import Form from '../Form';
15
+ import Table from '../DataGrid';
16
+ import TableFilter from '../TableFilter';
17
+
18
+ function GenericDetail({ setting, urlParam, userProfile }) {
19
+ const routeParams = useParams();
20
+ const { filters, page, tabs } = setting;
21
+
22
+ /** Fileupload states */
23
+ const [files, setFiles] = useState([]);
24
+ const [loadingProgress, setLoadingProgress] = useState(0);
25
+
26
+ /** General States */
27
+ const [config, setConfig] = useState({});
28
+ const [data, setData] = useState({});
29
+ const [dataReload, setDataReload] = useState(0);
30
+ const [subnav, setSubnav] = useState(() => {
31
+ return filters.map((f) => {
32
+ if (
33
+ f.hasOwnProperty('url') &&
34
+ f.baseUrl &&
35
+ f.urlParam &&
36
+ routeParams[f.urlParam]
37
+ ) {
38
+ return {
39
+ ...f,
40
+ url: `${f.baseUrl}${routeParams[f.urlParam]}`,
41
+ };
42
+ } else {
43
+ return f;
44
+ }
45
+ });
46
+ });
47
+
48
+ const [total, setTotal] = useState(0);
49
+
50
+ /** General Functions */
51
+ const handleReload = () => {
52
+ const min = 0;
53
+ const max = 999999999;
54
+ const randomNumber = Math.floor(Math.random() * (max - min + 1) + min);
55
+
56
+ setDataReload(randomNumber);
57
+ };
58
+
59
+ const getValueFromData = (data, path, id) => {
60
+ let value =
61
+ path.reduce(
62
+ (result, key) =>
63
+ result
64
+ ? Array.isArray(result[key])
65
+ ? result[key][0]
66
+ : result[key]
67
+ : '',
68
+ data
69
+ ) ?? '';
70
+
71
+ if (value) {
72
+ if (Array.isArray(id)) {
73
+ return id.map((key) => value[key]).join(' ');
74
+ } else {
75
+ return value[id];
76
+ }
77
+ } else {
78
+ return '';
79
+ }
80
+ };
81
+
82
+ const formatDate = (date) => {
83
+ return date && moment(date).format('DD-MM-YYYY') !== '01-01-1970'
84
+ ? moment(date).format('DD-MM-YYYY')
85
+ : '';
86
+ };
87
+
88
+ const renderValue = (item, data) => {
89
+ const { type, id, relation, size } = item;
90
+ const value =
91
+ relation && relation.length > 0
92
+ ? getValueFromData(data, [...relation], id)
93
+ : getValueFromData(data, [], id);
94
+
95
+ const stringValue = String(value ? value : ''); // Convert value to a string
96
+
97
+ switch (type) {
98
+ case 'date':
99
+ return formatDate(value);
100
+ case 'exist':
101
+ return value ? 'Yes' : 'No';
102
+ case 'latest_notes':
103
+ const noteData = data?.[id]?.[0];
104
+ const name = noteData?.user?.name ?? '';
105
+ const description = noteData?.description ?? '';
106
+ const created_at = noteData?.created_at ?? '';
107
+ const formattedDate = formatDate(created_at);
108
+
109
+ if (name !== '' && description !== '' && formattedDate !== '') {
110
+ return parse(
111
+ `<br /><p>${name} - ${formattedDate}</p><p>${description}</p>`
112
+ );
113
+ } else {
114
+ return null;
115
+ }
116
+ default:
117
+ return size === 'full' && stringValue
118
+ ? parse(`<br /><p>${stringValue}</p>`)
119
+ : stringValue;
120
+ }
121
+ };
122
+
123
+ const renderContent = () => {
124
+ if (subnav && subnav.length > 0) {
125
+ const activeTab = subnav.find((s) => s.show === true);
126
+
127
+ if (activeTab) {
128
+ const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
129
+
130
+ if (activeTabConfig && activeTabConfig.type) {
131
+ switch (activeTabConfig.type) {
132
+ case 'dropzone':
133
+ const downloadFile = (id) => {
134
+ window.open(
135
+ `/ajax/file/download/${id}/${activeTabConfig.folder}`
136
+ );
137
+ };
138
+
139
+ const deleteFile = (e, id, name) => {
140
+ if (e) {
141
+ e.stopPropagation();
142
+
143
+ confirmAlert({
144
+ title: 'Confirm to Delete File',
145
+ message:
146
+ 'Are you sure you want to delete ' +
147
+ name,
148
+ buttons: [
149
+ {
150
+ label: 'Yes',
151
+ onClick: () => {
152
+ CustomFetch(
153
+ `${activeTabConfig.deleteUrl}/${routeParams[urlParam]}`,
154
+ 'POST',
155
+ {
156
+ file_id: id,
157
+ },
158
+ function (result) {
159
+ setFiles(result);
160
+ }
161
+ );
162
+ },
163
+ },
164
+ {
165
+ label: 'No',
166
+ onClick: () => close(),
167
+ },
168
+ ],
169
+ });
170
+ }
171
+ };
172
+
173
+ return (
174
+ <div className="gridtxt">
175
+ <div className="gridtxt__header">Files</div>
176
+ <div className="progress">
177
+ <motion.div
178
+ className="progress__bar"
179
+ initial={{ width: '0%' }}
180
+ animate={{
181
+ width: loadingProgress + '%',
182
+ }}
183
+ transition={{
184
+ duration: 2,
185
+ ease: [0.165, 0.84, 0.44, 1.0],
186
+ }}
187
+ />
188
+ </div>
189
+ <Dropzone
190
+ onDrop={(acceptedFiles) => {
191
+ const uploadFile = (file) => {
192
+ Vapor.store(file, {
193
+ progress: (progress) => {
194
+ setLoadingProgress(
195
+ progress * 100
196
+ );
197
+ },
198
+ }).then((response) => {
199
+ setLoadingProgress(0);
200
+ const requestBody = {
201
+ uuid: response.uuid,
202
+ key: response.key,
203
+ bucket: response.bucket,
204
+ name: file.name,
205
+ size: file.size,
206
+ extension:
207
+ response.extension,
208
+ };
209
+
210
+ CustomFetch(
211
+ `${activeTabConfig.url}/${routeParams[urlParam]}`,
212
+ 'PUT',
213
+ requestBody,
214
+ (res) => {
215
+ setFiles(
216
+ res.data.files
217
+ );
218
+ }
219
+ );
220
+ });
221
+ };
222
+
223
+ acceptedFiles.forEach((a) => {
224
+ uploadFile(a);
225
+ });
226
+ }}
227
+ >
228
+ {({ getRootProps, getInputProps }) => (
229
+ <section className="dropzone__container">
230
+ <div {...getRootProps()}>
231
+ <input
232
+ {...getInputProps()}
233
+ />
234
+ <p>
235
+ Drag 'n' drop some files
236
+ here, or click to select
237
+ files
238
+ </p>
239
+ </div>
240
+ <ul className="dropzone__files">
241
+ {files.map((a, b) => (
242
+ <li
243
+ onClick={() => {
244
+ downloadFile(
245
+ a.id
246
+ );
247
+ }}
248
+ key={'tf-' + b}
249
+ >
250
+ <File
251
+ strokeWidth={2}
252
+ size={18}
253
+ />
254
+ {a.file_name}
255
+ <button
256
+ onClick={(
257
+ event
258
+ ) => {
259
+ deleteFile(
260
+ event,
261
+ a.id,
262
+ a.file_name
263
+ );
264
+ }}
265
+ >
266
+ <TrashCan
267
+ strokeWidth={
268
+ 2
269
+ }
270
+ size={18}
271
+ />
272
+ </button>
273
+ </li>
274
+ ))}
275
+ </ul>
276
+ </section>
277
+ )}
278
+ </Dropzone>
279
+ </div>
280
+ );
281
+ case 'form':
282
+ return (
283
+ <div className="gridtxt">
284
+ <div className="gridtxt__header">
285
+ <span>{activeTabConfig.title}</span>
286
+ </div>
287
+ <Form
288
+ columnId={routeParams[urlParam]}
289
+ formSettings={activeTabConfig.form}
290
+ formType="update"
291
+ style={
292
+ userProfile?.settings?.style || {}
293
+ }
294
+ updateForm={setFormData}
295
+ type="inline"
296
+ />
297
+ </div>
298
+ );
299
+ case 'overview':
300
+ return (
301
+ <>
302
+ <div className="gridactions">
303
+ <ul>
304
+ <li>
305
+ <button
306
+ className="btn"
307
+ onClick={() =>
308
+ modalOpen(
309
+ 'update',
310
+ routeParams[
311
+ urlParam
312
+ ]
313
+ )
314
+ }
315
+ >
316
+ Edit
317
+ </button>
318
+ </li>
319
+ </ul>
320
+ </div>
321
+ <div className="gridtxt">
322
+ {activeTabConfig.sections.map(
323
+ (section, sectionIndex) => (
324
+ <React.Fragment
325
+ key={`section-${activeTabConfig.id}-${sectionIndex}`}
326
+ >
327
+ <div className="gridtxt__header">
328
+ <span>
329
+ {section.title}
330
+ </span>
331
+ </div>
332
+ {section.content &&
333
+ section.content.length >
334
+ 0 && (
335
+ <ul className="customer__overview">
336
+ {section.content.map(
337
+ (
338
+ item,
339
+ itemKey
340
+ ) => (
341
+ <li
342
+ key={`li-${item.id}-${itemKey}`}
343
+ className={
344
+ item.size ===
345
+ 'full'
346
+ ? 'fw-grid-item notecolor'
347
+ : null
348
+ }
349
+ >
350
+ <strong>
351
+ {
352
+ item.label
353
+ }
354
+
355
+ :
356
+ </strong>{' '}
357
+ {renderValue(
358
+ item,
359
+ data
360
+ )}
361
+ </li>
362
+ )
363
+ )}
364
+ </ul>
365
+ )}
366
+ </React.Fragment>
367
+ )
368
+ )}
369
+ </div>
370
+ </>
371
+ );
372
+ case 'table':
373
+ if (
374
+ config.ajaxSetting &&
375
+ config.form &&
376
+ config.columns &&
377
+ config.settings
378
+ ) {
379
+ return (
380
+ <Table
381
+ {...config}
382
+ setConfig={setConfig}
383
+ setTotal={setTotal}
384
+ style={
385
+ userProfile?.settings?.style || {}
386
+ }
387
+ />
388
+ );
389
+ } else {
390
+ return null;
391
+ }
392
+ default:
393
+ return null;
394
+ }
395
+ }
396
+ }
397
+ }
398
+ };
399
+
400
+ /** General Hooks */
401
+ useEffect(() => {
402
+ if (tabs && tabs.length > 0) {
403
+ const activeTab = subnav.find((s) => s.show === true);
404
+
405
+ if (activeTab) {
406
+ const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
407
+
408
+ const applyUrlParamsToData = (data) => {
409
+ return data.map((item) => {
410
+ return {
411
+ ...item,
412
+ value:
413
+ item.hasOwnProperty('value') &&
414
+ item.urlParam &&
415
+ routeParams[item.urlParam]
416
+ ? routeParams[item.urlParam]
417
+ : item.value,
418
+ };
419
+ });
420
+ };
421
+
422
+ if (activeTabConfig) {
423
+ switch (activeTabConfig.type) {
424
+ case 'table':
425
+ if (
426
+ activeTabConfig.ajaxSetting &&
427
+ activeTabConfig.ajaxSetting.where &&
428
+ activeTabConfig.ajaxSetting.where.length > 0
429
+ ) {
430
+ const updatedAjaxSetting = {
431
+ ...activeTabConfig.ajaxSetting,
432
+ where: applyUrlParamsToData(
433
+ activeTabConfig.ajaxSetting.where
434
+ ),
435
+ };
436
+ const updatedForm = {
437
+ ...activeTabConfig.form,
438
+ fields: applyUrlParamsToData(
439
+ activeTabConfig.form.fields
440
+ ),
441
+ };
442
+ setConfig({
443
+ ajaxSetting: updatedAjaxSetting,
444
+ form: updatedForm,
445
+ columns: activeTabConfig.columns,
446
+ settings: activeTabConfig.settings,
447
+ });
448
+ }
449
+ break;
450
+ case 'overview':
451
+ setConfig({});
452
+ if (activeTabConfig.form) {
453
+ setFormData(activeTabConfig.form);
454
+ }
455
+ break;
456
+ default:
457
+ setConfig({});
458
+ break;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ }, [subnav, tabs, routeParams]);
464
+
465
+ useEffect(() => {
466
+ if (routeParams[urlParam] && routeParams[urlParam] > 0) {
467
+ CustomFetch(
468
+ `${page.fetchUrl}/${routeParams[urlParam]}`,
469
+ 'GET',
470
+ {},
471
+ (res) => {
472
+ setData(res);
473
+
474
+ tabs.forEach((tab) => {
475
+ if (tab.type && tab.type === 'dropzone') {
476
+ if (res.files) {
477
+ setFiles(res.files);
478
+ }
479
+ }
480
+ });
481
+ }
482
+ );
483
+ }
484
+ }, [routeParams, dataReload]);
485
+
486
+ /** Modal States */
487
+ const [formData, setFormData] = useState({});
488
+ const [formType, setFormType] = useState('');
489
+ const [formId, setFormId] = useState(0);
490
+ const [modalShow, setModalShow] = useState(false);
491
+
492
+ /** Modal Functions */
493
+ const modalOpen = (formType, formId) => {
494
+ setModalShow(true);
495
+ setFormType(formType);
496
+ setFormId(formId);
497
+ };
498
+
499
+ const modalClose = () => {
500
+ setModalShow(false);
501
+ };
502
+
503
+ return (
504
+ <>
505
+ <div className="grid">
506
+ <div className="grid__row">
507
+ <div className="grid__full crmtitle">
508
+ <h1>
509
+ <Link
510
+ to={
511
+ page && page.previousUrl
512
+ ? page.previousUrl
513
+ : null
514
+ }
515
+ >
516
+ {page && page.title ? page.title : null}
517
+ </Link>{' '}
518
+ <CircleChevronRight strokeWidth={2} size={18} />{' '}
519
+ {data && data[page.titleKey]
520
+ ? data[page.titleKey]
521
+ : null}
522
+ </h1>
523
+ {total > 0 && (
524
+ <div className="titleInfo">
525
+ <span>
526
+ [<strong>{total}</strong> Total]
527
+ </span>
528
+ </div>
529
+ )}
530
+ </div>
531
+ </div>
532
+ </div>
533
+ <div className="grid">
534
+ <div className="grid__subrow">
535
+ <div className="grid__subnav">
536
+ <TableFilter filters={subnav} setFilters={setSubnav} />
537
+ </div>
538
+ <div className="grid__subcontent">
539
+ {config ? renderContent() : null}
540
+ </div>
541
+ </div>
542
+ </div>
543
+
544
+ <Popup
545
+ open={modalShow}
546
+ onClose={modalClose}
547
+ closeOnDocumentClick={false}
548
+ >
549
+ <Form
550
+ closeModal={modalClose}
551
+ columnId={formId}
552
+ fetchTable={handleReload}
553
+ formSettings={formData}
554
+ formType={formType}
555
+ style={userProfile?.settings?.style || {}}
556
+ updateForm={setFormData}
557
+ />
558
+ </Popup>
559
+ </>
560
+ );
561
+ }
562
+
563
+ export default GenericDetail;
@@ -0,0 +1,112 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Outlet } from 'react-router-dom';
3
+ import Table from '../DataGrid';
4
+ import TableFilter from '../TableFilter';
5
+
6
+ function GenericIndex({ setting, userProfile }) {
7
+ const { filters, page, tabs, ajaxSetting, form, columns, settings } =
8
+ setting;
9
+
10
+ const [config, setConfig] = useState({});
11
+ const [subnav, setSubnav] = useState(filters || []);
12
+ const [total, setTotal] = useState(0);
13
+ const pageTitle = page?.title;
14
+
15
+ useEffect(() => {
16
+ if (!tabs || tabs.length === 0) {
17
+ setConfig(() => ({
18
+ ajaxSetting,
19
+ form,
20
+ columns,
21
+ settings,
22
+ }));
23
+ }
24
+
25
+ setSubnav(filters);
26
+ }, [setting]);
27
+
28
+ useEffect(() => {
29
+ if (tabs && tabs.length > 0) {
30
+ const activeTab = subnav.find((s) => s.show === true);
31
+
32
+ if (activeTab) {
33
+ const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
34
+
35
+ if (activeTabConfig) {
36
+ setConfig(() => ({
37
+ ajaxSetting: activeTabConfig.ajaxSetting,
38
+ form: activeTabConfig.form,
39
+ columns: activeTabConfig.columns,
40
+ settings: activeTabConfig.settings,
41
+ }));
42
+ }
43
+ }
44
+ }
45
+ }, [subnav, tabs]);
46
+
47
+ return (
48
+ <>
49
+ <div className="grid">
50
+ <div className="grid__row">
51
+ <div className="grid__full crmtitle">
52
+ <h1>{pageTitle}</h1>
53
+ <div className="titleInfo">
54
+ <span>
55
+ [<strong>{total}</strong> Total {pageTitle}]
56
+ </span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ {config.ajaxSetting &&
63
+ config.form &&
64
+ config.columns &&
65
+ config.settings && (
66
+ <>
67
+ <div className="grid">
68
+ <div className="grid__row">
69
+ {subnav && subnav.length > 0 ? (
70
+ <>
71
+ <div className="grid__subnav">
72
+ <TableFilter
73
+ filters={subnav}
74
+ setFilters={setSubnav}
75
+ setSettings={setConfig}
76
+ />
77
+ </div>
78
+ <div className="grid__subcontent">
79
+ <Table
80
+ {...config}
81
+ setConfig={setConfig}
82
+ setTotal={setTotal}
83
+ style={
84
+ userProfile?.settings
85
+ ?.style || {}
86
+ }
87
+ />
88
+ </div>
89
+ </>
90
+ ) : (
91
+ <div className="grid__full">
92
+ <Table
93
+ {...config}
94
+ setConfig={setConfig}
95
+ setTotal={setTotal}
96
+ style={
97
+ userProfile?.settings?.style ||
98
+ {}
99
+ }
100
+ />
101
+ </div>
102
+ )}
103
+ </div>
104
+ </div>
105
+ <Outlet />
106
+ </>
107
+ )}
108
+ </>
109
+ );
110
+ }
111
+
112
+ export default GenericIndex;