@visns-studio/visns-components 5.8.11 → 5.8.13
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 +12 -12
- package/src/components/crm/Autocomplete.jsx +211 -49
- package/src/components/crm/Field.jsx +28 -1
- package/src/components/crm/Form.jsx +20 -7
- package/src/components/crm/SectionHeader.jsx +29 -0
- package/src/components/crm/examples/FormStylingGuide.md +71 -0
- package/src/components/crm/examples/FormWithSectionHeaders.jsx +266 -0
- package/src/components/crm/generic/ConfirmationDialog.jsx +97 -0
- package/src/components/crm/generic/GenericIndex.jsx +2 -3
- package/src/components/crm/generic/GenericQuote.jsx +413 -120
- package/src/components/crm/generic/SortableQuoteItems.jsx +114 -70
- package/src/components/crm/styles/Autocomplete.module.scss +97 -13
- package/src/components/crm/styles/Field.module.scss +88 -5
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import SectionHeader from '../SectionHeader';
|
|
3
|
+
import Field from '../Field';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Example form component that demonstrates how to use the SectionHeader component
|
|
7
|
+
* This shows how to implement the form seen in the screenshot with improved styling
|
|
8
|
+
*/
|
|
9
|
+
const FormWithSectionHeaders = ({ formData, setFormData, onChange }) => {
|
|
10
|
+
// Sample form settings for demonstration
|
|
11
|
+
const formSettings = {
|
|
12
|
+
// Form settings would go here
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Sample input classes
|
|
16
|
+
const inputClass = {
|
|
17
|
+
// Input classes would go here
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<form>
|
|
22
|
+
{/* Site Details Section */}
|
|
23
|
+
<SectionHeader label="Site Details" />
|
|
24
|
+
|
|
25
|
+
{/* Site Name Field */}
|
|
26
|
+
<Field
|
|
27
|
+
settings={{
|
|
28
|
+
id: 'site_name',
|
|
29
|
+
type: 'text',
|
|
30
|
+
label: 'Site Name',
|
|
31
|
+
required: true,
|
|
32
|
+
size: 'half'
|
|
33
|
+
}}
|
|
34
|
+
inputValue={formData.site_name}
|
|
35
|
+
inputClass={inputClass}
|
|
36
|
+
onChange={onChange}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
{/* Address Fields */}
|
|
40
|
+
<Field
|
|
41
|
+
settings={{
|
|
42
|
+
id: 'address_1',
|
|
43
|
+
type: 'text',
|
|
44
|
+
label: 'Address #1',
|
|
45
|
+
required: true,
|
|
46
|
+
size: 'half'
|
|
47
|
+
}}
|
|
48
|
+
inputValue={formData.address_1}
|
|
49
|
+
inputClass={inputClass}
|
|
50
|
+
onChange={onChange}
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
<Field
|
|
54
|
+
settings={{
|
|
55
|
+
id: 'address_2',
|
|
56
|
+
type: 'text',
|
|
57
|
+
label: 'Address #2',
|
|
58
|
+
size: 'half'
|
|
59
|
+
}}
|
|
60
|
+
inputValue={formData.address_2}
|
|
61
|
+
inputClass={inputClass}
|
|
62
|
+
onChange={onChange}
|
|
63
|
+
/>
|
|
64
|
+
|
|
65
|
+
{/* Suburb Field */}
|
|
66
|
+
<Field
|
|
67
|
+
settings={{
|
|
68
|
+
id: 'suburb',
|
|
69
|
+
type: 'text',
|
|
70
|
+
label: 'Suburb',
|
|
71
|
+
required: true,
|
|
72
|
+
size: 'half'
|
|
73
|
+
}}
|
|
74
|
+
inputValue={formData.suburb}
|
|
75
|
+
inputClass={inputClass}
|
|
76
|
+
onChange={onChange}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
{/* Postcode Field */}
|
|
80
|
+
<Field
|
|
81
|
+
settings={{
|
|
82
|
+
id: 'postcode',
|
|
83
|
+
type: 'text',
|
|
84
|
+
label: 'Postcode',
|
|
85
|
+
required: true,
|
|
86
|
+
size: 'half'
|
|
87
|
+
}}
|
|
88
|
+
inputValue={formData.postcode}
|
|
89
|
+
inputClass={inputClass}
|
|
90
|
+
onChange={onChange}
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
{/* State Field */}
|
|
94
|
+
<Field
|
|
95
|
+
settings={{
|
|
96
|
+
id: 'state',
|
|
97
|
+
type: 'text',
|
|
98
|
+
label: 'State',
|
|
99
|
+
required: true,
|
|
100
|
+
size: 'half'
|
|
101
|
+
}}
|
|
102
|
+
inputValue={formData.state}
|
|
103
|
+
inputClass={inputClass}
|
|
104
|
+
onChange={onChange}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
{/* Country Field */}
|
|
108
|
+
<Field
|
|
109
|
+
settings={{
|
|
110
|
+
id: 'country',
|
|
111
|
+
type: 'text',
|
|
112
|
+
label: 'Country',
|
|
113
|
+
required: true,
|
|
114
|
+
size: 'half'
|
|
115
|
+
}}
|
|
116
|
+
inputValue={formData.country}
|
|
117
|
+
inputClass={inputClass}
|
|
118
|
+
onChange={onChange}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
{/* Delivery Address Section */}
|
|
122
|
+
<Field
|
|
123
|
+
settings={{
|
|
124
|
+
id: 'delivery_address_section',
|
|
125
|
+
type: 'line-break',
|
|
126
|
+
label: 'Delivery Address #1',
|
|
127
|
+
size: 'full'
|
|
128
|
+
}}
|
|
129
|
+
inputValue=""
|
|
130
|
+
inputClass={inputClass}
|
|
131
|
+
/>
|
|
132
|
+
|
|
133
|
+
{/* Delivery Address Fields */}
|
|
134
|
+
<Field
|
|
135
|
+
settings={{
|
|
136
|
+
id: 'delivery_address_1',
|
|
137
|
+
type: 'text',
|
|
138
|
+
label: 'Delivery Address #1',
|
|
139
|
+
required: true,
|
|
140
|
+
size: 'half'
|
|
141
|
+
}}
|
|
142
|
+
inputValue={formData.delivery_address_1}
|
|
143
|
+
inputClass={inputClass}
|
|
144
|
+
onChange={onChange}
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
<Field
|
|
148
|
+
settings={{
|
|
149
|
+
id: 'delivery_address_2',
|
|
150
|
+
type: 'text',
|
|
151
|
+
label: 'Delivery Address #2',
|
|
152
|
+
size: 'half'
|
|
153
|
+
}}
|
|
154
|
+
inputValue={formData.delivery_address_2}
|
|
155
|
+
inputClass={inputClass}
|
|
156
|
+
onChange={onChange}
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
{/* Delivery Suburb Field */}
|
|
160
|
+
<Field
|
|
161
|
+
settings={{
|
|
162
|
+
id: 'delivery_suburb',
|
|
163
|
+
type: 'text',
|
|
164
|
+
label: 'Delivery Suburb',
|
|
165
|
+
required: true,
|
|
166
|
+
size: 'half'
|
|
167
|
+
}}
|
|
168
|
+
inputValue={formData.delivery_suburb}
|
|
169
|
+
inputClass={inputClass}
|
|
170
|
+
onChange={onChange}
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
{/* Delivery Postcode Field */}
|
|
174
|
+
<Field
|
|
175
|
+
settings={{
|
|
176
|
+
id: 'delivery_postcode',
|
|
177
|
+
type: 'text',
|
|
178
|
+
label: 'Delivery Postcode',
|
|
179
|
+
required: true,
|
|
180
|
+
size: 'half'
|
|
181
|
+
}}
|
|
182
|
+
inputValue={formData.delivery_postcode}
|
|
183
|
+
inputClass={inputClass}
|
|
184
|
+
onChange={onChange}
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
{/* Delivery State Field */}
|
|
188
|
+
<Field
|
|
189
|
+
settings={{
|
|
190
|
+
id: 'delivery_state',
|
|
191
|
+
type: 'text',
|
|
192
|
+
label: 'Delivery State',
|
|
193
|
+
required: true,
|
|
194
|
+
size: 'half'
|
|
195
|
+
}}
|
|
196
|
+
inputValue={formData.delivery_state}
|
|
197
|
+
inputClass={inputClass}
|
|
198
|
+
onChange={onChange}
|
|
199
|
+
/>
|
|
200
|
+
|
|
201
|
+
{/* Delivery Country Field */}
|
|
202
|
+
<Field
|
|
203
|
+
settings={{
|
|
204
|
+
id: 'delivery_country',
|
|
205
|
+
type: 'text',
|
|
206
|
+
label: 'Delivery Country',
|
|
207
|
+
required: true,
|
|
208
|
+
size: 'half'
|
|
209
|
+
}}
|
|
210
|
+
inputValue={formData.delivery_country}
|
|
211
|
+
inputClass={inputClass}
|
|
212
|
+
onChange={onChange}
|
|
213
|
+
/>
|
|
214
|
+
|
|
215
|
+
{/* Contact Details Section */}
|
|
216
|
+
<SectionHeader label="Contact Details" />
|
|
217
|
+
|
|
218
|
+
{/* Firstname Field */}
|
|
219
|
+
<Field
|
|
220
|
+
settings={{
|
|
221
|
+
id: 'firstname',
|
|
222
|
+
type: 'text',
|
|
223
|
+
label: 'Firstname',
|
|
224
|
+
required: true,
|
|
225
|
+
size: 'half'
|
|
226
|
+
}}
|
|
227
|
+
inputValue={formData.firstname}
|
|
228
|
+
inputClass={inputClass}
|
|
229
|
+
onChange={onChange}
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
{/* Surname Field */}
|
|
233
|
+
<Field
|
|
234
|
+
settings={{
|
|
235
|
+
id: 'surname',
|
|
236
|
+
type: 'text',
|
|
237
|
+
label: 'Surname',
|
|
238
|
+
size: 'half'
|
|
239
|
+
}}
|
|
240
|
+
inputValue={formData.surname}
|
|
241
|
+
inputClass={inputClass}
|
|
242
|
+
onChange={onChange}
|
|
243
|
+
/>
|
|
244
|
+
|
|
245
|
+
{/* Save Button */}
|
|
246
|
+
<div style={{ width: '100%', textAlign: 'center', marginTop: '20px' }}>
|
|
247
|
+
<button
|
|
248
|
+
type="submit"
|
|
249
|
+
style={{
|
|
250
|
+
background: 'var(--primary-color, #2684FF)',
|
|
251
|
+
color: 'var(--secondary-color, #fff)',
|
|
252
|
+
padding: '10px 20px',
|
|
253
|
+
border: 'none',
|
|
254
|
+
borderRadius: 'var(--br, 3px)',
|
|
255
|
+
cursor: 'pointer',
|
|
256
|
+
fontWeight: 'bold'
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
Save & Close
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</form>
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export default FormWithSectionHeaders;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Swal from 'sweetalert2';
|
|
3
|
+
import './styles/SweetAlert.module.css';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A utility function that uses SweetAlert2 for confirmation dialogs
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options - Configuration options
|
|
9
|
+
* @param {string} options.title - The title of the confirmation dialog
|
|
10
|
+
* @param {string} options.message - The message to display in the dialog
|
|
11
|
+
* @param {string} options.confirmLabel - Label for the confirm button
|
|
12
|
+
* @param {string} options.cancelLabel - Label for the cancel button
|
|
13
|
+
* @param {Function} options.onConfirm - Function to call when confirmed
|
|
14
|
+
* @param {Function} options.onCancel - Function to call when cancelled
|
|
15
|
+
* @param {Object} options.data - Optional data object to pass to the dialog
|
|
16
|
+
* @returns {Promise} - A promise that resolves when the dialog is closed
|
|
17
|
+
*/
|
|
18
|
+
export const showConfirmDialog = ({
|
|
19
|
+
title = 'Confirm',
|
|
20
|
+
message = 'Are you sure?',
|
|
21
|
+
confirmLabel = 'Yes',
|
|
22
|
+
cancelLabel = 'No',
|
|
23
|
+
onConfirm = () => {},
|
|
24
|
+
onCancel = () => {},
|
|
25
|
+
data = null,
|
|
26
|
+
}) => {
|
|
27
|
+
// Enhanced message with item details if available
|
|
28
|
+
let enhancedMessage = message;
|
|
29
|
+
let itemDetails = '';
|
|
30
|
+
|
|
31
|
+
// Extract meaningful information from the data object if available
|
|
32
|
+
if (data) {
|
|
33
|
+
// Common identifiers that might be present in data objects
|
|
34
|
+
const identifiers = [
|
|
35
|
+
'name',
|
|
36
|
+
'label',
|
|
37
|
+
'title',
|
|
38
|
+
'email',
|
|
39
|
+
'username',
|
|
40
|
+
'id',
|
|
41
|
+
'code',
|
|
42
|
+
'reference',
|
|
43
|
+
'description',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Find the first available identifier
|
|
47
|
+
for (const id of identifiers) {
|
|
48
|
+
if (
|
|
49
|
+
data[id] &&
|
|
50
|
+
typeof data[id] === 'string' &&
|
|
51
|
+
data[id].trim() !== ''
|
|
52
|
+
) {
|
|
53
|
+
itemDetails = data[id];
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If we found an identifier, enhance the message
|
|
59
|
+
if (itemDetails && !message.includes(itemDetails)) {
|
|
60
|
+
// If the message already contains a question mark, add the details before it
|
|
61
|
+
if (message.includes('?')) {
|
|
62
|
+
enhancedMessage = message.replace('?', `"${itemDetails}"?`);
|
|
63
|
+
} else {
|
|
64
|
+
// Otherwise, append the details
|
|
65
|
+
enhancedMessage = `${message} "${itemDetails}"`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Configure SweetAlert2 options
|
|
71
|
+
const swalOptions = {
|
|
72
|
+
title: title,
|
|
73
|
+
html: enhancedMessage,
|
|
74
|
+
icon: 'question',
|
|
75
|
+
showCancelButton: true,
|
|
76
|
+
confirmButtonText: confirmLabel,
|
|
77
|
+
cancelButtonText: cancelLabel,
|
|
78
|
+
confirmButtonColor: 'var(--primary-color, #4f46e5)',
|
|
79
|
+
cancelButtonColor: '#6b7280',
|
|
80
|
+
allowOutsideClick: true,
|
|
81
|
+
allowEscapeKey: true,
|
|
82
|
+
reverseButtons: true,
|
|
83
|
+
focusCancel: false,
|
|
84
|
+
buttonsStyling: true,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Show SweetAlert2 dialog
|
|
88
|
+
return Swal.fire(swalOptions).then((result) => {
|
|
89
|
+
if (result.isConfirmed) {
|
|
90
|
+
onConfirm();
|
|
91
|
+
} else if (result.dismiss === Swal.DismissReason.cancel) {
|
|
92
|
+
onCancel();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default showConfirmDialog;
|
|
@@ -1496,6 +1496,8 @@ function GenericIndex({
|
|
|
1496
1496
|
|
|
1497
1497
|
// Add a ref to store the latest rowsSelected value
|
|
1498
1498
|
const rowsSelectedRef = useRef({});
|
|
1499
|
+
// Create a ref to track if we've already processed active filter combinations
|
|
1500
|
+
const activeFilterRef = useRef(null);
|
|
1499
1501
|
|
|
1500
1502
|
// Update the ref whenever rowsSelected changes
|
|
1501
1503
|
useEffect(() => {
|
|
@@ -1847,9 +1849,6 @@ function GenericIndex({
|
|
|
1847
1849
|
return;
|
|
1848
1850
|
}
|
|
1849
1851
|
|
|
1850
|
-
// Create a ref to track if we've already processed this combination
|
|
1851
|
-
const activeFilterRef = useRef(null);
|
|
1852
|
-
|
|
1853
1852
|
// Find the active filter and its child
|
|
1854
1853
|
const activeFilter = subnav?.find((filter) => {
|
|
1855
1854
|
if (filter.isParent) {
|