@visns-studio/visns-components 4.10.48 → 5.0.1-8.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 (61) hide show
  1. package/package.json +29 -29
  2. package/src/components/cms/Field.jsx +39 -63
  3. package/src/components/crm/AsyncSelect.jsx +121 -15
  4. package/src/components/crm/Breadcrumb.jsx +2 -18
  5. package/src/components/crm/DataGrid.jsx +51 -424
  6. package/src/components/crm/Field.jsx +185 -358
  7. package/src/components/crm/Form.jsx +104 -92
  8. package/src/components/crm/MultiSelect.jsx +66 -45
  9. package/src/components/crm/Navigation.jsx +9 -92
  10. package/src/components/crm/QuickAction.jsx +1 -0
  11. package/src/components/crm/auth/Login.jsx +9 -1
  12. package/src/components/crm/auth/Profile.jsx +4 -2
  13. package/src/components/crm/auth/styles/Login.module.scss +52 -82
  14. package/src/components/crm/auth/styles/Profile.module.scss +150 -44
  15. package/src/components/crm/auth/styles/Reset.module.scss +55 -37
  16. package/src/components/crm/auth/styles/Verify.module.scss +55 -76
  17. package/src/components/crm/generic/GenericAuth.jsx +12 -5
  18. package/src/components/crm/generic/GenericDashboard.jsx +30 -340
  19. package/src/components/crm/generic/GenericDetail.jsx +534 -86
  20. package/src/components/crm/generic/GenericDynamic.jsx +76 -94
  21. package/src/components/crm/generic/GenericEditableTable.jsx +407 -0
  22. package/src/components/crm/generic/GenericFormBuilder.jsx +19 -79
  23. package/src/components/crm/generic/GenericIndex.jsx +1 -2
  24. package/src/components/crm/generic/styles/AuditLog.module.scss +4 -37
  25. package/src/components/crm/generic/styles/AuditLogs.module.scss +0 -51
  26. package/src/components/crm/generic/styles/GenericDetail.module.scss +263 -244
  27. package/src/components/crm/generic/styles/GenericDynamic.module.scss +100 -198
  28. package/src/components/crm/generic/styles/GenericEditableTable.module.scss +178 -0
  29. package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +63 -101
  30. package/src/components/crm/generic/styles/GenericIndex.module.scss +46 -66
  31. package/src/components/crm/generic/styles/GenericMain.module.scss +7 -7
  32. package/src/components/crm/generic/styles/GenericSort.module.scss +0 -36
  33. package/src/components/crm/generic/styles/NotificationList.module.scss +1 -32
  34. package/src/components/crm/styles/Autocomplete.module.scss +12 -20
  35. package/src/components/crm/styles/DataGrid.module.scss +28 -86
  36. package/src/components/crm/styles/Field.module.scss +72 -252
  37. package/src/components/crm/styles/Form.module.scss +141 -450
  38. package/src/components/crm/styles/Navigation.module.scss +356 -507
  39. package/src/components/crm/styles/Notification.module.scss +1 -0
  40. package/src/components/crm/styles/QrCode.module.scss +4 -18
  41. package/src/components/crm/styles/SwitchAccount.module.scss +0 -1
  42. package/src/components/crm/styles/TableFilter.module.scss +2 -1
  43. package/src/components/styles/global.css +164 -34
  44. package/src/index.js +0 -8
  45. package/src/components/crm/generic/styles/GenericDashboard.module.scss +0 -325
  46. package/src/components/crm/sketch/SketchField.jsx +0 -395
  47. package/src/components/crm/sketch/arrow.jsx +0 -108
  48. package/src/components/crm/sketch/circle.jsx +0 -75
  49. package/src/components/crm/sketch/default-tool.jsx +0 -16
  50. package/src/components/crm/sketch/fabrictool.jsx +0 -22
  51. package/src/components/crm/sketch/history.jsx +0 -144
  52. package/src/components/crm/sketch/json/config.json +0 -14
  53. package/src/components/crm/sketch/line.jsx +0 -64
  54. package/src/components/crm/sketch/pan.jsx +0 -48
  55. package/src/components/crm/sketch/pencil.jsx +0 -36
  56. package/src/components/crm/sketch/rectangle-label-object.jsx +0 -69
  57. package/src/components/crm/sketch/rectangle-label.jsx +0 -93
  58. package/src/components/crm/sketch/rectangle.jsx +0 -76
  59. package/src/components/crm/sketch/select.jsx +0 -16
  60. package/src/components/crm/sketch/tools.jsx +0 -11
  61. package/src/components/crm/sketch/utils.jsx +0 -67
@@ -31,8 +31,6 @@ const updateField = (fields, name, value) => {
31
31
 
32
32
  const renderValue = (field, data) => {
33
33
  switch (field.type) {
34
- case 'canvas':
35
- return field.value || '';
36
34
  case 'datetime':
37
35
  case 'date':
38
36
  return field.value && moment(field.value).isValid()
@@ -105,7 +103,6 @@ const GenericDynamic = ({
105
103
  }) => {
106
104
  const [activeForm, setActiveForm] = useState({});
107
105
  const [activeFormKey, setActiveFormKey] = useState(0);
108
- const [canvasSaveCounter, setCanvasSaveCounter] = useState(0);
109
106
 
110
107
  const handleChange = (e) => {
111
108
  const {
@@ -177,35 +174,6 @@ const GenericDynamic = ({
177
174
  }
178
175
  };
179
176
 
180
- const handleChangeCanvas = (fieldSetting, value, imageData) => {
181
- // Check if activeForm has keys
182
- if (activeForm && typeof activeForm === 'object') {
183
- // Iterate over the keys in activeForm to find the matching id
184
- for (const key in activeForm) {
185
- if (activeForm[key].id === fieldSetting.id) {
186
- // If a matching id is found, update the corresponding field
187
- setActiveForm((prevState) => ({
188
- ...prevState,
189
- [key]: {
190
- ...prevState[key],
191
- value, // Update the value for the matching id
192
- imageData,
193
- },
194
- }));
195
-
196
- setCanvasSaveCounter((prevState) => prevState + 1);
197
- return; // Exit after updating to prevent further iterations
198
- }
199
- }
200
- }
201
- };
202
-
203
- useEffect(() => {
204
- if (canvasSaveCounter > 0) {
205
- handleSaveForm(false);
206
- }
207
- }, [canvasSaveCounter]);
208
-
209
177
  const handleChangeCheckbox = (e) => {
210
178
  const { checked, name, value } = e.target;
211
179
 
@@ -279,20 +247,33 @@ const GenericDynamic = ({
279
247
  return prevActiveForm;
280
248
  }
281
249
 
282
- // Create a shallow copy of rows to target only the necessary updates
250
+ // Create a new rows array with updated value properties based on input type
283
251
  const updatedRows = prevActiveForm[formKey].rows.map((row) => {
284
252
  if (row.id === rowId) {
285
- // Update only the specific cell in the target row
286
- const newValue = {
287
- ...row.value,
288
- [columnId]: type === 'checkbox' ? checked : value,
253
+ // Determine new value based on input type
254
+ let newValue;
255
+ if (type === 'checkbox') {
256
+ newValue = { ...row.value, [columnId]: checked };
257
+ } else if (
258
+ type === 'date' ||
259
+ type === 'time' ||
260
+ type === 'text'
261
+ ) {
262
+ newValue = { ...row.value, [columnId]: value };
263
+ } else {
264
+ // For other input types, use the value directly
265
+ newValue = { ...row.value, [columnId]: value };
266
+ }
267
+
268
+ return {
269
+ ...row,
270
+ value: newValue,
289
271
  };
290
- return { ...row, value: newValue }; // Return the updated row
291
272
  }
292
- return row; // Return other rows unchanged
273
+ return row;
293
274
  });
294
275
 
295
- // Update only the formKey section of activeForm
276
+ // Return the updated activeForm state
296
277
  return {
297
278
  ...prevActiveForm,
298
279
  [formKey]: {
@@ -582,29 +563,27 @@ const GenericDynamic = ({
582
563
  });
583
564
  };
584
565
 
585
- const handleSaveForm = async (validate = true) => {
566
+ const handleSaveForm = async () => {
586
567
  try {
587
568
  let error = '';
588
569
 
589
- if (validate) {
590
- const excludedTypes = [
591
- 'dynamicdata',
592
- 'plaintext',
593
- 'plaintextheading',
594
- 'section',
595
- 'table_radio',
596
- ];
570
+ const excludedTypes = [
571
+ 'dynamicdata',
572
+ 'plaintext',
573
+ 'plaintextheading',
574
+ 'section',
575
+ 'table_radio',
576
+ ];
597
577
 
598
- Object.entries(activeForm).forEach(([key, field]) => {
599
- if (
600
- field.required === true &&
601
- !excludedTypes.includes(field.type) &&
602
- (field.value === null || field.value === '')
603
- ) {
604
- error += `<p>${field.label} is required</p>`;
605
- }
606
- });
607
- }
578
+ Object.entries(activeForm).forEach(([key, field]) => {
579
+ if (
580
+ field.required === true &&
581
+ !excludedTypes.includes(field.type) &&
582
+ (field.value === null || field.value === '')
583
+ ) {
584
+ error += `<p>${field.label} is required</p>`;
585
+ }
586
+ });
608
587
 
609
588
  if (error === '') {
610
589
  const res = await CustomFetch(setting.save, 'POST', {
@@ -629,34 +608,47 @@ const GenericDynamic = ({
629
608
  };
630
609
 
631
610
  useEffect(() => {
632
- if (!data[type] || data[type].length === 0) {
633
- setActiveForm({});
634
- return;
635
- }
611
+ if (data[type] && data[type].length > 0) {
612
+ if (multiple) {
613
+ if (templateStatus) {
614
+ let lastForm = data[type][data[type].length - 1];
615
+
616
+ if (lastForm && typeof lastForm === 'object') {
617
+ Object.entries(lastForm).forEach(([key, field]) => {
618
+ if (field && field.required) {
619
+ if (
620
+ field.required === 'yes' ||
621
+ field.required === 'no'
622
+ ) {
623
+ lastForm[key].required =
624
+ field.required === 'yes';
625
+ }
626
+ }
627
+ });
636
628
 
637
- const formData = multiple
638
- ? data[type][data[type].length - 1]
639
- : data[type].hasOwnProperty('created_at')
640
- ? data[type][0] || data[type]
641
- : data[type];
642
-
643
- if (
644
- multiple &&
645
- templateStatus &&
646
- formData &&
647
- typeof formData === 'object'
648
- ) {
649
- // Process the last form for required fields
650
- Object.entries(formData).forEach(([key, field]) => {
651
- if (field?.required) {
652
- field.required = field.required === 'yes';
629
+ setActiveForm(lastForm);
630
+ setActiveFormKey(data[type].length - 1);
631
+ }
653
632
  }
654
- });
633
+ } else {
634
+ if (data[type].hasOwnProperty('id')) {
635
+ if (
636
+ Object.keys(activeForm).length === 0 &&
637
+ templateStatus &&
638
+ data[type][0]
639
+ ) {
640
+ setActiveForm(data[type][0]);
641
+ setActiveFormKey(0);
642
+ }
643
+ } else {
644
+ setActiveForm(data[type]);
645
+ setActiveFormKey(0);
646
+ }
647
+ }
648
+ } else {
649
+ setActiveForm({});
655
650
  }
656
-
657
- setActiveForm(formData);
658
- setActiveFormKey(0);
659
- }, [data, type, multiple, templateStatus]);
651
+ }, [data, type]);
660
652
 
661
653
  useEffect(() => {
662
654
  if (data[type] && data[type].length > 0) {
@@ -824,7 +816,6 @@ const GenericDynamic = ({
824
816
  ? Object.entries(activeForm)
825
817
  .filter(
826
818
  ([key, value]) =>
827
- value && // Add this check to ensure value is not null
828
819
  typeof value === 'object' &&
829
820
  [
830
821
  'id',
@@ -851,9 +842,6 @@ const GenericDynamic = ({
851
842
  onChange={
852
843
  handleChange
853
844
  }
854
- onChangeCanvas={
855
- handleChangeCanvas
856
- }
857
845
  onChangeCheckbox={
858
846
  handleChangeCheckbox
859
847
  }
@@ -902,18 +890,12 @@ const GenericDynamic = ({
902
890
  originalData
903
891
  )}
904
892
  onChange={handleChange}
905
- onChangeCanvas={
906
- handleChangeCanvas
907
- }
908
893
  onChangeCheckbox={
909
894
  handleChangeCheckbox
910
895
  }
911
896
  onChangeDate={
912
897
  handleChangeDate
913
898
  }
914
- onChangeNumberFormat={
915
- handleChangeNumberFormat
916
- }
917
899
  onChangeTableRadio={
918
900
  handleChangeTableRadio
919
901
  }
@@ -0,0 +1,407 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import debounce from 'lodash.debounce';
3
+ import { toast } from 'react-toastify';
4
+ import Popup from 'reactjs-popup';
5
+ import { confirmAlert } from 'react-confirm-alert';
6
+ import { TrashCan, ArrowUp, ArrowDown } from 'akar-icons';
7
+
8
+ import CustomFetch from '../Fetch';
9
+ import Form from '../Form';
10
+ import MultiSelect from '../MultiSelect';
11
+
12
+ import 'react-confirm-alert/src/react-confirm-alert.css';
13
+ import styles from './styles/GenericEditableTable.module.scss';
14
+
15
+ const GenericEditableTable = ({
16
+ schedulingConfig,
17
+ data,
18
+ dataId,
19
+ modalOpen,
20
+ preloadedOptions,
21
+ }) => {
22
+ const { columns, rows, form } = schedulingConfig;
23
+
24
+ const [localData, setLocalData] = useState([]);
25
+ const [groupedCategories, setGroupedCategories] = useState([]);
26
+
27
+ useEffect(() => {
28
+ const enrichedData = data[rows.key]?.map((entry, index) => ({
29
+ ...entry,
30
+ sort_order: entry.sort_order ?? index + 1,
31
+ }));
32
+ setLocalData(enrichedData || []);
33
+ }, [data, rows.key]);
34
+
35
+ const debouncedUpdate = useMemo(
36
+ () =>
37
+ debounce(async (updatedData) => {
38
+ try {
39
+ const res = await CustomFetch(
40
+ `${form.url}/${dataId}`,
41
+ 'PUT',
42
+ updatedData
43
+ );
44
+ if (!res.error) {
45
+ toast.success('Template updated successfully');
46
+ } else {
47
+ toast.error(res.error);
48
+ }
49
+ } catch (error) {
50
+ console.error('Error updating field:', error);
51
+ }
52
+ }, 1000),
53
+ []
54
+ );
55
+
56
+ const groupCategoriesByType = (data) => {
57
+ const grouped = {};
58
+
59
+ data.forEach((category, categoryKey) => {
60
+ const categoryType =
61
+ category[rows.categoryKey.id]?.[rows.categoryKey.label] || null;
62
+
63
+ const categoryId = category[rows.categoryKey.id]?.id || null;
64
+
65
+ const groupKey = categoryId || 'no_category';
66
+
67
+ if (!grouped[groupKey]) {
68
+ grouped[groupKey] = {
69
+ id: categoryId || 'no_category',
70
+ category: categoryType || '',
71
+ entries: [],
72
+ };
73
+ }
74
+ grouped[groupKey].entries.push({
75
+ ...category,
76
+ keyCounter: categoryKey,
77
+ });
78
+ });
79
+
80
+ Object.values(grouped).forEach((group) => {
81
+ group.entries.sort((a, b) => a.sort_order - b.sort_order);
82
+ });
83
+
84
+ // Ensure 'no_category' group is at the top
85
+ const groupedArray = Object.values(grouped);
86
+ return groupedArray.sort((a, b) =>
87
+ a.id === 'no_category' ? -1 : b.id === 'no_category' ? 1 : 0
88
+ );
89
+ };
90
+
91
+ const handleFieldChange = (value, fieldId, keyCounter) => {
92
+ setLocalData((prev) => {
93
+ const updatedDetail = [...prev];
94
+ updatedDetail[keyCounter] = {
95
+ ...updatedDetail[keyCounter],
96
+ [fieldId]: value,
97
+ };
98
+
99
+ debouncedUpdate({ [rows.key]: updatedDetail });
100
+
101
+ return updatedDetail;
102
+ });
103
+ };
104
+
105
+ const handleSortChange = (groupId, entryId, direction) => {
106
+ setLocalData((prev) => {
107
+ const updatedDetail = [...prev];
108
+ const group = groupCategoriesByType(updatedDetail).find(
109
+ (group) => group.id === groupId
110
+ );
111
+
112
+ if (group) {
113
+ const index = group.entries.findIndex(
114
+ (entry) => entry.id === entryId
115
+ );
116
+
117
+ if (
118
+ (direction === 'up' && index > 0) ||
119
+ (direction === 'down' && index < group.entries.length - 1)
120
+ ) {
121
+ const swapIndex =
122
+ direction === 'up' ? index - 1 : index + 1;
123
+ [
124
+ group.entries[index].sort_order,
125
+ group.entries[swapIndex].sort_order,
126
+ ] = [
127
+ group.entries[swapIndex].sort_order,
128
+ group.entries[index].sort_order,
129
+ ];
130
+
131
+ const reordered = updatedDetail.map((item) => {
132
+ const entry = group.entries.find(
133
+ (e) => e.id === item.id
134
+ );
135
+ return entry
136
+ ? { ...item, sort_order: entry.sort_order }
137
+ : item;
138
+ });
139
+
140
+ debouncedUpdate({ [rows.key]: reordered });
141
+ return reordered;
142
+ }
143
+ }
144
+
145
+ return updatedDetail;
146
+ });
147
+ };
148
+
149
+ const handleDeleteRow = (entry) => {
150
+ confirmAlert({
151
+ title: 'Confirm to Delete',
152
+ message: 'Are you sure you want to delete this entry?',
153
+ buttons: [
154
+ {
155
+ label: 'Yes',
156
+ onClick: async () => {
157
+ setLocalData((prev) => {
158
+ const updatedDetail = prev.filter(
159
+ (item) => item.id !== entry.id
160
+ );
161
+ debouncedUpdate({ [rows.key]: updatedDetail });
162
+ return updatedDetail;
163
+ });
164
+ },
165
+ },
166
+ {
167
+ label: 'No',
168
+ },
169
+ ],
170
+ });
171
+ };
172
+
173
+ const renderScheduledTableField = (entry, column, keyCounter) => {
174
+ if (!entry || !column) return null;
175
+
176
+ switch (column.type) {
177
+ case 'currency':
178
+ case 'number':
179
+ return (
180
+ <input
181
+ type="text"
182
+ value={entry[column.id] || ''}
183
+ onChange={(e) =>
184
+ handleFieldChange(
185
+ e.target.value,
186
+ column.id,
187
+ keyCounter
188
+ )
189
+ }
190
+ style={{ textAlign: 'right' }}
191
+ />
192
+ );
193
+ case 'date':
194
+ return (
195
+ <input
196
+ type="date"
197
+ value={entry[column.id] || ''}
198
+ onChange={(e) =>
199
+ handleFieldChange(
200
+ e.target.value,
201
+ column.id,
202
+ keyCounter
203
+ )
204
+ }
205
+ style={{ textAlign: 'right' }}
206
+ />
207
+ );
208
+ case 'dropdown':
209
+ return (
210
+ <select
211
+ value={entry[column.id] || ''}
212
+ onChange={(e) =>
213
+ handleFieldChange(
214
+ e.target.value,
215
+ column.id,
216
+ keyCounter
217
+ )
218
+ }
219
+ >
220
+ {column.options.map((option) => (
221
+ <option key={option.id} value={option.id}>
222
+ {option.label}
223
+ </option>
224
+ ))}
225
+ </select>
226
+ );
227
+ case 'dropdown-ajax':
228
+ const options = preloadedOptions[column.id] || [];
229
+ return (
230
+ <select
231
+ value={entry[column.id] || ''}
232
+ onChange={(e) =>
233
+ handleFieldChange(
234
+ e.target.value,
235
+ column.id,
236
+ keyCounter
237
+ )
238
+ }
239
+ >
240
+ <option>Select an Option</option>
241
+ {options.map((option) => (
242
+ <option key={option.id} value={option.id}>
243
+ {option.label}
244
+ </option>
245
+ ))}
246
+ </select>
247
+ );
248
+ case 'multi-dropdown-ajax':
249
+ const multiOptions = preloadedOptions[column.id] || [];
250
+ return (
251
+ <MultiSelect
252
+ settings={{
253
+ id: column.id,
254
+ }}
255
+ className={styles.selectFullWidth}
256
+ multi={false}
257
+ onChange={(value) =>
258
+ handleFieldChange(value, column.id, keyCounter)
259
+ }
260
+ inputValue={entry[column.id] || ''}
261
+ options={multiOptions}
262
+ />
263
+ );
264
+ default:
265
+ return (
266
+ <input
267
+ type="text"
268
+ defaultValue={entry[column.id] || ''}
269
+ onChange={(e) =>
270
+ handleFieldChange(
271
+ e.target.value,
272
+ column.id,
273
+ keyCounter
274
+ )
275
+ }
276
+ />
277
+ );
278
+ }
279
+ };
280
+
281
+ useEffect(() => {
282
+ setGroupedCategories(groupCategoriesByType(localData));
283
+ }, [localData]);
284
+
285
+ return (
286
+ <div className={styles.gridtxt}>
287
+ <div className={styles.gridtxt__header}>
288
+ <span>{schedulingConfig.title || 'Estimation Schedule'}</span>
289
+ </div>
290
+ {groupedCategories.length > 0 ? (
291
+ <table className={styles.schedulingTable}>
292
+ <thead>
293
+ <tr>
294
+ {columns.map((column) => (
295
+ <th
296
+ key={column.id}
297
+ style={{ width: column.width || 'auto' }}
298
+ >
299
+ {column.label}
300
+ </th>
301
+ ))}
302
+ <th style={{ width: '5%' }}></th>
303
+ </tr>
304
+ </thead>
305
+ <tbody>
306
+ {groupedCategories.map((group) => (
307
+ <React.Fragment key={group.id}>
308
+ <tr className={styles.categoryRow}>
309
+ <td colSpan={columns.length + 1}>
310
+ {group.category}
311
+ </td>
312
+ </tr>
313
+ {group.entries.map((entry, idx) => (
314
+ <tr key={entry.id}>
315
+ {columns.map((column) => (
316
+ <td
317
+ key={column.id}
318
+ style={{
319
+ width:
320
+ column.width || 'auto',
321
+ textAlign:
322
+ column.type ===
323
+ 'currency'
324
+ ? 'right'
325
+ : 'left',
326
+ }}
327
+ >
328
+ {renderScheduledTableField(
329
+ entry,
330
+ column,
331
+ entry.keyCounter
332
+ )}
333
+ </td>
334
+ ))}
335
+ <td>
336
+ <div className={styles.tdactions}>
337
+ {idx > 0 && (
338
+ <ArrowUp
339
+ size={18}
340
+ onClick={() =>
341
+ handleSortChange(
342
+ group.id,
343
+ entry.id,
344
+ 'up'
345
+ )
346
+ }
347
+ style={{
348
+ cursor: 'pointer',
349
+ }}
350
+ />
351
+ )}
352
+ {idx <
353
+ group.entries.length -
354
+ 1 && (
355
+ <ArrowDown
356
+ size={18}
357
+ onClick={() =>
358
+ handleSortChange(
359
+ group.id,
360
+ entry.id,
361
+ 'down'
362
+ )
363
+ }
364
+ style={{
365
+ cursor: 'pointer',
366
+ }}
367
+ />
368
+ )}
369
+ <TrashCan
370
+ size={18}
371
+ onClick={() =>
372
+ handleDeleteRow(entry)
373
+ }
374
+ style={{
375
+ cursor: 'pointer',
376
+ }}
377
+ />
378
+ </div>
379
+ </td>
380
+ </tr>
381
+ ))}
382
+ </React.Fragment>
383
+ ))}
384
+ </tbody>
385
+ </table>
386
+ ) : (
387
+ <div className={styles.noDataMessage}>
388
+ No data available. Please check your filters or try
389
+ reloading the data.
390
+ </div>
391
+ )}
392
+
393
+ <div className={styles.polActions}>
394
+ <button
395
+ className={styles.btn}
396
+ onClick={() => {
397
+ modalOpen('create', 0);
398
+ }}
399
+ >
400
+ Add
401
+ </button>
402
+ </div>
403
+ </div>
404
+ );
405
+ };
406
+
407
+ export default GenericEditableTable;