@visns-studio/visns-components 3.4.12 → 3.5.0

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.
@@ -25,8 +25,6 @@ const SortableItem = ({
25
25
  const renderImage = (d) => {
26
26
  let htmlContainer = '';
27
27
 
28
- console.info(showImage, 'aaa');
29
-
30
28
  if (showImage) {
31
29
  const imageUrl = d.image_url || d.file_url;
32
30
  htmlContainer = imageUrl ? (
@@ -0,0 +1,531 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import moment from 'moment';
3
+ import 'react-confirm-alert/src/react-confirm-alert.css';
4
+ import { toast } from 'react-toastify';
5
+ import { confirmAlert } from 'react-confirm-alert';
6
+ import { saveAs } from 'file-saver';
7
+ import parse from 'html-react-parser';
8
+ import { Backspace, CloudDownload } from 'akar-icons';
9
+
10
+ import CustomFetch from '../Fetch';
11
+ import Download from '../Download';
12
+ import Field from '../Field';
13
+
14
+ const updateField = (fields, name, value) => {
15
+ return Object.entries(fields).reduce((updatedFields, [key, field]) => {
16
+ // Check if the current field is the one to be updated
17
+ if (field.id === name) {
18
+ // Update the value of the field
19
+ updatedFields[key] = { ...field, value };
20
+ } else {
21
+ // Keep the field as is
22
+ updatedFields[key] = field;
23
+ }
24
+ return updatedFields;
25
+ }, {});
26
+ };
27
+
28
+ const renderValue = (field) => {
29
+ switch (field.type) {
30
+ case 'datetime':
31
+ case 'date':
32
+ case 'time':
33
+ return field.value && moment(field.value).isValid()
34
+ ? moment(field.value).toDate()
35
+ : '';
36
+ default:
37
+ return field.value || '';
38
+ }
39
+ };
40
+
41
+ const GenericDynamic = ({ data, fetchData, label, setData, type }) => {
42
+ const toastId = useRef(null);
43
+ const [activeForm, setActiveForm] = useState([]);
44
+ const [activeFormKey, setActiveFormKey] = useState(0);
45
+
46
+ const handleChange = (e) => {
47
+ const {
48
+ checked,
49
+ dataset: { name },
50
+ files,
51
+ value,
52
+ } = e.target;
53
+
54
+ let newValue;
55
+
56
+ if (checked) {
57
+ newValue = checked;
58
+
59
+ setActiveForm((prevState) =>
60
+ updateField(prevState, name, newValue)
61
+ );
62
+ } else if (files && files[0]) {
63
+ Vapor.store(files[0], {
64
+ progress: (progress) => {
65
+ if (
66
+ toastId.current === null ||
67
+ toast.isActive(toastId.current) === false
68
+ ) {
69
+ toastId.current = toast(`File Upload in Progress`, {
70
+ progress,
71
+ });
72
+ } else {
73
+ toast.update(toastId.current, { progress });
74
+ }
75
+ },
76
+ }).then((response) => {
77
+ setActiveForm((prevState) =>
78
+ updateField(prevState, name, {
79
+ extension: response.extension,
80
+ key: response.key,
81
+ url: response.url,
82
+ uuid: response.uuid,
83
+ bucket: response.bucket,
84
+ filename: files[0].name,
85
+ filesize: files[0].size,
86
+ })
87
+ );
88
+ toast.dismiss(toastId.current);
89
+ });
90
+ } else {
91
+ newValue = value;
92
+
93
+ setActiveForm((prevState) =>
94
+ updateField(prevState, name, newValue)
95
+ );
96
+ }
97
+ };
98
+
99
+ const handleChangeCheckbox = (e) => {
100
+ const { checked, name, value } = e.target;
101
+
102
+ let tmpValue;
103
+
104
+ Object.entries(activeForm).forEach(([key, field]) => {
105
+ if (field.id && field.id === name) {
106
+ if (
107
+ field.value === null ||
108
+ field.value === '' ||
109
+ field.value.length === 0
110
+ ) {
111
+ tmpValue = [];
112
+
113
+ field.options.forEach((option) => {
114
+ tmpValue.push(false);
115
+ });
116
+ } else {
117
+ tmpValue = [...field.value];
118
+ }
119
+
120
+ field.options.forEach((option, optionKey) => {
121
+ if (option.label === value) {
122
+ tmpValue[optionKey] = checked;
123
+ }
124
+ });
125
+ }
126
+ });
127
+
128
+ setActiveForm((prevState) => updateField(prevState, name, tmpValue));
129
+ };
130
+
131
+ const handleChangeDate = (value, id) => {
132
+ setActiveForm((prevState) => updateField(prevState, id, value));
133
+ };
134
+
135
+ const handleFileDownload = async (d) => {
136
+ try {
137
+ const res = await Download('/ajax/clients/download', 'POST', {
138
+ extension: d.extension,
139
+ uuid: d.uuid,
140
+ filename: d.filename,
141
+ });
142
+
143
+ if (res.data) {
144
+ saveAs(res.data, d.filename);
145
+ } else {
146
+ toast.error('Error downloading file');
147
+ }
148
+ } catch (err) {
149
+ console.error(err);
150
+ toast.error('Error downloading file');
151
+ }
152
+ };
153
+
154
+ const handleSelectForm = (form, key) => {
155
+ setActiveForm(form);
156
+ setActiveFormKey(key);
157
+ };
158
+
159
+ const handleAddNewForm = async () => {
160
+ try {
161
+ if (activeForm && activeForm.id) {
162
+ await CustomFetch('/ajax/clients/saveForm', 'POST', {
163
+ client_id: data.id,
164
+ form_data: {
165
+ ...activeForm,
166
+ },
167
+ form_key: activeFormKey,
168
+ form_type: type,
169
+ });
170
+ }
171
+
172
+ const res = await CustomFetch('/ajax/clients/addNewForm', 'POST', {
173
+ client_id: data.id,
174
+ form_name: type,
175
+ });
176
+ if (res.data.error === '') {
177
+ toast.success('Form added successfully');
178
+ fetchData();
179
+ }
180
+ } catch (err) {
181
+ console.error(err);
182
+ toast.error('Error adding form');
183
+ }
184
+ };
185
+
186
+ // Utility function to slugify a string
187
+ const slugify = (text) => {
188
+ return text
189
+ .toString()
190
+ .toLowerCase()
191
+ .replace(/\s+/g, '-') // Replace spaces with -
192
+ .replace(/[^\w\-]+/g, '') // Remove all non-word chars
193
+ .replace(/\-\-+/g, '-') // Replace multiple - with single -
194
+ .replace(/^-+/, '') // Trim - from start of text
195
+ .replace(/-+$/, ''); // Trim - from end of text
196
+ };
197
+
198
+ const handleDownloadForm = async (item, key) => {
199
+ try {
200
+ const res = await Download(
201
+ '/ajax/pdf/viewParticipantForm',
202
+ 'POST',
203
+ {
204
+ data: item,
205
+ label: label,
206
+ }
207
+ );
208
+
209
+ if (res.data) {
210
+ const fileName = slugify(label) + '.pdf';
211
+ saveAs(res.data, fileName); // Use file-saver to save the file with a slugified label as the filename
212
+ } else {
213
+ toast.error('Error downloading form');
214
+ }
215
+ } catch (err) {
216
+ console.error(err);
217
+ toast.error('Error downloading form');
218
+ }
219
+ };
220
+
221
+ const handleDeleteForm = async (id, key) => {
222
+ const deleteForm = async () => {
223
+ try {
224
+ const res = await CustomFetch(
225
+ '/ajax/clients/deleteForm',
226
+ 'POST',
227
+ {
228
+ client_id: data.id,
229
+ form_id: id,
230
+ form_key: key,
231
+ form_type: type,
232
+ }
233
+ );
234
+
235
+ if (res.data.error === '') {
236
+ toast.success('Form deleted successfully.');
237
+ fetchData();
238
+ } else {
239
+ // Handle case where there is an error in the response
240
+ toast.error('Error: ' + res.data.error);
241
+ }
242
+ } catch (err) {
243
+ console.error(err);
244
+ toast.error('Error deleting form');
245
+ }
246
+ };
247
+
248
+ confirmAlert({
249
+ title: 'Delete File',
250
+ message: 'Are you sure?',
251
+ buttons: [
252
+ {
253
+ label: 'Yes',
254
+ onClick: deleteForm,
255
+ },
256
+ {
257
+ label: 'No',
258
+ onClick: () => close(), // Ensure 'close' function is defined or remove this line
259
+ },
260
+ ],
261
+ });
262
+ };
263
+
264
+ const handleSaveForm = async () => {
265
+ try {
266
+ let error = '';
267
+
268
+ Object.entries(activeForm).forEach(([key, field]) => {
269
+ if (
270
+ field.required === true &&
271
+ (field.value === null || field.value === '')
272
+ ) {
273
+ error += `<p>${field.label} is required</p>`;
274
+ }
275
+ });
276
+
277
+ if (error === '') {
278
+ const res = await CustomFetch(
279
+ '/ajax/clients/saveForm',
280
+ 'POST',
281
+ {
282
+ client_id: data.id,
283
+ form_data: {
284
+ ...activeForm,
285
+ },
286
+ form_key: activeFormKey,
287
+ form_type: type,
288
+ }
289
+ );
290
+
291
+ if (res.data.error === '') {
292
+ toast.success('Form saved successfully.');
293
+ fetchData();
294
+ }
295
+ } else {
296
+ toast.error(<div>{parse(error)}</div>);
297
+ }
298
+ } catch (err) {
299
+ console.log(err);
300
+ }
301
+ };
302
+
303
+ useEffect(() => {
304
+ if (data[type]) {
305
+ // Retrieve the last form of the specified type
306
+ let lastForm = data[type][data[type].length - 1];
307
+
308
+ Object.entries(data[type][data[type].length - 1]).forEach(
309
+ ([key, field]) => {
310
+ if (field.required) {
311
+ if (
312
+ field.required === 'yes' ||
313
+ field.required === 'no'
314
+ ) {
315
+ lastForm[key].required =
316
+ field.required === 'yes' ? true : false;
317
+ }
318
+ }
319
+ }
320
+ );
321
+
322
+ // Update the active form with the modified lastForm
323
+ setActiveForm(lastForm);
324
+ setActiveFormKey(data[type].length - 1);
325
+ } else {
326
+ handleAddNewForm();
327
+ }
328
+ }, [data, type]);
329
+
330
+ return (
331
+ <>
332
+ <div className="formSplit">
333
+ {data[type] && data[type].length > 1 && (
334
+ <>
335
+ <div className="gridtxt__header">
336
+ <span>Form List</span>
337
+ </div>
338
+ <table
339
+ className="content-table"
340
+ style={{ marginBottom: '30px' }}
341
+ >
342
+ <thead>
343
+ <tr className="grid-headers">
344
+ <th>ID</th>
345
+ <th>Created By</th>
346
+ <th>Created At</th>
347
+ <th>Updated By</th>
348
+ <th>Updated At</th>
349
+ <th></th>
350
+ </tr>
351
+ </thead>
352
+ <tbody>
353
+ {data[type].map((item, index) => {
354
+ const idAsNumber = parseFloat(item.id);
355
+ if (
356
+ !isNaN(idAsNumber) &&
357
+ Number.isInteger(idAsNumber)
358
+ ) {
359
+ return (
360
+ <tr
361
+ key={`form-list-${index}`}
362
+ onClick={(e) => {
363
+ e.preventDefault();
364
+
365
+ handleSelectForm(
366
+ item,
367
+ index
368
+ );
369
+ }}
370
+ >
371
+ <td>Form #{item.id}</td>
372
+ <td>{item.created_by}</td>
373
+ <td>
374
+ {item.created_at
375
+ ? moment(
376
+ item.created_at
377
+ ).format(
378
+ 'DD-MM-YYYY hh:mm A'
379
+ )
380
+ : null}
381
+ </td>
382
+ <td>{item.updated_by}</td>
383
+ <td>
384
+ {item.updated_at
385
+ ? moment(
386
+ item.updated_at
387
+ ).format(
388
+ 'DD-MM-YYYY hh:mm A'
389
+ )
390
+ : null}
391
+ </td>
392
+ <td>
393
+ <div className="tdactions">
394
+ <span
395
+ data-tooltip-id="system-tooltip"
396
+ data-tooltip-content={
397
+ 'Download Form'
398
+ }
399
+ className="tooltip"
400
+ onClick={() =>
401
+ handleDownloadForm(
402
+ item,
403
+ index
404
+ )
405
+ }
406
+ >
407
+ <CloudDownload
408
+ strokeWidth={2}
409
+ size={18}
410
+ className="tdaction"
411
+ />
412
+ </span>
413
+ <span
414
+ data-tooltip-id="system-tooltip"
415
+ data-tooltip-content={
416
+ 'Delete Form'
417
+ }
418
+ className="tooltip"
419
+ onClick={() =>
420
+ handleDeleteForm(
421
+ item.id,
422
+ index
423
+ )
424
+ }
425
+ >
426
+ <Backspace
427
+ strokeWidth={2}
428
+ size={18}
429
+ className="tdaction"
430
+ />
431
+ </span>
432
+ </div>
433
+ </td>
434
+ </tr>
435
+ );
436
+ }
437
+ return null;
438
+ })}
439
+ </tbody>
440
+ </table>
441
+ </>
442
+ )}
443
+
444
+ {activeForm && (
445
+ <>
446
+ <div className="gridtxt__header">
447
+ <span>
448
+ {label} - Form #{activeForm.id}
449
+ </span>
450
+ </div>
451
+ <div
452
+ className="formcontainer"
453
+ style={{ paddingBottom: '50px' }}
454
+ >
455
+ <div className="modalForm">
456
+ {activeForm &&
457
+ Object.entries(activeForm)
458
+ .filter(([key, value]) => {
459
+ // Check if the value is an object and has the required keys
460
+ return (
461
+ value &&
462
+ typeof value === 'object' &&
463
+ [
464
+ 'id',
465
+ 'label',
466
+ 'type',
467
+ 'size',
468
+ 'value',
469
+ ].every((k) =>
470
+ value.hasOwnProperty(k)
471
+ )
472
+ );
473
+ })
474
+ .map(([key, field]) => {
475
+ // Check if the field is not hidden
476
+ if (field.hide === 0) {
477
+ return (
478
+ <Field
479
+ settings={field}
480
+ key={`field-${field.id}`}
481
+ inputClass={''}
482
+ inputValue={renderValue(
483
+ field
484
+ )}
485
+ onChange={handleChange}
486
+ onChangeCheckbox={
487
+ handleChangeCheckbox
488
+ }
489
+ onChangeColour={() => {}}
490
+ onChangeDate={
491
+ handleChangeDate
492
+ }
493
+ onChangeSelect={() => {}}
494
+ onChangeRicheditor={() => {}}
495
+ onChangeToggle={
496
+ handleChange
497
+ }
498
+ onChangeNumberFormat={() => {}}
499
+ onFileDownload={
500
+ handleFileDownload
501
+ }
502
+ autocompleteCallback={() => {}}
503
+ childDropdownCallback={() => {}}
504
+ setFormData={setData}
505
+ style={{}}
506
+ />
507
+ );
508
+ }
509
+ return null;
510
+ })}
511
+ </div>
512
+ </div>
513
+ </>
514
+ )}
515
+ </div>
516
+ <div className="polActions">
517
+ <button className="btn" onClick={handleAddNewForm}>
518
+ Add New Form
519
+ </button>
520
+
521
+ {activeForm && activeForm.form_status === 0 && (
522
+ <button className="btn" onClick={handleSaveForm}>
523
+ Save
524
+ </button>
525
+ )}
526
+ </div>
527
+ </>
528
+ );
529
+ };
530
+
531
+ export default GenericDynamic;