@visns-studio/visns-components 5.1.19 → 5.1.21
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
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
78
78
|
},
|
|
79
79
|
"name": "@visns-studio/visns-components",
|
|
80
|
-
"version": "5.1.
|
|
80
|
+
"version": "5.1.21",
|
|
81
81
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
82
82
|
"main": "src/index.js",
|
|
83
83
|
"files": [
|
|
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
|
|
|
3
3
|
import debounce from 'lodash.debounce';
|
|
4
4
|
import { toast } from 'react-toastify';
|
|
5
5
|
import { confirmAlert } from 'react-confirm-alert';
|
|
6
|
-
import { TrashCan, ArrowUp, ArrowDown } from 'akar-icons';
|
|
6
|
+
import { TrashCan, ArrowUp, ArrowDown, Copy, Water } from 'akar-icons';
|
|
7
7
|
import { CompactPicker } from 'react-color';
|
|
8
8
|
|
|
9
9
|
import CustomFetch from '../Fetch';
|
|
@@ -11,25 +11,37 @@ import MultiSelect from '../MultiSelect';
|
|
|
11
11
|
|
|
12
12
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
13
13
|
import styles from './styles/GenericEditableTable.module.scss';
|
|
14
|
-
import { set } from 'lodash';
|
|
15
14
|
|
|
15
|
+
// Updated ColourPicker with an expanded pastel palette and a refined Akar icon
|
|
16
16
|
const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
|
|
17
17
|
const [colorPickerShow, setColorPickerShow] = useState(false);
|
|
18
18
|
const colorPickerRef = useRef(null);
|
|
19
|
-
const buttonRef = useRef(null);
|
|
20
|
-
const popoverRef = useRef(null);
|
|
19
|
+
const buttonRef = useRef(null);
|
|
20
|
+
const popoverRef = useRef(null);
|
|
21
|
+
|
|
22
|
+
// Expanded custom palette of pastel colours
|
|
23
|
+
const pastelColors = [
|
|
24
|
+
'#FFB3BA', // pastel red
|
|
25
|
+
'#FFDFBA', // pastel orange
|
|
26
|
+
'#FFFFBA', // pastel yellow
|
|
27
|
+
'#BAFFC9', // pastel green
|
|
28
|
+
'#BAE1FF', // pastel blue
|
|
29
|
+
'#BFFCC6', // additional pastel green
|
|
30
|
+
'#E2BAFF', // pastel purple
|
|
31
|
+
'#C3B1E1', // pastel lavender
|
|
32
|
+
'#F0E6EF', // pastel pink
|
|
33
|
+
'#D0E8F2', // pastel light blue
|
|
34
|
+
];
|
|
21
35
|
|
|
22
|
-
// Handle clicks outside of the color picker to close it
|
|
23
36
|
useEffect(() => {
|
|
24
37
|
const handleClickOutside = (event) => {
|
|
25
|
-
// Check if click is outside both the button and the popover
|
|
26
38
|
if (
|
|
27
39
|
popoverRef.current &&
|
|
28
40
|
!popoverRef.current.contains(event.target) &&
|
|
29
41
|
buttonRef.current &&
|
|
30
42
|
!buttonRef.current.contains(event.target)
|
|
31
43
|
) {
|
|
32
|
-
setColorPickerShow(false);
|
|
44
|
+
setColorPickerShow(false);
|
|
33
45
|
}
|
|
34
46
|
};
|
|
35
47
|
|
|
@@ -40,33 +52,34 @@ const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
|
|
|
40
52
|
|
|
41
53
|
const buttonRect = buttonRef.current
|
|
42
54
|
? buttonRef.current.getBoundingClientRect()
|
|
43
|
-
: {};
|
|
55
|
+
: {};
|
|
44
56
|
|
|
45
57
|
return (
|
|
46
58
|
<div className={styles.cpicker}>
|
|
47
59
|
<div className={styles.cpicker__btn} ref={colorPickerRef}>
|
|
48
60
|
<button
|
|
49
|
-
ref={buttonRef}
|
|
61
|
+
ref={buttonRef}
|
|
50
62
|
style={
|
|
51
63
|
value && value !== 'null' ? { background: value } : {}
|
|
52
64
|
}
|
|
53
65
|
onClick={(e) => {
|
|
54
66
|
e.preventDefault();
|
|
55
|
-
setColorPickerShow(!colorPickerShow);
|
|
67
|
+
setColorPickerShow(!colorPickerShow);
|
|
56
68
|
}}
|
|
57
69
|
>
|
|
58
|
-
|
|
70
|
+
{/* Using the Akar Water icon with increased size and some margin */}
|
|
71
|
+
<Water size={20} style={{ color: 'black' }} />
|
|
59
72
|
</button>
|
|
60
73
|
|
|
61
74
|
{colorPickerShow &&
|
|
62
75
|
ReactDOM.createPortal(
|
|
63
76
|
<div
|
|
64
|
-
ref={popoverRef}
|
|
77
|
+
ref={popoverRef}
|
|
65
78
|
className={styles.colour_popover}
|
|
66
79
|
style={{
|
|
67
80
|
position: 'absolute',
|
|
68
|
-
left: buttonRect.left,
|
|
69
|
-
top: buttonRect.bottom + 5,
|
|
81
|
+
left: buttonRect.left,
|
|
82
|
+
top: buttonRect.bottom + 5,
|
|
70
83
|
zIndex: 9999,
|
|
71
84
|
}}
|
|
72
85
|
>
|
|
@@ -76,13 +89,14 @@ const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
|
|
|
76
89
|
/>
|
|
77
90
|
<CompactPicker
|
|
78
91
|
color={value || ''}
|
|
92
|
+
colors={pastelColors}
|
|
79
93
|
onChangeComplete={(color) => {
|
|
80
94
|
onChange(color.hex, columnId, keyCounter);
|
|
81
95
|
setColorPickerShow(false);
|
|
82
96
|
}}
|
|
83
97
|
/>
|
|
84
98
|
</div>,
|
|
85
|
-
document.body
|
|
99
|
+
document.body
|
|
86
100
|
)}
|
|
87
101
|
</div>
|
|
88
102
|
</div>
|
|
@@ -98,16 +112,30 @@ const GenericEditableTable = ({
|
|
|
98
112
|
setData,
|
|
99
113
|
}) => {
|
|
100
114
|
const { columns, rows, form } = schedulingConfig;
|
|
115
|
+
const hasCategory = rows && rows.categoryKey; // Only group if defined
|
|
101
116
|
|
|
102
117
|
const [localData, setLocalData] = useState([]);
|
|
103
|
-
const [groupedCategories, setGroupedCategories] = useState([]);
|
|
104
118
|
|
|
119
|
+
// Helper to sort by sort_order without mutating the original array
|
|
120
|
+
const sortBySortOrder = (dataArray) =>
|
|
121
|
+
[...dataArray].sort((a, b) => a.sort_order - b.sort_order);
|
|
122
|
+
|
|
123
|
+
// Helper: if there is a column of type 'colour', return that value from the entry.
|
|
124
|
+
const getRowBackground = (entry) => {
|
|
125
|
+
const colourColumn = columns.find((col) => col.type === 'colour');
|
|
126
|
+
return colourColumn && entry[colourColumn.id]
|
|
127
|
+
? entry[colourColumn.id]
|
|
128
|
+
: undefined;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// On load, enrich the data and sort by sort_order
|
|
105
132
|
useEffect(() => {
|
|
106
133
|
const enrichedData = data[rows.key]?.map((entry, index) => ({
|
|
107
134
|
...entry,
|
|
108
135
|
sort_order: entry.sort_order ?? index + 1,
|
|
109
136
|
}));
|
|
110
|
-
|
|
137
|
+
const sortedData = enrichedData ? sortBySortOrder(enrichedData) : [];
|
|
138
|
+
setLocalData(sortedData);
|
|
111
139
|
}, [data, rows.key]);
|
|
112
140
|
|
|
113
141
|
const debouncedUpdate = useMemo(
|
|
@@ -123,7 +151,6 @@ const GenericEditableTable = ({
|
|
|
123
151
|
if (res.data?.data) {
|
|
124
152
|
setData(res.data.data);
|
|
125
153
|
}
|
|
126
|
-
// toast.success('Template updated successfully');
|
|
127
154
|
} else {
|
|
128
155
|
toast.error(res.error);
|
|
129
156
|
}
|
|
@@ -131,18 +158,20 @@ const GenericEditableTable = ({
|
|
|
131
158
|
console.error('Error updating field:', error);
|
|
132
159
|
}
|
|
133
160
|
}, 1000),
|
|
134
|
-
[]
|
|
161
|
+
[form.url, dataId, setData]
|
|
135
162
|
);
|
|
136
163
|
|
|
137
|
-
|
|
164
|
+
// Group data by category (if defined) and sort each group by sort_order
|
|
165
|
+
const groupCategoriesByType = (dataArray) => {
|
|
166
|
+
if (!rows.categoryKey) return [];
|
|
138
167
|
const grouped = {};
|
|
139
168
|
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
dataArray.forEach((entry, index) => {
|
|
170
|
+
const categoryInfo = entry[rows.categoryKey.id];
|
|
171
|
+
const categoryType = categoryInfo
|
|
172
|
+
? categoryInfo[rows.categoryKey.label]
|
|
173
|
+
: null;
|
|
174
|
+
const categoryId = categoryInfo ? categoryInfo.id : null;
|
|
146
175
|
const groupKey = categoryId || 'no_category';
|
|
147
176
|
|
|
148
177
|
if (!grouped[groupKey]) {
|
|
@@ -153,8 +182,8 @@ const GenericEditableTable = ({
|
|
|
153
182
|
};
|
|
154
183
|
}
|
|
155
184
|
grouped[groupKey].entries.push({
|
|
156
|
-
...
|
|
157
|
-
keyCounter:
|
|
185
|
+
...entry,
|
|
186
|
+
keyCounter: index,
|
|
158
187
|
});
|
|
159
188
|
});
|
|
160
189
|
|
|
@@ -169,6 +198,42 @@ const GenericEditableTable = ({
|
|
|
169
198
|
);
|
|
170
199
|
};
|
|
171
200
|
|
|
201
|
+
const groupedCategories = useMemo(() => {
|
|
202
|
+
if (hasCategory) {
|
|
203
|
+
return groupCategoriesByType(localData);
|
|
204
|
+
}
|
|
205
|
+
return [];
|
|
206
|
+
}, [localData, hasCategory]);
|
|
207
|
+
|
|
208
|
+
// Copy all values from a column to the clipboard
|
|
209
|
+
const handleColumnCopy = (column, group = null) => {
|
|
210
|
+
let values;
|
|
211
|
+
|
|
212
|
+
if (group) {
|
|
213
|
+
values = group.entries.map((entry) => entry[column.id]);
|
|
214
|
+
} else {
|
|
215
|
+
values = localData.map((entry) => entry[column.id]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const textToCopy = values
|
|
219
|
+
.filter((val) => val !== undefined && val !== null)
|
|
220
|
+
.join('\n');
|
|
221
|
+
|
|
222
|
+
navigator.clipboard
|
|
223
|
+
.writeText(textToCopy)
|
|
224
|
+
.then(() =>
|
|
225
|
+
toast.success(
|
|
226
|
+
`Copied ${values.length} value${
|
|
227
|
+
values.length !== 1 ? 's' : ''
|
|
228
|
+
} from "${column.label}"${
|
|
229
|
+
group ? ` (Group: ${group.category})` : ''
|
|
230
|
+
}`
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
.catch(() => toast.error('Failed to copy to clipboard'));
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Update a field’s value (keyCounter corresponds to the row index in sorted data)
|
|
172
237
|
const handleFieldChange = (value, fieldId, keyCounter) => {
|
|
173
238
|
setLocalData((prev) => {
|
|
174
239
|
const updatedDetail = [...prev];
|
|
@@ -178,16 +243,17 @@ const GenericEditableTable = ({
|
|
|
178
243
|
};
|
|
179
244
|
|
|
180
245
|
debouncedUpdate({ [rows.key]: updatedDetail });
|
|
181
|
-
|
|
182
246
|
return updatedDetail;
|
|
183
247
|
});
|
|
184
248
|
};
|
|
185
249
|
|
|
186
|
-
|
|
250
|
+
// For grouped data, sort handler remains unchanged
|
|
251
|
+
const handleSortChangeGrouped = (groupId, entryId, direction) => {
|
|
252
|
+
if (!hasCategory) return;
|
|
187
253
|
setLocalData((prev) => {
|
|
188
254
|
const updatedDetail = [...prev];
|
|
189
255
|
const group = groupCategoriesByType(updatedDetail).find(
|
|
190
|
-
(
|
|
256
|
+
(grp) => grp.id === groupId
|
|
191
257
|
);
|
|
192
258
|
|
|
193
259
|
if (group) {
|
|
@@ -210,11 +276,11 @@ const GenericEditableTable = ({
|
|
|
210
276
|
];
|
|
211
277
|
|
|
212
278
|
const reordered = updatedDetail.map((item) => {
|
|
213
|
-
const
|
|
279
|
+
const found = group.entries.find(
|
|
214
280
|
(e) => e.id === item.id
|
|
215
281
|
);
|
|
216
|
-
return
|
|
217
|
-
? { ...item, sort_order:
|
|
282
|
+
return found
|
|
283
|
+
? { ...item, sort_order: found.sort_order }
|
|
218
284
|
: item;
|
|
219
285
|
});
|
|
220
286
|
|
|
@@ -222,11 +288,34 @@ const GenericEditableTable = ({
|
|
|
222
288
|
return reordered;
|
|
223
289
|
}
|
|
224
290
|
}
|
|
225
|
-
|
|
226
291
|
return updatedDetail;
|
|
227
292
|
});
|
|
228
293
|
};
|
|
229
294
|
|
|
295
|
+
// For ungrouped data, sort before swapping items.
|
|
296
|
+
const handleSortChangeUngrouped = (entryId, direction) => {
|
|
297
|
+
setLocalData((prev) => {
|
|
298
|
+
const sortedData = sortBySortOrder(prev);
|
|
299
|
+
const index = sortedData.findIndex((entry) => entry.id === entryId);
|
|
300
|
+
|
|
301
|
+
if (
|
|
302
|
+
(direction === 'up' && index === 0) ||
|
|
303
|
+
(direction === 'down' && index === sortedData.length - 1)
|
|
304
|
+
) {
|
|
305
|
+
return prev;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const swapIndex = direction === 'up' ? index - 1 : index + 1;
|
|
309
|
+
[sortedData[index].sort_order, sortedData[swapIndex].sort_order] = [
|
|
310
|
+
sortedData[swapIndex].sort_order,
|
|
311
|
+
sortedData[index].sort_order,
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
debouncedUpdate({ [rows.key]: sortedData });
|
|
315
|
+
return sortedData;
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
230
319
|
const handleDeleteRow = (entry) => {
|
|
231
320
|
confirmAlert({
|
|
232
321
|
title: 'Confirm to Delete',
|
|
@@ -314,7 +403,7 @@ const GenericEditableTable = ({
|
|
|
314
403
|
))}
|
|
315
404
|
</select>
|
|
316
405
|
);
|
|
317
|
-
case 'dropdown-ajax':
|
|
406
|
+
case 'dropdown-ajax': {
|
|
318
407
|
const options = preloadedOptions[column.id] || [];
|
|
319
408
|
return (
|
|
320
409
|
<select
|
|
@@ -335,13 +424,12 @@ const GenericEditableTable = ({
|
|
|
335
424
|
))}
|
|
336
425
|
</select>
|
|
337
426
|
);
|
|
338
|
-
|
|
427
|
+
}
|
|
428
|
+
case 'multi-dropdown-ajax': {
|
|
339
429
|
const multiOptions = preloadedOptions[column.id] || [];
|
|
340
430
|
return (
|
|
341
431
|
<MultiSelect
|
|
342
|
-
settings={{
|
|
343
|
-
id: column.id,
|
|
344
|
-
}}
|
|
432
|
+
settings={{ id: column.id, compact: true }}
|
|
345
433
|
className={styles.selectFullWidth}
|
|
346
434
|
multi={false}
|
|
347
435
|
onChange={(value) =>
|
|
@@ -351,19 +439,17 @@ const GenericEditableTable = ({
|
|
|
351
439
|
options={multiOptions}
|
|
352
440
|
/>
|
|
353
441
|
);
|
|
354
|
-
|
|
442
|
+
}
|
|
443
|
+
case 'total': {
|
|
355
444
|
const subtotal = column.keys.reduce(
|
|
356
445
|
(sum, key) => sum + (parseFloat(entry[key]) || 0),
|
|
357
446
|
0
|
|
358
447
|
);
|
|
359
|
-
|
|
360
|
-
// Format the subtotal as currency
|
|
361
|
-
const formattedSubtotal = new Intl.NumberFormat('en-AU', {
|
|
448
|
+
return new Intl.NumberFormat('en-AU', {
|
|
362
449
|
style: 'currency',
|
|
363
450
|
currency: 'AUD',
|
|
364
451
|
}).format(subtotal);
|
|
365
|
-
|
|
366
|
-
return formattedSubtotal;
|
|
452
|
+
}
|
|
367
453
|
default:
|
|
368
454
|
return (
|
|
369
455
|
<input
|
|
@@ -381,151 +467,247 @@ const GenericEditableTable = ({
|
|
|
381
467
|
}
|
|
382
468
|
};
|
|
383
469
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
470
|
+
// Render header cell with a label and copy icon
|
|
471
|
+
const renderHeaderCell = (column, group = null) => (
|
|
472
|
+
<th
|
|
473
|
+
key={`${column.id}${group ? `-${group.id}` : ''}`}
|
|
474
|
+
style={{ width: column.width || 'auto' }}
|
|
475
|
+
>
|
|
476
|
+
<div
|
|
477
|
+
style={{
|
|
478
|
+
display: 'flex',
|
|
479
|
+
alignItems: 'center',
|
|
480
|
+
justifyContent: 'space-between',
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<span>{column.label}</span>
|
|
484
|
+
{column.copy && (
|
|
485
|
+
<span
|
|
486
|
+
onClick={(e) => {
|
|
487
|
+
e.stopPropagation();
|
|
488
|
+
handleColumnCopy(column, group);
|
|
489
|
+
}}
|
|
490
|
+
style={{ cursor: 'pointer' }}
|
|
491
|
+
>
|
|
492
|
+
<Copy size={16} />
|
|
493
|
+
</span>
|
|
494
|
+
)}
|
|
495
|
+
</div>
|
|
496
|
+
</th>
|
|
497
|
+
);
|
|
387
498
|
|
|
388
499
|
return (
|
|
389
500
|
<div className={styles.gridtxt}>
|
|
390
501
|
<div className={styles.gridtxt__header}>
|
|
391
502
|
<span>{schedulingConfig.title || 'Estimation Schedule'}</span>
|
|
392
503
|
</div>
|
|
393
|
-
{
|
|
394
|
-
|
|
395
|
-
<
|
|
396
|
-
<
|
|
397
|
-
{
|
|
398
|
-
<
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
504
|
+
{hasCategory ? (
|
|
505
|
+
groupedCategories.length > 0 ? (
|
|
506
|
+
<table className={styles.schedulingTable}>
|
|
507
|
+
<tbody>
|
|
508
|
+
{groupedCategories.map((group) => (
|
|
509
|
+
<React.Fragment key={group.id}>
|
|
510
|
+
<tr className={styles.categoryRow}>
|
|
511
|
+
<td colSpan={columns.length + 1}>
|
|
512
|
+
{group.category}
|
|
513
|
+
</td>
|
|
514
|
+
</tr>
|
|
515
|
+
<tr>
|
|
516
|
+
{columns.map((column) =>
|
|
517
|
+
renderHeaderCell(column, group)
|
|
518
|
+
)}
|
|
519
|
+
<th style={{ width: '5%' }}></th>
|
|
520
|
+
</tr>
|
|
521
|
+
{group.entries.map((entry, idx) => (
|
|
522
|
+
<tr
|
|
523
|
+
key={entry.id}
|
|
524
|
+
style={{
|
|
525
|
+
background:
|
|
526
|
+
getRowBackground(entry),
|
|
527
|
+
}}
|
|
528
|
+
>
|
|
529
|
+
{columns.map((column) => (
|
|
530
|
+
<td
|
|
531
|
+
key={column.id}
|
|
532
|
+
style={{
|
|
533
|
+
textAlign:
|
|
534
|
+
column.type ===
|
|
535
|
+
'currency'
|
|
536
|
+
? 'right'
|
|
537
|
+
: 'left',
|
|
538
|
+
}}
|
|
539
|
+
>
|
|
540
|
+
{renderScheduledTableField(
|
|
541
|
+
entry,
|
|
542
|
+
column,
|
|
543
|
+
entry.keyCounter
|
|
544
|
+
)}
|
|
545
|
+
</td>
|
|
546
|
+
))}
|
|
547
|
+
<td>
|
|
548
|
+
<div
|
|
549
|
+
className={styles.tdactions}
|
|
550
|
+
>
|
|
551
|
+
{idx > 0 && (
|
|
552
|
+
<ArrowUp
|
|
553
|
+
size={16}
|
|
554
|
+
onClick={() =>
|
|
555
|
+
handleSortChangeGrouped(
|
|
556
|
+
group.id,
|
|
557
|
+
entry.id,
|
|
558
|
+
'up'
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
style={{
|
|
562
|
+
cursor: 'pointer',
|
|
563
|
+
}}
|
|
564
|
+
/>
|
|
565
|
+
)}
|
|
566
|
+
{idx <
|
|
567
|
+
group.entries.length -
|
|
568
|
+
1 && (
|
|
569
|
+
<ArrowDown
|
|
570
|
+
size={16}
|
|
571
|
+
onClick={() =>
|
|
572
|
+
handleSortChangeGrouped(
|
|
573
|
+
group.id,
|
|
574
|
+
entry.id,
|
|
575
|
+
'down'
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
style={{
|
|
579
|
+
cursor: 'pointer',
|
|
580
|
+
}}
|
|
581
|
+
/>
|
|
582
|
+
)}
|
|
583
|
+
<TrashCan
|
|
459
584
|
size={16}
|
|
460
585
|
onClick={() =>
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
entry.id,
|
|
464
|
-
'down'
|
|
586
|
+
handleDeleteRow(
|
|
587
|
+
entry
|
|
465
588
|
)
|
|
466
589
|
}
|
|
467
590
|
style={{
|
|
468
591
|
cursor: 'pointer',
|
|
469
592
|
}}
|
|
470
593
|
/>
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
onClick={() =>
|
|
475
|
-
handleDeleteRow(entry)
|
|
476
|
-
}
|
|
477
|
-
style={{
|
|
478
|
-
cursor: 'pointer',
|
|
479
|
-
}}
|
|
480
|
-
/>
|
|
481
|
-
</div>
|
|
482
|
-
</td>
|
|
483
|
-
</tr>
|
|
484
|
-
))}
|
|
485
|
-
<tr className={styles.subtotalRow}>
|
|
486
|
-
{columns.map((column, index) => (
|
|
487
|
-
<td key={column.id}>
|
|
488
|
-
{index === columns.length - 1
|
|
489
|
-
? // Total column: Calculate the sum of each row's total
|
|
490
|
-
group.entries
|
|
491
|
-
.reduce((sum, entry) => {
|
|
492
|
-
// For each row, sum the values based on column.keys
|
|
493
|
-
const subtotal =
|
|
494
|
-
Array.isArray(
|
|
495
|
-
column.keys
|
|
496
|
-
) // Ensure column.keys is an array
|
|
497
|
-
? column.keys.reduce(
|
|
498
|
-
(
|
|
499
|
-
rowSum,
|
|
500
|
-
key
|
|
501
|
-
) =>
|
|
502
|
-
rowSum +
|
|
503
|
-
(parseFloat(
|
|
504
|
-
entry[
|
|
505
|
-
key
|
|
506
|
-
]
|
|
507
|
-
) ||
|
|
508
|
-
0),
|
|
509
|
-
0
|
|
510
|
-
)
|
|
511
|
-
: 0; // Default to 0 if column.keys is not an array
|
|
512
|
-
return sum + subtotal;
|
|
513
|
-
}, 0)
|
|
514
|
-
.toLocaleString('en-AU', {
|
|
515
|
-
style: 'currency',
|
|
516
|
-
currency: 'AUD',
|
|
517
|
-
})
|
|
518
|
-
: index === columns.length - 2
|
|
519
|
-
? // The column immediately before 'total' shows 'Subtotal'
|
|
520
|
-
'Subtotal'
|
|
521
|
-
: // All other columns are left empty
|
|
522
|
-
''}
|
|
523
|
-
</td>
|
|
594
|
+
</div>
|
|
595
|
+
</td>
|
|
596
|
+
</tr>
|
|
524
597
|
))}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
598
|
+
</React.Fragment>
|
|
599
|
+
))}
|
|
600
|
+
</tbody>
|
|
601
|
+
</table>
|
|
602
|
+
) : (
|
|
603
|
+
<div className={styles.noDataMessage}>
|
|
604
|
+
No data available. Please check your filters or try
|
|
605
|
+
reloading the data.
|
|
606
|
+
</div>
|
|
607
|
+
)
|
|
608
|
+
) : localData.length > 0 ? (
|
|
609
|
+
<table className={styles.schedulingTable}>
|
|
610
|
+
<thead>
|
|
611
|
+
<tr>
|
|
612
|
+
{columns.map((column) => renderHeaderCell(column))}
|
|
613
|
+
<th style={{ width: '5%' }}></th>
|
|
614
|
+
</tr>
|
|
615
|
+
</thead>
|
|
616
|
+
<tbody>
|
|
617
|
+
{sortBySortOrder(localData).map((entry, idx) => (
|
|
618
|
+
<tr
|
|
619
|
+
key={entry.id}
|
|
620
|
+
style={{ background: getRowBackground(entry) }}
|
|
621
|
+
>
|
|
622
|
+
{columns.map((column) => (
|
|
623
|
+
<td
|
|
624
|
+
key={column.id}
|
|
625
|
+
style={{
|
|
626
|
+
width: column.width || 'auto',
|
|
627
|
+
textAlign:
|
|
628
|
+
column.type === 'currency'
|
|
629
|
+
? 'right'
|
|
630
|
+
: 'left',
|
|
631
|
+
}}
|
|
632
|
+
>
|
|
633
|
+
{renderScheduledTableField(
|
|
634
|
+
entry,
|
|
635
|
+
column,
|
|
636
|
+
idx
|
|
637
|
+
)}
|
|
638
|
+
</td>
|
|
639
|
+
))}
|
|
640
|
+
<td>
|
|
641
|
+
<div className={styles.tdactions}>
|
|
642
|
+
{idx > 0 && (
|
|
643
|
+
<ArrowUp
|
|
644
|
+
size={16}
|
|
645
|
+
onClick={() =>
|
|
646
|
+
handleSortChangeUngrouped(
|
|
647
|
+
entry.id,
|
|
648
|
+
'up'
|
|
649
|
+
)
|
|
650
|
+
}
|
|
651
|
+
style={{ cursor: 'pointer' }}
|
|
652
|
+
/>
|
|
653
|
+
)}
|
|
654
|
+
{idx <
|
|
655
|
+
sortBySortOrder(localData).length -
|
|
656
|
+
1 && (
|
|
657
|
+
<ArrowDown
|
|
658
|
+
size={16}
|
|
659
|
+
onClick={() =>
|
|
660
|
+
handleSortChangeUngrouped(
|
|
661
|
+
entry.id,
|
|
662
|
+
'down'
|
|
663
|
+
)
|
|
664
|
+
}
|
|
665
|
+
style={{ cursor: 'pointer' }}
|
|
666
|
+
/>
|
|
667
|
+
)}
|
|
668
|
+
<TrashCan
|
|
669
|
+
size={16}
|
|
670
|
+
onClick={() =>
|
|
671
|
+
handleDeleteRow(entry)
|
|
672
|
+
}
|
|
673
|
+
style={{ cursor: 'pointer' }}
|
|
674
|
+
/>
|
|
675
|
+
</div>
|
|
676
|
+
</td>
|
|
677
|
+
</tr>
|
|
528
678
|
))}
|
|
679
|
+
<tr className={styles.subtotalRow}>
|
|
680
|
+
{columns.map((column, index) => (
|
|
681
|
+
<td key={column.id}>
|
|
682
|
+
{index === columns.length - 1
|
|
683
|
+
? sortBySortOrder(localData)
|
|
684
|
+
.reduce((sum, entry) => {
|
|
685
|
+
const subtotal =
|
|
686
|
+
Array.isArray(column.keys)
|
|
687
|
+
? column.keys.reduce(
|
|
688
|
+
(rowSum, key) =>
|
|
689
|
+
rowSum +
|
|
690
|
+
(parseFloat(
|
|
691
|
+
entry[
|
|
692
|
+
key
|
|
693
|
+
]
|
|
694
|
+
) || 0),
|
|
695
|
+
0
|
|
696
|
+
)
|
|
697
|
+
: 0;
|
|
698
|
+
return sum + subtotal;
|
|
699
|
+
}, 0)
|
|
700
|
+
.toLocaleString('en-AU', {
|
|
701
|
+
style: 'currency',
|
|
702
|
+
currency: 'AUD',
|
|
703
|
+
})
|
|
704
|
+
: index === columns.length - 2
|
|
705
|
+
? 'Subtotal'
|
|
706
|
+
: ''}
|
|
707
|
+
</td>
|
|
708
|
+
))}
|
|
709
|
+
<td></td>
|
|
710
|
+
</tr>
|
|
529
711
|
</tbody>
|
|
530
712
|
</table>
|
|
531
713
|
) : (
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
.schedulingTable {
|
|
2
2
|
width: 100%;
|
|
3
3
|
border-collapse: collapse;
|
|
4
|
-
margin:
|
|
4
|
+
margin: 0.5em 0; // Reduced margin
|
|
5
5
|
|
|
6
6
|
th,
|
|
7
7
|
td {
|
|
8
|
-
padding: 0.
|
|
8
|
+
padding: 0.2rem; // Reduced padding for cells
|
|
9
9
|
text-align: left;
|
|
10
10
|
border: 1px solid rgba(var(--primary-rgb), 0.1);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
th {
|
|
14
|
-
padding: 0.
|
|
15
|
-
font-size:
|
|
14
|
+
padding: 0.25rem 0.5rem; // Less padding than before
|
|
15
|
+
font-size: 0.9rem; // Slightly smaller font size
|
|
16
16
|
font-weight: bold;
|
|
17
|
-
background-color: var(--primary-color);
|
|
18
|
-
color: white;
|
|
17
|
+
background-color: var(--primary-color);
|
|
18
|
+
color: white;
|
|
19
19
|
text-align: center;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -25,16 +25,13 @@
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.categoryRow {
|
|
28
|
-
background-color: rgba(
|
|
29
|
-
|
|
30
|
-
0.15
|
|
31
|
-
); /* Lighter background for category */
|
|
32
|
-
font-size: 1.1rem; /* Increase font size */
|
|
28
|
+
background-color: rgba(var(--primary-rgb), 0.15);
|
|
29
|
+
font-size: 1rem; // Reduced font size
|
|
33
30
|
font-weight: bold;
|
|
34
31
|
text-align: left;
|
|
35
|
-
padding: 0.75rem
|
|
36
|
-
border-bottom: 2px solid var(--primary-color);
|
|
37
|
-
color: var(--primary-color);
|
|
32
|
+
padding: 0.5rem 0.75rem; // Reduced padding
|
|
33
|
+
border-bottom: 2px solid var(--primary-color);
|
|
34
|
+
color: var(--primary-color);
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
.totalRow {
|
|
@@ -46,9 +43,9 @@
|
|
|
46
43
|
.noDataMessage {
|
|
47
44
|
text-align: center;
|
|
48
45
|
color: var(--secondary-color);
|
|
49
|
-
font-size:
|
|
50
|
-
padding:
|
|
51
|
-
margin:
|
|
46
|
+
font-size: 1rem;
|
|
47
|
+
padding: 1em;
|
|
48
|
+
margin: 1em auto;
|
|
52
49
|
background: #f9f9f9;
|
|
53
50
|
border: 1px solid #ddd;
|
|
54
51
|
border-radius: 6px;
|
|
@@ -186,16 +183,13 @@
|
|
|
186
183
|
}
|
|
187
184
|
|
|
188
185
|
.subtotalRow {
|
|
189
|
-
background-color: rgba(
|
|
190
|
-
var(--secondary-rgb),
|
|
191
|
-
0.15
|
|
192
|
-
); /* Lighter background for subtotal */
|
|
186
|
+
background-color: rgba(var(--secondary-rgb), 0.15);
|
|
193
187
|
font-weight: bold;
|
|
194
|
-
text-align: right;
|
|
195
|
-
padding:
|
|
196
|
-
font-size: 0.
|
|
197
|
-
color: var(--primary-color);
|
|
198
|
-
border-top: 2px solid var(--primary-color);
|
|
188
|
+
text-align: right;
|
|
189
|
+
padding: 0.5em; // Reduced padding
|
|
190
|
+
font-size: 0.8rem; // Reduced font size
|
|
191
|
+
color: var(--primary-color);
|
|
192
|
+
border-top: 2px solid var(--primary-color);
|
|
199
193
|
}
|
|
200
194
|
|
|
201
195
|
.colour {
|
|
@@ -210,9 +204,9 @@
|
|
|
210
204
|
&__popover {
|
|
211
205
|
position: absolute;
|
|
212
206
|
z-index: 9999;
|
|
213
|
-
left: 50%;
|
|
214
|
-
top: 50%;
|
|
215
|
-
transform: translate(-50%, -50%);
|
|
207
|
+
left: 50%;
|
|
208
|
+
top: 50%;
|
|
209
|
+
transform: translate(-50%, -50%);
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
212
|
|
|
@@ -226,31 +220,70 @@
|
|
|
226
220
|
flex: 1;
|
|
227
221
|
|
|
228
222
|
button {
|
|
229
|
-
width:
|
|
230
|
-
height:
|
|
231
|
-
font-size: 1.5rem;
|
|
223
|
+
width: 32px;
|
|
224
|
+
height: 32px;
|
|
225
|
+
font-size: 1.5rem;
|
|
232
226
|
font-weight: 600;
|
|
233
|
-
color: #fff;
|
|
234
|
-
background-color: #b0b0b0;
|
|
235
|
-
border: 2px solid #999;
|
|
236
|
-
border-radius: 8px;
|
|
227
|
+
color: #fff;
|
|
228
|
+
background-color: #b0b0b0;
|
|
229
|
+
border: 2px solid #999;
|
|
230
|
+
border-radius: 8px;
|
|
237
231
|
cursor: pointer;
|
|
238
|
-
transition: all 0.3s ease;
|
|
232
|
+
transition: all 0.3s ease;
|
|
239
233
|
|
|
240
234
|
&:hover {
|
|
241
|
-
background-color: #999;
|
|
242
|
-
border-color: #666;
|
|
235
|
+
background-color: #999;
|
|
236
|
+
border-color: #666;
|
|
243
237
|
}
|
|
244
238
|
|
|
245
239
|
&:focus {
|
|
246
|
-
outline: none;
|
|
247
|
-
box-shadow: 0 0 0 2px rgba(72, 168, 58, 0.5);
|
|
240
|
+
outline: none;
|
|
241
|
+
box-shadow: 0 0 0 2px rgba(72, 168, 58, 0.5);
|
|
248
242
|
}
|
|
249
243
|
|
|
250
244
|
&:active {
|
|
251
|
-
background-color: #888;
|
|
252
|
-
transform: scale(0.98);
|
|
245
|
+
background-color: #888;
|
|
246
|
+
transform: scale(0.98);
|
|
253
247
|
}
|
|
254
248
|
}
|
|
255
249
|
}
|
|
256
250
|
}
|
|
251
|
+
|
|
252
|
+
/* Compact styling for native input and select elements */
|
|
253
|
+
input,
|
|
254
|
+
select {
|
|
255
|
+
padding: 0.25rem 0.5rem;
|
|
256
|
+
margin: 0;
|
|
257
|
+
font-size: 0.85rem;
|
|
258
|
+
height: 28px;
|
|
259
|
+
line-height: 1;
|
|
260
|
+
border: 1px solid rgba(var(--primary-rgb), 0.1);
|
|
261
|
+
box-sizing: border-box;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Compact styling for react-select components */
|
|
265
|
+
.react-select__control {
|
|
266
|
+
min-height: 28px;
|
|
267
|
+
padding: 0;
|
|
268
|
+
font-size: 0.85rem;
|
|
269
|
+
border: 1px solid rgba(var(--primary-rgb), 0.1);
|
|
270
|
+
box-sizing: border-box;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.react-select__value-container {
|
|
274
|
+
padding: 0 0.5rem;
|
|
275
|
+
margin: 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.react-select__input {
|
|
279
|
+
margin: 0;
|
|
280
|
+
padding: 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.react-select__indicator-separator {
|
|
284
|
+
display: none;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.react-select__indicators {
|
|
288
|
+
padding: 0 0.25rem;
|
|
289
|
+
}
|