@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.
|
@@ -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;
|