@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
@@ -2,11 +2,11 @@ import { useNavigate } from 'react-router-dom';
2
2
  import { toast } from 'react-toastify';
3
3
  import { saveAs } from 'file-saver';
4
4
  import {
5
- LinkOut,
5
+ ExternalLink,
6
6
  Plus,
7
7
  Search,
8
- Sort,
9
- } from 'akar-icons';
8
+ ArrowUpDown,
9
+ } from 'lucide-react';
10
10
  import Download from '../Download';
11
11
  import styles from '../styles/DataGrid.module.scss';
12
12
 
@@ -100,7 +100,7 @@ const DataGridSearch = ({
100
100
  <div className={buttonClassName}>
101
101
  {form.export && form.export.url && (
102
102
  <button className={styles.btn} onClick={exportTrigger}>
103
- <LinkOut
103
+ <ExternalLink
104
104
  strokeWidth={2}
105
105
  size={16}
106
106
  style={{ marginRight: '10px' }}
@@ -110,7 +110,7 @@ const DataGridSearch = ({
110
110
  )}
111
111
  {form.sort && form.sort.title && (
112
112
  <button className={styles.btn} onClick={sortTrigger}>
113
- <Sort
113
+ <ArrowUpDown
114
114
  strokeWidth={2}
115
115
  size={16}
116
116
  style={{ marginRight: '10px' }}
@@ -0,0 +1,100 @@
1
+ import Swal from 'sweetalert2';
2
+ import './styles/SweetAlert.module.css';
3
+ import './styles/AlternativeActionModal.css';
4
+
5
+ /**
6
+ * Show an alternative action modal for choosing between different workflows
7
+ * @param {Object} options - Configuration options
8
+ * @param {string} options.title - Modal title
9
+ * @param {string} options.message - Modal message
10
+ * @param {Array} options.alternatives - Array of alternative actions
11
+ * @param {Object} options.data - Additional data to pass to callbacks
12
+ * @param {Function} options.onSelect - Callback when alternative is selected
13
+ * @param {Function} options.onCancel - Callback when cancelled
14
+ */
15
+ export const showAlternativeActionModal = async ({
16
+ title = 'Choose Action',
17
+ message = 'Please select how you would like to proceed:',
18
+ alternatives = [],
19
+ data = {},
20
+ onSelect,
21
+ onCancel
22
+ }) => {
23
+ console.log('Alternative Action Modal - Configuration:', {
24
+ title,
25
+ alternatives: alternatives.map(alt => ({ id: alt.id, label: alt.label, workflow: alt.workflow })),
26
+ data
27
+ });
28
+
29
+ // Generate HTML for alternatives
30
+ const alternativesHtml = alternatives.map(alternative => {
31
+ const icon = alternative.icon || '📋';
32
+ const description = alternative.description || '';
33
+
34
+ return `
35
+ <div class="alternative-action-option" data-alternative-id="${alternative.id}">
36
+ <div class="alternative-icon">${icon}</div>
37
+ <div class="alternative-content">
38
+ <div class="alternative-label">${alternative.label}</div>
39
+ ${description ? `<div class="alternative-description">${description}</div>` : ''}
40
+ </div>
41
+ </div>
42
+ `;
43
+ }).join('');
44
+
45
+ const result = await Swal.fire({
46
+ title: title,
47
+ html: `
48
+ <div class="alternative-action-modal">
49
+ <p class="alternative-message">${message}</p>
50
+ <div class="alternative-options">
51
+ ${alternativesHtml}
52
+ </div>
53
+ </div>
54
+ `,
55
+ showConfirmButton: false,
56
+ showCancelButton: true,
57
+ cancelButtonText: 'Cancel',
58
+ customClass: {
59
+ container: 'alternative-swal-container',
60
+ popup: 'alternative-swal-popup',
61
+ content: 'alternative-swal-content',
62
+ cancelButton: 'alternative-swal-cancel'
63
+ },
64
+ didOpen: () => {
65
+ // Add click handlers for each alternative
66
+ const options = document.querySelectorAll('.alternative-action-option');
67
+ options.forEach(option => {
68
+ option.addEventListener('click', () => {
69
+ const alternativeId = option.getAttribute('data-alternative-id');
70
+ const selectedAlternative = alternatives.find(alt => alt.id === alternativeId);
71
+
72
+ if (selectedAlternative) {
73
+ console.log('Alternative selected:', selectedAlternative);
74
+ Swal.close();
75
+
76
+ // Call the onSelect callback with the selected alternative
77
+ if (onSelect) {
78
+ onSelect(selectedAlternative);
79
+ }
80
+ }
81
+ });
82
+
83
+ // Add hover effects
84
+ option.addEventListener('mouseenter', () => {
85
+ option.style.backgroundColor = '#f8f9fa';
86
+ option.style.borderColor = '#007bff';
87
+ });
88
+
89
+ option.addEventListener('mouseleave', () => {
90
+ option.style.backgroundColor = '';
91
+ option.style.borderColor = '';
92
+ });
93
+ });
94
+ }
95
+ });
96
+
97
+ if (result.isDismissed && onCancel) {
98
+ onCancel();
99
+ }
100
+ };
@@ -37,6 +37,9 @@ export const showContactSelectorModal = ({
37
37
  urlParams = {},
38
38
  allowManualEntry = true,
39
39
  fields = ['name', 'email'],
40
+ fieldTypes = {},
41
+ fieldLabels = {},
42
+ validation = {},
40
43
  multiple = true,
41
44
  maxContacts = 10
42
45
  } = contactConfig;
@@ -137,7 +140,8 @@ export const showContactSelectorModal = ({
137
140
  <div class="contact-item" data-index="${index}">
138
141
  <div class="contact-info">
139
142
  <strong>${contact.name || 'Unknown Contact'}</strong>
140
- ${contact.email ? `<br><small>${contact.email}</small>` : ''}
143
+ ${contact.email ? `<br><small>📧 ${contact.email}</small>` : ''}
144
+ ${contact.notes ? `<br><small>📝 ${contact.notes.length > 50 ? contact.notes.substring(0, 50) + '...' : contact.notes}</small>` : ''}
141
145
  <span class="contact-type">${contact.type === 'contact' ? 'Existing Contact' : 'Manual Entry'}</span>
142
146
  </div>
143
147
  <button type="button" class="remove-contact-btn" data-index="${index}">×</button>
@@ -148,20 +152,48 @@ export const showContactSelectorModal = ({
148
152
  `<option value="${contact.value}">${contact.label}</option>`
149
153
  ).join('');
150
154
 
151
- const manualFieldsHtml = fields.map(field => `
152
- <div class="form-group">
153
- <div class="form-field-row">
154
- <label for="manual-${field}">${field.charAt(0).toUpperCase() + field.slice(1)}:</label>
155
- <input
156
- type="${field === 'email' ? 'email' : 'text'}"
157
- id="manual-${field}"
158
- class="swal2-input"
159
- placeholder="Enter ${field}"
160
- ${field === 'email' ? 'required' : ''}
161
- >
162
- </div>
163
- </div>
164
- `).join('');
155
+ const manualFieldsHtml = fields.map(field => {
156
+ const fieldType = fieldTypes[field] || (field === 'email' ? 'email' : 'text');
157
+ const fieldLabel = fieldLabels[field] || field.charAt(0).toUpperCase() + field.slice(1);
158
+ const fieldValidation = validation[field] || {};
159
+ const isRequired = fieldValidation.required !== false && (field === 'name' || field === 'email');
160
+ const maxLength = fieldValidation.maxLength || (fieldType === 'textarea' ? 500 : 255);
161
+
162
+ if (fieldType === 'textarea') {
163
+ return `
164
+ <div class="form-group">
165
+ <div class="form-field-row">
166
+ <label for="manual-${field}">${fieldLabel}:</label>
167
+ <textarea
168
+ id="manual-${field}"
169
+ class="swal2-textarea"
170
+ placeholder="Enter ${fieldLabel.toLowerCase()}"
171
+ rows="3"
172
+ maxlength="${maxLength}"
173
+ ${isRequired ? 'required' : ''}
174
+ ></textarea>
175
+ <small class="field-help">Max ${maxLength} characters</small>
176
+ </div>
177
+ </div>
178
+ `;
179
+ } else {
180
+ return `
181
+ <div class="form-group">
182
+ <div class="form-field-row">
183
+ <label for="manual-${field}">${fieldLabel}:</label>
184
+ <input
185
+ type="${fieldType}"
186
+ id="manual-${field}"
187
+ class="swal2-input"
188
+ placeholder="Enter ${fieldLabel.toLowerCase()}"
189
+ maxlength="${maxLength}"
190
+ ${isRequired ? 'required' : ''}
191
+ >
192
+ </div>
193
+ </div>
194
+ `;
195
+ }
196
+ }).join('');
165
197
 
166
198
  return `
167
199
  <div class="contact-selector-modal">
@@ -271,14 +303,25 @@ export const showContactSelectorModal = ({
271
303
  fields.forEach(field => {
272
304
  const input = document.getElementById(`manual-${field}`);
273
305
  const value = input ? input.value.trim() : '';
306
+ const fieldValidation = validation[field] || {};
307
+ const isRequired = fieldValidation.required !== false && (field === 'name' || field === 'email');
274
308
 
309
+ // Validate email format
275
310
  if (field === 'email' && value && !isValidEmail(value)) {
276
311
  isValid = false;
277
312
  input.style.borderColor = '#dc2626';
278
313
  return;
279
314
  }
280
315
 
281
- if (field === 'name' && !value) {
316
+ // Validate required fields
317
+ if (isRequired && !value) {
318
+ isValid = false;
319
+ input.style.borderColor = '#dc2626';
320
+ return;
321
+ }
322
+
323
+ // Validate max length
324
+ if (fieldValidation.maxLength && value.length > fieldValidation.maxLength) {
282
325
  isValid = false;
283
326
  input.style.borderColor = '#dc2626';
284
327
  return;
@@ -0,0 +1,181 @@
1
+ import Swal from 'sweetalert2';
2
+ import moment from 'moment';
3
+ import './styles/SweetAlert.module.css';
4
+ import './styles/DateRangeSelectorModal.css';
5
+
6
+ /**
7
+ * Show a date range selector modal for selecting start and end dates
8
+ * @param {Object} options - Configuration options
9
+ * @param {string} options.title - Modal title
10
+ * @param {string} options.message - Modal message
11
+ * @param {string} options.confirmLabel - Confirm button label
12
+ * @param {string} options.cancelLabel - Cancel button label
13
+ * @param {string|null} options.defaultStartDate - Default start date value
14
+ * @param {string|null} options.defaultEndDate - Default end date value
15
+ * @param {Object} options.data - Additional data to pass to callbacks
16
+ * @param {string} options.timezone - Timezone for date handling
17
+ * @param {boolean} options.forcePerth - Force Australia/Perth timezone
18
+ * @param {boolean} options.dateOnly - Return date only (no time conversion)
19
+ * @param {Function} options.onConfirm - Callback when confirmed
20
+ * @param {Function} options.onCancel - Callback when cancelled
21
+ */
22
+ export const showDateRangeSelectorModal = async ({
23
+ title = 'Select Date Range',
24
+ message = 'Please select start and end dates:',
25
+ confirmLabel = 'Next: Select Contacts',
26
+ cancelLabel = 'Cancel',
27
+ defaultStartDate = null,
28
+ defaultEndDate = null,
29
+ data = {},
30
+ timezone = 'auto',
31
+ forcePerth = false,
32
+ dateOnly = false,
33
+ onConfirm,
34
+ onCancel
35
+ }) => {
36
+ // Determine timezone
37
+ let userTimezone = 'UTC';
38
+ if (forcePerth) {
39
+ userTimezone = 'Australia/Perth';
40
+ } else if (timezone === 'auto') {
41
+ userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
42
+ } else if (timezone && timezone !== 'auto') {
43
+ userTimezone = timezone;
44
+ }
45
+
46
+ console.log('Date Range Selector - Timezone Configuration:', {
47
+ inputTimezone: timezone,
48
+ forcePerth,
49
+ resolvedTimezone: userTimezone,
50
+ dateOnly
51
+ });
52
+
53
+ // Convert UTC dates to local timezone for display
54
+ const convertUTCToLocal = (utcDateString) => {
55
+ if (!utcDateString || utcDateString === 'now') return '';
56
+
57
+ try {
58
+ const utcMoment = moment.utc(utcDateString);
59
+ if (!utcMoment.isValid()) return '';
60
+
61
+ if (dateOnly) {
62
+ // For DATE fields, just return the date part
63
+ return utcMoment.format('YYYY-MM-DD');
64
+ } else {
65
+ // For DATETIME fields, convert to local timezone
66
+ const localMoment = utcMoment.clone().tz(userTimezone);
67
+ return localMoment.format('YYYY-MM-DD');
68
+ }
69
+ } catch (error) {
70
+ console.error('Error converting UTC to local:', error);
71
+ return '';
72
+ }
73
+ };
74
+
75
+ // Convert local date to UTC for API
76
+ const convertLocalToUTC = (localDateString) => {
77
+ if (!localDateString) return null;
78
+
79
+ try {
80
+ if (dateOnly) {
81
+ // For DATE fields, return just the date string
82
+ return localDateString;
83
+ } else {
84
+ // For DATETIME fields, convert to UTC
85
+ const localMoment = moment.tz(localDateString, userTimezone);
86
+ return localMoment.utc().toISOString();
87
+ }
88
+ } catch (error) {
89
+ console.error('Error converting local to UTC:', error);
90
+ return localDateString;
91
+ }
92
+ };
93
+
94
+ // Prepare default values
95
+ const displayStartDate = convertUTCToLocal(defaultStartDate);
96
+ const displayEndDate = convertUTCToLocal(defaultEndDate);
97
+
98
+ const timezoneLabel = userTimezone === 'Australia/Perth' ? ' (Perth Time)' : userTimezone !== 'UTC' ? ` (${userTimezone})` : '';
99
+
100
+ const result = await Swal.fire({
101
+ title: title,
102
+ html: `
103
+ <div class="date-range-selector-modal">
104
+ <p class="date-range-message">${message}</p>
105
+ <div class="date-range-inputs">
106
+ <div class="date-input-group">
107
+ <label for="start-date-input" class="date-input-label">
108
+ Start Date${timezoneLabel}:
109
+ </label>
110
+ <input
111
+ type="date"
112
+ id="start-date-input"
113
+ class="date-range-input"
114
+ value="${displayStartDate}"
115
+ />
116
+ </div>
117
+ <div class="date-input-group">
118
+ <label for="end-date-input" class="date-input-label">
119
+ End Date${timezoneLabel}:
120
+ </label>
121
+ <input
122
+ type="date"
123
+ id="end-date-input"
124
+ class="date-range-input"
125
+ value="${displayEndDate}"
126
+ />
127
+ </div>
128
+ </div>
129
+ </div>
130
+ `,
131
+ showCancelButton: true,
132
+ confirmButtonText: confirmLabel,
133
+ cancelButtonText: cancelLabel,
134
+ customClass: {
135
+ container: 'date-range-swal-container',
136
+ popup: 'date-range-swal-popup',
137
+ content: 'date-range-swal-content',
138
+ confirmButton: 'date-range-swal-confirm',
139
+ cancelButton: 'date-range-swal-cancel'
140
+ },
141
+ preConfirm: () => {
142
+ const startDateInput = document.getElementById('start-date-input');
143
+ const endDateInput = document.getElementById('end-date-input');
144
+ const startDate = startDateInput.value;
145
+ const endDate = endDateInput.value;
146
+
147
+ if (!startDate) {
148
+ Swal.showValidationMessage('Please select a start date');
149
+ return false;
150
+ }
151
+
152
+ if (!endDate) {
153
+ Swal.showValidationMessage('Please select an end date');
154
+ return false;
155
+ }
156
+
157
+ if (moment(startDate).isAfter(moment(endDate))) {
158
+ Swal.showValidationMessage('Start date must be before end date');
159
+ return false;
160
+ }
161
+
162
+ console.log('Date Range Selected:', {
163
+ startDate: startDate,
164
+ endDate: endDate,
165
+ timezone: userTimezone,
166
+ dateOnly: dateOnly
167
+ });
168
+
169
+ return {
170
+ startDate: convertLocalToUTC(startDate),
171
+ endDate: convertLocalToUTC(endDate)
172
+ };
173
+ }
174
+ });
175
+
176
+ if (result.isConfirmed && onConfirm) {
177
+ await onConfirm(result.value.startDate, result.value.endDate);
178
+ } else if (result.isDismissed && onCancel) {
179
+ onCancel();
180
+ }
181
+ };
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Routes, Route, useNavigate } from 'react-router-dom';
3
3
  import { toast } from 'react-toastify';
4
- import { SignOut } from 'akar-icons';
4
+ import { LogOut as SignOut } from 'lucide-react';
5
5
 
6
6
  import CustomFetch from '../Fetch';
7
7
 
@@ -8,7 +8,7 @@ import Popup from 'reactjs-popup';
8
8
  import dayjs from 'dayjs';
9
9
  import parse from 'html-react-parser';
10
10
  import { createSwapy } from 'swapy';
11
- import { CircleX, Minus, Plus, Star, TrashCan } from 'akar-icons';
11
+ import { X, Minus, Plus, Star, Trash2 } from 'lucide-react';
12
12
  import { confirmDialog } from '../../utils/ConfirmDialog';
13
13
 
14
14
  import CustomFetch from '../../crm/Fetch';
@@ -1320,7 +1320,7 @@ function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
1320
1320
  <button
1321
1321
  onClick={() => confirmDelete(widget.id)}
1322
1322
  >
1323
- <TrashCan strokeWidth={2} size={12} />
1323
+ <Trash2 strokeWidth={2} size={12} />
1324
1324
  </button>
1325
1325
  </div>
1326
1326
  </div>
@@ -1370,7 +1370,7 @@ function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
1370
1370
  className="modal__close"
1371
1371
  onClick={closeModal}
1372
1372
  >
1373
- <CircleX strokeWidth={1} size={24} />
1373
+ <X strokeWidth={1} size={24} />
1374
1374
  </button>
1375
1375
  </div>
1376
1376
  <div className="modal__content">
@@ -1397,7 +1397,7 @@ function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
1397
1397
  className="modal__close"
1398
1398
  onClick={() => setShowAddWidgetModal(false)}
1399
1399
  >
1400
- <CircleX strokeWidth={1} size={24} />
1400
+ <X strokeWidth={1} size={24} />
1401
1401
  </button>
1402
1402
  </div>
1403
1403
  <div className="modal__content">
@@ -49,24 +49,26 @@ import { toast } from 'react-toastify';
49
49
  import imageCompression from 'browser-image-compression';
50
50
  import fetchUtil from '../../../utils/fetchUtil';
51
51
  import {
52
- CircleCheck,
52
+ CheckCircle as CircleCheck,
53
53
  Copy,
54
- EyeOpen,
55
- EyeSlashed,
54
+ Eye as EyeOpen,
55
+ EyeOff as EyeSlashed,
56
56
  File,
57
- Pencil,
58
- TrashCan,
59
- } from 'akar-icons';
57
+ Edit as Pencil,
58
+ Trash2 as TrashCan,
59
+ } from 'lucide-react';
60
60
  import { confirmDialog } from '../../utils/ConfirmDialog';
61
61
 
62
62
  import 'react-big-calendar/lib/css/react-big-calendar.css';
63
63
  import 'react-big-calendar/lib/addons/dragAndDrop/styles.css';
64
64
 
65
65
  import Breadcrumb from '../Breadcrumb';
66
+ import AssociationManager from '../AssociationManager';
66
67
  import CustomFetch from '../Fetch';
67
68
  import Form from '../Form';
68
69
  import GenericDynamic from './GenericDynamic';
69
70
  import GenericEditableTable from './GenericEditableTable';
71
+ import MergeEntity from '../MergeEntity';
70
72
  import MultiSelect from '../MultiSelect';
71
73
  import QrCode from '../QrCode';
72
74
  import Table from '../DataGrid';
@@ -2490,6 +2492,29 @@ function GenericDetail({
2490
2492
  setData={setData}
2491
2493
  />
2492
2494
  );
2495
+ case 'custom':
2496
+ // Handle custom components
2497
+ if (activeTabConfig.component === 'AssociationManager' || activeTabConfig.component === 'ClientAssociationManager') {
2498
+ return (
2499
+ <AssociationManager
2500
+ endpoints={activeTabConfig.endpoints}
2501
+ config={activeTabConfig.config}
2502
+ dataId={routeParams[urlParam]}
2503
+ onUpdate={handleReload}
2504
+ />
2505
+ );
2506
+ }
2507
+ if (activeTabConfig.component === 'MergeEntity') {
2508
+ return (
2509
+ <MergeEntity
2510
+ endpoints={activeTabConfig.endpoints}
2511
+ config={activeTabConfig.config}
2512
+ dataId={routeParams[urlParam]}
2513
+ onUpdate={handleReload}
2514
+ />
2515
+ );
2516
+ }
2517
+ return null;
2493
2518
  default:
2494
2519
  return null;
2495
2520
  }
@@ -3,7 +3,7 @@ import moment from 'moment';
3
3
  import { toast } from 'react-toastify';
4
4
  import { saveAs } from 'file-saver';
5
5
  import parse from 'html-react-parser';
6
- import { Backspace, CloudDownload } from 'akar-icons';
6
+ import { Delete, Download as DownloadIcon } from 'lucide-react';
7
7
  import { confirmDialog } from '../../utils/ConfirmDialog';
8
8
 
9
9
  import CustomFetch from '../Fetch';
@@ -1056,7 +1056,7 @@ const GenericDynamic = ({
1056
1056
  )
1057
1057
  }
1058
1058
  >
1059
- <CloudDownload
1059
+ <DownloadIcon
1060
1060
  strokeWidth={
1061
1061
  2
1062
1062
  }
@@ -1077,7 +1077,7 @@ const GenericDynamic = ({
1077
1077
  )
1078
1078
  }
1079
1079
  >
1080
- <Backspace
1080
+ <Delete
1081
1081
  strokeWidth={
1082
1082
  2
1083
1083
  }
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import ReactDOM from 'react-dom';
3
3
  import debounce from 'lodash.debounce';
4
4
  import { toast } from 'react-toastify';
5
- import { TrashCan, ArrowUp, ArrowDown, Copy, Water } from 'akar-icons';
5
+ import { Trash2, ArrowUp, ArrowDown, Copy, Droplets } from 'lucide-react';
6
6
  import { CompactPicker } from 'react-color';
7
7
  import { confirmDialog } from '../../utils/ConfirmDialog';
8
8
 
@@ -65,7 +65,7 @@ const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
65
65
  setColorPickerShow(!colorPickerShow);
66
66
  }}
67
67
  >
68
- <Water size={20} style={{ color: 'black' }} />
68
+ <Droplets size={20} style={{ color: 'black' }} />
69
69
  </button>
70
70
 
71
71
  {colorPickerShow &&
@@ -1059,7 +1059,7 @@ const GenericEditableTable = ({
1059
1059
  }}
1060
1060
  />
1061
1061
  )}
1062
- <TrashCan
1062
+ <Trash2
1063
1063
  size={16}
1064
1064
  onClick={() =>
1065
1065
  handleDeleteRow(
@@ -1199,7 +1199,7 @@ const GenericEditableTable = ({
1199
1199
  style={{ cursor: 'pointer' }}
1200
1200
  />
1201
1201
  )}
1202
- <TrashCan
1202
+ <Trash2
1203
1203
  size={16}
1204
1204
  onClick={() =>
1205
1205
  handleDeleteRow(entry)
@@ -28,12 +28,12 @@ import { v4 as uuidv4 } from 'uuid';
28
28
  import { Editor } from '@tinymce/tinymce-react';
29
29
  import { Tooltip } from 'react-tooltip';
30
30
  import {
31
- CirclePlus,
32
- CircleX,
33
- TrashCan,
34
- Pencil,
35
- ChevronVertical,
36
- } from 'akar-icons';
31
+ PlusCircle as CirclePlus,
32
+ X as CircleX,
33
+ Trash2 as TrashCan,
34
+ Edit as Pencil,
35
+ GripVertical as ChevronVertical,
36
+ } from 'lucide-react';
37
37
  import { confirmDialog } from '../../utils/ConfirmDialog';
38
38
 
39
39
  import 'react-toggle/style.css';