@visns-studio/visns-components 5.11.1 → 5.11.2
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/Autocomplete.jsx +3 -3
- package/src/components/crm/Breadcrumb.jsx +4 -4
- package/src/components/crm/BusinessCardOcr.jsx +681 -95
- 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/Navigation.jsx +11 -11
- 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/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 +6 -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/BusinessCardOcr.module.scss +197 -4
|
@@ -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 '
|
|
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 '
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
+
}
|