@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
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { toast } from 'react-toastify';
3
3
  import { saveAs } from 'file-saver';
4
- import { CloudDownload } from 'akar-icons';
4
+ import { Download as CloudDownload } from 'lucide-react';
5
5
 
6
6
  import Download from '../Download';
7
7
  import Field from '../Field';
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useCallback } from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
3
  import Table from '../DataGrid';
4
- import { Bell, Check, ArrowLeft } from 'akar-icons';
4
+ import { Bell, Check, ArrowLeft } from 'lucide-react';
5
5
  import { toast } from 'react-toastify';
6
6
  import CustomFetch from '../Fetch';
7
7
  import styles from './styles/NotificationList.module.scss';
@@ -0,0 +1,194 @@
1
+ import Swal from 'sweetalert2';
2
+ import './styles/SweetAlert.module.css';
3
+ import './styles/ReasonCollectorModal.css';
4
+
5
+ /**
6
+ * Show a reason collector modal for collecting "not sent" reasons
7
+ * @param {Object} options - Configuration options
8
+ * @param {string} options.title - Modal title
9
+ * @param {string} options.message - Modal message
10
+ * @param {string} options.confirmLabel - Confirm button label
11
+ * @param {string} options.cancelLabel - Cancel button label
12
+ * @param {Array} options.reasonOptions - Array of predefined reason options
13
+ * @param {boolean} options.allowCustomReason - Allow custom reason input
14
+ * @param {boolean} options.reasonRequired - Whether reason is required
15
+ * @param {string} options.placeholder - Placeholder for custom reason input
16
+ * @param {string} options.defaultReason - Default selected reason
17
+ * @param {Object} options.data - Additional data to pass to callbacks
18
+ * @param {Function} options.onConfirm - Callback when confirmed
19
+ * @param {Function} options.onCancel - Callback when cancelled
20
+ */
21
+ export const showReasonCollectorModal = async ({
22
+ title = 'Provide Reason',
23
+ message = 'Please provide a reason:',
24
+ confirmLabel = 'Save',
25
+ cancelLabel = 'Cancel',
26
+ reasonOptions = [
27
+ 'Survey not applicable',
28
+ 'Client does not want survey',
29
+ 'Other'
30
+ ],
31
+ allowCustomReason = true,
32
+ reasonRequired = true,
33
+ placeholder = 'Enter custom reason...',
34
+ defaultReason = '',
35
+ data = {},
36
+ onConfirm,
37
+ onCancel
38
+ }) => {
39
+ console.log('Reason Collector Modal - Configuration:', {
40
+ title,
41
+ reasonOptions,
42
+ allowCustomReason,
43
+ reasonRequired,
44
+ defaultReason,
45
+ data
46
+ });
47
+
48
+ // Generate HTML for reason options (handle "Other" specially for custom input)
49
+ const reasonOptionsHtml = reasonOptions.map((option, index) => {
50
+ const isSelected = option === defaultReason;
51
+ const isOther = option.toLowerCase() === 'other';
52
+
53
+ if (isOther && allowCustomReason) {
54
+ return `
55
+ <div class="reason-option reason-option-other">
56
+ <div class="reason-radio-container">
57
+ <input
58
+ type="radio"
59
+ id="reason-${index}"
60
+ name="reason"
61
+ value="custom"
62
+ ${isSelected ? 'checked' : ''}
63
+ class="reason-radio"
64
+ />
65
+ <label for="reason-${index}" class="reason-label">${option}</label>
66
+ </div>
67
+ <div class="custom-reason-input-container" style="display: none;">
68
+ <input
69
+ type="text"
70
+ id="custom-reason-input"
71
+ class="custom-reason-input compact"
72
+ placeholder="${placeholder}"
73
+ />
74
+ </div>
75
+ </div>
76
+ `;
77
+ } else {
78
+ return `
79
+ <div class="reason-option">
80
+ <input
81
+ type="radio"
82
+ id="reason-${index}"
83
+ name="reason"
84
+ value="${option}"
85
+ ${isSelected ? 'checked' : ''}
86
+ class="reason-radio"
87
+ />
88
+ <label for="reason-${index}" class="reason-label">${option}</label>
89
+ </div>
90
+ `;
91
+ }
92
+ }).join('');
93
+
94
+ const result = await Swal.fire({
95
+ title: title,
96
+ html: `
97
+ <div class="reason-collector-modal compact">
98
+ <p class="reason-message">${message}</p>
99
+ <div class="reason-options-container compact">
100
+ ${reasonOptionsHtml}
101
+ </div>
102
+ </div>
103
+ `,
104
+ showCancelButton: true,
105
+ confirmButtonText: confirmLabel,
106
+ cancelButtonText: cancelLabel,
107
+ customClass: {
108
+ container: 'reason-swal-container',
109
+ popup: 'reason-swal-popup',
110
+ content: 'reason-swal-content',
111
+ confirmButton: 'reason-swal-confirm',
112
+ cancelButton: 'reason-swal-cancel'
113
+ },
114
+ didOpen: () => {
115
+ // Handle custom reason toggle for "Other" option
116
+ if (allowCustomReason) {
117
+ const customInputContainer = document.querySelector('.custom-reason-input-container');
118
+ const customInput = document.getElementById('custom-reason-input');
119
+ const allRadios = document.querySelectorAll('input[name="reason"]');
120
+
121
+ // Find the "Other" radio button
122
+ let otherRadio = null;
123
+ allRadios.forEach(radio => {
124
+ if (radio.value === 'custom') {
125
+ otherRadio = radio;
126
+ }
127
+ });
128
+
129
+ if (otherRadio && customInputContainer && customInput) {
130
+ // Show/hide custom input based on selection
131
+ allRadios.forEach(radio => {
132
+ radio.addEventListener('change', () => {
133
+ if (radio.value === 'custom') {
134
+ customInputContainer.style.display = 'block';
135
+ customInput.focus();
136
+ } else {
137
+ customInputContainer.style.display = 'none';
138
+ customInput.value = ''; // Clear input when switching away
139
+ }
140
+ });
141
+ });
142
+
143
+ // Auto-select "Other" radio when typing in input
144
+ customInput.addEventListener('input', () => {
145
+ if (customInput.value.trim()) {
146
+ otherRadio.checked = true;
147
+ customInputContainer.style.display = 'block';
148
+ }
149
+ });
150
+ }
151
+ }
152
+ },
153
+ preConfirm: () => {
154
+ const selectedRadio = document.querySelector('input[name="reason"]:checked');
155
+
156
+ if (reasonRequired && !selectedRadio) {
157
+ Swal.showValidationMessage('Please select a reason');
158
+ return false;
159
+ }
160
+
161
+ let reason = '';
162
+
163
+ if (selectedRadio) {
164
+ if (selectedRadio.value === 'custom') {
165
+ const customInput = document.getElementById('custom-reason-input');
166
+ reason = customInput.value.trim();
167
+
168
+ if (reasonRequired && !reason) {
169
+ Swal.showValidationMessage('Please enter a custom reason');
170
+ return false;
171
+ }
172
+ } else {
173
+ reason = selectedRadio.value;
174
+ }
175
+ }
176
+
177
+ console.log('Reason selected:', {
178
+ reason: reason,
179
+ isCustom: selectedRadio?.value === 'custom'
180
+ });
181
+
182
+ return {
183
+ reason: reason,
184
+ isCustom: selectedRadio?.value === 'custom'
185
+ };
186
+ }
187
+ });
188
+
189
+ if (result.isConfirmed && onConfirm) {
190
+ await onConfirm(result.value.reason, result.value.isCustom);
191
+ } else if (result.isDismissed && onCancel) {
192
+ onCancel();
193
+ }
194
+ };
@@ -4,7 +4,7 @@ import {
4
4
  SortableElement,
5
5
  sortableHandle,
6
6
  } from 'react-sortable-hoc';
7
- import { ChevronVertical, Pencil, TrashCan } from 'akar-icons';
7
+ import { GripVertical, Pencil, Trash2 } from 'lucide-react';
8
8
  import get from 'lodash/get';
9
9
  import { formatCurrency } from './utils/formatters';
10
10
 
@@ -12,7 +12,7 @@ import styles from './styles/SortableQuoteItems.module.scss';
12
12
 
13
13
  // Drag handle component
14
14
  const DragHandle = sortableHandle(() => (
15
- <ChevronVertical className={styles.dragHandle} strokeWidth={1} size={22} />
15
+ <GripVertical className={styles.dragHandle} strokeWidth={1} size={22} />
16
16
  ));
17
17
 
18
18
  // Sortable table row component
@@ -61,7 +61,7 @@ const SortableRow = SortableElement(
61
61
  onEdit(row);
62
62
  }}
63
63
  />
64
- <TrashCan
64
+ <Trash2
65
65
  className={styles.deleteButton}
66
66
  strokeWidth={2}
67
67
  size={20}
@@ -2,17 +2,123 @@ import parse from 'html-react-parser';
2
2
  import moment from 'moment';
3
3
  import numeral from 'numeral';
4
4
 
5
+ /**
6
+ * Format quarter columns with status, contacts, and reasons in tooltips
7
+ * @param {*} value - The date value
8
+ * @param {Object} column - Column configuration
9
+ * @param {Object} row - Row data with status and contacts
10
+ * @returns {Object} Formatted quarter cell content with tooltip data
11
+ */
12
+ const formatQuarterWithStatus = (value, column, row) => {
13
+ const statusField = column.key + '_status';
14
+ const contactsField = column.key + '_contacts';
15
+ const reasonField = column.key + '_reason';
16
+
17
+ const status = row[statusField] || 'pending';
18
+ const contacts = row[contactsField] || [];
19
+ const reason = row[reasonField];
20
+
21
+ console.log('Quarter formatting:', {
22
+ column: column.key,
23
+ status,
24
+ contacts,
25
+ reason,
26
+ value
27
+ });
28
+
29
+ // If status is 'sent' and we have a date
30
+ if (status === 'sent' && value) {
31
+ const formattedDate = moment(value).isValid()
32
+ ? moment(value).format('DD/MM/YYYY')
33
+ : value;
34
+
35
+ const contactCount = Array.isArray(contacts) ? contacts.length : 0;
36
+
37
+ // Build tooltip content
38
+ let tooltipText = `Status: Sent on ${formattedDate}`;
39
+ if (contactCount > 0) {
40
+ tooltipText += `\nContacts: ${contactCount} contact${contactCount !== 1 ? 's' : ''}`;
41
+ if (Array.isArray(contacts)) {
42
+ contacts.forEach((contact, index) => {
43
+ if (index < 3) { // Show first 3 contacts
44
+ tooltipText += `\n • ${contact.name || 'Unknown'} ${contact.email ? `(${contact.email})` : ''}`;
45
+ }
46
+ });
47
+ if (contacts.length > 3) {
48
+ tooltipText += `\n ... and ${contacts.length - 3} more`;
49
+ }
50
+ }
51
+ }
52
+
53
+ return {
54
+ display: `📅 ${formattedDate}${contactCount > 0 ? ` (${contactCount})` : ''}`,
55
+ tooltip: tooltipText,
56
+ style: { color: '#059669', fontWeight: '500' }
57
+ };
58
+ }
59
+
60
+ // If status is 'not_sent' with reason
61
+ if (status === 'not_sent' && reason) {
62
+ const tooltipText = `Status: Not Sent\nReason: ${reason}`;
63
+
64
+ return {
65
+ display: `❌ Not Sent`,
66
+ tooltip: tooltipText,
67
+ style: { color: '#dc2626', fontWeight: '500' }
68
+ };
69
+ }
70
+
71
+ // If we have a date but no clear status (legacy data)
72
+ if (value) {
73
+ const formattedDate = moment(value).isValid()
74
+ ? moment(value).format('DD/MM/YYYY')
75
+ : value;
76
+
77
+ const contactCount = Array.isArray(contacts) ? contacts.length : 0;
78
+
79
+ let tooltipText = `Date: ${formattedDate}`;
80
+ if (contactCount > 0) {
81
+ tooltipText += `\nContacts: ${contactCount} contact${contactCount !== 1 ? 's' : ''}`;
82
+ if (Array.isArray(contacts)) {
83
+ contacts.forEach((contact, index) => {
84
+ if (index < 3) {
85
+ tooltipText += `\n • ${contact.name || 'Unknown'} ${contact.email ? `(${contact.email})` : ''}`;
86
+ }
87
+ });
88
+ if (contacts.length > 3) {
89
+ tooltipText += `\n ... and ${contacts.length - 3} more`;
90
+ }
91
+ }
92
+ }
93
+
94
+ return {
95
+ display: `${formattedDate}${contactCount > 0 ? ` (${contactCount})` : ''}`,
96
+ tooltip: tooltipText,
97
+ style: { color: '#374151' }
98
+ };
99
+ }
100
+
101
+ // Default case - return empty or pending
102
+ return null;
103
+ };
104
+
5
105
  /**
6
106
  * Format cell content based on column type or value type
7
107
  * @param {*} value - The value to format
8
108
  * @param {Object} column - Column configuration object
109
+ * @param {Object} row - Row data (optional, for quarter status display)
9
110
  * @returns {*} Formatted value
10
111
  */
11
- export const formatCellContent = (value, column) => {
112
+ export const formatCellContent = (value, column, row = null) => {
12
113
  if (value === null || value === undefined) return '';
13
114
  if (value === 0) return '0'; // Explicitly handle zero values
14
115
  if (value === '') return '';
15
116
 
117
+ // Special handling for quarter columns with status
118
+ if (column.key && (column.key.startsWith('quarter_') || column.key === 'anniversary') && row) {
119
+ return formatQuarterWithStatus(value, column, row);
120
+ }
121
+
16
122
  // Try to infer the type if not provided
17
123
  const valueType = column.type || typeof value;
18
124
 
@@ -0,0 +1,127 @@
1
+ /* Alternative Action Modal Styles */
2
+ .alternative-action-modal {
3
+ text-align: center;
4
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
5
+ }
6
+
7
+ .alternative-message {
8
+ margin-bottom: 24px;
9
+ color: #4a5568;
10
+ font-size: 16px;
11
+ line-height: 1.5;
12
+ }
13
+
14
+ .alternative-options {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 12px;
18
+ margin: 20px 0;
19
+ }
20
+
21
+ .alternative-action-option {
22
+ display: flex;
23
+ align-items: center;
24
+ padding: 16px 20px;
25
+ border: 2px solid #e2e8f0;
26
+ border-radius: 12px;
27
+ cursor: pointer;
28
+ transition: all 0.2s ease;
29
+ background-color: #ffffff;
30
+ text-align: left;
31
+ }
32
+
33
+ .alternative-action-option:hover {
34
+ border-color: #4299e1;
35
+ background-color: #f7fafc;
36
+ transform: translateY(-1px);
37
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
38
+ }
39
+
40
+ .alternative-action-option:active {
41
+ transform: translateY(0);
42
+ }
43
+
44
+ .alternative-icon {
45
+ font-size: 24px;
46
+ margin-right: 16px;
47
+ min-width: 32px;
48
+ text-align: center;
49
+ }
50
+
51
+ .alternative-content {
52
+ flex: 1;
53
+ }
54
+
55
+ .alternative-label {
56
+ font-weight: 600;
57
+ color: #2d3748;
58
+ font-size: 16px;
59
+ margin-bottom: 4px;
60
+ }
61
+
62
+ .alternative-description {
63
+ font-size: 14px;
64
+ color: #718096;
65
+ line-height: 1.4;
66
+ }
67
+
68
+ /* SweetAlert2 custom classes */
69
+ .alternative-swal-container {
70
+ z-index: 10000;
71
+ }
72
+
73
+ .alternative-swal-popup {
74
+ border-radius: 16px;
75
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
76
+ max-width: 480px;
77
+ }
78
+
79
+ .alternative-swal-content {
80
+ padding: 24px;
81
+ }
82
+
83
+ .alternative-swal-cancel {
84
+ background-color: #e2e8f0 !important;
85
+ color: #4a5568 !important;
86
+ border: none !important;
87
+ border-radius: 8px !important;
88
+ font-weight: 600 !important;
89
+ padding: 12px 24px !important;
90
+ font-size: 14px !important;
91
+ }
92
+
93
+ .alternative-swal-cancel:hover {
94
+ background-color: #cbd5e0 !important;
95
+ }
96
+
97
+ /* Responsive design */
98
+ @media (max-width: 640px) {
99
+ .alternative-action-option {
100
+ padding: 14px 16px;
101
+ }
102
+
103
+ .alternative-icon {
104
+ font-size: 20px;
105
+ margin-right: 12px;
106
+ min-width: 28px;
107
+ }
108
+
109
+ .alternative-label {
110
+ font-size: 15px;
111
+ }
112
+
113
+ .alternative-description {
114
+ font-size: 13px;
115
+ }
116
+
117
+ .alternative-options {
118
+ gap: 10px;
119
+ }
120
+ }
121
+
122
+ /* Focus styles for accessibility */
123
+ .alternative-action-option:focus {
124
+ outline: none;
125
+ border-color: #4299e1;
126
+ box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
127
+ }
@@ -0,0 +1,115 @@
1
+ /* Date Range Selector Modal Styles */
2
+ .date-range-selector-modal {
3
+ text-align: center;
4
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
5
+ }
6
+
7
+ .date-range-message {
8
+ margin-bottom: 20px;
9
+ color: #4a5568;
10
+ font-size: 16px;
11
+ line-height: 1.5;
12
+ }
13
+
14
+ .date-range-inputs {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 15px;
18
+ margin: 20px 0;
19
+ }
20
+
21
+ .date-input-group {
22
+ display: flex;
23
+ flex-direction: column;
24
+ text-align: left;
25
+ }
26
+
27
+ .date-input-label {
28
+ font-weight: 600;
29
+ color: #2d3748;
30
+ margin-bottom: 5px;
31
+ font-size: 14px;
32
+ }
33
+
34
+ .date-range-input {
35
+ padding: 12px 16px;
36
+ border: 2px solid #e2e8f0;
37
+ border-radius: 8px;
38
+ font-size: 16px;
39
+ transition: all 0.2s ease;
40
+ background-color: #ffffff;
41
+ color: #2d3748;
42
+ width: 100%;
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ .date-range-input:focus {
47
+ outline: none;
48
+ border-color: #4299e1;
49
+ box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
50
+ }
51
+
52
+ .date-range-input:hover {
53
+ border-color: #cbd5e0;
54
+ }
55
+
56
+ .date-range-input:invalid {
57
+ border-color: #e53e3e;
58
+ }
59
+
60
+ /* SweetAlert2 custom classes */
61
+ .date-range-swal-container {
62
+ z-index: 10000;
63
+ }
64
+
65
+ .date-range-swal-popup {
66
+ border-radius: 12px;
67
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
68
+ }
69
+
70
+ .date-range-swal-content {
71
+ padding: 20px;
72
+ }
73
+
74
+ .date-range-swal-confirm {
75
+ background-color: #4299e1 !important;
76
+ border: none !important;
77
+ border-radius: 8px !important;
78
+ font-weight: 600 !important;
79
+ padding: 12px 24px !important;
80
+ font-size: 14px !important;
81
+ }
82
+
83
+ .date-range-swal-confirm:hover {
84
+ background-color: #3182ce !important;
85
+ }
86
+
87
+ .date-range-swal-cancel {
88
+ background-color: #e2e8f0 !important;
89
+ color: #4a5568 !important;
90
+ border: none !important;
91
+ border-radius: 8px !important;
92
+ font-weight: 600 !important;
93
+ padding: 12px 24px !important;
94
+ font-size: 14px !important;
95
+ }
96
+
97
+ .date-range-swal-cancel:hover {
98
+ background-color: #cbd5e0 !important;
99
+ }
100
+
101
+ /* Responsive design */
102
+ @media (max-width: 640px) {
103
+ .date-range-inputs {
104
+ gap: 12px;
105
+ }
106
+
107
+ .date-range-input {
108
+ padding: 10px 12px;
109
+ font-size: 16px; /* Prevent zoom on iOS */
110
+ }
111
+
112
+ .date-input-label {
113
+ font-size: 13px;
114
+ }
115
+ }