@visns-studio/visns-components 2.5.17 → 2.6.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.
@@ -1,16 +1,15 @@
1
- import React, { useState, useCallback } from "react";
2
- import { motion } from "framer-motion";
3
- import { useDropzone } from "react-dropzone";
4
- import { confirmAlert } from "react-confirm-alert";
5
- import { File, TrashCan } from "akar-icons";
6
- import { toast } from "react-toastify";
1
+ import React, { useState, useCallback } from 'react';
2
+ import { calcLength, motion } from 'framer-motion';
3
+ import { useDropzone } from 'react-dropzone';
4
+ import { confirmAlert } from 'react-confirm-alert';
5
+ import { File, TrashCan } from 'akar-icons';
6
+ import { toast } from 'react-toastify';
7
7
 
8
- import "react-confirm-alert/src/react-confirm-alert.css";
8
+ import 'react-confirm-alert/src/react-confirm-alert.css';
9
9
 
10
- import CustomFetch from "../crm/Fetch";
10
+ import CustomFetch from '../crm/Fetch';
11
11
 
12
- function VisnsDropZone(props) {
13
- const { fetchData, files, settings } = props;
12
+ function VisnsDropZone({ fetchData, files, settings }) {
14
13
  const [loadingProgress, setLoadingProgress] = useState(0);
15
14
 
16
15
  const onDrop = useCallback((acceptedFiles) => {
@@ -34,12 +33,14 @@ function VisnsDropZone(props) {
34
33
  ...settings.data,
35
34
  },
36
35
  function (result) {
37
- if (result.error === "") {
38
- fetchData();
36
+ if (result.error === '') {
37
+ if (fetchData) {
38
+ fetchData();
39
39
 
40
- toast.success(
41
- "File has been successfully uploaded."
42
- );
40
+ toast.success(
41
+ 'File has been successfully uploaded.'
42
+ );
43
+ }
43
44
  } else {
44
45
  toast.error(String(result.error));
45
46
  }
@@ -54,10 +55,10 @@ function VisnsDropZone(props) {
54
55
  });
55
56
 
56
57
  const handleDownload = (id) => {
57
- if (id && id > 0) {
58
+ if (id && id > 0 && settings.model && settings.model !== '') {
58
59
  window.open(`/ajax/file/download/${id}/${settings.model}`);
59
60
  } else {
60
- toast.warning("Issue with downloading the selected file.");
61
+ toast.warning('Issue with downloading the selected file.');
61
62
  }
62
63
  };
63
64
 
@@ -68,46 +69,71 @@ function VisnsDropZone(props) {
68
69
 
69
70
  if (id && id > 0) {
70
71
  confirmAlert({
71
- title: "Confirm to Delete File",
72
- message: "Are you sure you want to delete " + name,
72
+ title: 'Confirm to Delete File',
73
+ message: 'Are you sure you want to delete ' + name,
73
74
  buttons: [
74
75
  {
75
- label: "Yes",
76
+ label: 'Yes',
76
77
  onClick: () => {
77
78
  CustomFetch(
78
79
  `/ajax/file/delete`,
79
- "POST",
80
+ 'POST',
80
81
  {
81
82
  file_id: id,
82
83
  },
83
84
  function (result) {
84
- if (result.error === "") {
85
- fetchData();
85
+ if (result.error === '') {
86
+ if (fetchData) {
87
+ fetchData();
88
+ }
86
89
  }
87
90
  }
88
91
  );
89
92
  },
90
93
  },
91
94
  {
92
- label: "No",
95
+ label: 'No',
93
96
  onClick: () => close(),
94
97
  },
95
98
  ],
96
99
  });
97
100
  } else {
98
- toast.error("Issue with deleting the selected file.");
101
+ toast.error('Issue with deleting the selected file.');
99
102
  }
100
103
  }
101
104
  };
102
105
 
103
106
  return (
104
107
  <>
108
+ {settings.type === 'gallery' ? (
109
+ <div class="image-container">
110
+ {files && files.length > 0
111
+ ? files.map((a) => (
112
+ <div class="image-with-close">
113
+ <img src={a.file_url} />
114
+ <span
115
+ class="close-icon"
116
+ onClick={(event) => {
117
+ handleDelete(
118
+ event,
119
+ a.id,
120
+ a.file_name
121
+ );
122
+ }}
123
+ >
124
+ ×
125
+ </span>
126
+ </div>
127
+ ))
128
+ : null}
129
+ </div>
130
+ ) : null}
105
131
  <div className="progress">
106
132
  <motion.div
107
133
  className="progress__bar"
108
- initial={{ width: "0%" }}
134
+ initial={{ width: '0%' }}
109
135
  animate={{
110
- width: loadingProgress + "%",
136
+ width: loadingProgress + '%',
111
137
  }}
112
138
  transition={{
113
139
  duration: 1,
@@ -127,32 +153,34 @@ function VisnsDropZone(props) {
127
153
  </p>
128
154
  )}
129
155
  </div>
130
- <ul className="dropzone__files">
131
- {files && files.length > 0
132
- ? files.map((a, b) => (
133
- <li
134
- onClick={() => {
135
- handleDownload(a.id);
136
- }}
137
- key={"tf-" + b}
138
- >
139
- <File strokeWidth={2} size={18} />
140
- {a.file_name}
141
- <button
142
- onClick={(event) => {
143
- handleDelete(
144
- event,
145
- a.id,
146
- a.file_name
147
- );
156
+ {settings.type !== 'gallery' ? (
157
+ <ul className="dropzone__files">
158
+ {files && files.length > 0
159
+ ? files.map((a, b) => (
160
+ <li
161
+ onClick={() => {
162
+ handleDownload(a.id);
148
163
  }}
164
+ key={'tf-' + b}
149
165
  >
150
- <TrashCan strokeWidth={2} size={18} />
151
- </button>
152
- </li>
153
- ))
154
- : null}
155
- </ul>
166
+ <File strokeWidth={2} size={18} />
167
+ {a.file_name}
168
+ <button
169
+ onClick={(event) => {
170
+ handleDelete(
171
+ event,
172
+ a.id,
173
+ a.file_name
174
+ );
175
+ }}
176
+ >
177
+ <TrashCan strokeWidth={2} size={18} />
178
+ </button>
179
+ </li>
180
+ ))
181
+ : null}
182
+ </ul>
183
+ ) : null}
156
184
  </section>
157
185
  </>
158
186
  );
@@ -17,6 +17,7 @@ import CustomFetch from '../crm/Fetch';
17
17
  import Select from '../crm/Select';
18
18
  import MultiSelect from '../crm/MultiSelect';
19
19
  import VisnsAutocomplete from '../crm/Autocomplete';
20
+ import VisnsDropZone from './DropZone';
20
21
  import moment from 'moment';
21
22
 
22
23
  Quill.register('modules/imageUploader', ImageUploader);
@@ -60,7 +61,8 @@ const modules = {
60
61
  };
61
62
 
62
63
  const Field = ({
63
- settings,
64
+ baseData,
65
+ fetchData,
64
66
  inputValue,
65
67
  inputClass,
66
68
  onChange,
@@ -70,6 +72,7 @@ const Field = ({
70
72
  onChangeSelect,
71
73
  onChangeToggle,
72
74
  setFormData,
75
+ settings,
73
76
  uploadProgress,
74
77
  }) => {
75
78
  const inputFile = useRef(null);
@@ -238,9 +241,9 @@ const Field = ({
238
241
  <div className={formItemClass} style={formItemStyle}>
239
242
  <label
240
243
  className={
241
- settings.type === 'image'
242
- ? 'fi__flabel'
243
- : settings.type === 'richeditor'
244
+ settings.type === 'image' ||
245
+ settings.type === 'richeditor' ||
246
+ settings.type === 'gallery'
244
247
  ? 'fi__flabel'
245
248
  : 'fi__label'
246
249
  }
@@ -266,7 +269,8 @@ const Field = ({
266
269
  {(() => {
267
270
  if (
268
271
  settings.type === 'image' ||
269
- settings.type === 'richeditor'
272
+ settings.type === 'richeditor' ||
273
+ settings.type === 'gallery'
270
274
  ) {
271
275
  return (
272
276
  <>
@@ -542,7 +546,8 @@ const Field = ({
542
546
  settings.type !== 'multi-dropdown-ajax' &&
543
547
  settings.type !== 'toggle' &&
544
548
  settings.type !== 'richeditor' &&
545
- settings.type !== 'image'
549
+ settings.type !== 'image' &&
550
+ settings.type !== 'gallery'
546
551
  ) {
547
552
  return (
548
553
  <span className="fi__span">
@@ -739,6 +744,28 @@ const Field = ({
739
744
  </>
740
745
  );
741
746
  break;
747
+ case 'gallery':
748
+ if (
749
+ baseData[settings.key] &&
750
+ baseData[settings.key] > 0
751
+ ) {
752
+ const dropzoneUrl = `${settings.url}/${
753
+ baseData[settings.key]
754
+ }`;
755
+
756
+ htmlContainer = (
757
+ <VisnsDropZone
758
+ fetchData={fetchData}
759
+ files={baseData[settings.id]}
760
+ settings={{
761
+ url: dropzoneUrl,
762
+ method: settings.method,
763
+ type: 'gallery',
764
+ }}
765
+ />
766
+ );
767
+ }
768
+ break;
742
769
  default:
743
770
  htmlContainer = '';
744
771
  break;
@@ -392,7 +392,6 @@ function Form(props) {
392
392
  <div className="dynamicform">
393
393
  {form.fields.map((a, b) => (
394
394
  <Field
395
- settings={a}
396
395
  key={
397
396
  b +
398
397
  '-fields-' +
@@ -400,6 +399,9 @@ function Form(props) {
400
399
  ? a.id + '-' + a.key
401
400
  : a.id)
402
401
  }
402
+ baseData={data}
403
+ fetchData={fetchData}
404
+ inputClass={dataClass}
403
405
  inputValue={
404
406
  a.hasOwnProperty('key')
405
407
  ? data[a.key]
@@ -410,7 +412,6 @@ function Form(props) {
410
412
  : ''
411
413
  : data[a.id]
412
414
  }
413
- inputClass={dataClass}
414
415
  onChange={handleChange}
415
416
  onChangeDate={handleChangeDate}
416
417
  onChangeFile={handleChangeFile}
@@ -418,6 +419,7 @@ function Form(props) {
418
419
  onChangeSelect={handleChangeSelect}
419
420
  onChangeToggle={handleChangeToggle}
420
421
  setFormData={setData}
422
+ settings={a}
421
423
  uploadProgress={uploadProgress}
422
424
  />
423
425
  ))}
@@ -5,7 +5,9 @@ import ReactQuill from 'react-quill';
5
5
  import Toggle from 'react-toggle';
6
6
  import moment from 'moment';
7
7
  import { BlockPicker } from 'react-color';
8
+ import { CopyToClipboard } from 'react-copy-to-clipboard';
8
9
  import { NumericFormat, PatternFormat } from 'react-number-format';
10
+ import { Copy } from 'akar-icons';
9
11
 
10
12
  import CustomFetch from './Fetch';
11
13
  import Select from './Select';
@@ -206,19 +208,20 @@ function Field({
206
208
  if (
207
209
  [
208
210
  'address',
211
+ 'async-dropdown-ajax',
212
+ 'checkbox',
213
+ 'colour',
214
+ 'copytoclipboard',
209
215
  'date',
210
216
  'datetime',
211
- 'checkbox',
212
217
  'multi-dropdown',
213
218
  'multi-dropdown-ajax',
214
- 'async-dropdown-ajax',
215
- 'richeditor',
216
- 'colour',
217
219
  'plaindate',
218
220
  'plaindatetime',
219
221
  'plainrelation',
220
222
  'plainrelationarray',
221
223
  'plaintext',
224
+ 'richeditor',
222
225
  ].includes(settings.type)
223
226
  ) {
224
227
  return (
@@ -284,6 +287,37 @@ function Field({
284
287
  }
285
288
  />
286
289
  );
290
+ case 'copytoclipboard':
291
+ return (
292
+ <>
293
+ <input
294
+ type={settings.type}
295
+ data-name={settings.id}
296
+ className={inputClass[settings.id]}
297
+ value={
298
+ inputValue && inputValue !== 'null'
299
+ ? inputValue
300
+ : ''
301
+ }
302
+ readOnly
303
+ />
304
+ <CopyToClipboard
305
+ text={
306
+ inputValue && inputValue !== 'null'
307
+ ? inputValue
308
+ : ''
309
+ }
310
+ onCopy={() => console.info('copied')}
311
+ >
312
+ <Copy
313
+ data-tooltip-id="system-tooltip"
314
+ data-tooltip-content="Copy to Clipboard"
315
+ strokeWidth={2}
316
+ size={24}
317
+ />
318
+ </CopyToClipboard>
319
+ </>
320
+ );
287
321
  case 'plaindate':
288
322
  return (
289
323
  <div
@@ -611,21 +645,22 @@ function Field({
611
645
  if (
612
646
  ![
613
647
  'address',
648
+ 'async-dropdown-ajax',
649
+ 'checkbox',
650
+ 'colour',
651
+ 'copytoclipboard',
614
652
  'date',
615
653
  'datetime',
616
654
  'line-break',
617
- 'checkbox',
618
655
  'multi-dropdown',
619
656
  'multi-dropdown-ajax',
620
- 'async-dropdown-ajax',
621
- 'toggle',
622
- 'richeditor',
623
- 'colour',
624
657
  'plaindate',
625
658
  'plaindatetime',
626
659
  'plainrelation',
627
660
  'plainrelationarray',
628
661
  'plaintext',
662
+ 'richeditor',
663
+ 'toggle',
629
664
  ].includes(settings.type)
630
665
  ) {
631
666
  return (
@@ -6,7 +6,15 @@ import Popup from 'reactjs-popup';
6
6
  import { motion } from 'framer-motion';
7
7
  import Dropzone from 'react-dropzone';
8
8
  import { confirmAlert } from 'react-confirm-alert';
9
- import { CircleCheck, CircleChevronRight, File, TrashCan } from 'akar-icons';
9
+ import truncate from 'truncate';
10
+ import { CopyToClipboard } from 'react-copy-to-clipboard';
11
+ import {
12
+ CircleCheck,
13
+ CircleChevronRight,
14
+ Copy,
15
+ File,
16
+ TrashCan,
17
+ } from 'akar-icons';
10
18
 
11
19
  import 'react-confirm-alert/src/react-confirm-alert.css';
12
20
 
@@ -100,14 +108,18 @@ function GenericDetail({ setting, urlParam, userProfile }) {
100
108
  };
101
109
 
102
110
  const renderValue = (item, data) => {
103
- const { type, id, relation, relationKey, size } = item;
111
+ const { type, id, relation, relationKey, size, truncateLength } = item;
104
112
  const value =
105
113
  relation && relation.length > 0
106
114
  ? getValueFromData(data, [...relation], id)
107
115
  : getValueFromData(data, [], id);
108
-
109
116
  const stringValue = String(value ?? ''); // Convert value to a string
110
117
 
118
+ // Conditionally truncate the value if the 'truncate' key is present
119
+ const truncatedValue = truncateLength
120
+ ? truncate(stringValue, truncateLength)
121
+ : stringValue;
122
+
111
123
  const formatDateValue = (date) => formatDate(date);
112
124
 
113
125
  const formatNoteValue = () => {
@@ -133,37 +145,84 @@ function GenericDetail({ setting, urlParam, userProfile }) {
133
145
  return `${total} / ${value}`;
134
146
  };
135
147
 
148
+ const renderCopyToClipboard = () => {
149
+ return (
150
+ <>
151
+ {size === 'full' && truncatedValue
152
+ ? parse(`<br /><p>${truncatedValue}</p>`)
153
+ : truncatedValue}
154
+ <CopyToClipboard
155
+ text={
156
+ truncatedValue && truncatedValue !== 'null'
157
+ ? truncatedValue
158
+ : ''
159
+ }
160
+ onCopy={() => console.info('copied')}
161
+ >
162
+ <Copy
163
+ data-tooltip-id="system-tooltip"
164
+ data-tooltip-content="Copy to Clipboard"
165
+ strokeWidth={2}
166
+ size={24}
167
+ />
168
+ </CopyToClipboard>
169
+ </>
170
+ );
171
+ };
172
+
173
+ const renderDate = () => formatDateValue(truncatedValue);
174
+
175
+ const renderExist = () => (truncatedValue ? 'Yes' : 'No');
176
+
177
+ const renderJson = () =>
178
+ truncatedValue ? (
179
+ <pre>{JSON.stringify(JSON.parse(truncatedValue), null, 4)}</pre>
180
+ ) : null;
181
+
182
+ const renderLatestNotes = () => formatNoteValue();
183
+
184
+ const renderRichText = () => {
185
+ if (!truncatedValue) {
186
+ return null;
187
+ }
188
+
189
+ const lines = truncatedValue.split('\n');
190
+
191
+ if (lines.length === 0) {
192
+ return null;
193
+ }
194
+
195
+ return (
196
+ <>
197
+ {lines.map((line, index) => (
198
+ <p key={index}>{line}</p>
199
+ ))}
200
+ </>
201
+ );
202
+ };
203
+
204
+ const renderTotal = () => formatTotalValue();
205
+
206
+ // Render based on the item's type
136
207
  switch (type) {
208
+ case 'copyToClipboard':
209
+ return renderCopyToClipboard();
137
210
  case 'date':
138
- return formatDateValue(value);
211
+ return renderDate();
139
212
  case 'exist':
140
- return value ? 'Yes' : 'No';
213
+ return renderExist();
214
+ case 'json':
215
+ return renderJson();
141
216
  case 'latest_notes':
142
- return formatNoteValue();
217
+ return renderLatestNotes();
143
218
  case 'richtext':
144
- if (!value) {
145
- return null;
146
- }
147
-
148
- const lines = value.split('\n');
149
-
150
- if (lines.length === 0) {
151
- return null;
152
- }
153
-
154
- return (
155
- <>
156
- {lines.map((line, index) => (
157
- <p key={index}>{line}</p>
158
- ))}
159
- </>
160
- );
219
+ return renderRichText();
161
220
  case 'total':
162
- return formatTotalValue();
221
+ return renderTotal();
163
222
  default:
164
- return size === 'full' && stringValue
165
- ? parse(`<br /><p>${stringValue}</p>`)
166
- : stringValue;
223
+ return size === 'full' && truncatedValue
224
+ ? parse(`<br /><p>${truncatedValue}</p>`)
225
+ : truncatedValue;
167
226
  }
168
227
  };
169
228
 
@@ -336,44 +395,50 @@ function GenericDetail({ setting, urlParam, userProfile }) {
336
395
  formConfig,
337
396
  routeParams
338
397
  ) => {
339
- formConfig.stages.data.forEach((stage) => {
340
- const stageFormFields =
341
- stage.form?.fields || [];
342
- const stageAjaxWhereFields = [];
398
+ if (formConfig.stages) {
399
+ formConfig.stages.data.forEach((stage) => {
400
+ const stageFormFields =
401
+ stage.form?.fields || [];
402
+ const stageAjaxWhereFields = [];
403
+
404
+ stageFormFields.forEach((field) => {
405
+ if (field.type === 'table') {
406
+ if (
407
+ field.ajaxSetting &&
408
+ field.ajaxSetting.where
409
+ ) {
410
+ stageAjaxWhereFields.push(
411
+ ...field.ajaxSetting
412
+ .where
413
+ );
414
+ }
343
415
 
344
- stageFormFields.forEach((field) => {
345
- if (field.type === 'table') {
346
- if (
347
- field.ajaxSetting &&
348
- field.ajaxSetting.where
349
- ) {
350
- stageAjaxWhereFields.push(
351
- ...field.ajaxSetting.where
416
+ const tableFormFields =
417
+ field.form?.fields || [];
418
+ tableFormFields.forEach(
419
+ (formField) => {
420
+ updateValueFromParam(
421
+ formField,
422
+ routeParams[
423
+ formField
424
+ .urlParam
425
+ ]
426
+ );
427
+ }
352
428
  );
353
429
  }
430
+ });
354
431
 
355
- const tableFormFields =
356
- field.form?.fields || [];
357
- tableFormFields.forEach(
358
- (formField) => {
359
- updateValueFromParam(
360
- formField,
361
- routeParams[
362
- formField.urlParam
363
- ]
364
- );
365
- }
366
- );
367
- }
368
- });
369
-
370
- stageAjaxWhereFields.forEach((where) => {
371
- updateValueFromParam(
372
- where,
373
- routeParams[where.urlParam]
432
+ stageAjaxWhereFields.forEach(
433
+ (where) => {
434
+ updateValueFromParam(
435
+ where,
436
+ routeParams[where.urlParam]
437
+ );
438
+ }
374
439
  );
375
440
  });
376
- });
441
+ }
377
442
  };
378
443
 
379
444
  const formConfig = activeTabConfig;
@@ -468,25 +533,27 @@ function GenericDetail({ setting, urlParam, userProfile }) {
468
533
  case 'overview':
469
534
  return (
470
535
  <>
471
- <div className="gridactions">
472
- <ul>
473
- <li>
474
- <button
475
- className="btn"
476
- onClick={() =>
477
- modalOpen(
478
- 'update',
479
- routeParams[
480
- urlParam
481
- ]
482
- )
483
- }
484
- >
485
- Edit
486
- </button>
487
- </li>
488
- </ul>
489
- </div>
536
+ {activeTabConfig.form && (
537
+ <div className="gridactions">
538
+ <ul>
539
+ <li>
540
+ <button
541
+ className="btn"
542
+ onClick={() =>
543
+ modalOpen(
544
+ 'update',
545
+ routeParams[
546
+ urlParam
547
+ ]
548
+ )
549
+ }
550
+ >
551
+ Edit
552
+ </button>
553
+ </li>
554
+ </ul>
555
+ </div>
556
+ )}
490
557
 
491
558
  <div className="gridtxt">
492
559
  {activeTabConfig.sections.map(
package/package.json CHANGED
@@ -2,12 +2,12 @@
2
2
  "dependencies": {
3
3
  "@inovua/reactdatagrid-community": "^5.10.2",
4
4
  "add": "^2.0.6",
5
- "akar-icons": "^1.9.28",
5
+ "akar-icons": "^1.9.29",
6
6
  "array-move": "^4.0.0",
7
7
  "awesome-debounce-promise": "^2.1.0",
8
8
  "axios": "^1.5.0",
9
- "framer-motion": "^10.16.1",
10
- "html-react-parser": "^4.2.1",
9
+ "framer-motion": "^10.16.4",
10
+ "html-react-parser": "^4.2.2",
11
11
  "lodash": "^4.17.21",
12
12
  "moment": "^2.29.4",
13
13
  "motion": "^10.16.2",
@@ -15,9 +15,10 @@
15
15
  "quill-image-uploader": "^1.3.0",
16
16
  "react-color": "^2.19.3",
17
17
  "react-confirm-alert": "^3.0.6",
18
+ "react-copy-to-clipboard": "^5.1.0",
18
19
  "react-datepicker": "^4.16.0",
19
20
  "react-dropzone": "^14.2.3",
20
- "react-number-format": "^5.3.0",
21
+ "react-number-format": "^5.3.1",
21
22
  "react-password-strength-bar": "^0.4.1",
22
23
  "react-promise-tracker": "^2.1.1",
23
24
  "react-quill": "^2.0.0",
@@ -27,9 +28,10 @@
27
28
  "react-sortable-hoc": "^2.0.0",
28
29
  "react-toastify": "^9.1.3",
29
30
  "react-toggle": "^4.1.3",
30
- "react-tooltip": "^5.21.1",
31
+ "react-tooltip": "^5.21.3",
31
32
  "react-window": "^1.8.9",
32
- "reactjs-popup": "^2.0.5",
33
+ "reactjs-popup": "^2.0.6",
34
+ "truncate": "^3.0.0",
33
35
  "validator": "^13.11.0",
34
36
  "yarn": "^1.22.19"
35
37
  },
@@ -42,7 +44,7 @@
42
44
  "react-dom": "^17.0.1"
43
45
  },
44
46
  "name": "@visns-studio/visns-components",
45
- "version": "2.5.17",
47
+ "version": "2.6.1",
46
48
  "description": "Various packages to assist in the development of our Custom Applications.",
47
49
  "main": "index.js",
48
50
  "scripts": {