@visns-studio/visns-components 5.12.11 → 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.
@@ -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,6 +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
214
+ const hasColourColumn = columns.some((col) => col.id === 'colours');
149
215
 
150
216
  // dataSets: each item => { created_at: string, data: array of rows }
151
217
  const [dataSets, setDataSets] = useState([]);
@@ -157,6 +223,9 @@ const GenericEditableTable = ({
157
223
  const [newEntries, setNewEntries] = useState({});
158
224
  const [newEntry, setNewEntry] = useState({});
159
225
 
226
+ // Row selection state for copy functionality
227
+ const [selectedRows, setSelectedRows] = useState(new Set());
228
+
160
229
  // On mount or when 'data' changes, read from data[schedulingConfig.rows.key] and handle both new & old formats
161
230
  useEffect(() => {
162
231
  const raw = data && data[schedulingConfig.rows.key];
@@ -219,15 +288,19 @@ const GenericEditableTable = ({
219
288
  } else {
220
289
  setLocalData([]);
221
290
  }
291
+ // Clear selected rows when dataset changes
292
+ setSelectedRows(new Set());
222
293
  }, [dataSets, activeDatasetIndex]);
223
294
 
224
295
  // Helper to sort by sort_order
225
296
  const sortBySortOrder = (dataArray) =>
226
297
  [...dataArray].sort((a, b) => a.sort_order - b.sort_order);
227
298
 
228
- // Return background color from row_colour field
299
+ // Return background color from row_colour field or colours column
229
300
  const getRowBackground = (entry) => {
230
- return entry.row_colour || undefined;
301
+ // Check if there's a colour picked from the colours column
302
+ const coloursValue = entry.colours || entry.row_colour;
303
+ return coloursValue ? `${coloursValue}40` : undefined; // Add 40% opacity
231
304
  };
232
305
 
233
306
  // Calculate total for columns of type 'total'
@@ -501,11 +574,50 @@ const GenericEditableTable = ({
501
574
  return hasCategory ? groupCategoriesByType(localData) : [];
502
575
  }, [localData, hasCategory]);
503
576
 
504
- // 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
505
610
  const handleColumnCopy = (column, group = null) => {
506
- let values = group
507
- ? group.entries.map((entry) => entry[column.id])
508
- : 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]);
509
621
 
510
622
  const textToCopy = values
511
623
  .filter((val) => val !== undefined && val !== null)
@@ -513,19 +625,23 @@ const GenericEditableTable = ({
513
625
 
514
626
  navigator.clipboard
515
627
  .writeText(textToCopy)
516
- .then(() =>
628
+ .then(() => {
629
+ const selectionInfo =
630
+ selectedRows.size > 0
631
+ ? ` (${selectedRows.size} selected rows)`
632
+ : ' (all rows)';
517
633
  toast.success(
518
634
  `Copied ${values.length} value${
519
635
  values.length !== 1 ? 's' : ''
520
636
  } from "${column.label}"${
521
637
  group ? ` (Group: ${group.category})` : ''
522
- }`
523
- )
524
- )
638
+ }${selectionInfo}`
639
+ );
640
+ })
525
641
  .catch(() => toast.error('Failed to copy to clipboard'));
526
642
  };
527
643
 
528
- // 6) Sorting: grouped
644
+ // 7) Sorting: grouped
529
645
  const handleSortChangeGrouped = (groupId, entryId, direction) => {
530
646
  if (!hasCategory) return;
531
647
  setLocalData((prev) => {
@@ -577,7 +693,7 @@ const GenericEditableTable = ({
577
693
  });
578
694
  };
579
695
 
580
- // 7) Sorting: ungrouped
696
+ // 8) Sorting: ungrouped
581
697
  const handleSortChangeUngrouped = (entryId, direction) => {
582
698
  setLocalData((prev) => {
583
699
  const sortedData = sortBySortOrder(prev);
@@ -608,7 +724,7 @@ const GenericEditableTable = ({
608
724
  });
609
725
  };
610
726
 
611
- // 8) Deletion
727
+ // 9) Deletion
612
728
  const handleDeleteRow = (entry) => {
613
729
  confirmDialog({
614
730
  title: 'Confirm to Delete',
@@ -642,9 +758,25 @@ const GenericEditableTable = ({
642
758
  const renderScheduledTableField = (entry, column, keyCounter) => {
643
759
  if (!entry || !column) return null;
644
760
 
761
+ // Special handling for colours column regardless of type
762
+ if (column.id === 'colours') {
763
+ return (
764
+ <ColourPicker
765
+ value={entry[column.id] || ''}
766
+ columnId={column.id}
767
+ keyCounter={keyCounter}
768
+ onChange={(value, fieldId, keyCounter) => {
769
+ // Update both the colours field and row_colour for row highlighting
770
+ handleFieldChange(value, fieldId, keyCounter);
771
+ handleFieldChange(value, 'row_colour', keyCounter);
772
+ }}
773
+ />
774
+ );
775
+ }
776
+
645
777
  switch (column.type) {
646
778
  case 'colour':
647
- // Colour picker is now rendered in the action column
779
+ // For other colour columns, picker is rendered in the action column
648
780
  return null;
649
781
  case 'currency':
650
782
  case 'number':
@@ -765,9 +897,26 @@ const GenericEditableTable = ({
765
897
  // Render new row field
766
898
  const renderNewRowField = (newRowData, column, groupId = null) => {
767
899
  const value = newRowData ? newRowData[column.id] : '';
900
+
901
+ // Special handling for colours column regardless of type
902
+ if (column.id === 'colours') {
903
+ return (
904
+ <ColourPicker
905
+ value={value || ''}
906
+ columnId={column.id}
907
+ keyCounter={0}
908
+ onChange={(val, colId) => {
909
+ // Update both the colours field and row_colour for row highlighting
910
+ handleNewFieldChange(val, colId, groupId);
911
+ handleNewFieldChange(val, 'row_colour', groupId);
912
+ }}
913
+ />
914
+ );
915
+ }
916
+
768
917
  switch (column.type) {
769
918
  case 'colour':
770
- // Colour picker is now rendered in the action column
919
+ // For other colour columns, picker is rendered in the action column
771
920
  return null;
772
921
  case 'currency':
773
922
  case 'number':
@@ -885,6 +1034,7 @@ const GenericEditableTable = ({
885
1034
  const newRowData = hasCategory ? newEntries[groupId] || {} : newEntry;
886
1035
  return (
887
1036
  <tr className={styles.newRow}>
1037
+ <td className={styles.checkboxColumn}></td>
888
1038
  {columns.map((column) => (
889
1039
  <td
890
1040
  key={column.id}
@@ -910,6 +1060,51 @@ const GenericEditableTable = ({
910
1060
  );
911
1061
  };
912
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
+
913
1108
  // Header cell with optional copy icon
914
1109
  const renderHeaderCell = (column, group = null) => (
915
1110
  <th
@@ -932,7 +1127,7 @@ const GenericEditableTable = ({
932
1127
  }}
933
1128
  style={{ cursor: 'pointer' }}
934
1129
  >
935
- <Copy size={16} />
1130
+ <Copy size={20} />
936
1131
  </span>
937
1132
  )}
938
1133
  </div>
@@ -991,12 +1186,13 @@ const GenericEditableTable = ({
991
1186
  {groupedCategories.map((group) => (
992
1187
  <React.Fragment key={group.id}>
993
1188
  <tr className={styles.categoryRow}>
994
- <td colSpan={columns.length + 1}>
1189
+ <td colSpan={columns.length + 2}>
995
1190
  {group.category}
996
1191
  </td>
997
1192
  </tr>
998
1193
  <tr>
999
- {columns.filter(col => col.type !== 'colour').map((column) =>
1194
+ {renderSelectAllCheckbox(group)}
1195
+ {columns.map((column) =>
1000
1196
  renderHeaderCell(column, group)
1001
1197
  )}
1002
1198
  <th style={{ width: '5%' }}></th>
@@ -1010,6 +1206,7 @@ const GenericEditableTable = ({
1010
1206
  getRowBackground(entry),
1011
1207
  }}
1012
1208
  >
1209
+ {renderRowCheckbox(entry)}
1013
1210
  {columns.map((column) => (
1014
1211
  <td
1015
1212
  key={column.id}
@@ -1032,55 +1229,72 @@ const GenericEditableTable = ({
1032
1229
  <div
1033
1230
  className={styles.tdactions}
1034
1231
  >
1035
- <ColourPicker
1036
- value={entry.row_colour || ''}
1037
- columnId={'row_colour'}
1038
- keyCounter={entry.keyCounter}
1039
- onChange={handleFieldChange}
1040
- />
1041
- {idx > 0 && (
1042
- <ArrowUp
1043
- size={16}
1044
- onClick={() =>
1045
- handleSortChangeGrouped(
1046
- group.id,
1047
- entry.id,
1048
- 'up'
1049
- )
1232
+ {!hasColourColumn && (
1233
+ <ColourPicker
1234
+ value={
1235
+ entry.row_colour ||
1236
+ ''
1237
+ }
1238
+ columnId={
1239
+ 'row_colour'
1240
+ }
1241
+ keyCounter={
1242
+ entry.keyCounter
1243
+ }
1244
+ onChange={
1245
+ handleFieldChange
1050
1246
  }
1051
- style={{
1052
- cursor: 'pointer',
1053
- }}
1054
1247
  />
1055
1248
  )}
1249
+ {idx > 0 && (
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>
1265
+ )}
1056
1266
  {idx <
1057
1267
  group.entries.length -
1058
1268
  1 && (
1059
- <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
1060
1287
  size={16}
1061
1288
  onClick={() =>
1062
- handleSortChangeGrouped(
1063
- group.id,
1064
- entry.id,
1065
- 'down'
1289
+ handleDeleteRow(
1290
+ entry
1066
1291
  )
1067
1292
  }
1068
1293
  style={{
1069
1294
  cursor: 'pointer',
1070
1295
  }}
1071
1296
  />
1072
- )}
1073
- <Trash2
1074
- size={16}
1075
- onClick={() =>
1076
- handleDeleteRow(
1077
- entry
1078
- )
1079
- }
1080
- style={{
1081
- cursor: 'pointer',
1082
- }}
1083
- />
1297
+ </Tooltip>
1084
1298
  </div>
1085
1299
  </td>
1086
1300
  </tr>
@@ -1092,9 +1306,10 @@ const GenericEditableTable = ({
1092
1306
  (col) => col.type === 'total'
1093
1307
  ) !== -1 && (
1094
1308
  <tr className={styles.subtotalRow}>
1095
- {columns.filter(col => col.type !== 'colour').map((col, index) => {
1309
+ <td></td>
1310
+ {columns.map((col, index) => {
1096
1311
  const totalColIndex =
1097
- columns.filter(col => col.type !== 'colour').findIndex(
1312
+ columns.findIndex(
1098
1313
  (c) =>
1099
1314
  c.type === 'total'
1100
1315
  );
@@ -1116,7 +1331,7 @@ const GenericEditableTable = ({
1116
1331
  sum +
1117
1332
  calculateTotal(
1118
1333
  entry,
1119
- columns.filter(col => col.type !== 'colour')[
1334
+ columns[
1120
1335
  totalColIndex
1121
1336
  ].keys
1122
1337
  ),
@@ -1154,124 +1369,153 @@ const GenericEditableTable = ({
1154
1369
  <table className={styles.schedulingTable}>
1155
1370
  <thead>
1156
1371
  <tr>
1157
- {columns.filter(col => col.type !== 'colour').map((column) => renderHeaderCell(column))}
1372
+ {renderSelectAllCheckbox()}
1373
+ {columns.map((column) => renderHeaderCell(column))}
1158
1374
  <th style={{ width: '5%' }}></th>
1159
1375
  </tr>
1160
1376
  </thead>
1161
1377
  <tbody>
1162
- {localData.length > 0 && sortBySortOrder(localData).map((entry, idx) => (
1163
- <tr
1164
- key={entry.id}
1165
- style={{ background: getRowBackground(entry) }}
1166
- >
1167
- {columns.map((column) => (
1168
- <td
1169
- key={column.id}
1170
- style={{
1171
- width: column.width || 'auto',
1172
- textAlign:
1173
- column.type === 'currency'
1174
- ? 'right'
1175
- : 'left',
1176
- }}
1177
- >
1178
- {renderScheduledTableField(
1179
- entry,
1180
- column,
1181
- idx
1182
- )}
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>
1183
1464
  </td>
1184
- ))}
1185
- <td>
1186
- <div className={styles.tdactions}>
1187
- <ColourPicker
1188
- value={entry.row_colour || ''}
1189
- columnId={'row_colour'}
1190
- keyCounter={idx}
1191
- onChange={handleFieldChange}
1192
- />
1193
- {idx > 0 && (
1194
- <ArrowUp
1195
- size={16}
1196
- onClick={() =>
1197
- handleSortChangeUngrouped(
1198
- entry.id,
1199
- 'up'
1200
- )
1201
- }
1202
- style={{ cursor: 'pointer' }}
1203
- />
1204
- )}
1205
- {idx <
1206
- sortBySortOrder(localData).length -
1207
- 1 && (
1208
- <ArrowDown
1209
- size={16}
1210
- onClick={() =>
1211
- handleSortChangeUngrouped(
1212
- entry.id,
1213
- 'down'
1214
- )
1215
- }
1216
- style={{ cursor: 'pointer' }}
1217
- />
1218
- )}
1219
- <Trash2
1220
- size={16}
1221
- onClick={() =>
1222
- handleDeleteRow(entry)
1223
- }
1224
- style={{ cursor: 'pointer' }}
1225
- />
1226
- </div>
1227
- </td>
1228
- </tr>
1229
- ))}
1465
+ </tr>
1466
+ ))}
1230
1467
 
1231
1468
  {renderNewRowForm()}
1232
1469
 
1233
- {localData.length > 0 && columns.findIndex((col) => col.type === 'total') !==
1234
- -1 && (
1235
- <tr className={styles.subtotalRow}>
1236
- {columns.filter(col => col.type !== 'colour').map((col, index) => {
1237
- const totalColIndex = columns.filter(col => col.type !== 'colour').findIndex(
1238
- (c) => c.type === 'total'
1239
- );
1240
- if (
1241
- index === totalColIndex - 1 &&
1242
- index > 0
1243
- ) {
1244
- return <td key={col.id}>Subtotal</td>;
1245
- }
1246
- if (index === totalColIndex) {
1247
- const overallTotal = sortBySortOrder(
1248
- localData
1249
- ).reduce(
1250
- (sum, entry) =>
1251
- sum +
1252
- calculateTotal(
1253
- entry,
1254
- columns.filter(col => col.type !== 'colour')[totalColIndex].keys
1255
- ),
1256
- 0
1257
- );
1258
- return (
1259
- <td key={col.id}>
1260
- {overallTotal.toLocaleString(
1261
- 'en-AU',
1262
- {
1263
- style: 'currency',
1264
- currency: 'AUD',
1265
- }
1266
- )}
1267
- </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'
1268
1478
  );
1269
- }
1270
- return <td key={col.id} />;
1271
- })}
1272
- <td></td>
1273
- </tr>
1274
- )}
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
+ )}
1275
1519
  </tbody>
1276
1520
  </table>
1277
1521
  )}