@visns-studio/visns-components 5.11.1 → 5.11.3

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 (60) hide show
  1. package/package.json +2 -2
  2. package/src/components/cms/DataGrid.jsx +8 -8
  3. package/src/components/cms/DropZone.jsx +1 -1
  4. package/src/components/cms/Form.jsx +1 -1
  5. package/src/components/cms/Gallery.jsx +1 -1
  6. package/src/components/cms/sorting/Item.jsx +1 -1
  7. package/src/components/crm/AssociationManager.jsx +431 -0
  8. package/src/components/crm/Autocomplete.jsx +3 -3
  9. package/src/components/crm/Breadcrumb.jsx +4 -4
  10. package/src/components/crm/BusinessCardOcr.jsx +681 -95
  11. package/src/components/crm/ClientAssociationManager.jsx +362 -0
  12. package/src/components/crm/DataGrid.jsx +34 -32
  13. package/src/components/crm/Field.jsx +12 -12
  14. package/src/components/crm/Form.jsx +2 -2
  15. package/src/components/crm/MergeEntity.jsx +1049 -0
  16. package/src/components/crm/MultiSelect.jsx +14 -3
  17. package/src/components/crm/Navigation.jsx +14 -15
  18. package/src/components/crm/Notification.jsx +2 -2
  19. package/src/components/crm/QuickAction.jsx +3 -3
  20. package/src/components/crm/SectionHeader.jsx +1 -1
  21. package/src/components/crm/SwitchAccount.jsx +4 -4
  22. package/src/components/crm/auth/ClientLogin.jsx +2 -2
  23. package/src/components/crm/auth/ClientOTPVerify.jsx +2 -2
  24. package/src/components/crm/auth/Login.jsx +3 -3
  25. package/src/components/crm/auth/Reset.jsx +2 -2
  26. package/src/components/crm/auth/TwoFactorAuth.jsx +2 -2
  27. package/src/components/crm/cells/CellWithTooltip.jsx +35 -28
  28. package/src/components/crm/columns/ColumnRenderers.jsx +320 -259
  29. package/src/components/crm/controls/DataGridSearch.jsx +5 -5
  30. package/src/components/crm/generic/AlternativeActionModal.jsx +100 -0
  31. package/src/components/crm/generic/ContactSelectorModal.jsx +59 -16
  32. package/src/components/crm/generic/DateRangeSelectorModal.jsx +181 -0
  33. package/src/components/crm/generic/GenericClientPortal.jsx +1 -1
  34. package/src/components/crm/generic/GenericDashboard.jsx +4 -4
  35. package/src/components/crm/generic/GenericDetail.jsx +31 -6
  36. package/src/components/crm/generic/GenericDynamic.jsx +3 -3
  37. package/src/components/crm/generic/GenericEditableTable.jsx +4 -4
  38. package/src/components/crm/generic/GenericFormBuilder.jsx +6 -6
  39. package/src/components/crm/generic/GenericGrid.jsx +1622 -268
  40. package/src/components/crm/generic/GenericReport.jsx +966 -646
  41. package/src/components/crm/generic/GenericReportForm.jsx +1 -1
  42. package/src/components/crm/generic/NotificationList.jsx +1 -1
  43. package/src/components/crm/generic/ReasonCollectorModal.jsx +194 -0
  44. package/src/components/crm/generic/SortableQuoteItems.jsx +3 -3
  45. package/src/components/crm/generic/shared/formatters.js +107 -1
  46. package/src/components/crm/generic/styles/AlternativeActionModal.css +127 -0
  47. package/src/components/crm/generic/styles/DateRangeSelectorModal.css +115 -0
  48. package/src/components/crm/generic/styles/GenericIndex.module.scss +206 -0
  49. package/src/components/crm/generic/styles/GenericReport.module.scss +96 -0
  50. package/src/components/crm/generic/styles/ReasonCollectorModal.css +217 -0
  51. package/src/components/crm/modals/GalleryModal.jsx +2 -2
  52. package/src/components/crm/sorting/Item.jsx +3 -3
  53. package/src/components/crm/styles/AssociationManager.module.scss +364 -0
  54. package/src/components/crm/styles/BusinessCardOcr.module.scss +197 -4
  55. package/src/components/crm/styles/ClientAssociationManager.module.scss +290 -0
  56. package/src/components/crm/styles/MergeEntity.module.scss +690 -0
  57. package/src/components/crm/styles/MultiSelect.module.scss +18 -0
  58. package/src/components/crm/styles/Navigation.module.scss +68 -8
  59. package/src/components/crm/styles/global-datagrid.css +9 -0
  60. package/src/index.js +6 -0
@@ -0,0 +1,362 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { toast } from 'react-toastify';
3
+ import CustomFetch from './Fetch';
4
+ import MultiSelect from './MultiSelect';
5
+ import styles from './styles/ClientAssociationManager.module.scss';
6
+
7
+ const ClientAssociationManager = ({
8
+ endpoints,
9
+ config,
10
+ dataId,
11
+ onUpdate = () => {}
12
+ }) => {
13
+ const [associations, setAssociations] = useState([]);
14
+ const [availableOptions, setAvailableOptions] = useState([]);
15
+ const [loading, setLoading] = useState(true);
16
+
17
+ // Dynamic state based on config
18
+ const [newAssociation, setNewAssociation] = useState(() => {
19
+ const initialState = {};
20
+
21
+ // Set up dynamic fields based on config
22
+ if (config.fields) {
23
+ config.fields.forEach(field => {
24
+ initialState[field.id] = field.defaultValue || (field.type === 'boolean' ? false :
25
+ field.type === 'select' ? (field.options?.[0]?.value || '') :
26
+ field.type === 'multi-select' ? [] : '');
27
+ });
28
+ }
29
+
30
+ return initialState;
31
+ });
32
+
33
+ // Replace placeholders in endpoint URLs and data
34
+ const processTemplate = (template, data = {}) => {
35
+ let processed = template;
36
+
37
+ // Replace {dataId} with current dataId
38
+ processed = processed.replace(/\{dataId\}/g, dataId);
39
+
40
+ // Replace any other placeholders from provided data
41
+ Object.keys(data).forEach(key => {
42
+ const placeholder = new RegExp(`\\{${key}\\}`, 'g');
43
+ processed = processed.replace(placeholder, data[key]);
44
+ });
45
+
46
+ return processed;
47
+ };
48
+
49
+ // Load existing associations
50
+ const loadAssociations = async () => {
51
+ if (!endpoints.list) return;
52
+
53
+ try {
54
+ setLoading(true);
55
+ const url = processTemplate(endpoints.list);
56
+ const method = endpoints.listMethod || 'GET';
57
+ const response = await CustomFetch(url, method, endpoints.listParams || {});
58
+
59
+ const dataPath = config.dataPath || 'data';
60
+ const associationsData = dataPath.split('.').reduce((obj, key) => obj?.[key], response);
61
+
62
+ // Ensure associationsData is always an array
63
+ const validAssociations = Array.isArray(associationsData) ? associationsData : [];
64
+ setAssociations(validAssociations);
65
+ } catch (error) {
66
+ toast.error(config.messages?.loadError || 'Failed to load associations');
67
+ console.error('Error loading associations:', error);
68
+ setAssociations([]); // Set empty array on error
69
+ } finally {
70
+ setLoading(false);
71
+ }
72
+ };
73
+
74
+ // Load available options for dropdown/select fields
75
+ const loadAvailableOptions = async () => {
76
+ if (!config.optionsEndpoint) return;
77
+
78
+ try {
79
+ const url = processTemplate(config.optionsEndpoint.url);
80
+ const method = config.optionsEndpoint.method || 'POST';
81
+ const params = config.optionsEndpoint.params || {};
82
+
83
+ const response = await CustomFetch(url, method, params);
84
+ const dataPath = config.optionsEndpoint.dataPath || 'data.data';
85
+ const optionsData = dataPath.split('.').reduce((obj, key) => obj?.[key], response) || [];
86
+
87
+ setAvailableOptions(optionsData);
88
+ } catch (error) {
89
+ console.error('Failed to load options:', error);
90
+ }
91
+ };
92
+
93
+ // Generic action handler
94
+ const handleAction = async (actionKey, data = {}) => {
95
+ const endpoint = endpoints[actionKey];
96
+ if (!endpoint) return;
97
+
98
+ try {
99
+ const url = processTemplate(endpoint.url || endpoint, data);
100
+ const method = endpoint.method || 'POST';
101
+ const params = { ...endpoint.params, ...data };
102
+
103
+ await CustomFetch(url, method, params);
104
+
105
+ const successMessage = config.messages?.[`${actionKey}Success`] ||
106
+ endpoint.successMessage ||
107
+ 'Action completed successfully';
108
+ toast.success(successMessage);
109
+
110
+ // Reset form if it's an associate action
111
+ if (actionKey === 'associate') {
112
+ const resetState = {};
113
+ if (config.fields) {
114
+ config.fields.forEach(field => {
115
+ resetState[field.id] = field.defaultValue || (field.type === 'boolean' ? false :
116
+ field.type === 'select' ? (field.options?.[0]?.value || '') :
117
+ field.type === 'multi-select' ? [] : '');
118
+ });
119
+ }
120
+ setNewAssociation(resetState);
121
+ }
122
+
123
+ loadAssociations();
124
+ onUpdate();
125
+ } catch (error) {
126
+ const errorMessage = config.messages?.[`${actionKey}Error`] ||
127
+ endpoint.errorMessage ||
128
+ 'Action failed';
129
+ toast.error(errorMessage);
130
+ console.error(error);
131
+ }
132
+ };
133
+
134
+ // Associate handler
135
+ const handleAssociate = async () => {
136
+ // Validate required fields
137
+ const requiredField = config.fields?.find(field => field.required);
138
+ const primaryField = config.fields?.find(field => field.isPrimary);
139
+
140
+ if (requiredField && !newAssociation[requiredField.id]) {
141
+ toast.error(`Please select a ${requiredField.label || requiredField.id}`);
142
+ return;
143
+ }
144
+
145
+ // Prepare data for submission
146
+ const submitData = { ...newAssociation };
147
+
148
+ // Handle complex field values (like objects from MultiSelect)
149
+ Object.keys(submitData).forEach(key => {
150
+ const value = submitData[key];
151
+ if (value && typeof value === 'object' && value.value !== undefined) {
152
+ submitData[key] = value.value;
153
+ }
154
+ });
155
+
156
+ await handleAction('associate', submitData);
157
+ };
158
+
159
+ // Remove association handler
160
+ const handleDissociate = async (itemId) => {
161
+ const deleteField = config.deleteField || 'id';
162
+ await handleAction('dissociate', { [deleteField]: itemId });
163
+ };
164
+
165
+ // Set primary handler
166
+ const handleSetPrimary = async (itemId) => {
167
+ if (!endpoints.setPrimary) return;
168
+
169
+ const primaryField = config.fields?.find(field => field.isPrimary);
170
+ const idField = primaryField?.targetField || 'id';
171
+
172
+ await handleAction('setPrimary', { [idField]: itemId });
173
+ };
174
+
175
+ useEffect(() => {
176
+ if (dataId) {
177
+ loadAssociations();
178
+ loadAvailableOptions();
179
+ }
180
+ }, [dataId]);
181
+
182
+ if (loading) {
183
+ return <div className={styles.loading}>{config.messages?.loading || 'Loading associations...'}</div>;
184
+ }
185
+
186
+ return (
187
+ <div className={styles.container}>
188
+ <div className={styles.header}>
189
+ <h3>{config.title || 'Associations'}</h3>
190
+ <p>{config.description || 'Manage associations'}</p>
191
+ </div>
192
+
193
+ {/* Existing Associations */}
194
+ <div className={styles.existingAssociations}>
195
+ <h4>{config.labels?.currentAssociations || 'Current Associations'}</h4>
196
+ {associations.length === 0 ? (
197
+ <div className={styles.noAssociations}>
198
+ {config.messages?.noAssociations || 'No associations found.'}
199
+ </div>
200
+ ) : (
201
+ <div className={styles.associationsList}>
202
+ {associations.map((association) => {
203
+ const nameField = config.displayFields?.name || 'name';
204
+ const primaryField = config.displayFields?.primary || 'is_primary';
205
+ const relationshipField = config.displayFields?.relationship || 'relationship_type';
206
+ const notesField = config.displayFields?.notes || 'notes';
207
+ const dateField = config.displayFields?.date || 'associated_at';
208
+
209
+ return (
210
+ <div
211
+ key={association.id}
212
+ className={`${styles.associationItem} ${association[primaryField] ? styles.primary : ''}`}
213
+ >
214
+ <div className={styles.associationInfo}>
215
+ <div className={styles.companyName}>
216
+ {association[nameField]}
217
+ {association[primaryField] && (
218
+ <span className={styles.primaryBadge}>{config.labels?.primaryBadge || 'Primary'}</span>
219
+ )}
220
+ </div>
221
+ <div className={styles.relationshipInfo}>
222
+ <span className={styles.relationshipType}>
223
+ {association[relationshipField]}
224
+ </span>
225
+ {association[notesField] && (
226
+ <span className={styles.notes}>
227
+ - {association[notesField]}
228
+ </span>
229
+ )}
230
+ </div>
231
+ <div className={styles.associatedAt}>
232
+ {config.labels?.associatedAt || 'Associated'}: {new Date(association[dateField]).toLocaleDateString()}
233
+ </div>
234
+ </div>
235
+ <div className={styles.associationActions}>
236
+ {!association[primaryField] && endpoints.setPrimary && (
237
+ <button
238
+ type="button"
239
+ className={styles.setPrimaryBtn}
240
+ onClick={() => handleSetPrimary(association.id)}
241
+ >
242
+ {config.labels?.setPrimary || 'Set Primary'}
243
+ </button>
244
+ )}
245
+ <button
246
+ type="button"
247
+ className={styles.removeBtn}
248
+ onClick={() => handleDissociate(association.id)}
249
+ >
250
+ {config.labels?.remove || 'Remove'}
251
+ </button>
252
+ </div>
253
+ </div>
254
+ );
255
+ })}
256
+ </div>
257
+ )}
258
+ </div>
259
+
260
+ {/* Add New Association */}
261
+ <div className={styles.newAssociation}>
262
+ <h4>{config.labels?.addNew || 'Add New Association'}</h4>
263
+ <div className={styles.associationForm}>
264
+ {config.fields && config.fields.map((field, index) => {
265
+ const fieldValue = newAssociation[field.id] || '';
266
+
267
+ return (
268
+ <div key={field.id} className={styles.formRow}>
269
+ <div className={styles.formField}>
270
+ <label>
271
+ {field.label}{field.required && ' *'}
272
+ </label>
273
+
274
+ {field.type === 'multi-select' && (
275
+ <MultiSelect
276
+ settings={{ id: field.id }}
277
+ multi={field.multi || false}
278
+ onChange={(value) => setNewAssociation(prev => ({ ...prev, [field.id]: value }))}
279
+ inputValue={fieldValue}
280
+ options={availableOptions}
281
+ placeholder={field.placeholder || `Select ${field.label}...`}
282
+ />
283
+ )}
284
+
285
+ {field.type === 'select' && (
286
+ <select
287
+ value={fieldValue}
288
+ onChange={(e) => setNewAssociation(prev => ({
289
+ ...prev,
290
+ [field.id]: e.target.value
291
+ }))}
292
+ className={styles.selectField}
293
+ >
294
+ {field.options && field.options.map(option => (
295
+ <option key={option.value || option} value={option.value || option}>
296
+ {option.label || option}
297
+ </option>
298
+ ))}
299
+ </select>
300
+ )}
301
+
302
+ {field.type === 'boolean' && (
303
+ <label>
304
+ <input
305
+ type="checkbox"
306
+ checked={fieldValue}
307
+ onChange={(e) => setNewAssociation(prev => ({
308
+ ...prev,
309
+ [field.id]: e.target.checked
310
+ }))}
311
+ />
312
+ {field.checkboxLabel || field.label}
313
+ </label>
314
+ )}
315
+
316
+ {field.type === 'textarea' && (
317
+ <textarea
318
+ value={fieldValue}
319
+ onChange={(e) => setNewAssociation(prev => ({
320
+ ...prev,
321
+ [field.id]: e.target.value
322
+ }))}
323
+ placeholder={field.placeholder || ''}
324
+ className={styles.textareaField}
325
+ rows={field.rows || 3}
326
+ />
327
+ )}
328
+
329
+ {field.type === 'text' && (
330
+ <input
331
+ type="text"
332
+ value={fieldValue}
333
+ onChange={(e) => setNewAssociation(prev => ({
334
+ ...prev,
335
+ [field.id]: e.target.value
336
+ }))}
337
+ placeholder={field.placeholder || ''}
338
+ className={styles.textField}
339
+ />
340
+ )}
341
+ </div>
342
+ </div>
343
+ );
344
+ })}
345
+
346
+ <div className={styles.formActions}>
347
+ <button
348
+ type="button"
349
+ className={styles.associateBtn}
350
+ onClick={handleAssociate}
351
+ disabled={!newAssociation[config.fields?.find(f => f.required)?.id || 'id']}
352
+ >
353
+ {config.labels?.addButton || 'Add Association'}
354
+ </button>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+ );
360
+ };
361
+
362
+ export default ClientAssociationManager;
@@ -42,30 +42,31 @@ import { toast } from 'react-toastify';
42
42
  import fetchUtil from '../../utils/fetchUtil';
43
43
  import { confirmDialog } from '../utils/ConfirmDialog';
44
44
  import {
45
- Alarm,
46
- ArrowCounterClockwise,
47
- ArrowCycle,
48
- Backspace,
45
+ AlarmClock,
46
+ RotateCcw,
47
+ RefreshCw,
48
+ Trash2,
49
49
  BookOpen,
50
50
  Check,
51
- CircleCheck,
51
+ CheckCircle,
52
52
  Clock,
53
- CloudDownload,
53
+ Download as DownloadIcon,
54
+ Upload,
54
55
  Copy,
55
56
  Edit,
56
- Envelope,
57
+ Mail,
57
58
  File,
58
59
  Folder,
59
60
  Image,
60
61
  Inbox,
61
- LinkOut,
62
- LockOn,
62
+ ExternalLink,
63
+ Lock,
63
64
  Network,
64
- Ribbon,
65
- ShippingBoxV1,
66
- SoundOn,
67
- TriangleAlert,
68
- } from 'akar-icons';
65
+ Award,
66
+ Archive,
67
+ Volume2,
68
+ AlertTriangle,
69
+ } from 'lucide-react';
69
70
  import styles from './styles/DataGrid.module.scss';
70
71
 
71
72
  import '@visns-studio/visns-datagrid-enterprise/index.css';
@@ -1710,7 +1711,7 @@ const DataGrid = forwardRef(
1710
1711
  let IconComponent = null;
1711
1712
 
1712
1713
  if (icon === 'archive') {
1713
- IconComponent = ShippingBoxV1;
1714
+ IconComponent = Archive;
1714
1715
  } else if (icon === 'bookOpen') {
1715
1716
  IconComponent = BookOpen;
1716
1717
  } else if (icon === 'folder') {
@@ -1720,7 +1721,7 @@ const DataGrid = forwardRef(
1720
1721
  } else if (icon === 'network') {
1721
1722
  IconComponent = Network;
1722
1723
  } else if (icon === 'urgent') {
1723
- IconComponent = TriangleAlert;
1724
+ IconComponent = AlertTriangle;
1724
1725
  }
1725
1726
 
1726
1727
  if (IconComponent) {
@@ -2185,15 +2186,15 @@ const DataGrid = forwardRef(
2185
2186
  if (allow) {
2186
2187
  switch (s.id) {
2187
2188
  case 'activate':
2188
- return getIconComponent(CircleCheck);
2189
+ return getIconComponent(CheckCircle);
2189
2190
  case 'archive':
2190
- return getIconComponent(ShippingBoxV1);
2191
+ return getIconComponent(Archive);
2191
2192
  case 'arrowCycle':
2192
- return getIconComponent(ArrowCycle);
2193
+ return getIconComponent(RefreshCw);
2193
2194
  case 'cloudUpload':
2194
- return getIconComponent(CloudUpload);
2195
+ return getIconComponent(Upload);
2195
2196
  case 'oauth2':
2196
- return getIconComponent(LockOn);
2197
+ return getIconComponent(Lock);
2197
2198
  case 'clone':
2198
2199
  return getIconComponent(Copy);
2199
2200
  case 'complete':
@@ -2201,9 +2202,9 @@ const DataGrid = forwardRef(
2201
2202
  case 'copy':
2202
2203
  return getIconComponent(Copy);
2203
2204
  case 'delete':
2204
- return getIconComponent(Backspace);
2205
+ return getIconComponent(Trash2);
2205
2206
  case 'envelope':
2206
- return getIconComponent(Envelope);
2207
+ return getIconComponent(Mail);
2207
2208
  case 'family':
2208
2209
  return getIconComponent(Network);
2209
2210
  case 'file':
@@ -2213,17 +2214,17 @@ const DataGrid = forwardRef(
2213
2214
  case 'image':
2214
2215
  return getIconComponent(Image);
2215
2216
  case 'link':
2216
- return getIconComponent(LinkOut);
2217
+ return getIconComponent(ExternalLink);
2217
2218
  case 'primary':
2218
- return getIconComponent(Ribbon);
2219
+ return getIconComponent(Award);
2219
2220
  case 'restore':
2220
- return getIconComponent(ArrowCounterClockwise);
2221
+ return getIconComponent(RotateCcw);
2221
2222
  case 'sound':
2222
- return getIconComponent(SoundOn);
2223
+ return getIconComponent(Volume2);
2223
2224
  case 'ssa':
2224
2225
  return getIconComponent(Clock);
2225
2226
  case 'undo':
2226
- return getIconComponent(ArrowCounterClockwise);
2227
+ return getIconComponent(RotateCcw);
2227
2228
  case 'update':
2228
2229
  return getIconComponent(Edit);
2229
2230
  default:
@@ -3078,6 +3079,7 @@ const DataGrid = forwardRef(
3078
3079
  {...tableSetting}
3079
3080
  columns={gridColumns}
3080
3081
  dataSource={dataSource}
3082
+ defaultFilterValue={filterValue}
3081
3083
  {...(ajaxSetting && ajaxSetting.groupBy
3082
3084
  ? {
3083
3085
  defaultGroupBy: ajaxSetting.groupBy,
@@ -3258,13 +3260,13 @@ const DataGrid = forwardRef(
3258
3260
  case 'clock':
3259
3261
  return Clock;
3260
3262
  case 'alarm':
3261
- return Alarm;
3263
+ return AlarmClock;
3262
3264
  case 'check':
3263
3265
  return Check;
3264
3266
  case 'edit':
3265
3267
  return Edit;
3266
3268
  case 'envelope':
3267
- return Envelope;
3269
+ return Mail;
3268
3270
  case 'file':
3269
3271
  return File;
3270
3272
  case 'image':
@@ -3272,9 +3274,9 @@ const DataGrid = forwardRef(
3272
3274
  case 'copy':
3273
3275
  return Copy;
3274
3276
  case 'cloudUpload':
3275
- return CloudDownload;
3277
+ return Upload;
3276
3278
  case 'cloudDownload':
3277
- return CloudDownload;
3279
+ return DownloadIcon;
3278
3280
  default:
3279
3281
  return Clock; // Default fallback
3280
3282
  }
@@ -15,15 +15,15 @@ import {
15
15
  Edit,
16
16
  ChevronRight,
17
17
  Circle,
18
- CircleFill,
18
+ Dot,
19
19
  CirclePlus,
20
- CloudDownload,
20
+ Download,
21
21
  Copy,
22
22
  Minus,
23
- ArrowBack,
24
- TrashCan,
25
- CircleX,
26
- } from 'akar-icons';
23
+ ArrowLeft,
24
+ Trash2,
25
+ X,
26
+ } from 'lucide-react';
27
27
  import { toast } from 'react-toastify';
28
28
  import styles from './styles/Field.module.scss';
29
29
 
@@ -835,7 +835,7 @@ function Field({
835
835
  );
836
836
  }}
837
837
  >
838
- <TrashCan
838
+ <Trash2
839
839
  strokeWidth={2}
840
840
  size={20}
841
841
  />
@@ -903,7 +903,7 @@ function Field({
903
903
  onFileDownload(inputValue);
904
904
  }}
905
905
  >
906
- <CloudDownload size={24} />
906
+ <Download size={24} />
907
907
  </div>
908
908
  )}
909
909
  </div>
@@ -2142,7 +2142,7 @@ function Field({
2142
2142
  className={styles.modal__close}
2143
2143
  onClick={() => setCanvasPopupOpen(false)}
2144
2144
  >
2145
- <CircleX size={24} color="#FFF" />
2145
+ <X size={24} color="#FFF" />
2146
2146
  </button>
2147
2147
  </div>
2148
2148
  <div
@@ -2157,7 +2157,7 @@ function Field({
2157
2157
  }}
2158
2158
  className={styles['drawactions--save']}
2159
2159
  >
2160
- <CloudDownload size={16} /> Save
2160
+ <Download size={16} /> Save
2161
2161
  </button>
2162
2162
  </li>
2163
2163
  <li>
@@ -2218,7 +2218,7 @@ function Field({
2218
2218
  onClick={() => sketchRef.current.undo()}
2219
2219
  className={styles['drawaction-undo']}
2220
2220
  >
2221
- <ArrowBack size={16} /> Undo
2221
+ <ArrowLeft size={16} /> Undo
2222
2222
  </button>
2223
2223
  </li>
2224
2224
  <li>
@@ -2228,7 +2228,7 @@ function Field({
2228
2228
  }
2229
2229
  className={styles['drawactions--clear']}
2230
2230
  >
2231
- <TrashCan size={16} /> Clear
2231
+ <Trash2 size={16} /> Clear
2232
2232
  </button>
2233
2233
  </li>
2234
2234
  </ul>
@@ -9,7 +9,7 @@ import moment from 'moment';
9
9
  import parse from 'html-react-parser';
10
10
  import _ from 'lodash';
11
11
  import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
12
- import { CircleX } from 'akar-icons';
12
+ import { X } from 'lucide-react';
13
13
  import imageCompression from 'browser-image-compression';
14
14
  import { confirmDialog } from '../utils/ConfirmDialog';
15
15
 
@@ -2231,7 +2231,7 @@ function Form({
2231
2231
  className={styles.modal__close}
2232
2232
  onClick={closeModal}
2233
2233
  >
2234
- <CircleX strokeWidth={2} size={24} />
2234
+ <X strokeWidth={2} size={24} />
2235
2235
  </button>
2236
2236
  </div>
2237
2237
  <div className={styles.modal__content}>