@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.
- package/package.json +2 -2
- package/src/components/cms/DataGrid.jsx +8 -8
- package/src/components/cms/DropZone.jsx +1 -1
- package/src/components/cms/Form.jsx +1 -1
- package/src/components/cms/Gallery.jsx +1 -1
- package/src/components/cms/sorting/Item.jsx +1 -1
- package/src/components/crm/AssociationManager.jsx +431 -0
- package/src/components/crm/Autocomplete.jsx +3 -3
- package/src/components/crm/Breadcrumb.jsx +4 -4
- package/src/components/crm/BusinessCardOcr.jsx +681 -95
- package/src/components/crm/ClientAssociationManager.jsx +362 -0
- package/src/components/crm/DataGrid.jsx +34 -32
- package/src/components/crm/Field.jsx +12 -12
- package/src/components/crm/Form.jsx +2 -2
- package/src/components/crm/MergeEntity.jsx +1049 -0
- package/src/components/crm/MultiSelect.jsx +14 -3
- package/src/components/crm/Navigation.jsx +14 -15
- package/src/components/crm/Notification.jsx +2 -2
- package/src/components/crm/QuickAction.jsx +3 -3
- package/src/components/crm/SectionHeader.jsx +1 -1
- package/src/components/crm/SwitchAccount.jsx +4 -4
- package/src/components/crm/auth/ClientLogin.jsx +2 -2
- package/src/components/crm/auth/ClientOTPVerify.jsx +2 -2
- package/src/components/crm/auth/Login.jsx +3 -3
- package/src/components/crm/auth/Reset.jsx +2 -2
- package/src/components/crm/auth/TwoFactorAuth.jsx +2 -2
- package/src/components/crm/cells/CellWithTooltip.jsx +35 -28
- package/src/components/crm/columns/ColumnRenderers.jsx +320 -259
- package/src/components/crm/controls/DataGridSearch.jsx +5 -5
- package/src/components/crm/generic/AlternativeActionModal.jsx +100 -0
- package/src/components/crm/generic/ContactSelectorModal.jsx +59 -16
- package/src/components/crm/generic/DateRangeSelectorModal.jsx +181 -0
- package/src/components/crm/generic/GenericClientPortal.jsx +1 -1
- package/src/components/crm/generic/GenericDashboard.jsx +4 -4
- package/src/components/crm/generic/GenericDetail.jsx +31 -6
- package/src/components/crm/generic/GenericDynamic.jsx +3 -3
- package/src/components/crm/generic/GenericEditableTable.jsx +4 -4
- package/src/components/crm/generic/GenericFormBuilder.jsx +6 -6
- package/src/components/crm/generic/GenericGrid.jsx +1622 -268
- package/src/components/crm/generic/GenericReport.jsx +966 -646
- package/src/components/crm/generic/GenericReportForm.jsx +1 -1
- package/src/components/crm/generic/NotificationList.jsx +1 -1
- package/src/components/crm/generic/ReasonCollectorModal.jsx +194 -0
- package/src/components/crm/generic/SortableQuoteItems.jsx +3 -3
- package/src/components/crm/generic/shared/formatters.js +107 -1
- package/src/components/crm/generic/styles/AlternativeActionModal.css +127 -0
- package/src/components/crm/generic/styles/DateRangeSelectorModal.css +115 -0
- package/src/components/crm/generic/styles/GenericIndex.module.scss +206 -0
- package/src/components/crm/generic/styles/GenericReport.module.scss +96 -0
- package/src/components/crm/generic/styles/ReasonCollectorModal.css +217 -0
- package/src/components/crm/modals/GalleryModal.jsx +2 -2
- package/src/components/crm/sorting/Item.jsx +3 -3
- package/src/components/crm/styles/AssociationManager.module.scss +364 -0
- package/src/components/crm/styles/BusinessCardOcr.module.scss +197 -4
- package/src/components/crm/styles/ClientAssociationManager.module.scss +290 -0
- package/src/components/crm/styles/MergeEntity.module.scss +690 -0
- package/src/components/crm/styles/MultiSelect.module.scss +18 -0
- package/src/components/crm/styles/Navigation.module.scss +68 -8
- package/src/components/crm/styles/global-datagrid.css +9 -0
- 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
|
-
|
|
5
|
+
ExternalLink,
|
|
6
6
|
Plus,
|
|
7
7
|
Search,
|
|
8
|
-
|
|
9
|
-
} from '
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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 '
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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 '
|
|
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 {
|
|
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
|
-
<
|
|
1059
|
+
<DownloadIcon
|
|
1060
1060
|
strokeWidth={
|
|
1061
1061
|
2
|
|
1062
1062
|
}
|
|
@@ -1077,7 +1077,7 @@ const GenericDynamic = ({
|
|
|
1077
1077
|
)
|
|
1078
1078
|
}
|
|
1079
1079
|
>
|
|
1080
|
-
<
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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 '
|
|
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';
|