@visns-studio/visns-components 5.12.2 → 5.12.4
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/README.md +2 -2
- package/package.json +1 -1
- package/src/components/DataGrid.jsx +95 -51
- package/src/components/columns/ColumnRenderers.jsx +322 -20
- package/src/components/generic/GenericDetail.jsx +200 -3
- package/src/components/generic/GenericIndex.jsx +31 -18
- package/src/components/generic/StagePopupModal.jsx +451 -0
- package/src/components/styles/StagePopupModal.module.scss +234 -0
- package/src/components/utils/ConfirmDialog.js +1 -1
- package/src/index.js +2 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import Swal from 'sweetalert2';
|
|
3
|
+
import CustomFetch from '../Fetch';
|
|
4
|
+
import styles from '../styles/StagePopupModal.module.scss';
|
|
5
|
+
|
|
6
|
+
const StagePopupModal = ({
|
|
7
|
+
show,
|
|
8
|
+
stage,
|
|
9
|
+
popupConfig,
|
|
10
|
+
onConfirm,
|
|
11
|
+
onCancel,
|
|
12
|
+
entityData = {},
|
|
13
|
+
routeParams = {}
|
|
14
|
+
}) => {
|
|
15
|
+
const [formData, setFormData] = useState({});
|
|
16
|
+
const [fieldOptions, setFieldOptions] = useState({});
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
const [validationErrors, setValidationErrors] = useState({});
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (show && popupConfig?.fields) {
|
|
22
|
+
initializeForm();
|
|
23
|
+
}
|
|
24
|
+
}, [show, popupConfig]);
|
|
25
|
+
|
|
26
|
+
// Re-render form fields when fieldOptions change
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
console.log('[StagePopupModal] useEffect triggered:', {
|
|
29
|
+
show,
|
|
30
|
+
loading,
|
|
31
|
+
fieldOptionsKeys: Object.keys(fieldOptions),
|
|
32
|
+
fieldOptions
|
|
33
|
+
});
|
|
34
|
+
if (show && !loading && Object.keys(fieldOptions).length > 0) {
|
|
35
|
+
console.log('[StagePopupModal] Re-rendering form fields after fieldOptions update');
|
|
36
|
+
renderFormFields();
|
|
37
|
+
}
|
|
38
|
+
}, [fieldOptions, loading, formData, validationErrors, show]);
|
|
39
|
+
|
|
40
|
+
const initializeForm = async () => {
|
|
41
|
+
const initialData = {};
|
|
42
|
+
const options = {};
|
|
43
|
+
|
|
44
|
+
setLoading(true);
|
|
45
|
+
|
|
46
|
+
for (const field of popupConfig.fields) {
|
|
47
|
+
// Set default values
|
|
48
|
+
if (field.value !== undefined) {
|
|
49
|
+
initialData[field.id] = field.value;
|
|
50
|
+
} else if (field.urlParam && routeParams[field.urlParam]) {
|
|
51
|
+
initialData[field.id] = routeParams[field.urlParam];
|
|
52
|
+
} else if (entityData[field.id] !== undefined) {
|
|
53
|
+
initialData[field.id] = entityData[field.id];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Load dropdown options
|
|
57
|
+
if (field.type === 'select' && field.url) {
|
|
58
|
+
try {
|
|
59
|
+
let url = field.url;
|
|
60
|
+
|
|
61
|
+
// Replace URL parameters
|
|
62
|
+
Object.keys(routeParams).forEach(key => {
|
|
63
|
+
url = url.replace(`{${key}}`, routeParams[key]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const response = await CustomFetch(url, 'POST');
|
|
67
|
+
console.log(`[StagePopupModal] Response for ${field.id}:`, response);
|
|
68
|
+
console.log(`[StagePopupModal] Response.data:`, response.data);
|
|
69
|
+
|
|
70
|
+
if (response.data && !response.data.error) {
|
|
71
|
+
// Handle both nested (response.data.data) and direct (response.data) array formats
|
|
72
|
+
const optionsData = Array.isArray(response.data.data) ? response.data.data :
|
|
73
|
+
Array.isArray(response.data) ? response.data : [];
|
|
74
|
+
console.log(`[StagePopupModal] Processed options for ${field.id}:`, optionsData);
|
|
75
|
+
options[field.id] = optionsData;
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(`Failed to load options for ${field.id}:`, error);
|
|
79
|
+
options[field.id] = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`[StagePopupModal] Final options object:`, options);
|
|
85
|
+
console.log(`[StagePopupModal] Setting fieldOptions state:`, options);
|
|
86
|
+
|
|
87
|
+
setFormData(initialData);
|
|
88
|
+
setFieldOptions(options);
|
|
89
|
+
setLoading(false);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleFieldChange = (fieldId, value) => {
|
|
93
|
+
setFormData(prev => ({
|
|
94
|
+
...prev,
|
|
95
|
+
[fieldId]: value
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// Clear validation error for this field
|
|
99
|
+
if (validationErrors[fieldId]) {
|
|
100
|
+
setValidationErrors(prev => ({
|
|
101
|
+
...prev,
|
|
102
|
+
[fieldId]: null
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const validateForm = () => {
|
|
108
|
+
const errors = {};
|
|
109
|
+
|
|
110
|
+
popupConfig.fields?.forEach(field => {
|
|
111
|
+
if (field.required && (!formData[field.id] || formData[field.id] === '')) {
|
|
112
|
+
errors[field.id] = `${field.label} is required`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (field.validation) {
|
|
116
|
+
const value = formData[field.id];
|
|
117
|
+
|
|
118
|
+
if (field.validation.minLength && value && value.length < field.validation.minLength) {
|
|
119
|
+
errors[field.id] = `${field.label} must be at least ${field.validation.minLength} characters`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (field.validation.maxLength && value && value.length > field.validation.maxLength) {
|
|
123
|
+
errors[field.id] = `${field.label} must not exceed ${field.validation.maxLength} characters`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
setValidationErrors(errors);
|
|
129
|
+
return Object.keys(errors).length === 0;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const handleConfirm = () => {
|
|
133
|
+
if (!validateForm()) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const dataToSend = {};
|
|
138
|
+
|
|
139
|
+
// Only include fields specified in saveFields, or all fields if not specified
|
|
140
|
+
const fieldsToSave = popupConfig.saveFields || popupConfig.fields?.map(f => f.id) || [];
|
|
141
|
+
|
|
142
|
+
fieldsToSave.forEach(fieldId => {
|
|
143
|
+
if (formData[fieldId] !== undefined) {
|
|
144
|
+
dataToSend[fieldId] = formData[fieldId];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
onConfirm(dataToSend);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const renderField = (field) => {
|
|
152
|
+
const value = formData[field.id] || '';
|
|
153
|
+
const hasError = validationErrors[field.id];
|
|
154
|
+
const fieldClass = `${styles.field} ${hasError ? styles.fieldError : ''}`;
|
|
155
|
+
|
|
156
|
+
switch (field.type) {
|
|
157
|
+
case 'select':
|
|
158
|
+
return (
|
|
159
|
+
<div key={field.id} className={fieldClass}>
|
|
160
|
+
<label className={styles.label}>
|
|
161
|
+
{field.label}
|
|
162
|
+
{field.required && <span className={styles.required}>*</span>}
|
|
163
|
+
</label>
|
|
164
|
+
<select
|
|
165
|
+
className={styles.select}
|
|
166
|
+
value={value}
|
|
167
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value)}
|
|
168
|
+
disabled={loading}
|
|
169
|
+
>
|
|
170
|
+
<option value="">-- Select {field.label} --</option>
|
|
171
|
+
{(() => {
|
|
172
|
+
console.log(`[StagePopupModal] Rendering JSX options for ${field.id}:`, fieldOptions[field.id]);
|
|
173
|
+
return (fieldOptions[field.id] || []).map(option => (
|
|
174
|
+
<option key={option.id || option.value} value={option.id || option.value}>
|
|
175
|
+
{option.label || option.name || option.text}
|
|
176
|
+
</option>
|
|
177
|
+
));
|
|
178
|
+
})()}
|
|
179
|
+
</select>
|
|
180
|
+
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
case 'textarea':
|
|
185
|
+
return (
|
|
186
|
+
<div key={field.id} className={fieldClass}>
|
|
187
|
+
<label className={styles.label}>
|
|
188
|
+
{field.label}
|
|
189
|
+
{field.required && <span className={styles.required}>*</span>}
|
|
190
|
+
</label>
|
|
191
|
+
<textarea
|
|
192
|
+
className={styles.textarea}
|
|
193
|
+
value={value}
|
|
194
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value)}
|
|
195
|
+
placeholder={field.placeholder}
|
|
196
|
+
rows={field.rows || 3}
|
|
197
|
+
disabled={loading}
|
|
198
|
+
/>
|
|
199
|
+
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
case 'date':
|
|
204
|
+
return (
|
|
205
|
+
<div key={field.id} className={fieldClass}>
|
|
206
|
+
<label className={styles.label}>
|
|
207
|
+
{field.label}
|
|
208
|
+
{field.required && <span className={styles.required}>*</span>}
|
|
209
|
+
</label>
|
|
210
|
+
<input
|
|
211
|
+
type="date"
|
|
212
|
+
className={styles.input}
|
|
213
|
+
value={value}
|
|
214
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value)}
|
|
215
|
+
disabled={loading}
|
|
216
|
+
/>
|
|
217
|
+
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
case 'datetime':
|
|
222
|
+
return (
|
|
223
|
+
<div key={field.id} className={fieldClass}>
|
|
224
|
+
<label className={styles.label}>
|
|
225
|
+
{field.label}
|
|
226
|
+
{field.required && <span className={styles.required}>*</span>}
|
|
227
|
+
</label>
|
|
228
|
+
<input
|
|
229
|
+
type="datetime-local"
|
|
230
|
+
className={styles.input}
|
|
231
|
+
value={value}
|
|
232
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value)}
|
|
233
|
+
disabled={loading}
|
|
234
|
+
/>
|
|
235
|
+
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
case 'number':
|
|
240
|
+
return (
|
|
241
|
+
<div key={field.id} className={fieldClass}>
|
|
242
|
+
<label className={styles.label}>
|
|
243
|
+
{field.label}
|
|
244
|
+
{field.required && <span className={styles.required}>*</span>}
|
|
245
|
+
</label>
|
|
246
|
+
<input
|
|
247
|
+
type="number"
|
|
248
|
+
className={styles.input}
|
|
249
|
+
value={value}
|
|
250
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value)}
|
|
251
|
+
placeholder={field.placeholder}
|
|
252
|
+
min={field.min}
|
|
253
|
+
max={field.max}
|
|
254
|
+
step={field.step}
|
|
255
|
+
disabled={loading}
|
|
256
|
+
/>
|
|
257
|
+
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
case 'text':
|
|
262
|
+
default:
|
|
263
|
+
return (
|
|
264
|
+
<div key={field.id} className={fieldClass}>
|
|
265
|
+
<label className={styles.label}>
|
|
266
|
+
{field.label}
|
|
267
|
+
{field.required && <span className={styles.required}>*</span>}
|
|
268
|
+
</label>
|
|
269
|
+
<input
|
|
270
|
+
type="text"
|
|
271
|
+
className={styles.input}
|
|
272
|
+
value={value}
|
|
273
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value)}
|
|
274
|
+
placeholder={field.placeholder}
|
|
275
|
+
disabled={loading}
|
|
276
|
+
/>
|
|
277
|
+
{hasError && <span className={styles.errorText}>{validationErrors[field.id]}</span>}
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (show && popupConfig) {
|
|
285
|
+
if (popupConfig.type === 'confirm') {
|
|
286
|
+
// Simple confirmation dialog
|
|
287
|
+
Swal.fire({
|
|
288
|
+
title: popupConfig.title || 'Confirm Action',
|
|
289
|
+
text: popupConfig.message || 'Are you sure you want to proceed?',
|
|
290
|
+
icon: 'question',
|
|
291
|
+
showCancelButton: true,
|
|
292
|
+
confirmButtonText: popupConfig.confirmText || 'Yes, proceed',
|
|
293
|
+
cancelButtonText: popupConfig.cancelText || 'Cancel',
|
|
294
|
+
customClass: {
|
|
295
|
+
popup: styles.swalPopup,
|
|
296
|
+
confirmButton: styles.swalConfirm,
|
|
297
|
+
cancelButton: styles.swalCancel
|
|
298
|
+
}
|
|
299
|
+
}).then((result) => {
|
|
300
|
+
if (result.isConfirmed) {
|
|
301
|
+
onConfirm({});
|
|
302
|
+
} else {
|
|
303
|
+
onCancel();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
} else if (popupConfig.type === 'form') {
|
|
307
|
+
// Custom form dialog
|
|
308
|
+
const formHTML = `
|
|
309
|
+
<div class="${styles.stagePopupForm}">
|
|
310
|
+
${popupConfig.message ? `<p class="${styles.message}">${popupConfig.message}</p>` : ''}
|
|
311
|
+
<div id="stage-popup-fields" class="${styles.fields}">
|
|
312
|
+
${loading ? '<div class="' + styles.loading + '">Loading...</div>' : ''}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
`;
|
|
316
|
+
|
|
317
|
+
Swal.fire({
|
|
318
|
+
title: popupConfig.title || 'Additional Information Required',
|
|
319
|
+
html: formHTML,
|
|
320
|
+
showCancelButton: true,
|
|
321
|
+
confirmButtonText: popupConfig.confirmText || 'Complete Stage',
|
|
322
|
+
cancelButtonText: popupConfig.cancelText || 'Cancel',
|
|
323
|
+
customClass: {
|
|
324
|
+
popup: styles.swalPopup,
|
|
325
|
+
confirmButton: styles.swalConfirm,
|
|
326
|
+
cancelButton: styles.swalCancel,
|
|
327
|
+
htmlContainer: styles.swalHtmlContainer
|
|
328
|
+
},
|
|
329
|
+
didOpen: () => {
|
|
330
|
+
renderFormFields();
|
|
331
|
+
},
|
|
332
|
+
preConfirm: () => {
|
|
333
|
+
if (!validateForm()) {
|
|
334
|
+
Swal.showValidationMessage('Please correct the errors below');
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
}).then((result) => {
|
|
340
|
+
if (result.isConfirmed) {
|
|
341
|
+
handleConfirm();
|
|
342
|
+
} else {
|
|
343
|
+
onCancel();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}, [show, popupConfig, loading, formData, fieldOptions]);
|
|
349
|
+
|
|
350
|
+
const renderFormFields = () => {
|
|
351
|
+
console.log('[StagePopupModal] renderFormFields called');
|
|
352
|
+
const fieldsContainer = document.getElementById('stage-popup-fields');
|
|
353
|
+
console.log('[StagePopupModal] fieldsContainer:', fieldsContainer);
|
|
354
|
+
console.log('[StagePopupModal] loading:', loading);
|
|
355
|
+
|
|
356
|
+
if (!fieldsContainer || loading) {
|
|
357
|
+
console.log('[StagePopupModal] Early return - no container or loading');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
console.log('[StagePopupModal] Clearing container and rendering fields');
|
|
362
|
+
fieldsContainer.innerHTML = '';
|
|
363
|
+
|
|
364
|
+
console.log('[StagePopupModal] popupConfig.fields:', popupConfig.fields);
|
|
365
|
+
popupConfig.fields?.forEach(field => {
|
|
366
|
+
console.log(`[StagePopupModal] Processing field: ${field.id}, type: ${field.type}`);
|
|
367
|
+
const fieldElement = document.createElement('div');
|
|
368
|
+
fieldElement.className = styles.fieldWrapper;
|
|
369
|
+
|
|
370
|
+
const fieldHTML = renderFieldHTML(field);
|
|
371
|
+
console.log(`[StagePopupModal] Generated HTML for ${field.id}:`, fieldHTML);
|
|
372
|
+
fieldElement.innerHTML = fieldHTML;
|
|
373
|
+
|
|
374
|
+
fieldsContainer.appendChild(fieldElement);
|
|
375
|
+
|
|
376
|
+
// Add event listeners
|
|
377
|
+
const input = fieldElement.querySelector('input, select, textarea');
|
|
378
|
+
if (input) {
|
|
379
|
+
input.addEventListener('change', (e) => {
|
|
380
|
+
handleFieldChange(field.id, e.target.value);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const renderFieldHTML = (field) => {
|
|
387
|
+
const value = formData[field.id] || '';
|
|
388
|
+
const hasError = validationErrors[field.id];
|
|
389
|
+
const requiredStar = field.required ? '<span class="' + styles.required + '">*</span>' : '';
|
|
390
|
+
|
|
391
|
+
switch (field.type) {
|
|
392
|
+
case 'select':
|
|
393
|
+
console.log(`[StagePopupModal] Rendering HTML options for ${field.id}:`, fieldOptions[field.id]);
|
|
394
|
+
const options = (fieldOptions[field.id] || []).map(option =>
|
|
395
|
+
`<option value="${option.id || option.value}" ${value === (option.id || option.value) ? 'selected' : ''}>
|
|
396
|
+
${option.label || option.name || option.text}
|
|
397
|
+
</option>`
|
|
398
|
+
).join('');
|
|
399
|
+
|
|
400
|
+
return `
|
|
401
|
+
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
402
|
+
<select class="${styles.select}" data-field-id="${field.id}">
|
|
403
|
+
<option value="">-- Select ${field.label} --</option>
|
|
404
|
+
${options}
|
|
405
|
+
</select>
|
|
406
|
+
${hasError ? `<span class="${styles.errorText}">${validationErrors[field.id]}</span>` : ''}
|
|
407
|
+
`;
|
|
408
|
+
|
|
409
|
+
case 'textarea':
|
|
410
|
+
return `
|
|
411
|
+
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
412
|
+
<textarea class="${styles.textarea}" data-field-id="${field.id}" placeholder="${field.placeholder || ''}" rows="${field.rows || 3}">${value}</textarea>
|
|
413
|
+
${hasError ? `<span class="${styles.errorText}">${validationErrors[field.id]}</span>` : ''}
|
|
414
|
+
`;
|
|
415
|
+
|
|
416
|
+
case 'date':
|
|
417
|
+
return `
|
|
418
|
+
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
419
|
+
<input type="date" class="${styles.input}" data-field-id="${field.id}" value="${value}">
|
|
420
|
+
${hasError ? `<span class="${styles.errorText}">${validationErrors[field.id]}</span>` : ''}
|
|
421
|
+
`;
|
|
422
|
+
|
|
423
|
+
case 'datetime':
|
|
424
|
+
return `
|
|
425
|
+
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
426
|
+
<input type="datetime-local" class="${styles.input}" data-field-id="${field.id}" value="${value}">
|
|
427
|
+
${hasError ? `<span class="${styles.errorText}">${validationErrors[field.id]}</span>` : ''}
|
|
428
|
+
`;
|
|
429
|
+
|
|
430
|
+
case 'number':
|
|
431
|
+
return `
|
|
432
|
+
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
433
|
+
<input type="number" class="${styles.input}" data-field-id="${field.id}" value="${value}" placeholder="${field.placeholder || ''}" min="${field.min || ''}" max="${field.max || ''}" step="${field.step || ''}">
|
|
434
|
+
${hasError ? `<span class="${styles.errorText}">${validationErrors[field.id]}</span>` : ''}
|
|
435
|
+
`;
|
|
436
|
+
|
|
437
|
+
case 'text':
|
|
438
|
+
default:
|
|
439
|
+
return `
|
|
440
|
+
<label class="${styles.label}">${field.label}${requiredStar}</label>
|
|
441
|
+
<input type="text" class="${styles.input}" data-field-id="${field.id}" value="${value}" placeholder="${field.placeholder || ''}">
|
|
442
|
+
${hasError ? `<span class="${styles.errorText}">${validationErrors[field.id]}</span>` : ''}
|
|
443
|
+
`;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// This component doesn't render directly - it uses SweetAlert2 for the modal
|
|
448
|
+
return null;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
export default StagePopupModal;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
.stagePopupForm {
|
|
2
|
+
text-align: left;
|
|
3
|
+
min-width: 400px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.message {
|
|
7
|
+
margin-bottom: 20px;
|
|
8
|
+
color: #666;
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
line-height: 1.4;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.fields {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 16px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.fieldWrapper {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 6px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.field {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
gap: 6px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.fieldError {
|
|
32
|
+
.input,
|
|
33
|
+
.select,
|
|
34
|
+
.textarea {
|
|
35
|
+
border-color: #e74c3c;
|
|
36
|
+
box-shadow: 0 0 0 2px rgba(231, 76, 60, 0.1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.label {
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
color: #333;
|
|
43
|
+
font-size: 14px;
|
|
44
|
+
margin-bottom: 4px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.required {
|
|
48
|
+
color: #e74c3c;
|
|
49
|
+
margin-left: 2px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.input,
|
|
53
|
+
.select,
|
|
54
|
+
.textarea {
|
|
55
|
+
padding: 8px 12px;
|
|
56
|
+
border: 1px solid #ddd;
|
|
57
|
+
border-radius: 4px;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
font-family: inherit;
|
|
60
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
61
|
+
|
|
62
|
+
&:focus {
|
|
63
|
+
outline: none;
|
|
64
|
+
border-color: #3498db;
|
|
65
|
+
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&:disabled {
|
|
69
|
+
background-color: #f8f9fa;
|
|
70
|
+
color: #6c757d;
|
|
71
|
+
cursor: not-allowed;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.select {
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
|
|
78
|
+
&:disabled {
|
|
79
|
+
cursor: not-allowed;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.textarea {
|
|
84
|
+
resize: vertical;
|
|
85
|
+
min-height: 80px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.errorText {
|
|
89
|
+
color: #e74c3c;
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
margin-top: 4px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.loading {
|
|
95
|
+
text-align: center;
|
|
96
|
+
padding: 20px;
|
|
97
|
+
color: #666;
|
|
98
|
+
font-style: italic;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// SweetAlert2 customizations
|
|
102
|
+
.swalPopup {
|
|
103
|
+
border-radius: 8px;
|
|
104
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.swalHtmlContainer {
|
|
108
|
+
padding: 0 !important;
|
|
109
|
+
margin: 16px 0 !important;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.swalConfirm {
|
|
113
|
+
background-color: #3498db !important;
|
|
114
|
+
border: none !important;
|
|
115
|
+
border-radius: 4px !important;
|
|
116
|
+
font-weight: 500 !important;
|
|
117
|
+
padding: 10px 20px !important;
|
|
118
|
+
|
|
119
|
+
&:hover {
|
|
120
|
+
background-color: #2980b9 !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&:focus {
|
|
124
|
+
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.3) !important;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.swalCancel {
|
|
129
|
+
background-color: #95a5a6 !important;
|
|
130
|
+
border: none !important;
|
|
131
|
+
border-radius: 4px !important;
|
|
132
|
+
font-weight: 500 !important;
|
|
133
|
+
padding: 10px 20px !important;
|
|
134
|
+
|
|
135
|
+
&:hover {
|
|
136
|
+
background-color: #7f8c8d !important;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&:focus {
|
|
140
|
+
box-shadow: 0 0 0 2px rgba(149, 165, 166, 0.3) !important;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Responsive adjustments
|
|
145
|
+
@media (max-width: 768px) {
|
|
146
|
+
.stagePopupForm {
|
|
147
|
+
min-width: 300px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.fields {
|
|
151
|
+
gap: 12px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.input,
|
|
155
|
+
.select,
|
|
156
|
+
.textarea {
|
|
157
|
+
font-size: 16px; // Prevents zoom on iOS
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Theme variations
|
|
162
|
+
.stagePopupForm {
|
|
163
|
+
&.theme-dark {
|
|
164
|
+
.label {
|
|
165
|
+
color: #f8f9fa;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.message {
|
|
169
|
+
color: #adb5bd;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.input,
|
|
173
|
+
.select,
|
|
174
|
+
.textarea {
|
|
175
|
+
background-color: #343a40;
|
|
176
|
+
border-color: #495057;
|
|
177
|
+
color: #f8f9fa;
|
|
178
|
+
|
|
179
|
+
&:focus {
|
|
180
|
+
border-color: #3498db;
|
|
181
|
+
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&:disabled {
|
|
185
|
+
background-color: #495057;
|
|
186
|
+
color: #6c757d;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.select option {
|
|
191
|
+
background-color: #343a40;
|
|
192
|
+
color: #f8f9fa;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Animation for field validation
|
|
198
|
+
.field {
|
|
199
|
+
transition: all 0.2s ease;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.fieldError {
|
|
203
|
+
animation: shake 0.3s ease-in-out;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@keyframes shake {
|
|
207
|
+
0%, 100% { transform: translateX(0); }
|
|
208
|
+
25% { transform: translateX(-2px); }
|
|
209
|
+
75% { transform: translateX(2px); }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Loading state
|
|
213
|
+
.loading {
|
|
214
|
+
position: relative;
|
|
215
|
+
|
|
216
|
+
&::after {
|
|
217
|
+
content: '';
|
|
218
|
+
position: absolute;
|
|
219
|
+
top: 50%;
|
|
220
|
+
right: 20px;
|
|
221
|
+
transform: translateY(-50%);
|
|
222
|
+
width: 16px;
|
|
223
|
+
height: 16px;
|
|
224
|
+
border: 2px solid #ddd;
|
|
225
|
+
border-top: 2px solid #3498db;
|
|
226
|
+
border-radius: 50%;
|
|
227
|
+
animation: spin 1s linear infinite;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@keyframes spin {
|
|
232
|
+
0% { transform: translateY(-50%) rotate(0deg); }
|
|
233
|
+
100% { transform: translateY(-50%) rotate(360deg); }
|
|
234
|
+
}
|