@visns-studio/visns-components 5.6.3 → 5.6.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/package.json
CHANGED
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
85
85
|
},
|
|
86
86
|
"name": "@visns-studio/visns-components",
|
|
87
|
-
"version": "5.6.
|
|
87
|
+
"version": "5.6.4",
|
|
88
88
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
89
89
|
"main": "src/index.js",
|
|
90
90
|
"files": [
|
|
@@ -35,28 +35,64 @@ const CustomDownload = (
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
method: method,
|
|
40
|
-
data: formData,
|
|
41
|
-
headers: headers,
|
|
42
|
-
url: url,
|
|
43
|
-
responseType: 'blob',
|
|
44
|
-
};
|
|
45
|
-
|
|
38
|
+
// First make a request to check if the request is valid
|
|
46
39
|
return trackPromise(
|
|
47
40
|
new Promise((resolve, reject) => {
|
|
48
|
-
|
|
41
|
+
// First, make a request without responseType: 'blob' to check for errors
|
|
42
|
+
axios({
|
|
43
|
+
method: method,
|
|
44
|
+
data: formData,
|
|
45
|
+
headers: headers,
|
|
46
|
+
url: url,
|
|
47
|
+
})
|
|
49
48
|
.then((response) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// If the response is JSON and contains an error, handle it
|
|
50
|
+
if (response.data && response.data.success === false) {
|
|
51
|
+
let errorMessage =
|
|
52
|
+
response.data.message || 'Unknown error';
|
|
53
|
+
if (response.data.error) {
|
|
54
|
+
errorMessage += ': ' + response.data.error;
|
|
55
|
+
}
|
|
54
56
|
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
+
if (errorCallback) {
|
|
58
|
+
errorCallback(errorMessage);
|
|
59
|
+
} else {
|
|
60
|
+
toast.error(<div>{parse(errorMessage)}</div>);
|
|
57
61
|
}
|
|
62
|
+
|
|
63
|
+
// Resolve with the error response
|
|
64
|
+
resolve(response);
|
|
65
|
+
} else {
|
|
66
|
+
// If no error, make the actual blob request
|
|
67
|
+
axios({
|
|
68
|
+
method: method,
|
|
69
|
+
data: formData,
|
|
70
|
+
headers: headers,
|
|
71
|
+
url: url,
|
|
72
|
+
responseType: 'blob',
|
|
73
|
+
})
|
|
74
|
+
.then((blobResponse) => {
|
|
75
|
+
if (successCallback) {
|
|
76
|
+
successCallback(blobResponse.data);
|
|
77
|
+
}
|
|
78
|
+
resolve(blobResponse);
|
|
79
|
+
})
|
|
80
|
+
.catch((blobError) => {
|
|
81
|
+
let errorMessage =
|
|
82
|
+
blobError.message ||
|
|
83
|
+
'Error downloading file';
|
|
84
|
+
|
|
85
|
+
if (errorCallback) {
|
|
86
|
+
errorCallback(errorMessage);
|
|
87
|
+
} else {
|
|
88
|
+
toast.error(
|
|
89
|
+
<div>{parse(errorMessage)}</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
reject(blobError);
|
|
94
|
+
});
|
|
58
95
|
}
|
|
59
|
-
resolve(response);
|
|
60
96
|
})
|
|
61
97
|
.catch((error) => {
|
|
62
98
|
let errorMessage = '';
|
|
@@ -177,6 +177,96 @@ const isJsonValue = (value) => {
|
|
|
177
177
|
return typeof value === 'object' && value !== null;
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
+
// Helper function to validate filter criteria
|
|
181
|
+
const validateFilterCriteria = (filterCriteria) => {
|
|
182
|
+
// If it's already a flat array (legacy format)
|
|
183
|
+
if (Array.isArray(filterCriteria)) {
|
|
184
|
+
// Check each filter in the array
|
|
185
|
+
for (let i = 0; i < filterCriteria.length; i++) {
|
|
186
|
+
const filter = filterCriteria[i];
|
|
187
|
+
|
|
188
|
+
// Skip validation for IS NULL and IS NOT NULL operators which don't need a value
|
|
189
|
+
if (
|
|
190
|
+
filter.operator === 'IS NULL' ||
|
|
191
|
+
filter.operator === 'IS NOT NULL'
|
|
192
|
+
) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if any required field is missing
|
|
197
|
+
if (
|
|
198
|
+
!filter.table ||
|
|
199
|
+
!filter.column ||
|
|
200
|
+
!filter.operator ||
|
|
201
|
+
filter.value === undefined ||
|
|
202
|
+
filter.value === null ||
|
|
203
|
+
filter.value === ''
|
|
204
|
+
) {
|
|
205
|
+
return {
|
|
206
|
+
valid: false,
|
|
207
|
+
message: `Filter #${
|
|
208
|
+
i + 1
|
|
209
|
+
} is missing required fields (table, column, operator, or value)`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { valid: true };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// If it's the new structure with groups and operators
|
|
217
|
+
if (filterCriteria.groups && Array.isArray(filterCriteria.groups)) {
|
|
218
|
+
// Process each group
|
|
219
|
+
for (
|
|
220
|
+
let groupIndex = 0;
|
|
221
|
+
groupIndex < filterCriteria.groups.length;
|
|
222
|
+
groupIndex++
|
|
223
|
+
) {
|
|
224
|
+
const group = filterCriteria.groups[groupIndex];
|
|
225
|
+
|
|
226
|
+
// Only validate non-empty groups
|
|
227
|
+
if (group.filters && group.filters.length > 0) {
|
|
228
|
+
// Check all filters in this group
|
|
229
|
+
for (
|
|
230
|
+
let filterIndex = 0;
|
|
231
|
+
filterIndex < group.filters.length;
|
|
232
|
+
filterIndex++
|
|
233
|
+
) {
|
|
234
|
+
const filter = group.filters[filterIndex];
|
|
235
|
+
|
|
236
|
+
// Skip validation for IS NULL and IS NOT NULL operators which don't need a value
|
|
237
|
+
if (
|
|
238
|
+
filter.operator === 'IS NULL' ||
|
|
239
|
+
filter.operator === 'IS NOT NULL'
|
|
240
|
+
) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check if any required field is missing
|
|
245
|
+
if (
|
|
246
|
+
!filter.table ||
|
|
247
|
+
!filter.column ||
|
|
248
|
+
!filter.operator ||
|
|
249
|
+
filter.value === undefined ||
|
|
250
|
+
filter.value === null ||
|
|
251
|
+
filter.value === ''
|
|
252
|
+
) {
|
|
253
|
+
return {
|
|
254
|
+
valid: false,
|
|
255
|
+
message: `Filter #${filterIndex + 1} in group #${
|
|
256
|
+
groupIndex + 1
|
|
257
|
+
} is missing required fields (table, column, operator, or value)`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return { valid: true };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// If it's neither a flat array nor the new structure, consider it valid (empty)
|
|
267
|
+
return { valid: true };
|
|
268
|
+
};
|
|
269
|
+
|
|
180
270
|
// Helper function to convert the enhanced filter structure to a flat array for the backend
|
|
181
271
|
const flattenFilterCriteria = (filterCriteria) => {
|
|
182
272
|
// If it's already a flat array (legacy format), return it as is
|
|
@@ -750,9 +840,40 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
750
840
|
setSelectedColumns(updatedColumns);
|
|
751
841
|
};
|
|
752
842
|
|
|
843
|
+
// Start editing a column name
|
|
844
|
+
const handleStartEditColumn = (index) => {
|
|
845
|
+
const column = selectedColumns[index];
|
|
846
|
+
setEditingColumnIndex(index);
|
|
847
|
+
setEditingColumnName(column.alias || column.displayName);
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// Save the edited column name
|
|
851
|
+
const handleSaveColumnName = () => {
|
|
852
|
+
if (editingColumnIndex !== null) {
|
|
853
|
+
const updatedColumns = [...selectedColumns];
|
|
854
|
+
updatedColumns[editingColumnIndex] = {
|
|
855
|
+
...updatedColumns[editingColumnIndex],
|
|
856
|
+
alias: editingColumnName,
|
|
857
|
+
};
|
|
858
|
+
setSelectedColumns(updatedColumns);
|
|
859
|
+
setEditingColumnIndex(null);
|
|
860
|
+
setEditingColumnName('');
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// Cancel column name editing
|
|
865
|
+
const handleCancelEditColumn = () => {
|
|
866
|
+
setEditingColumnIndex(null);
|
|
867
|
+
setEditingColumnName('');
|
|
868
|
+
};
|
|
869
|
+
|
|
753
870
|
// State for drag and drop
|
|
754
871
|
const [draggedItemIndex, setDraggedItemIndex] = useState(null);
|
|
755
872
|
|
|
873
|
+
// State for column renaming
|
|
874
|
+
const [editingColumnIndex, setEditingColumnIndex] = useState(null);
|
|
875
|
+
const [editingColumnName, setEditingColumnName] = useState('');
|
|
876
|
+
|
|
756
877
|
// Handle drag start for column reordering
|
|
757
878
|
const handleDragStart = (e, index) => {
|
|
758
879
|
setDraggedItemIndex(index);
|
|
@@ -1336,7 +1457,7 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1336
1457
|
}
|
|
1337
1458
|
};
|
|
1338
1459
|
|
|
1339
|
-
// Export report as CSV or
|
|
1460
|
+
// Export report as CSV, Excel or PDF
|
|
1340
1461
|
const handleExportReport = async (format = 'csv') => {
|
|
1341
1462
|
if (selectedColumns.length === 0) {
|
|
1342
1463
|
toast.error('Please select at least one column for your report');
|
|
@@ -1348,6 +1469,15 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1348
1469
|
return;
|
|
1349
1470
|
}
|
|
1350
1471
|
|
|
1472
|
+
// Validate filter criteria
|
|
1473
|
+
if (filterCriteria) {
|
|
1474
|
+
const validationResult = validateFilterCriteria(filterCriteria);
|
|
1475
|
+
if (!validationResult.valid) {
|
|
1476
|
+
toast.error(validationResult.message);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1351
1481
|
setIsLoading(true);
|
|
1352
1482
|
const toastId = toast.loading(
|
|
1353
1483
|
`Exporting report as ${format.toUpperCase()}...`
|
|
@@ -1368,8 +1498,8 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1368
1498
|
sourceColumn: join.sourceColumn,
|
|
1369
1499
|
targetColumn: join.targetColumn,
|
|
1370
1500
|
})),
|
|
1371
|
-
filters:
|
|
1372
|
-
sorting:
|
|
1501
|
+
filters: flattenFilterCriteria(filterCriteria),
|
|
1502
|
+
sorting: sortCriteria,
|
|
1373
1503
|
};
|
|
1374
1504
|
|
|
1375
1505
|
// Prepare export request data
|
|
@@ -1395,16 +1525,66 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1395
1525
|
const date = moment().format('YYYYMMDD');
|
|
1396
1526
|
const fileName = `${date}_${reportName}.${format}`;
|
|
1397
1527
|
|
|
1398
|
-
//
|
|
1399
|
-
|
|
1528
|
+
// For PDF (HTML) format, open in a new window for printing
|
|
1529
|
+
if (format === 'pdf') {
|
|
1530
|
+
// Convert blob to URL
|
|
1531
|
+
const blob = new Blob([res.data], { type: 'text/html' });
|
|
1532
|
+
const url = URL.createObjectURL(blob);
|
|
1533
|
+
|
|
1534
|
+
// Open in a new window
|
|
1535
|
+
const printWindow = window.open(url, '_blank');
|
|
1536
|
+
|
|
1537
|
+
// Add print instructions
|
|
1538
|
+
if (printWindow) {
|
|
1539
|
+
printWindow.onload = function () {
|
|
1540
|
+
// Add print button at the top
|
|
1541
|
+
const printButton =
|
|
1542
|
+
printWindow.document.createElement('button');
|
|
1543
|
+
printButton.innerHTML = 'Print PDF';
|
|
1544
|
+
printButton.style.position = 'fixed';
|
|
1545
|
+
printButton.style.top = '10px';
|
|
1546
|
+
printButton.style.right = '10px';
|
|
1547
|
+
printButton.style.zIndex = '9999';
|
|
1548
|
+
printButton.style.padding = '8px 16px';
|
|
1549
|
+
printButton.style.backgroundColor = '#4CAF50';
|
|
1550
|
+
printButton.style.color = 'white';
|
|
1551
|
+
printButton.style.border = 'none';
|
|
1552
|
+
printButton.style.borderRadius = '4px';
|
|
1553
|
+
printButton.style.cursor = 'pointer';
|
|
1554
|
+
|
|
1555
|
+
printButton.onclick = function () {
|
|
1556
|
+
// Hide the button before printing
|
|
1557
|
+
this.style.display = 'none';
|
|
1558
|
+
printWindow.print();
|
|
1559
|
+
this.style.display = 'block';
|
|
1560
|
+
};
|
|
1400
1561
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1562
|
+
printWindow.document.body.insertBefore(
|
|
1563
|
+
printButton,
|
|
1564
|
+
printWindow.document.body.firstChild
|
|
1565
|
+
);
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
toast.update(toastId, {
|
|
1570
|
+
render: `Report opened in a new window. Use browser's print function to save as PDF.`,
|
|
1571
|
+
type: 'success',
|
|
1572
|
+
isLoading: false,
|
|
1573
|
+
autoClose: 5000,
|
|
1574
|
+
closeButton: true,
|
|
1575
|
+
});
|
|
1576
|
+
} else {
|
|
1577
|
+
// Use FileSaver.js to save the file for CSV and Excel
|
|
1578
|
+
saveAs(res.data, fileName);
|
|
1579
|
+
|
|
1580
|
+
toast.update(toastId, {
|
|
1581
|
+
render: `Report exported successfully as ${format.toUpperCase()}`,
|
|
1582
|
+
type: 'success',
|
|
1583
|
+
isLoading: false,
|
|
1584
|
+
autoClose: 5000,
|
|
1585
|
+
closeButton: true,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1408
1588
|
} else {
|
|
1409
1589
|
throw new Error('Invalid response format');
|
|
1410
1590
|
}
|
|
@@ -1666,6 +1846,15 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1666
1846
|
return;
|
|
1667
1847
|
}
|
|
1668
1848
|
|
|
1849
|
+
// Validate filter criteria
|
|
1850
|
+
if (filterCriteria) {
|
|
1851
|
+
const validationResult = validateFilterCriteria(filterCriteria);
|
|
1852
|
+
if (!validationResult.valid) {
|
|
1853
|
+
toast.error(validationResult.message);
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1669
1858
|
setIsLoading(true);
|
|
1670
1859
|
toast.info('Executing query...');
|
|
1671
1860
|
|
|
@@ -1845,6 +2034,15 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1845
2034
|
return;
|
|
1846
2035
|
}
|
|
1847
2036
|
|
|
2037
|
+
// Validate filter criteria
|
|
2038
|
+
if (filterCriteria) {
|
|
2039
|
+
const validationResult = validateFilterCriteria(filterCriteria);
|
|
2040
|
+
if (!validationResult.valid) {
|
|
2041
|
+
toast.error(validationResult.message);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1848
2046
|
setIsLoading(true);
|
|
1849
2047
|
|
|
1850
2048
|
try {
|
|
@@ -2248,7 +2446,12 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
2248
2446
|
)}
|
|
2249
2447
|
</div>
|
|
2250
2448
|
|
|
2251
|
-
<
|
|
2449
|
+
<div className={styles.columnSectionHeader}>
|
|
2450
|
+
<h3>Selected Columns</h3>
|
|
2451
|
+
<span className={styles.instructionText}>
|
|
2452
|
+
Click the pencil icon (✎) to rename columns
|
|
2453
|
+
</span>
|
|
2454
|
+
</div>
|
|
2252
2455
|
{selectedColumns.length > 0 ? (
|
|
2253
2456
|
<div className={styles.selectedColumns}>
|
|
2254
2457
|
{selectedColumns.map((column, index) => (
|
|
@@ -2276,10 +2479,86 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
2276
2479
|
>
|
|
2277
2480
|
⋮⋮
|
|
2278
2481
|
</div>
|
|
2279
|
-
<span>
|
|
2482
|
+
<span>
|
|
2483
|
+
{editingColumnIndex ===
|
|
2484
|
+
index ? (
|
|
2485
|
+
<div
|
|
2486
|
+
className={
|
|
2487
|
+
styles.inlineEditContainer
|
|
2488
|
+
}
|
|
2489
|
+
>
|
|
2490
|
+
<input
|
|
2491
|
+
type="text"
|
|
2492
|
+
className={
|
|
2493
|
+
styles.columnNameInput
|
|
2494
|
+
}
|
|
2495
|
+
value={
|
|
2496
|
+
editingColumnName
|
|
2497
|
+
}
|
|
2498
|
+
onChange={(e) =>
|
|
2499
|
+
setEditingColumnName(
|
|
2500
|
+
e.target
|
|
2501
|
+
.value
|
|
2502
|
+
)
|
|
2503
|
+
}
|
|
2504
|
+
autoFocus
|
|
2505
|
+
onKeyDown={(e) => {
|
|
2506
|
+
if (
|
|
2507
|
+
e.key ===
|
|
2508
|
+
'Enter'
|
|
2509
|
+
) {
|
|
2510
|
+
handleSaveColumnName();
|
|
2511
|
+
} else if (
|
|
2512
|
+
e.key ===
|
|
2513
|
+
'Escape'
|
|
2514
|
+
) {
|
|
2515
|
+
handleCancelEditColumn();
|
|
2516
|
+
}
|
|
2517
|
+
}}
|
|
2518
|
+
/>
|
|
2519
|
+
<button
|
|
2520
|
+
className={
|
|
2521
|
+
styles.saveBtn
|
|
2522
|
+
}
|
|
2523
|
+
onClick={
|
|
2524
|
+
handleSaveColumnName
|
|
2525
|
+
}
|
|
2526
|
+
title="Save"
|
|
2527
|
+
>
|
|
2528
|
+
✓
|
|
2529
|
+
</button>
|
|
2530
|
+
<button
|
|
2531
|
+
className={
|
|
2532
|
+
styles.cancelBtn
|
|
2533
|
+
}
|
|
2534
|
+
onClick={
|
|
2535
|
+
handleCancelEditColumn
|
|
2536
|
+
}
|
|
2537
|
+
title="Cancel"
|
|
2538
|
+
>
|
|
2539
|
+
✕
|
|
2540
|
+
</button>
|
|
2541
|
+
</div>
|
|
2542
|
+
) : (
|
|
2543
|
+
column.alias ||
|
|
2544
|
+
column.displayName
|
|
2545
|
+
)}
|
|
2546
|
+
</span>
|
|
2280
2547
|
<div
|
|
2281
2548
|
className={styles.columnActions}
|
|
2282
2549
|
>
|
|
2550
|
+
<button
|
|
2551
|
+
className={styles.editBtn}
|
|
2552
|
+
onClick={() =>
|
|
2553
|
+
handleStartEditColumn(
|
|
2554
|
+
index
|
|
2555
|
+
)
|
|
2556
|
+
}
|
|
2557
|
+
aria-label="Edit column name"
|
|
2558
|
+
title="Edit name"
|
|
2559
|
+
>
|
|
2560
|
+
✎
|
|
2561
|
+
</button>
|
|
2283
2562
|
<button
|
|
2284
2563
|
className={styles.removeBtn}
|
|
2285
2564
|
onClick={() =>
|
|
@@ -3890,6 +4169,7 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
3890
4169
|
{
|
|
3891
4170
|
keyInfo.percentage
|
|
3892
4171
|
}
|
|
4172
|
+
|
|
3893
4173
|
%
|
|
3894
4174
|
</span>
|
|
3895
4175
|
</div>
|
|
@@ -4033,6 +4313,17 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
4033
4313
|
>
|
|
4034
4314
|
Export as Excel
|
|
4035
4315
|
</button>
|
|
4316
|
+
<button
|
|
4317
|
+
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
4318
|
+
onClick={() => handleExportReport('pdf')}
|
|
4319
|
+
disabled={
|
|
4320
|
+
isLoading ||
|
|
4321
|
+
selectedColumns.length === 0 ||
|
|
4322
|
+
!reportName
|
|
4323
|
+
}
|
|
4324
|
+
>
|
|
4325
|
+
Export as PDF
|
|
4326
|
+
</button>
|
|
4036
4327
|
<button
|
|
4037
4328
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
4038
4329
|
onClick={handleSaveReport}
|
|
@@ -911,6 +911,55 @@
|
|
|
911
911
|
text-overflow: ellipsis;
|
|
912
912
|
white-space: nowrap;
|
|
913
913
|
}
|
|
914
|
+
|
|
915
|
+
.inlineEditContainer {
|
|
916
|
+
display: flex;
|
|
917
|
+
flex: 1;
|
|
918
|
+
align-items: center;
|
|
919
|
+
gap: 4px;
|
|
920
|
+
width: 100%;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.columnNameInput {
|
|
924
|
+
flex: 1;
|
|
925
|
+
padding: 4px 8px;
|
|
926
|
+
border: 1px solid var(--primary-color, #2563eb);
|
|
927
|
+
border-radius: 4px;
|
|
928
|
+
font-size: 0.9em;
|
|
929
|
+
color: var(--paragraph-color, #4b5563);
|
|
930
|
+
min-width: 50px;
|
|
931
|
+
max-width: 200px;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
.saveBtn,
|
|
935
|
+
.cancelBtn {
|
|
936
|
+
background: none;
|
|
937
|
+
border: none;
|
|
938
|
+
cursor: pointer;
|
|
939
|
+
padding: 2px 4px;
|
|
940
|
+
border-radius: 4px;
|
|
941
|
+
display: flex;
|
|
942
|
+
align-items: center;
|
|
943
|
+
justify-content: center;
|
|
944
|
+
transition: all 0.2s ease;
|
|
945
|
+
font-size: 0.9em;
|
|
946
|
+
min-width: 20px;
|
|
947
|
+
min-height: 20px;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.saveBtn {
|
|
951
|
+
color: var(--success-color, #10b981);
|
|
952
|
+
&:hover {
|
|
953
|
+
background: rgba(16, 185, 129, 0.1);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.cancelBtn {
|
|
958
|
+
color: var(--danger-color, #ef4444);
|
|
959
|
+
&:hover {
|
|
960
|
+
background: rgba(239, 68, 68, 0.1);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
914
963
|
}
|
|
915
964
|
|
|
916
965
|
.selectedColumnItem:hover {
|
|
@@ -949,6 +998,25 @@
|
|
|
949
998
|
flex-shrink: 0;
|
|
950
999
|
}
|
|
951
1000
|
|
|
1001
|
+
.editBtn {
|
|
1002
|
+
background: none;
|
|
1003
|
+
border: none;
|
|
1004
|
+
color: var(--primary-color, #2563eb);
|
|
1005
|
+
cursor: pointer;
|
|
1006
|
+
font-size: 1em;
|
|
1007
|
+
display: flex;
|
|
1008
|
+
align-items: center;
|
|
1009
|
+
justify-content: center;
|
|
1010
|
+
padding: 4px;
|
|
1011
|
+
border-radius: 4px;
|
|
1012
|
+
min-width: 24px;
|
|
1013
|
+
min-height: 24px;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.editBtn:hover {
|
|
1017
|
+
background: rgba(var(--primary-rgb, 37, 99, 235), 0.1);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
952
1020
|
.removeBtn {
|
|
953
1021
|
background: none;
|
|
954
1022
|
border: none;
|
|
@@ -1034,6 +1102,25 @@
|
|
|
1034
1102
|
margin: 0;
|
|
1035
1103
|
}
|
|
1036
1104
|
|
|
1105
|
+
.columnSectionHeader {
|
|
1106
|
+
display: flex;
|
|
1107
|
+
align-items: center;
|
|
1108
|
+
margin-bottom: 10px;
|
|
1109
|
+
flex-wrap: wrap;
|
|
1110
|
+
gap: 8px;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.columnSectionHeader h3 {
|
|
1114
|
+
margin: 0;
|
|
1115
|
+
margin-right: 8px;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.instructionText {
|
|
1119
|
+
font-size: 0.8em;
|
|
1120
|
+
color: var(--muted-color, #6b7280);
|
|
1121
|
+
font-style: italic;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1037
1124
|
.btnSmall {
|
|
1038
1125
|
padding: 4px 8px;
|
|
1039
1126
|
font-size: 0.8em;
|