@visns-studio/visns-components 5.12.12 → 5.12.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -87,7 +87,7 @@
87
87
  "react-dom": "^17.0.0 || ^18.0.0"
88
88
  },
89
89
  "name": "@visns-studio/visns-components",
90
- "version": "5.12.12",
90
+ "version": "5.12.13",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -10,6 +10,64 @@ import CustomFetch from '../Fetch';
10
10
  import MultiSelect from '../MultiSelect';
11
11
  import styles from '../styles/GenericEditableTable.module.scss';
12
12
 
13
+ // Simple Tooltip Component
14
+ const Tooltip = ({ children, text, position = 'top' }) => {
15
+ const [isVisible, setIsVisible] = useState(false);
16
+ const [coords, setCoords] = useState({ x: 0, y: 0 });
17
+ const elementRef = useRef(null);
18
+
19
+ const handleMouseEnter = () => {
20
+ if (!elementRef.current) return;
21
+ const rect = elementRef.current.getBoundingClientRect();
22
+ const scrollLeft =
23
+ window.pageXOffset || document.documentElement.scrollLeft;
24
+ const scrollTop =
25
+ window.pageYOffset || document.documentElement.scrollTop;
26
+
27
+ let x = rect.left + scrollLeft + rect.width / 2;
28
+ let y = rect.top + scrollTop;
29
+
30
+ if (position === 'bottom') {
31
+ y = rect.bottom + scrollTop + 5;
32
+ } else if (position === 'top') {
33
+ y = y - 5;
34
+ }
35
+
36
+ setCoords({ x, y });
37
+ setIsVisible(true);
38
+ };
39
+
40
+ const handleMouseLeave = () => {
41
+ setIsVisible(false);
42
+ };
43
+
44
+ return (
45
+ <span
46
+ ref={elementRef}
47
+ onMouseEnter={handleMouseEnter}
48
+ onMouseLeave={handleMouseLeave}
49
+ className={styles.tooltipContainer}
50
+ >
51
+ {children}
52
+ {isVisible &&
53
+ ReactDOM.createPortal(
54
+ <div
55
+ className={`${styles.tooltip} ${
56
+ styles[`tooltip-${position}`]
57
+ }`}
58
+ style={{
59
+ left: coords.x,
60
+ top: coords.y,
61
+ }}
62
+ >
63
+ {text}
64
+ </div>,
65
+ document.body
66
+ )}
67
+ </span>
68
+ );
69
+ };
70
+
13
71
  // Updated ColourPicker with an expanded pastel palette
14
72
  const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
15
73
  const [colorPickerShow, setColorPickerShow] = useState(false);
@@ -59,35 +117,42 @@ const ColourPicker = ({ value, columnId, keyCounter, onChange }) => {
59
117
  <div className={styles.cpicker__btn}>
60
118
  <button
61
119
  ref={buttonRef}
62
- style={value ? {
63
- background: value,
64
- border: '1px solid rgba(0,0,0,0.2)',
65
- borderRadius: '3px',
66
- width: '20px',
67
- height: '20px',
68
- padding: '0',
69
- cursor: 'pointer',
70
- display: 'flex',
71
- alignItems: 'center',
72
- justifyContent: 'center'
73
- } : {
74
- background: 'transparent',
75
- border: '1px solid rgba(0,0,0,0.2)',
76
- borderRadius: '3px',
77
- width: '20px',
78
- height: '20px',
79
- padding: '0',
80
- cursor: 'pointer',
81
- display: 'flex',
82
- alignItems: 'center',
83
- justifyContent: 'center'
84
- }}
120
+ style={
121
+ value
122
+ ? {
123
+ background: value,
124
+ border: '1px solid rgba(0,0,0,0.2)',
125
+ borderRadius: '3px',
126
+ width: '20px',
127
+ height: '20px',
128
+ padding: '0',
129
+ cursor: 'pointer',
130
+ display: 'flex',
131
+ alignItems: 'center',
132
+ justifyContent: 'center',
133
+ }
134
+ : {
135
+ background: 'transparent',
136
+ border: '1px solid rgba(0,0,0,0.2)',
137
+ borderRadius: '3px',
138
+ width: '20px',
139
+ height: '20px',
140
+ padding: '0',
141
+ cursor: 'pointer',
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ }
146
+ }
85
147
  onClick={(e) => {
86
148
  e.preventDefault();
87
149
  setColorPickerShow(!colorPickerShow);
88
150
  }}
89
151
  >
90
- <Droplets size={16} style={{ color: value ? 'white' : 'black' }} />
152
+ <Droplets
153
+ size={18}
154
+ style={{ color: value ? 'white' : 'black' }}
155
+ />
91
156
  </button>
92
157
 
93
158
  {colorPickerShow &&
@@ -146,7 +211,7 @@ const GenericEditableTable = ({
146
211
  const isInitialLoad = useRef(true);
147
212
  const { columns, rows, form, title } = schedulingConfig;
148
213
  const hasCategory = rows && rows.categoryKey; // Only group if defined
149
- const hasColourColumn = columns.some(col => col.id === 'colours');
214
+ const hasColourColumn = columns.some((col) => col.id === 'colours');
150
215
 
151
216
  // dataSets: each item => { created_at: string, data: array of rows }
152
217
  const [dataSets, setDataSets] = useState([]);
@@ -158,6 +223,9 @@ const GenericEditableTable = ({
158
223
  const [newEntries, setNewEntries] = useState({});
159
224
  const [newEntry, setNewEntry] = useState({});
160
225
 
226
+ // Row selection state for copy functionality
227
+ const [selectedRows, setSelectedRows] = useState(new Set());
228
+
161
229
  // On mount or when 'data' changes, read from data[schedulingConfig.rows.key] and handle both new & old formats
162
230
  useEffect(() => {
163
231
  const raw = data && data[schedulingConfig.rows.key];
@@ -220,6 +288,8 @@ const GenericEditableTable = ({
220
288
  } else {
221
289
  setLocalData([]);
222
290
  }
291
+ // Clear selected rows when dataset changes
292
+ setSelectedRows(new Set());
223
293
  }, [dataSets, activeDatasetIndex]);
224
294
 
225
295
  // Helper to sort by sort_order
@@ -504,11 +574,50 @@ const GenericEditableTable = ({
504
574
  return hasCategory ? groupCategoriesByType(localData) : [];
505
575
  }, [localData, hasCategory]);
506
576
 
507
- // 5) Column copy
577
+ // 5) Row selection handlers
578
+ const handleRowSelection = (rowId, isChecked) => {
579
+ setSelectedRows((prev) => {
580
+ const newSelected = new Set(prev);
581
+ if (isChecked) {
582
+ newSelected.add(rowId);
583
+ } else {
584
+ newSelected.delete(rowId);
585
+ }
586
+ return newSelected;
587
+ });
588
+ };
589
+
590
+ const handleSelectAll = (isChecked, dataToSelect = null) => {
591
+ if (isChecked) {
592
+ const rowIds = dataToSelect
593
+ ? dataToSelect.map((entry) => entry.id)
594
+ : localData.map((entry) => entry.id);
595
+ setSelectedRows(new Set(rowIds));
596
+ } else {
597
+ setSelectedRows(new Set());
598
+ }
599
+ };
600
+
601
+ const isAllSelected = (dataToCheck = null) => {
602
+ const dataSet = dataToCheck || localData;
603
+ return (
604
+ dataSet.length > 0 &&
605
+ dataSet.every((entry) => selectedRows.has(entry.id))
606
+ );
607
+ };
608
+
609
+ // 6) Enhanced column copy with row selection
508
610
  const handleColumnCopy = (column, group = null) => {
509
- let values = group
510
- ? group.entries.map((entry) => entry[column.id])
511
- : localData.map((entry) => entry[column.id]);
611
+ let dataToProcess = group ? group.entries : localData;
612
+
613
+ // Filter by selected rows if any are selected
614
+ if (selectedRows.size > 0) {
615
+ dataToProcess = dataToProcess.filter((entry) =>
616
+ selectedRows.has(entry.id)
617
+ );
618
+ }
619
+
620
+ let values = dataToProcess.map((entry) => entry[column.id]);
512
621
 
513
622
  const textToCopy = values
514
623
  .filter((val) => val !== undefined && val !== null)
@@ -516,19 +625,23 @@ const GenericEditableTable = ({
516
625
 
517
626
  navigator.clipboard
518
627
  .writeText(textToCopy)
519
- .then(() =>
628
+ .then(() => {
629
+ const selectionInfo =
630
+ selectedRows.size > 0
631
+ ? ` (${selectedRows.size} selected rows)`
632
+ : ' (all rows)';
520
633
  toast.success(
521
634
  `Copied ${values.length} value${
522
635
  values.length !== 1 ? 's' : ''
523
636
  } from "${column.label}"${
524
637
  group ? ` (Group: ${group.category})` : ''
525
- }`
526
- )
527
- )
638
+ }${selectionInfo}`
639
+ );
640
+ })
528
641
  .catch(() => toast.error('Failed to copy to clipboard'));
529
642
  };
530
643
 
531
- // 6) Sorting: grouped
644
+ // 7) Sorting: grouped
532
645
  const handleSortChangeGrouped = (groupId, entryId, direction) => {
533
646
  if (!hasCategory) return;
534
647
  setLocalData((prev) => {
@@ -580,7 +693,7 @@ const GenericEditableTable = ({
580
693
  });
581
694
  };
582
695
 
583
- // 7) Sorting: ungrouped
696
+ // 8) Sorting: ungrouped
584
697
  const handleSortChangeUngrouped = (entryId, direction) => {
585
698
  setLocalData((prev) => {
586
699
  const sortedData = sortBySortOrder(prev);
@@ -611,7 +724,7 @@ const GenericEditableTable = ({
611
724
  });
612
725
  };
613
726
 
614
- // 8) Deletion
727
+ // 9) Deletion
615
728
  const handleDeleteRow = (entry) => {
616
729
  confirmDialog({
617
730
  title: 'Confirm to Delete',
@@ -784,7 +897,7 @@ const GenericEditableTable = ({
784
897
  // Render new row field
785
898
  const renderNewRowField = (newRowData, column, groupId = null) => {
786
899
  const value = newRowData ? newRowData[column.id] : '';
787
-
900
+
788
901
  // Special handling for colours column regardless of type
789
902
  if (column.id === 'colours') {
790
903
  return (
@@ -800,7 +913,7 @@ const GenericEditableTable = ({
800
913
  />
801
914
  );
802
915
  }
803
-
916
+
804
917
  switch (column.type) {
805
918
  case 'colour':
806
919
  // For other colour columns, picker is rendered in the action column
@@ -921,6 +1034,7 @@ const GenericEditableTable = ({
921
1034
  const newRowData = hasCategory ? newEntries[groupId] || {} : newEntry;
922
1035
  return (
923
1036
  <tr className={styles.newRow}>
1037
+ <td className={styles.checkboxColumn}></td>
924
1038
  {columns.map((column) => (
925
1039
  <td
926
1040
  key={column.id}
@@ -946,6 +1060,51 @@ const GenericEditableTable = ({
946
1060
  );
947
1061
  };
948
1062
 
1063
+ // Checkbox column header
1064
+ const renderSelectAllCheckbox = (group = null) => (
1065
+ <th
1066
+ key="select-all"
1067
+ className={styles.checkboxColumn}
1068
+ onClick={() => {
1069
+ const currentlyAllSelected = isAllSelected(group?.entries);
1070
+ handleSelectAll(!currentlyAllSelected, group?.entries);
1071
+ }}
1072
+ style={{ cursor: 'pointer' }}
1073
+ title={
1074
+ group ? `Select all in ${group.category}` : 'Select all rows'
1075
+ }
1076
+ >
1077
+ <input
1078
+ type="checkbox"
1079
+ checked={isAllSelected(group?.entries)}
1080
+ onChange={() => {}} // Empty handler since we handle click on the cell
1081
+ style={{ cursor: 'pointer', pointerEvents: 'none' }}
1082
+ tabIndex={-1}
1083
+ />
1084
+ </th>
1085
+ );
1086
+
1087
+ // Row checkbox cell
1088
+ const renderRowCheckbox = (entry) => (
1089
+ <td
1090
+ key={`checkbox-${entry.id}`}
1091
+ className={styles.checkboxColumn}
1092
+ onClick={() => {
1093
+ const isCurrentlySelected = selectedRows.has(entry.id);
1094
+ handleRowSelection(entry.id, !isCurrentlySelected);
1095
+ }}
1096
+ style={{ cursor: 'pointer' }}
1097
+ >
1098
+ <input
1099
+ type="checkbox"
1100
+ checked={selectedRows.has(entry.id)}
1101
+ onChange={() => {}} // Empty handler since we handle click on the cell
1102
+ style={{ cursor: 'pointer', pointerEvents: 'none' }}
1103
+ tabIndex={-1}
1104
+ />
1105
+ </td>
1106
+ );
1107
+
949
1108
  // Header cell with optional copy icon
950
1109
  const renderHeaderCell = (column, group = null) => (
951
1110
  <th
@@ -968,7 +1127,7 @@ const GenericEditableTable = ({
968
1127
  }}
969
1128
  style={{ cursor: 'pointer' }}
970
1129
  >
971
- <Copy size={16} />
1130
+ <Copy size={20} />
972
1131
  </span>
973
1132
  )}
974
1133
  </div>
@@ -1027,11 +1186,12 @@ const GenericEditableTable = ({
1027
1186
  {groupedCategories.map((group) => (
1028
1187
  <React.Fragment key={group.id}>
1029
1188
  <tr className={styles.categoryRow}>
1030
- <td colSpan={columns.length + 1}>
1189
+ <td colSpan={columns.length + 2}>
1031
1190
  {group.category}
1032
1191
  </td>
1033
1192
  </tr>
1034
1193
  <tr>
1194
+ {renderSelectAllCheckbox(group)}
1035
1195
  {columns.map((column) =>
1036
1196
  renderHeaderCell(column, group)
1037
1197
  )}
@@ -1046,6 +1206,7 @@ const GenericEditableTable = ({
1046
1206
  getRowBackground(entry),
1047
1207
  }}
1048
1208
  >
1209
+ {renderRowCheckbox(entry)}
1049
1210
  {columns.map((column) => (
1050
1211
  <td
1051
1212
  key={column.id}
@@ -1070,55 +1231,70 @@ const GenericEditableTable = ({
1070
1231
  >
1071
1232
  {!hasColourColumn && (
1072
1233
  <ColourPicker
1073
- value={entry.row_colour || ''}
1074
- columnId={'row_colour'}
1075
- keyCounter={entry.keyCounter}
1076
- onChange={handleFieldChange}
1234
+ value={
1235
+ entry.row_colour ||
1236
+ ''
1237
+ }
1238
+ columnId={
1239
+ 'row_colour'
1240
+ }
1241
+ keyCounter={
1242
+ entry.keyCounter
1243
+ }
1244
+ onChange={
1245
+ handleFieldChange
1246
+ }
1077
1247
  />
1078
1248
  )}
1079
1249
  {idx > 0 && (
1080
- <ArrowUp
1081
- size={16}
1082
- onClick={() =>
1083
- handleSortChangeGrouped(
1084
- group.id,
1085
- entry.id,
1086
- 'up'
1087
- )
1088
- }
1089
- style={{
1090
- cursor: 'pointer',
1091
- }}
1092
- />
1250
+ <Tooltip text="Move up">
1251
+ <ArrowUp
1252
+ size={16}
1253
+ onClick={() =>
1254
+ handleSortChangeGrouped(
1255
+ group.id,
1256
+ entry.id,
1257
+ 'up'
1258
+ )
1259
+ }
1260
+ style={{
1261
+ cursor: 'pointer',
1262
+ }}
1263
+ />
1264
+ </Tooltip>
1093
1265
  )}
1094
1266
  {idx <
1095
1267
  group.entries.length -
1096
1268
  1 && (
1097
- <ArrowDown
1269
+ <Tooltip text="Move down">
1270
+ <ArrowDown
1271
+ size={16}
1272
+ onClick={() =>
1273
+ handleSortChangeGrouped(
1274
+ group.id,
1275
+ entry.id,
1276
+ 'down'
1277
+ )
1278
+ }
1279
+ style={{
1280
+ cursor: 'pointer',
1281
+ }}
1282
+ />
1283
+ </Tooltip>
1284
+ )}
1285
+ <Tooltip text="Delete row">
1286
+ <Trash2
1098
1287
  size={16}
1099
1288
  onClick={() =>
1100
- handleSortChangeGrouped(
1101
- group.id,
1102
- entry.id,
1103
- 'down'
1289
+ handleDeleteRow(
1290
+ entry
1104
1291
  )
1105
1292
  }
1106
1293
  style={{
1107
1294
  cursor: 'pointer',
1108
1295
  }}
1109
1296
  />
1110
- )}
1111
- <Trash2
1112
- size={16}
1113
- onClick={() =>
1114
- handleDeleteRow(
1115
- entry
1116
- )
1117
- }
1118
- style={{
1119
- cursor: 'pointer',
1120
- }}
1121
- />
1297
+ </Tooltip>
1122
1298
  </div>
1123
1299
  </td>
1124
1300
  </tr>
@@ -1130,6 +1306,7 @@ const GenericEditableTable = ({
1130
1306
  (col) => col.type === 'total'
1131
1307
  ) !== -1 && (
1132
1308
  <tr className={styles.subtotalRow}>
1309
+ <td></td>
1133
1310
  {columns.map((col, index) => {
1134
1311
  const totalColIndex =
1135
1312
  columns.findIndex(
@@ -1192,126 +1369,153 @@ const GenericEditableTable = ({
1192
1369
  <table className={styles.schedulingTable}>
1193
1370
  <thead>
1194
1371
  <tr>
1372
+ {renderSelectAllCheckbox()}
1195
1373
  {columns.map((column) => renderHeaderCell(column))}
1196
1374
  <th style={{ width: '5%' }}></th>
1197
1375
  </tr>
1198
1376
  </thead>
1199
1377
  <tbody>
1200
- {localData.length > 0 && sortBySortOrder(localData).map((entry, idx) => (
1201
- <tr
1202
- key={entry.id}
1203
- style={{ background: getRowBackground(entry) }}
1204
- >
1205
- {columns.map((column) => (
1206
- <td
1207
- key={column.id}
1208
- style={{
1209
- width: column.width || 'auto',
1210
- textAlign:
1211
- column.type === 'currency'
1212
- ? 'right'
1213
- : 'left',
1214
- }}
1215
- >
1216
- {renderScheduledTableField(
1217
- entry,
1218
- column,
1219
- idx
1220
- )}
1378
+ {localData.length > 0 &&
1379
+ sortBySortOrder(localData).map((entry, idx) => (
1380
+ <tr
1381
+ key={entry.id}
1382
+ style={{
1383
+ background: getRowBackground(entry),
1384
+ }}
1385
+ >
1386
+ {renderRowCheckbox(entry)}
1387
+ {columns.map((column) => (
1388
+ <td
1389
+ key={column.id}
1390
+ style={{
1391
+ width: column.width || 'auto',
1392
+ textAlign:
1393
+ column.type === 'currency'
1394
+ ? 'right'
1395
+ : 'left',
1396
+ }}
1397
+ >
1398
+ {renderScheduledTableField(
1399
+ entry,
1400
+ column,
1401
+ idx
1402
+ )}
1403
+ </td>
1404
+ ))}
1405
+ <td>
1406
+ <div className={styles.tdactions}>
1407
+ {!hasColourColumn && (
1408
+ <ColourPicker
1409
+ value={
1410
+ entry.row_colour || ''
1411
+ }
1412
+ columnId={'row_colour'}
1413
+ keyCounter={idx}
1414
+ onChange={handleFieldChange}
1415
+ />
1416
+ )}
1417
+ {idx > 0 && (
1418
+ <Tooltip text="Move up">
1419
+ <ArrowUp
1420
+ size={16}
1421
+ onClick={() =>
1422
+ handleSortChangeUngrouped(
1423
+ entry.id,
1424
+ 'up'
1425
+ )
1426
+ }
1427
+ style={{
1428
+ cursor: 'pointer',
1429
+ }}
1430
+ />
1431
+ </Tooltip>
1432
+ )}
1433
+ {idx <
1434
+ sortBySortOrder(localData)
1435
+ .length -
1436
+ 1 && (
1437
+ <Tooltip text="Move down">
1438
+ <ArrowDown
1439
+ size={16}
1440
+ onClick={() =>
1441
+ handleSortChangeUngrouped(
1442
+ entry.id,
1443
+ 'down'
1444
+ )
1445
+ }
1446
+ style={{
1447
+ cursor: 'pointer',
1448
+ }}
1449
+ />
1450
+ </Tooltip>
1451
+ )}
1452
+ <Tooltip text="Delete row">
1453
+ <Trash2
1454
+ size={16}
1455
+ onClick={() =>
1456
+ handleDeleteRow(entry)
1457
+ }
1458
+ style={{
1459
+ cursor: 'pointer',
1460
+ }}
1461
+ />
1462
+ </Tooltip>
1463
+ </div>
1221
1464
  </td>
1222
- ))}
1223
- <td>
1224
- <div className={styles.tdactions}>
1225
- {!hasColourColumn && (
1226
- <ColourPicker
1227
- value={entry.row_colour || ''}
1228
- columnId={'row_colour'}
1229
- keyCounter={idx}
1230
- onChange={handleFieldChange}
1231
- />
1232
- )}
1233
- {idx > 0 && (
1234
- <ArrowUp
1235
- size={16}
1236
- onClick={() =>
1237
- handleSortChangeUngrouped(
1238
- entry.id,
1239
- 'up'
1240
- )
1241
- }
1242
- style={{ cursor: 'pointer' }}
1243
- />
1244
- )}
1245
- {idx <
1246
- sortBySortOrder(localData).length -
1247
- 1 && (
1248
- <ArrowDown
1249
- size={16}
1250
- onClick={() =>
1251
- handleSortChangeUngrouped(
1252
- entry.id,
1253
- 'down'
1254
- )
1255
- }
1256
- style={{ cursor: 'pointer' }}
1257
- />
1258
- )}
1259
- <Trash2
1260
- size={16}
1261
- onClick={() =>
1262
- handleDeleteRow(entry)
1263
- }
1264
- style={{ cursor: 'pointer' }}
1265
- />
1266
- </div>
1267
- </td>
1268
- </tr>
1269
- ))}
1465
+ </tr>
1466
+ ))}
1270
1467
 
1271
1468
  {renderNewRowForm()}
1272
1469
 
1273
- {localData.length > 0 && columns.findIndex((col) => col.type === 'total') !==
1274
- -1 && (
1275
- <tr className={styles.subtotalRow}>
1276
- {columns.map((col, index) => {
1277
- const totalColIndex = columns.findIndex(
1278
- (c) => c.type === 'total'
1279
- );
1280
- if (
1281
- index === totalColIndex - 1 &&
1282
- index > 0
1283
- ) {
1284
- return <td key={col.id}>Subtotal</td>;
1285
- }
1286
- if (index === totalColIndex) {
1287
- const overallTotal = sortBySortOrder(
1288
- localData
1289
- ).reduce(
1290
- (sum, entry) =>
1291
- sum +
1292
- calculateTotal(
1293
- entry,
1294
- columns[totalColIndex].keys
1295
- ),
1296
- 0
1297
- );
1298
- return (
1299
- <td key={col.id}>
1300
- {overallTotal.toLocaleString(
1301
- 'en-AU',
1302
- {
1303
- style: 'currency',
1304
- currency: 'AUD',
1305
- }
1306
- )}
1307
- </td>
1470
+ {localData.length > 0 &&
1471
+ columns.findIndex((col) => col.type === 'total') !==
1472
+ -1 && (
1473
+ <tr className={styles.subtotalRow}>
1474
+ <td></td>
1475
+ {columns.map((col, index) => {
1476
+ const totalColIndex = columns.findIndex(
1477
+ (c) => c.type === 'total'
1308
1478
  );
1309
- }
1310
- return <td key={col.id} />;
1311
- })}
1312
- <td></td>
1313
- </tr>
1314
- )}
1479
+ if (
1480
+ index === totalColIndex - 1 &&
1481
+ index > 0
1482
+ ) {
1483
+ return (
1484
+ <td key={col.id}>Subtotal</td>
1485
+ );
1486
+ }
1487
+ if (index === totalColIndex) {
1488
+ const overallTotal =
1489
+ sortBySortOrder(
1490
+ localData
1491
+ ).reduce(
1492
+ (sum, entry) =>
1493
+ sum +
1494
+ calculateTotal(
1495
+ entry,
1496
+ columns[
1497
+ totalColIndex
1498
+ ].keys
1499
+ ),
1500
+ 0
1501
+ );
1502
+ return (
1503
+ <td key={col.id}>
1504
+ {overallTotal.toLocaleString(
1505
+ 'en-AU',
1506
+ {
1507
+ style: 'currency',
1508
+ currency: 'AUD',
1509
+ }
1510
+ )}
1511
+ </td>
1512
+ );
1513
+ }
1514
+ return <td key={col.id} />;
1515
+ })}
1516
+ <td></td>
1517
+ </tr>
1518
+ )}
1315
1519
  </tbody>
1316
1520
  </table>
1317
1521
  )}
@@ -5,12 +5,12 @@
5
5
 
6
6
  th,
7
7
  td {
8
- padding: 0.08rem 0.25rem; // Ultra-compact padding for cells
8
+ padding: 0.4rem 0.5rem; // Increased padding for better readability
9
9
  text-align: left;
10
10
  border: 1px solid rgba(var(--primary-rgb), 0.1);
11
- line-height: 1; // Minimal line height
11
+ line-height: 1.3; // Increased line height for better readability
12
12
  vertical-align: middle; // Ensure vertical alignment
13
- height: 22px; // Fixed row height
13
+ height: 40px; // Increased row height
14
14
  }
15
15
 
16
16
  // Additional high-specificity selectors to override any global styles
@@ -18,14 +18,14 @@
18
18
  td select,
19
19
  th input,
20
20
  th select {
21
- padding: 0.05rem 0.2rem !important;
21
+ padding: 0.3rem 0.5rem !important;
22
22
  margin: 0 !important;
23
- font-size: 0.7rem !important;
24
- height: 18px !important;
25
- line-height: 1 !important;
23
+ font-size: 0.9rem !important;
24
+ height: 32px !important;
25
+ line-height: 1.2 !important;
26
26
  border: 1px solid rgba(var(--primary-rgb), 0.1) !important;
27
27
  box-sizing: border-box !important;
28
- border-radius: 2px !important;
28
+ border-radius: 4px !important;
29
29
  width: 100% !important;
30
30
  appearance: none !important;
31
31
  background: white !important;
@@ -35,9 +35,9 @@
35
35
  th select {
36
36
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M6 9L1 4h10z" fill="%23666"/></svg>') !important;
37
37
  background-repeat: no-repeat !important;
38
- background-position: right 0.2rem center !important;
39
- background-size: 8px !important;
40
- padding: 0.05rem 1rem 0.05rem 0.2rem !important;
38
+ background-position: right 0.5rem center !important;
39
+ background-size: 10px !important;
40
+ padding: 0.3rem 1.5rem 0.3rem 0.5rem !important;
41
41
  cursor: pointer !important;
42
42
 
43
43
  &::-ms-expand {
@@ -46,13 +46,13 @@
46
46
  }
47
47
 
48
48
  th {
49
- padding: 0.1rem 0.25rem; // Ultra-compact header padding
50
- font-size: 0.75rem; // Smaller font size
49
+ padding: 0.5rem 0.5rem; // Increased header padding
50
+ font-size: 1rem; // Larger font size for headers
51
51
  font-weight: bold;
52
52
  background-color: var(--primary-color);
53
53
  color: white;
54
54
  text-align: center;
55
- height: 24px; // Fixed header height
55
+ height: 45px; // Increased header height
56
56
  }
57
57
 
58
58
  td {
@@ -64,17 +64,17 @@
64
64
  }
65
65
  }
66
66
 
67
- /* Ultra-compact styling for native input and select elements - override global styles */
67
+ /* Updated styling for native input and select elements */
68
68
  input,
69
69
  select {
70
- padding: 0.05rem 0.2rem !important; // Force minimal padding, override global styles
70
+ padding: 0.3rem 0.5rem !important; // Increased padding for better usability
71
71
  margin: 0 !important;
72
- font-size: 0.7rem !important; // Smaller font
73
- height: 18px !important; // Minimal height
74
- line-height: 1 !important;
72
+ font-size: 0.9rem !important; // Larger font size for readability
73
+ height: 32px !important; // Increased height
74
+ line-height: 1.2 !important;
75
75
  border: 1px solid rgba(var(--primary-rgb), 0.1) !important;
76
76
  box-sizing: border-box !important;
77
- border-radius: 2px !important; // Smaller border radius
77
+ border-radius: 4px !important; // Larger border radius
78
78
  width: 100% !important;
79
79
  appearance: none !important; // Remove default dropdown arrow for consistency
80
80
  background: white !important;
@@ -84,9 +84,9 @@
84
84
  &:not([type]) {
85
85
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M6 9L1 4h10z" fill="%23666"/></svg>') !important;
86
86
  background-repeat: no-repeat !important;
87
- background-position: right 0.2rem center !important;
88
- background-size: 8px !important;
89
- padding-right: 1rem !important;
87
+ background-position: right 0.5rem center !important;
88
+ background-size: 10px !important;
89
+ padding-right: 1.5rem !important;
90
90
  }
91
91
  }
92
92
 
@@ -96,9 +96,9 @@
96
96
  background: white !important;
97
97
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M6 9L1 4h10z" fill="%23666"/></svg>') !important;
98
98
  background-repeat: no-repeat !important;
99
- background-position: right 0.2rem center !important;
100
- background-size: 8px !important;
101
- padding: 0.05rem 1rem 0.05rem 0.2rem !important; // Force specific padding values
99
+ background-position: right 0.5rem center !important;
100
+ background-size: 10px !important;
101
+ padding: 0.3rem 1.5rem 0.3rem 0.5rem !important; // Increased padding values
102
102
  cursor: pointer !important;
103
103
 
104
104
  // Remove default arrow on different browsers
@@ -233,19 +233,19 @@
233
233
  width: max-content;
234
234
  display: inline-block;
235
235
  position: relative;
236
- padding: 0.2rem 0.4rem; /* Ultra-minimal padding */
236
+ padding: 0.4rem 0.8rem; /* Increased padding */
237
237
  cursor: pointer;
238
- font-size: 0.75rem; /* Even smaller font size */
238
+ font-size: 0.9rem; /* Larger font size */
239
239
  color: var(--tertiary-color);
240
240
  text-decoration: none;
241
241
  overflow: hidden;
242
242
  background: var(--primary-color);
243
243
  border: 1px solid rgba(var(--primary-color--rgb), 1.1);
244
- border-radius: 2px; /* Smaller border radius */
244
+ border-radius: 4px; /* Larger border radius */
245
245
  outline: none;
246
246
  transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1) 0s;
247
- height: 20px; /* Match input height */
248
- line-height: 1;
247
+ height: 34px; /* Match input height */
248
+ line-height: 1.2;
249
249
  display: flex;
250
250
  align-items: center;
251
251
  justify-content: center;
@@ -263,19 +263,19 @@
263
263
  flex-wrap: nowrap;
264
264
  justify-content: flex-start;
265
265
  align-items: center;
266
- gap: 0.05rem; // Reduced gap
266
+ gap: 0.3rem; // Increased gap
267
267
  background: transparent !important; /* Ensure no background color interference */
268
268
 
269
269
  span {
270
270
  display: flex;
271
271
  align-items: center;
272
272
  position: relative;
273
- width: 14px; // Smaller icons
274
- height: 14px;
273
+ width: 16px; // Smaller icons for sort and trash
274
+ height: 16px;
275
275
 
276
276
  svg {
277
- width: 14px;
278
- height: 14px;
277
+ width: 16px;
278
+ height: 16px;
279
279
  }
280
280
  }
281
281
  }
@@ -293,8 +293,8 @@
293
293
  margin: 0;
294
294
  outline: none;
295
295
  transition: all 0.2s ease;
296
- width: 16px; // Smaller color picker button
297
- height: 16px;
296
+ width: 24px; // Larger color picker button
297
+ height: 24px;
298
298
 
299
299
  &:hover {
300
300
  opacity: 0.8;
@@ -302,8 +302,8 @@
302
302
  }
303
303
 
304
304
  svg {
305
- width: 12px; // Smaller icon
306
- height: 12px;
305
+ width: 18px; // Larger icon
306
+ height: 18px;
307
307
  }
308
308
  }
309
309
  }
@@ -665,19 +665,19 @@
665
665
  border-top: 1px dashed rgba(var(--primary-rgb), 0.3);
666
666
  border-bottom: 1px dashed rgba(var(--primary-rgb), 0.3);
667
667
 
668
- /* Ultra-compact styling for new row inputs and selects - force override globals */
668
+ /* Updated styling for new row inputs and selects */
669
669
  input,
670
670
  select {
671
- height: 18px !important; // Consistent height
672
- padding: 0.05rem 0.2rem !important; // Force override global padding
673
- font-size: 0.7rem !important;
671
+ height: 32px !important; // Increased height
672
+ padding: 0.3rem 0.5rem !important; // Increased padding
673
+ font-size: 0.9rem !important;
674
674
  appearance: none !important;
675
675
  background: white !important;
676
676
  margin: 0 !important;
677
- line-height: 1 !important;
677
+ line-height: 1.2 !important;
678
678
  border: 1px solid rgba(var(--primary-rgb), 0.1) !important;
679
679
  box-sizing: border-box !important;
680
- border-radius: 2px !important;
680
+ border-radius: 4px !important;
681
681
  width: 100% !important;
682
682
 
683
683
  // Ensure selects have dropdown arrow
@@ -685,9 +685,9 @@
685
685
  &:not([type]) {
686
686
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M6 9L1 4h10z" fill="%23666"/></svg>') !important;
687
687
  background-repeat: no-repeat !important;
688
- background-position: right 0.2rem center !important;
689
- background-size: 8px !important;
690
- padding-right: 1rem !important;
688
+ background-position: right 0.5rem center !important;
689
+ background-size: 10px !important;
690
+ padding-right: 1.5rem !important;
691
691
  }
692
692
  }
693
693
 
@@ -696,9 +696,9 @@
696
696
  background: white !important;
697
697
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M6 9L1 4h10z" fill="%23666"/></svg>') !important;
698
698
  background-repeat: no-repeat !important;
699
- background-position: right 0.2rem center !important;
700
- background-size: 8px !important;
701
- padding: 0.05rem 1rem 0.05rem 0.2rem !important; // Force specific padding
699
+ background-position: right 0.5rem center !important;
700
+ background-size: 10px !important;
701
+ padding: 0.3rem 1.5rem 0.3rem 0.5rem !important; // Updated padding
702
702
  cursor: pointer !important;
703
703
 
704
704
  &::-ms-expand {
@@ -707,6 +707,108 @@
707
707
  }
708
708
  }
709
709
 
710
+ /* Tooltip styles */
711
+ .tooltipContainer {
712
+ position: relative;
713
+ display: inline-flex;
714
+ align-items: center;
715
+ justify-content: center;
716
+ }
717
+
718
+ .tooltip {
719
+ position: absolute;
720
+ background: rgba(0, 0, 0, 0.9);
721
+ color: white;
722
+ padding: 6px 10px;
723
+ border-radius: 4px;
724
+ font-size: 0.75rem;
725
+ font-weight: 500;
726
+ white-space: nowrap;
727
+ z-index: 10000;
728
+ pointer-events: none;
729
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
730
+ transform: translateX(-50%);
731
+
732
+ &::before {
733
+ content: '';
734
+ position: absolute;
735
+ left: 50%;
736
+ border: 5px solid transparent;
737
+ transform: translateX(-50%);
738
+ }
739
+
740
+ &.tooltip-top {
741
+ transform: translateX(-50%) translateY(-100%);
742
+ margin-top: -5px;
743
+
744
+ &::before {
745
+ top: 100%;
746
+ border-top-color: rgba(0, 0, 0, 0.9);
747
+ }
748
+ }
749
+
750
+ &.tooltip-bottom {
751
+ transform: translateX(-50%) translateY(0%);
752
+ margin-top: 5px;
753
+
754
+ &::before {
755
+ bottom: 100%;
756
+ border-bottom-color: rgba(0, 0, 0, 0.9);
757
+ }
758
+ }
759
+ }
760
+
761
+ /* Checkbox styling */
762
+ .schedulingTable input[type="checkbox"] {
763
+ cursor: pointer;
764
+ width: 14px !important;
765
+ height: 14px !important;
766
+ min-width: 14px !important;
767
+ min-height: 14px !important;
768
+ max-width: 14px !important;
769
+ max-height: 14px !important;
770
+ margin: 0 !important;
771
+ padding: 0 !important;
772
+ accent-color: var(--primary-color);
773
+ vertical-align: middle;
774
+ flex-shrink: 0;
775
+ display: inline-block;
776
+
777
+ &:hover {
778
+ transform: scale(1.05);
779
+ }
780
+ }
781
+
782
+ /* Checkbox column specific styling */
783
+ .checkboxColumn {
784
+ width: 30px !important;
785
+ max-width: 30px !important;
786
+ min-width: 30px !important;
787
+ padding: 0.2rem !important;
788
+ text-align: center !important;
789
+ vertical-align: middle !important;
790
+ cursor: pointer !important;
791
+ user-select: none !important;
792
+ transition: background-color 0.2s ease !important;
793
+
794
+ /* Ensure checkbox container doesn't stretch */
795
+ display: table-cell !important;
796
+
797
+ &:hover {
798
+ background-color: rgba(var(--primary-rgb), 0.08) !important;
799
+ }
800
+
801
+ &:active {
802
+ background-color: rgba(var(--primary-rgb), 0.12) !important;
803
+ }
804
+
805
+ input[type="checkbox"] {
806
+ display: inline-block !important;
807
+ vertical-align: middle !important;
808
+ pointer-events: none !important; /* Prevent direct checkbox interaction */
809
+ }
810
+ }
811
+
710
812
  .datasetSelector {
711
813
  display: flex;
712
814
  align-items: center;