@visns-studio/visns-components 5.1.19 → 5.1.20
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.20",
|
|
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,33 @@ 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) => {
|
|
210
|
+
const values = localData.map((entry) => entry[column.id]);
|
|
211
|
+
const textToCopy = values
|
|
212
|
+
.filter((val) => val !== undefined && val !== null)
|
|
213
|
+
.join('\n');
|
|
214
|
+
|
|
215
|
+
navigator.clipboard
|
|
216
|
+
.writeText(textToCopy)
|
|
217
|
+
.then(() =>
|
|
218
|
+
toast.success(
|
|
219
|
+
`Copied ${values.length} value${
|
|
220
|
+
values.length !== 1 ? 's' : ''
|
|
221
|
+
} from "${column.label}"`
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
.catch(() => toast.error('Failed to copy to clipboard'));
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Update a field’s value (keyCounter corresponds to the row index in sorted data)
|
|
172
228
|
const handleFieldChange = (value, fieldId, keyCounter) => {
|
|
173
229
|
setLocalData((prev) => {
|
|
174
230
|
const updatedDetail = [...prev];
|
|
@@ -178,16 +234,17 @@ const GenericEditableTable = ({
|
|
|
178
234
|
};
|
|
179
235
|
|
|
180
236
|
debouncedUpdate({ [rows.key]: updatedDetail });
|
|
181
|
-
|
|
182
237
|
return updatedDetail;
|
|
183
238
|
});
|
|
184
239
|
};
|
|
185
240
|
|
|
186
|
-
|
|
241
|
+
// For grouped data, sort handler remains unchanged
|
|
242
|
+
const handleSortChangeGrouped = (groupId, entryId, direction) => {
|
|
243
|
+
if (!hasCategory) return;
|
|
187
244
|
setLocalData((prev) => {
|
|
188
245
|
const updatedDetail = [...prev];
|
|
189
246
|
const group = groupCategoriesByType(updatedDetail).find(
|
|
190
|
-
(
|
|
247
|
+
(grp) => grp.id === groupId
|
|
191
248
|
);
|
|
192
249
|
|
|
193
250
|
if (group) {
|
|
@@ -210,11 +267,11 @@ const GenericEditableTable = ({
|
|
|
210
267
|
];
|
|
211
268
|
|
|
212
269
|
const reordered = updatedDetail.map((item) => {
|
|
213
|
-
const
|
|
270
|
+
const found = group.entries.find(
|
|
214
271
|
(e) => e.id === item.id
|
|
215
272
|
);
|
|
216
|
-
return
|
|
217
|
-
? { ...item, sort_order:
|
|
273
|
+
return found
|
|
274
|
+
? { ...item, sort_order: found.sort_order }
|
|
218
275
|
: item;
|
|
219
276
|
});
|
|
220
277
|
|
|
@@ -222,11 +279,34 @@ const GenericEditableTable = ({
|
|
|
222
279
|
return reordered;
|
|
223
280
|
}
|
|
224
281
|
}
|
|
225
|
-
|
|
226
282
|
return updatedDetail;
|
|
227
283
|
});
|
|
228
284
|
};
|
|
229
285
|
|
|
286
|
+
// For ungrouped data, sort before swapping items.
|
|
287
|
+
const handleSortChangeUngrouped = (entryId, direction) => {
|
|
288
|
+
setLocalData((prev) => {
|
|
289
|
+
const sortedData = sortBySortOrder(prev);
|
|
290
|
+
const index = sortedData.findIndex((entry) => entry.id === entryId);
|
|
291
|
+
|
|
292
|
+
if (
|
|
293
|
+
(direction === 'up' && index === 0) ||
|
|
294
|
+
(direction === 'down' && index === sortedData.length - 1)
|
|
295
|
+
) {
|
|
296
|
+
return prev;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const swapIndex = direction === 'up' ? index - 1 : index + 1;
|
|
300
|
+
[sortedData[index].sort_order, sortedData[swapIndex].sort_order] = [
|
|
301
|
+
sortedData[swapIndex].sort_order,
|
|
302
|
+
sortedData[index].sort_order,
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
debouncedUpdate({ [rows.key]: sortedData });
|
|
306
|
+
return sortedData;
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
|
|
230
310
|
const handleDeleteRow = (entry) => {
|
|
231
311
|
confirmAlert({
|
|
232
312
|
title: 'Confirm to Delete',
|
|
@@ -314,7 +394,7 @@ const GenericEditableTable = ({
|
|
|
314
394
|
))}
|
|
315
395
|
</select>
|
|
316
396
|
);
|
|
317
|
-
case 'dropdown-ajax':
|
|
397
|
+
case 'dropdown-ajax': {
|
|
318
398
|
const options = preloadedOptions[column.id] || [];
|
|
319
399
|
return (
|
|
320
400
|
<select
|
|
@@ -335,13 +415,12 @@ const GenericEditableTable = ({
|
|
|
335
415
|
))}
|
|
336
416
|
</select>
|
|
337
417
|
);
|
|
338
|
-
|
|
418
|
+
}
|
|
419
|
+
case 'multi-dropdown-ajax': {
|
|
339
420
|
const multiOptions = preloadedOptions[column.id] || [];
|
|
340
421
|
return (
|
|
341
422
|
<MultiSelect
|
|
342
|
-
settings={{
|
|
343
|
-
id: column.id,
|
|
344
|
-
}}
|
|
423
|
+
settings={{ id: column.id }}
|
|
345
424
|
className={styles.selectFullWidth}
|
|
346
425
|
multi={false}
|
|
347
426
|
onChange={(value) =>
|
|
@@ -351,19 +430,17 @@ const GenericEditableTable = ({
|
|
|
351
430
|
options={multiOptions}
|
|
352
431
|
/>
|
|
353
432
|
);
|
|
354
|
-
|
|
433
|
+
}
|
|
434
|
+
case 'total': {
|
|
355
435
|
const subtotal = column.keys.reduce(
|
|
356
436
|
(sum, key) => sum + (parseFloat(entry[key]) || 0),
|
|
357
437
|
0
|
|
358
438
|
);
|
|
359
|
-
|
|
360
|
-
// Format the subtotal as currency
|
|
361
|
-
const formattedSubtotal = new Intl.NumberFormat('en-AU', {
|
|
439
|
+
return new Intl.NumberFormat('en-AU', {
|
|
362
440
|
style: 'currency',
|
|
363
441
|
currency: 'AUD',
|
|
364
442
|
}).format(subtotal);
|
|
365
|
-
|
|
366
|
-
return formattedSubtotal;
|
|
443
|
+
}
|
|
367
444
|
default:
|
|
368
445
|
return (
|
|
369
446
|
<input
|
|
@@ -381,151 +458,298 @@ const GenericEditableTable = ({
|
|
|
381
458
|
}
|
|
382
459
|
};
|
|
383
460
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
461
|
+
// Render header cell with a label and copy icon
|
|
462
|
+
const renderHeaderCell = (column) => (
|
|
463
|
+
<th key={column.id} style={{ width: column.width || 'auto' }}>
|
|
464
|
+
<div
|
|
465
|
+
style={{
|
|
466
|
+
display: 'flex',
|
|
467
|
+
alignItems: 'center',
|
|
468
|
+
justifyContent: 'space-between',
|
|
469
|
+
}}
|
|
470
|
+
>
|
|
471
|
+
<span>{column.label}</span>
|
|
472
|
+
{column.copy && (
|
|
473
|
+
<span
|
|
474
|
+
onClick={(e) => {
|
|
475
|
+
e.stopPropagation();
|
|
476
|
+
handleColumnCopy(column);
|
|
477
|
+
}}
|
|
478
|
+
style={{ cursor: 'pointer' }}
|
|
479
|
+
>
|
|
480
|
+
<Copy size={16} />
|
|
481
|
+
</span>
|
|
482
|
+
)}
|
|
483
|
+
</div>
|
|
484
|
+
</th>
|
|
485
|
+
);
|
|
387
486
|
|
|
388
487
|
return (
|
|
389
488
|
<div className={styles.gridtxt}>
|
|
390
489
|
<div className={styles.gridtxt__header}>
|
|
391
490
|
<span>{schedulingConfig.title || 'Estimation Schedule'}</span>
|
|
392
491
|
</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
|
-
|
|
492
|
+
{hasCategory ? (
|
|
493
|
+
groupedCategories.length > 0 ? (
|
|
494
|
+
<table className={styles.schedulingTable}>
|
|
495
|
+
<thead>
|
|
496
|
+
<tr>
|
|
497
|
+
{columns.map((column) =>
|
|
498
|
+
renderHeaderCell(column)
|
|
499
|
+
)}
|
|
500
|
+
<th style={{ width: '5%' }}></th>
|
|
501
|
+
</tr>
|
|
502
|
+
</thead>
|
|
503
|
+
<tbody>
|
|
504
|
+
{groupedCategories.map((group) => (
|
|
505
|
+
<React.Fragment key={group.id}>
|
|
506
|
+
<tr className={styles.categoryRow}>
|
|
507
|
+
<td colSpan={columns.length + 1}>
|
|
508
|
+
{group.category}
|
|
509
|
+
</td>
|
|
510
|
+
</tr>
|
|
511
|
+
{group.entries.map((entry, idx) => (
|
|
512
|
+
<tr
|
|
513
|
+
key={entry.id}
|
|
514
|
+
style={{
|
|
515
|
+
background:
|
|
516
|
+
getRowBackground(entry),
|
|
517
|
+
}}
|
|
518
|
+
>
|
|
519
|
+
{columns.map((column) => (
|
|
520
|
+
<td
|
|
521
|
+
key={column.id}
|
|
522
|
+
style={{
|
|
523
|
+
width:
|
|
524
|
+
column.width ||
|
|
525
|
+
'auto',
|
|
526
|
+
textAlign:
|
|
527
|
+
column.type ===
|
|
528
|
+
'currency'
|
|
529
|
+
? 'right'
|
|
530
|
+
: 'left',
|
|
531
|
+
}}
|
|
532
|
+
>
|
|
533
|
+
{renderScheduledTableField(
|
|
534
|
+
entry,
|
|
535
|
+
column,
|
|
536
|
+
entry.keyCounter
|
|
537
|
+
)}
|
|
538
|
+
</td>
|
|
539
|
+
))}
|
|
540
|
+
<td>
|
|
541
|
+
<div
|
|
542
|
+
className={styles.tdactions}
|
|
543
|
+
>
|
|
544
|
+
{idx > 0 && (
|
|
545
|
+
<ArrowUp
|
|
546
|
+
size={16}
|
|
547
|
+
onClick={() =>
|
|
548
|
+
handleSortChangeGrouped(
|
|
549
|
+
group.id,
|
|
550
|
+
entry.id,
|
|
551
|
+
'up'
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
style={{
|
|
555
|
+
cursor: 'pointer',
|
|
556
|
+
}}
|
|
557
|
+
/>
|
|
558
|
+
)}
|
|
559
|
+
{idx <
|
|
560
|
+
group.entries.length -
|
|
561
|
+
1 && (
|
|
562
|
+
<ArrowDown
|
|
563
|
+
size={16}
|
|
564
|
+
onClick={() =>
|
|
565
|
+
handleSortChangeGrouped(
|
|
566
|
+
group.id,
|
|
567
|
+
entry.id,
|
|
568
|
+
'down'
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
style={{
|
|
572
|
+
cursor: 'pointer',
|
|
573
|
+
}}
|
|
574
|
+
/>
|
|
575
|
+
)}
|
|
576
|
+
<TrashCan
|
|
459
577
|
size={16}
|
|
460
578
|
onClick={() =>
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
entry.id,
|
|
464
|
-
'down'
|
|
579
|
+
handleDeleteRow(
|
|
580
|
+
entry
|
|
465
581
|
)
|
|
466
582
|
}
|
|
467
583
|
style={{
|
|
468
584
|
cursor: 'pointer',
|
|
469
585
|
}}
|
|
470
586
|
/>
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
587
|
+
</div>
|
|
588
|
+
</td>
|
|
589
|
+
</tr>
|
|
590
|
+
))}
|
|
591
|
+
<tr className={styles.subtotalRow}>
|
|
592
|
+
{columns.map((column, index) => (
|
|
593
|
+
<td key={column.id}>
|
|
594
|
+
{index === columns.length - 1
|
|
595
|
+
? group.entries
|
|
596
|
+
.reduce(
|
|
597
|
+
(sum, entry) => {
|
|
598
|
+
const subtotal =
|
|
599
|
+
Array.isArray(
|
|
600
|
+
column.keys
|
|
601
|
+
)
|
|
602
|
+
? column.keys.reduce(
|
|
603
|
+
(
|
|
604
|
+
rowSum,
|
|
605
|
+
key
|
|
606
|
+
) =>
|
|
607
|
+
rowSum +
|
|
608
|
+
(parseFloat(
|
|
609
|
+
entry[
|
|
610
|
+
key
|
|
611
|
+
]
|
|
612
|
+
) ||
|
|
613
|
+
0),
|
|
614
|
+
0
|
|
615
|
+
)
|
|
616
|
+
: 0;
|
|
617
|
+
return (
|
|
618
|
+
sum +
|
|
619
|
+
subtotal
|
|
620
|
+
);
|
|
621
|
+
},
|
|
622
|
+
0
|
|
623
|
+
)
|
|
624
|
+
.toLocaleString(
|
|
625
|
+
'en-AU',
|
|
626
|
+
{
|
|
627
|
+
style: 'currency',
|
|
628
|
+
currency:
|
|
629
|
+
'AUD',
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
: index ===
|
|
633
|
+
columns.length - 2
|
|
634
|
+
? 'Subtotal'
|
|
635
|
+
: ''}
|
|
636
|
+
</td>
|
|
637
|
+
))}
|
|
638
|
+
<td></td>
|
|
483
639
|
</tr>
|
|
640
|
+
</React.Fragment>
|
|
641
|
+
))}
|
|
642
|
+
</tbody>
|
|
643
|
+
</table>
|
|
644
|
+
) : (
|
|
645
|
+
<div className={styles.noDataMessage}>
|
|
646
|
+
No data available. Please check your filters or try
|
|
647
|
+
reloading the data.
|
|
648
|
+
</div>
|
|
649
|
+
)
|
|
650
|
+
) : localData.length > 0 ? (
|
|
651
|
+
<table className={styles.schedulingTable}>
|
|
652
|
+
<thead>
|
|
653
|
+
<tr>
|
|
654
|
+
{columns.map((column) => renderHeaderCell(column))}
|
|
655
|
+
<th style={{ width: '5%' }}></th>
|
|
656
|
+
</tr>
|
|
657
|
+
</thead>
|
|
658
|
+
<tbody>
|
|
659
|
+
{sortBySortOrder(localData).map((entry, idx) => (
|
|
660
|
+
<tr
|
|
661
|
+
key={entry.id}
|
|
662
|
+
style={{ background: getRowBackground(entry) }}
|
|
663
|
+
>
|
|
664
|
+
{columns.map((column) => (
|
|
665
|
+
<td
|
|
666
|
+
key={column.id}
|
|
667
|
+
style={{
|
|
668
|
+
width: column.width || 'auto',
|
|
669
|
+
textAlign:
|
|
670
|
+
column.type === 'currency'
|
|
671
|
+
? 'right'
|
|
672
|
+
: 'left',
|
|
673
|
+
}}
|
|
674
|
+
>
|
|
675
|
+
{renderScheduledTableField(
|
|
676
|
+
entry,
|
|
677
|
+
column,
|
|
678
|
+
idx
|
|
679
|
+
)}
|
|
680
|
+
</td>
|
|
484
681
|
))}
|
|
485
|
-
<
|
|
486
|
-
{
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
</td>
|
|
524
|
-
))}
|
|
525
|
-
<td></td>
|
|
526
|
-
</tr>
|
|
527
|
-
</React.Fragment>
|
|
682
|
+
<td>
|
|
683
|
+
<div className={styles.tdactions}>
|
|
684
|
+
{idx > 0 && (
|
|
685
|
+
<ArrowUp
|
|
686
|
+
size={16}
|
|
687
|
+
onClick={() =>
|
|
688
|
+
handleSortChangeUngrouped(
|
|
689
|
+
entry.id,
|
|
690
|
+
'up'
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
style={{ cursor: 'pointer' }}
|
|
694
|
+
/>
|
|
695
|
+
)}
|
|
696
|
+
{idx <
|
|
697
|
+
sortBySortOrder(localData).length -
|
|
698
|
+
1 && (
|
|
699
|
+
<ArrowDown
|
|
700
|
+
size={16}
|
|
701
|
+
onClick={() =>
|
|
702
|
+
handleSortChangeUngrouped(
|
|
703
|
+
entry.id,
|
|
704
|
+
'down'
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
style={{ cursor: 'pointer' }}
|
|
708
|
+
/>
|
|
709
|
+
)}
|
|
710
|
+
<TrashCan
|
|
711
|
+
size={16}
|
|
712
|
+
onClick={() =>
|
|
713
|
+
handleDeleteRow(entry)
|
|
714
|
+
}
|
|
715
|
+
style={{ cursor: 'pointer' }}
|
|
716
|
+
/>
|
|
717
|
+
</div>
|
|
718
|
+
</td>
|
|
719
|
+
</tr>
|
|
528
720
|
))}
|
|
721
|
+
<tr className={styles.subtotalRow}>
|
|
722
|
+
{columns.map((column, index) => (
|
|
723
|
+
<td key={column.id}>
|
|
724
|
+
{index === columns.length - 1
|
|
725
|
+
? sortBySortOrder(localData)
|
|
726
|
+
.reduce((sum, entry) => {
|
|
727
|
+
const subtotal =
|
|
728
|
+
Array.isArray(column.keys)
|
|
729
|
+
? column.keys.reduce(
|
|
730
|
+
(rowSum, key) =>
|
|
731
|
+
rowSum +
|
|
732
|
+
(parseFloat(
|
|
733
|
+
entry[
|
|
734
|
+
key
|
|
735
|
+
]
|
|
736
|
+
) || 0),
|
|
737
|
+
0
|
|
738
|
+
)
|
|
739
|
+
: 0;
|
|
740
|
+
return sum + subtotal;
|
|
741
|
+
}, 0)
|
|
742
|
+
.toLocaleString('en-AU', {
|
|
743
|
+
style: 'currency',
|
|
744
|
+
currency: 'AUD',
|
|
745
|
+
})
|
|
746
|
+
: index === columns.length - 2
|
|
747
|
+
? 'Subtotal'
|
|
748
|
+
: ''}
|
|
749
|
+
</td>
|
|
750
|
+
))}
|
|
751
|
+
<td></td>
|
|
752
|
+
</tr>
|
|
529
753
|
</tbody>
|
|
530
754
|
</table>
|
|
531
755
|
) : (
|
|
@@ -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.75rem
|
|
15
|
-
font-size:
|
|
14
|
+
padding: 0.5rem 0.75rem; // Less padding than before
|
|
15
|
+
font-size: 1rem; // 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,30 +220,30 @@
|
|
|
226
220
|
flex: 1;
|
|
227
221
|
|
|
228
222
|
button {
|
|
229
|
-
width: 40px;
|
|
230
|
-
height: 40px;
|
|
231
|
-
font-size: 1.5rem;
|
|
223
|
+
width: 40px;
|
|
224
|
+
height: 40px;
|
|
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
|
}
|