@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.19",
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); // Reference to the button for positioning
20
- const popoverRef = useRef(null); // Reference to the popover
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); // Close the color picker
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
- : {}; // Get button position
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} // Attach ref to the button
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); // Toggle visibility
67
+ setColorPickerShow(!colorPickerShow);
56
68
  }}
57
69
  >
58
- &#9744;
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} // Attach ref to the popover
77
+ ref={popoverRef}
65
78
  className={styles.colour_popover}
66
79
  style={{
67
80
  position: 'absolute',
68
- left: buttonRect.left, // Position it based on button's position
69
- top: buttonRect.bottom + 5, // Position below the button with a small offset
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 // Render the popover inside the 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
- setLocalData(enrichedData || []);
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
- const groupCategoriesByType = (data) => {
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
- data.forEach((category, categoryKey) => {
141
- const categoryType =
142
- category[rows.categoryKey.id]?.[rows.categoryKey.label] || null;
143
-
144
- const categoryId = category[rows.categoryKey.id]?.id || null;
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
- ...category,
157
- keyCounter: categoryKey,
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
- const handleSortChange = (groupId, entryId, direction) => {
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
- (group) => group.id === groupId
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 entry = group.entries.find(
279
+ const found = group.entries.find(
214
280
  (e) => e.id === item.id
215
281
  );
216
- return entry
217
- ? { ...item, sort_order: entry.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
- case 'multi-dropdown-ajax':
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
- case 'total':
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
- useEffect(() => {
385
- setGroupedCategories(groupCategoriesByType(localData));
386
- }, [localData]);
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
- {groupedCategories.length > 0 ? (
394
- <table className={styles.schedulingTable}>
395
- <thead>
396
- <tr>
397
- {columns.map((column) => (
398
- <th
399
- key={column.id}
400
- style={{ width: column.width || 'auto' }}
401
- >
402
- {column.label}
403
- </th>
404
- ))}
405
- <th style={{ width: '5%' }}></th>
406
- </tr>
407
- </thead>
408
- <tbody>
409
- {groupedCategories.map((group) => (
410
- <React.Fragment key={group.id}>
411
- <tr className={styles.categoryRow}>
412
- <td colSpan={columns.length + 1}>
413
- {group.category}
414
- </td>
415
- </tr>
416
- {group.entries.map((entry, idx) => (
417
- <tr key={entry.id}>
418
- {columns.map((column) => (
419
- <td
420
- key={column.id}
421
- style={{
422
- width:
423
- column.width || 'auto',
424
- textAlign:
425
- column.type ===
426
- 'currency'
427
- ? 'right'
428
- : 'left',
429
- }}
430
- >
431
- {renderScheduledTableField(
432
- entry,
433
- column,
434
- entry.keyCounter
435
- )}
436
- </td>
437
- ))}
438
- <td>
439
- <div className={styles.tdactions}>
440
- {idx > 0 && (
441
- <ArrowUp
442
- size={16}
443
- onClick={() =>
444
- handleSortChange(
445
- group.id,
446
- entry.id,
447
- 'up'
448
- )
449
- }
450
- style={{
451
- cursor: 'pointer',
452
- }}
453
- />
454
- )}
455
- {idx <
456
- group.entries.length -
457
- 1 && (
458
- <ArrowDown
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
- handleSortChange(
462
- group.id,
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
- <TrashCan
473
- size={16}
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
- <td></td>
526
- </tr>
527
- </React.Fragment>
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: 1em 0;
4
+ margin: 0.5em 0; // Reduced margin
5
5
 
6
6
  th,
7
7
  td {
8
- padding: 0.25rem;
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 1rem; /* Add more padding for better spacing */
15
- font-size: 1.1rem; /* Increase 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); /* Apply a background color */
18
- color: white; /* Change text color to 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
- var(--primary-rgb),
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 1rem; /* Add padding for better spacing */
36
- border-bottom: 2px solid var(--primary-color); /* Add border under category row */
37
- color: var(--primary-color); /* Set text color to 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: 1.125em;
50
- padding: 1.5em;
51
- margin: 2em auto;
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; /* Align to the right like total column */
195
- padding: 1em; /* Add padding for better spacing */
196
- font-size: 0.9rem; /* Increase font size */
197
- color: var(--primary-color); /* Set text color to primary color */
198
- border-top: 2px solid var(--primary-color); /* Add border top to separate subtotal row */
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%; /* Position to the center or adjust accordingly */
214
- top: 50%; /* Adjust top based on your layout */
215
- transform: translate(-50%, -50%); /* Center it */
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: 40px; /* Keep it compact */
230
- height: 40px; /* Square button */
231
- font-size: 1.5rem; /* Adjusted for a better icon size */
223
+ width: 32px;
224
+ height: 32px;
225
+ font-size: 1.5rem;
232
226
  font-weight: 600;
233
- color: #fff; /* White icon color */
234
- background-color: #b0b0b0; /* Neutral grey base */
235
- border: 2px solid #999; /* Light border for definition */
236
- border-radius: 8px; /* Rounded corners */
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; /* Smooth transition for hover effect */
232
+ transition: all 0.3s ease;
239
233
 
240
234
  &:hover {
241
- background-color: #999; /* Darken the background on hover */
242
- border-color: #666; /* Darken the border on hover */
235
+ background-color: #999;
236
+ border-color: #666;
243
237
  }
244
238
 
245
239
  &:focus {
246
- outline: none; /* Remove default focus outline */
247
- box-shadow: 0 0 0 2px rgba(72, 168, 58, 0.5); /* Custom focus outline */
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; /* Lighter grey on click */
252
- transform: scale(0.98); /* Shrink on click */
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
+ }