@visns-studio/visns-components 5.8.20 → 5.9.1

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.
@@ -43,6 +43,7 @@ import { toast } from 'react-toastify';
43
43
  import imageCompression from 'browser-image-compression';
44
44
  import Vapor from 'laravel-vapor';
45
45
  import Lightbox from 'yet-another-react-lightbox';
46
+ import { Tooltip } from 'react-tooltip';
46
47
  import fetchUtil from '../../utils/fetchUtil';
47
48
  import { confirmDialog } from '../utils/ConfirmDialog';
48
49
  import {
@@ -85,6 +86,101 @@ import Download from './Download';
85
86
  import Form from './Form';
86
87
  import _ from 'lodash';
87
88
 
89
+ // CSS styles to ensure consistent group header heights with compact design and click indicators
90
+ const groupHeaderStyles = `
91
+ .datagrid-fixed-group-headers .group-header-fixed-height {
92
+ height: 34px !important;
93
+ min-height: 34px !important;
94
+ max-height: 34px !important;
95
+ box-sizing: border-box !important;
96
+ line-height: 1.1 !important;
97
+ flex-shrink: 0 !important;
98
+ overflow: hidden !important;
99
+ transition: background-color 0.2s ease, box-shadow 0.2s ease !important;
100
+ cursor: pointer !important;
101
+ position: relative !important;
102
+ }
103
+
104
+ /* Hover effect for entire group header */
105
+ .datagrid-fixed-group-headers .group-header-fixed-height:hover {
106
+ background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%) !important;
107
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;
108
+ transform: translateY(-1px) !important;
109
+ }
110
+
111
+ /* Click indicator overlay */
112
+ .datagrid-fixed-group-headers .group-header-fixed-height::after {
113
+ content: '' !important;
114
+ position: absolute !important;
115
+ top: 0 !important;
116
+ left: 0 !important;
117
+ right: 0 !important;
118
+ bottom: 0 !important;
119
+ background: rgba(59, 130, 246, 0.05) !important;
120
+ opacity: 0 !important;
121
+ transition: opacity 0.2s ease !important;
122
+ pointer-events: none !important;
123
+ }
124
+
125
+ .datagrid-fixed-group-headers .group-header-fixed-height:hover::after {
126
+ opacity: 1 !important;
127
+ }
128
+
129
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row {
130
+ height: 34px !important;
131
+ min-height: 34px !important;
132
+ max-height: 34px !important;
133
+ }
134
+
135
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row .InovuaReactDataGrid__group-cell {
136
+ height: 34px !important;
137
+ min-height: 34px !important;
138
+ max-height: 34px !important;
139
+ padding: 0 !important;
140
+ overflow: hidden !important;
141
+ }
142
+
143
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row .InovuaReactDataGrid__group-cell > div {
144
+ height: 34px !important;
145
+ min-height: 34px !important;
146
+ max-height: 34px !important;
147
+ overflow: hidden !important;
148
+ }
149
+
150
+ /* Ensure group headers maintain consistent styling during expand/collapse */
151
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row--collapsed .group-header-fixed-height,
152
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row--expanded .group-header-fixed-height {
153
+ height: 34px !important;
154
+ min-height: 34px !important;
155
+ max-height: 34px !important;
156
+ transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease !important;
157
+ }
158
+
159
+ /* Force consistent height for all group headers regardless of content */
160
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row[data-group-by] {
161
+ height: 34px !important;
162
+ min-height: 34px !important;
163
+ max-height: 34px !important;
164
+ }
165
+
166
+ .datagrid-fixed-group-headers .InovuaReactDataGrid__group-row[data-group-by] .InovuaReactDataGrid__group-cell {
167
+ height: 34px !important;
168
+ min-height: 34px !important;
169
+ max-height: 34px !important;
170
+ }
171
+ `;
172
+
173
+ // Inject styles into the document head
174
+ if (typeof document !== 'undefined') {
175
+ const styleId = 'datagrid-group-header-styles';
176
+ if (!document.getElementById(styleId)) {
177
+ const style = document.createElement('style');
178
+ style.id = styleId;
179
+ style.textContent = groupHeaderStyles;
180
+ document.head.appendChild(style);
181
+ }
182
+ }
183
+
88
184
  const loadData = async (
89
185
  { skip, limit, sortInfo, filterValue },
90
186
  search,
@@ -146,6 +242,123 @@ const loadData = async (
146
242
  }
147
243
  };
148
244
 
245
+ // Utility function to check if text content is truncated
246
+ const isTextTruncated = (element) => {
247
+ if (!element) return false;
248
+ return (
249
+ element.scrollWidth > element.clientWidth ||
250
+ element.scrollHeight > element.clientHeight
251
+ );
252
+ };
253
+
254
+ // Utility function to format cell value for tooltip display
255
+ const formatCellValueForTooltip = (value, columnType) => {
256
+ if (value === null || value === undefined || value === '') {
257
+ return '';
258
+ }
259
+
260
+ switch (columnType) {
261
+ case 'date':
262
+ case 'datetime':
263
+ if (
264
+ moment(value).isValid() &&
265
+ moment(value).format('YYYY-MM-DD') !== '1970-01-01'
266
+ ) {
267
+ return moment(value).format('DD-MM-YYYY HH:mm');
268
+ }
269
+ return String(value);
270
+ case 'currency':
271
+ return numeral(value).format('$0,0.00');
272
+ case 'number':
273
+ return numeral(value).format('0,0');
274
+ case 'boolean':
275
+ return value ? 'Yes' : 'No';
276
+ case 'richtext':
277
+ // Strip HTML tags for tooltip
278
+ const tempDiv = document.createElement('div');
279
+ tempDiv.innerHTML = value;
280
+ return tempDiv.textContent || tempDiv.innerText || '';
281
+ default:
282
+ return String(value);
283
+ }
284
+ };
285
+
286
+ // Component to wrap cell content with tooltip functionality
287
+ const CellWithTooltip = ({
288
+ children,
289
+ value,
290
+ columnType = 'text',
291
+ className = '',
292
+ }) => {
293
+ const cellRef = useRef(null);
294
+ const [showTooltip, setShowTooltip] = useState(false);
295
+ const [tooltipId] = useState(
296
+ () => `cell-tooltip-${Math.random().toString(36).substr(2, 9)}`
297
+ );
298
+
299
+ useEffect(() => {
300
+ const checkTruncation = () => {
301
+ if (cellRef.current) {
302
+ const isTruncated = isTextTruncated(cellRef.current);
303
+ setShowTooltip(isTruncated);
304
+ }
305
+ };
306
+
307
+ // Check truncation on mount and when content changes
308
+ checkTruncation();
309
+
310
+ // Also check on window resize
311
+ const handleResize = () => checkTruncation();
312
+ window.addEventListener('resize', handleResize);
313
+
314
+ return () => {
315
+ window.removeEventListener('resize', handleResize);
316
+ };
317
+ }, [children, value]);
318
+
319
+ const tooltipContent = formatCellValueForTooltip(value, columnType);
320
+
321
+ // Only show tooltip if content is truncated and we have meaningful content
322
+ const shouldShowTooltip =
323
+ showTooltip && tooltipContent && tooltipContent.trim() !== '';
324
+
325
+ return (
326
+ <div
327
+ ref={cellRef}
328
+ className={className}
329
+ style={{
330
+ overflow: 'hidden',
331
+ textOverflow: 'ellipsis',
332
+ whiteSpace: 'nowrap',
333
+ width: '100%',
334
+ }}
335
+ {...(shouldShowTooltip && {
336
+ 'data-tooltip-id': tooltipId,
337
+ 'data-tooltip-content': tooltipContent,
338
+ })}
339
+ >
340
+ {children}
341
+ {shouldShowTooltip && (
342
+ <Tooltip
343
+ id={tooltipId}
344
+ style={{
345
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
346
+ color: 'white',
347
+ borderRadius: '6px',
348
+ padding: '8px 12px',
349
+ fontSize: '13px',
350
+ maxWidth: '300px',
351
+ wordWrap: 'break-word',
352
+ zIndex: 9999,
353
+ }}
354
+ place="top"
355
+ offset={5}
356
+ />
357
+ )}
358
+ </div>
359
+ );
360
+ };
361
+
149
362
  const DataGrid = forwardRef(
150
363
  (
151
364
  {
@@ -370,6 +583,75 @@ const DataGrid = forwardRef(
370
583
  /** Table States */
371
584
  const dataRef = useRef(null);
372
585
  const [dSearch, setDSearch] = useState('');
586
+
587
+ /** Group expansion state */
588
+ const [collapsedGroups, setCollapsedGroups] = useState({});
589
+
590
+ /** Force re-render trigger for group header consistency */
591
+ const [groupRenderKey, setGroupRenderKey] = useState(0);
592
+
593
+ /** Prevent rapid group selection clicks */
594
+ const groupClickTimeoutRef = useRef(null);
595
+
596
+ // Effect to ensure group headers maintain consistent styling after expand/collapse
597
+ useEffect(() => {
598
+ if (ajaxSetting?.groupBy) {
599
+ // Small delay to allow ReactDataGrid to complete its rendering
600
+ const timer = setTimeout(() => {
601
+ // Force a re-render to ensure consistent styling
602
+ setGroupRenderKey((prev) => prev + 1);
603
+
604
+ // Apply styles to any group headers that might have been missed
605
+ const groupHeaders = document.querySelectorAll(
606
+ '.group-header-fixed-height'
607
+ );
608
+ groupHeaders.forEach((header) => {
609
+ header.style.height = '34px';
610
+ header.style.minHeight = '34px';
611
+ header.style.maxHeight = '34px';
612
+ header.style.boxSizing = 'border-box';
613
+ header.style.lineHeight = '1.1';
614
+ header.style.flexShrink = '0';
615
+ header.style.overflow = 'hidden';
616
+ header.style.cursor = 'pointer';
617
+ header.style.position = 'relative';
618
+ });
619
+
620
+ // Also apply to ReactDataGrid group rows to fix height inconsistency
621
+ const groupRows = document.querySelectorAll(
622
+ '.datagrid-fixed-group-headers .InovuaReactDataGrid__group-row'
623
+ );
624
+ groupRows.forEach((row) => {
625
+ row.style.height = '34px';
626
+ row.style.minHeight = '34px';
627
+ row.style.maxHeight = '34px';
628
+
629
+ // Apply to group cells within the row
630
+ const groupCells = row.querySelectorAll(
631
+ '.InovuaReactDataGrid__group-cell'
632
+ );
633
+ groupCells.forEach((cell) => {
634
+ cell.style.height = '34px';
635
+ cell.style.minHeight = '34px';
636
+ cell.style.maxHeight = '34px';
637
+ cell.style.padding = '0';
638
+ cell.style.overflow = 'hidden';
639
+
640
+ // Apply to div elements within group cells
641
+ const divs = cell.querySelectorAll('div');
642
+ divs.forEach((div) => {
643
+ div.style.height = '34px';
644
+ div.style.minHeight = '34px';
645
+ div.style.maxHeight = '34px';
646
+ div.style.overflow = 'hidden';
647
+ });
648
+ });
649
+ });
650
+ }, 50);
651
+
652
+ return () => clearTimeout(timer);
653
+ }
654
+ }, [collapsedGroups, ajaxSetting?.groupBy]);
373
655
  const dataSource = useCallback(
374
656
  (params) => {
375
657
  const sqlResult = loadData(params, dSearch, ajaxSetting);
@@ -378,6 +660,18 @@ const DataGrid = forwardRef(
378
660
  }
379
661
 
380
662
  sqlResult.then((res) => {
663
+ // Sort data by grouping field if grouping is enabled
664
+ if (ajaxSetting?.groupBy?.length > 0) {
665
+ const groupField = ajaxSetting.groupBy[0]; // groupBy is array of strings
666
+ res.data.sort((a, b) => {
667
+ const aValue = a[groupField] || '';
668
+ const bValue = b[groupField] || '';
669
+ return aValue
670
+ .toString()
671
+ .localeCompare(bValue.toString());
672
+ });
673
+ }
674
+
381
675
  if (setTotal) {
382
676
  setTotal(res.count);
383
677
  }
@@ -390,7 +684,7 @@ const DataGrid = forwardRef(
390
684
 
391
685
  return sqlResult;
392
686
  },
393
- [ajaxSetting, dSearch]
687
+ [ajaxSetting, dSearch, collapsedGroups]
394
688
  );
395
689
  const [filterDataSource, setFilterDataSource] = useState([]);
396
690
  const [filterValue, setFilterValue] = useState([]);
@@ -547,9 +841,31 @@ const DataGrid = forwardRef(
547
841
  if (param.selected === true) {
548
842
  // Select all rows - this happens when header checkbox is clicked to select all
549
843
  const s = {};
550
- param.data.forEach((obj) => {
551
- s[obj.id] = obj;
552
- });
844
+
845
+ // Always prefer dataRef.current as it contains the most reliable data
846
+ // especially when grouping is active
847
+ if (dataRef.current && Array.isArray(dataRef.current)) {
848
+ dataRef.current.forEach((obj) => {
849
+ if (
850
+ obj &&
851
+ obj.id !== undefined &&
852
+ obj.id !== null
853
+ ) {
854
+ s[obj.id] = obj;
855
+ }
856
+ });
857
+ } else if (param.data && Array.isArray(param.data)) {
858
+ // Fallback to param.data if dataRef.current is not available
859
+ param.data.forEach((obj) => {
860
+ if (
861
+ obj &&
862
+ obj.id !== undefined &&
863
+ obj.id !== null
864
+ ) {
865
+ s[obj.id] = obj;
866
+ }
867
+ });
868
+ }
553
869
  setSelected(s);
554
870
  // Update parent component's rowsSelected state
555
871
  if (setRowsSelected) {
@@ -569,17 +885,148 @@ const DataGrid = forwardRef(
569
885
  } else if (typeof param.selected === 'object') {
570
886
  // Individual row selection/deselection - ReactDataGrid sends the complete new selection state
571
887
  // ReactDataGrid sends the complete new selection state, not just the changed row
572
- // So we can directly use param.selected as the new selection state
573
- setSelected(param.selected);
888
+ // Clean the selection to remove any group objects or invalid entries
889
+ const cleanSelection = {};
890
+ Object.keys(param.selected).forEach((key) => {
891
+ const item = param.selected[key];
892
+ // Only keep items that have valid IDs and are not group objects
893
+ if (
894
+ key !== 'undefined' &&
895
+ key !== 'null' &&
896
+ item &&
897
+ !item.__group &&
898
+ item.id !== undefined
899
+ ) {
900
+ cleanSelection[key] = item;
901
+ }
902
+ });
903
+
904
+ setSelected(cleanSelection);
574
905
  // Update parent component's rowsSelected state
575
906
  if (setRowsSelected) {
576
- setRowsSelected(param.selected);
907
+ setRowsSelected(cleanSelection);
577
908
  }
578
909
  }
579
910
  },
580
911
  [selected, setRowsSelected]
581
912
  );
582
913
 
914
+ // Group selection functions
915
+ const handleGroupSelectionChange = useCallback(
916
+ (groupValue, isChecked) => {
917
+ if (!dataRef.current || !ajaxSetting?.groupBy) {
918
+ return;
919
+ }
920
+
921
+ const groupField = ajaxSetting.groupBy[0];
922
+ const groupRows = dataRef.current.filter(
923
+ (row) => row[groupField] === groupValue
924
+ );
925
+
926
+ // Clean the selected state - remove any invalid entries
927
+ const cleanSelected = {};
928
+ Object.keys(selected).forEach((key) => {
929
+ const item = selected[key];
930
+ // Only keep items that have valid IDs and are not group objects
931
+ if (
932
+ key !== 'undefined' &&
933
+ key !== 'null' &&
934
+ item &&
935
+ !item.__group &&
936
+ item.id !== undefined
937
+ ) {
938
+ cleanSelected[key] = item;
939
+ }
940
+ });
941
+ const newSelected = { ...cleanSelected };
942
+
943
+ if (isChecked) {
944
+ // Select all rows in the group
945
+ groupRows.forEach((row) => {
946
+ if (row && row.id !== undefined && row.id !== null) {
947
+ newSelected[row.id] = row;
948
+ }
949
+ });
950
+ } else {
951
+ // Deselect all rows in the group
952
+ groupRows.forEach((row) => {
953
+ if (row && row.id !== undefined && row.id !== null) {
954
+ delete newSelected[row.id];
955
+ }
956
+ });
957
+ }
958
+
959
+ // Update the selection state
960
+ setSelected(newSelected);
961
+ if (setRowsSelected) {
962
+ setRowsSelected(newSelected);
963
+ }
964
+
965
+ // Force ReactDataGrid to update its selection using the grid API
966
+ setTimeout(() => {
967
+ if (gridRef.current && gridRef.current.setSelected) {
968
+ gridRef.current.setSelected(newSelected);
969
+ } else if (gridRef.current) {
970
+ // Try alternative methods
971
+ if (gridRef.current.updateSelected) {
972
+ gridRef.current.updateSelected(newSelected);
973
+ } else if (gridRef.current.setSelection) {
974
+ gridRef.current.setSelection(newSelected);
975
+ }
976
+ }
977
+ }, 10);
978
+ },
979
+ [selected, setRowsSelected, ajaxSetting]
980
+ );
981
+
982
+ const isGroupSelected = useCallback(
983
+ (groupValue) => {
984
+ if (!dataRef.current || !ajaxSetting?.groupBy) {
985
+ return false;
986
+ }
987
+
988
+ // Clean the selected state first
989
+ const cleanSelected = {};
990
+ Object.keys(selected).forEach((key) => {
991
+ const item = selected[key];
992
+ if (
993
+ key !== 'undefined' &&
994
+ key !== 'null' &&
995
+ item &&
996
+ !item.__group &&
997
+ item.id !== undefined
998
+ ) {
999
+ cleanSelected[key] = item;
1000
+ }
1001
+ });
1002
+
1003
+ const groupField = ajaxSetting.groupBy[0];
1004
+ const groupRows = dataRef.current.filter(
1005
+ (row) => row[groupField] === groupValue
1006
+ );
1007
+
1008
+ if (groupRows.length === 0) {
1009
+ return false;
1010
+ }
1011
+
1012
+ // Filter out rows with invalid IDs and check if all valid rows are selected
1013
+ const validRows = groupRows.filter(
1014
+ (row) => row && row.id !== undefined && row.id !== null
1015
+ );
1016
+
1017
+ if (validRows.length === 0) {
1018
+ return false;
1019
+ }
1020
+
1021
+ const allSelected = validRows.every(
1022
+ (row) => cleanSelected[row.id]
1023
+ );
1024
+
1025
+ return allSelected;
1026
+ },
1027
+ [selected, ajaxSetting]
1028
+ );
1029
+
583
1030
  const findUpdatedFilter = (filters, currentValues) => {
584
1031
  for (const filter of filters) {
585
1032
  const matchingCurrentValue = currentValues.find(
@@ -1213,6 +1660,7 @@ const DataGrid = forwardRef(
1213
1660
  }
1214
1661
  } else {
1215
1662
  // For non-checkbox columns, handle normal row clicks
1663
+ // But do NOT trigger checkbox selection
1216
1664
  onRowClick(rowProps, event);
1217
1665
 
1218
1666
  // Call the original onClick handler
@@ -1222,7 +1670,7 @@ const DataGrid = forwardRef(
1222
1670
  }
1223
1671
  };
1224
1672
  },
1225
- [selected, setRowsSelected]
1673
+ [selected, setRowsSelected, onRowClick]
1226
1674
  );
1227
1675
 
1228
1676
  const exportTrigger = async () => {
@@ -1538,10 +1986,11 @@ const DataGrid = forwardRef(
1538
1986
  'var(--light-bg-color, #f5f5f5)',
1539
1987
  borderRadius: '4px',
1540
1988
  padding: '4px',
1541
- width: '22px',
1542
- height: '22px',
1989
+ width: '28px',
1990
+ height: '28px',
1543
1991
  boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
1544
1992
  margin: '0 1px',
1993
+ cursor: 'pointer',
1545
1994
  }}
1546
1995
  >
1547
1996
  <img
@@ -1550,8 +1999,8 @@ const DataGrid = forwardRef(
1550
1999
  data-tooltip-id="system-tooltip"
1551
2000
  data-tooltip-content={tooltipContent}
1552
2001
  style={{
1553
- maxHeight: '16px',
1554
- maxWidth: '16px',
2002
+ maxHeight: '20px',
2003
+ maxWidth: '20px',
1555
2004
  objectFit: 'contain',
1556
2005
  }}
1557
2006
  />
@@ -1586,10 +2035,11 @@ const DataGrid = forwardRef(
1586
2035
  'var(--light-bg-color, #f5f5f5)',
1587
2036
  borderRadius: '4px',
1588
2037
  padding: '4px',
1589
- width: '22px',
1590
- height: '22px',
2038
+ width: '28px',
2039
+ height: '28px',
1591
2040
  boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
1592
2041
  margin: '0 1px',
2042
+ cursor: 'pointer',
1593
2043
  }}
1594
2044
  >
1595
2045
  <IconComponent
@@ -1670,6 +2120,82 @@ const DataGrid = forwardRef(
1670
2120
  }
1671
2121
  }, [selected]);
1672
2122
 
2123
+ // Add styling to grouped column header with enhanced debugging
2124
+ useEffect(() => {
2125
+ if (ajaxSetting?.groupBy && ajaxSetting.groupBy.length > 0) {
2126
+ const groupedColumnName = ajaxSetting.groupBy[0];
2127
+
2128
+ // Multiple attempts to find and style the grouped column header
2129
+ const applyGroupedStyling = () => {
2130
+ const headerCells = document.querySelectorAll(
2131
+ '.InovuaReactDataGrid__header-cell'
2132
+ );
2133
+
2134
+ headerCells.forEach((cell) => {
2135
+ const cellContent = cell.textContent?.trim();
2136
+ const dataColumnId =
2137
+ cell.getAttribute('data-column-id');
2138
+
2139
+ // Find the column that matches the grouped field
2140
+ const matchingColumn = gridColumns.find(
2141
+ (col) =>
2142
+ col.name === groupedColumnName ||
2143
+ col.header === cellContent ||
2144
+ col.id === groupedColumnName ||
2145
+ dataColumnId === groupedColumnName
2146
+ );
2147
+
2148
+ if (
2149
+ matchingColumn &&
2150
+ (matchingColumn.name === groupedColumnName ||
2151
+ matchingColumn.id === groupedColumnName ||
2152
+ dataColumnId === groupedColumnName)
2153
+ ) {
2154
+ cell.classList.add(
2155
+ 'InovuaReactDataGrid__header-cell--grouped'
2156
+ );
2157
+ // Force inline styles as backup
2158
+ cell.style.background =
2159
+ 'linear-gradient(135deg, var(--primary-color, #3b82f6) 0%, var(--secondary-color, #2563eb) 100%)';
2160
+ cell.style.color = 'white';
2161
+ cell.style.fontWeight = '800';
2162
+ cell.style.textTransform = 'uppercase';
2163
+ cell.style.letterSpacing = '0.1em';
2164
+ cell.style.borderBottom =
2165
+ '4px solid var(--secondary-color, #2563eb)';
2166
+ }
2167
+ });
2168
+
2169
+ // Also apply class to the main DataGrid container for debugging
2170
+ const dataGrids = document.querySelectorAll(
2171
+ '.InovuaReactDataGrid'
2172
+ );
2173
+ dataGrids.forEach((grid) => {
2174
+ if (
2175
+ grid.classList.contains(
2176
+ 'InovuaReactDataGrid--grouped'
2177
+ ) ||
2178
+ grid.classList.contains('datagrid-with-grouping')
2179
+ ) {
2180
+ // Main checkbox is now enabled for grouped data
2181
+ }
2182
+ });
2183
+ };
2184
+
2185
+ // Apply immediately and with delays
2186
+ applyGroupedStyling();
2187
+ const timer1 = setTimeout(applyGroupedStyling, 100);
2188
+ const timer2 = setTimeout(applyGroupedStyling, 500);
2189
+ const timer3 = setTimeout(applyGroupedStyling, 1000);
2190
+
2191
+ return () => {
2192
+ clearTimeout(timer1);
2193
+ clearTimeout(timer2);
2194
+ clearTimeout(timer3);
2195
+ };
2196
+ }
2197
+ }, [ajaxSetting?.groupBy, gridColumns]);
2198
+
1673
2199
  useEffect(() => {
1674
2200
  const delayedSetDSearch = debounce(() => {
1675
2201
  setDSearch(search);
@@ -2133,7 +2659,14 @@ const DataGrid = forwardRef(
2133
2659
 
2134
2660
  const value = `${addressValue} ${stateValue}`;
2135
2661
 
2136
- return <span>{value}</span>;
2662
+ return (
2663
+ <CellWithTooltip
2664
+ value={value}
2665
+ columnType="address"
2666
+ >
2667
+ <span>{value}</span>
2668
+ </CellWithTooltip>
2669
+ );
2137
2670
  },
2138
2671
  };
2139
2672
  case 'age':
@@ -2190,11 +2723,17 @@ const DataGrid = forwardRef(
2190
2723
  name: `${column.type}.${column.id}`,
2191
2724
  sortable: false,
2192
2725
  render: ({ data }) => {
2193
- if (data[column.id]) {
2194
- return 'Yes';
2195
- } else {
2196
- return 'No';
2197
- }
2726
+ const value = data[column.id]
2727
+ ? 'Yes'
2728
+ : 'No';
2729
+ return (
2730
+ <CellWithTooltip
2731
+ value={data[column.id]}
2732
+ columnType="boolean"
2733
+ >
2734
+ <span>{value}</span>
2735
+ </CellWithTooltip>
2736
+ );
2198
2737
  },
2199
2738
  };
2200
2739
  case 'coding':
@@ -2278,7 +2817,14 @@ const DataGrid = forwardRef(
2278
2817
  data[column.id]
2279
2818
  ).format('$0,0.00');
2280
2819
 
2281
- return <span>{data}</span>;
2820
+ return (
2821
+ <CellWithTooltip
2822
+ value={data[column.id]}
2823
+ columnType="currency"
2824
+ >
2825
+ <span>{data}</span>
2826
+ </CellWithTooltip>
2827
+ );
2282
2828
  } else {
2283
2829
  return null;
2284
2830
  }
@@ -2328,9 +2874,14 @@ const DataGrid = forwardRef(
2328
2874
  }
2329
2875
 
2330
2876
  return (
2331
- <span style={columnStyle}>
2332
- {value}
2333
- </span>
2877
+ <CellWithTooltip
2878
+ value={data[column.id]}
2879
+ columnType="date"
2880
+ >
2881
+ <span style={columnStyle}>
2882
+ {value}
2883
+ </span>
2884
+ </CellWithTooltip>
2334
2885
  );
2335
2886
  } else {
2336
2887
  return null;
@@ -2451,7 +3002,13 @@ const DataGrid = forwardRef(
2451
3002
  }}
2452
3003
  >
2453
3004
  <option value="">
2454
- Select an Option
3005
+ Select{' '}
3006
+ {/^[aeioAEIO]/.test(
3007
+ column.label
3008
+ )
3009
+ ? 'an'
3010
+ : 'a'}{' '}
3011
+ {column.label}
2455
3012
  </option>
2456
3013
  {dropdownData[column.id] &&
2457
3014
  dropdownData[column.id].length >
@@ -2505,7 +3062,7 @@ const DataGrid = forwardRef(
2505
3062
  data-tooltip-id={`system-tooltip`}
2506
3063
  data-tooltip-content={`Download File ${file.file_name}`}
2507
3064
  strokeWidth={2}
2508
- size={18}
3065
+ size={14}
2509
3066
  className={
2510
3067
  styles.tdaction
2511
3068
  }
@@ -2537,7 +3094,7 @@ const DataGrid = forwardRef(
2537
3094
  .file_name
2538
3095
  }`}
2539
3096
  strokeWidth={2}
2540
- size={18}
3097
+ size={14}
2541
3098
  className={
2542
3099
  styles.tdaction
2543
3100
  }
@@ -2622,7 +3179,14 @@ const DataGrid = forwardRef(
2622
3179
  column.jsonData
2623
3180
  ];
2624
3181
 
2625
- return <span>{data}</span>;
3182
+ return (
3183
+ <CellWithTooltip
3184
+ value={data}
3185
+ columnType="json"
3186
+ >
3187
+ <span>{data}</span>
3188
+ </CellWithTooltip>
3189
+ );
2626
3190
  } else {
2627
3191
  return null;
2628
3192
  }
@@ -2668,7 +3232,14 @@ const DataGrid = forwardRef(
2668
3232
  ];
2669
3233
  }
2670
3234
 
2671
- return <span>{newData}</span>;
3235
+ return (
3236
+ <CellWithTooltip
3237
+ value={newData}
3238
+ columnType="option"
3239
+ >
3240
+ <span>{newData}</span>
3241
+ </CellWithTooltip>
3242
+ );
2672
3243
  } else {
2673
3244
  return null;
2674
3245
  }
@@ -2682,9 +3253,14 @@ const DataGrid = forwardRef(
2682
3253
  render: ({ data }) => {
2683
3254
  if (data) {
2684
3255
  return (
2685
- <span>
2686
- {column.placeholder}
2687
- </span>
3256
+ <CellWithTooltip
3257
+ value={column.placeholder}
3258
+ columnType="placeholder"
3259
+ >
3260
+ <span>
3261
+ {column.placeholder}
3262
+ </span>
3263
+ </CellWithTooltip>
2688
3264
  );
2689
3265
  } else {
2690
3266
  return null;
@@ -2772,7 +3348,14 @@ const DataGrid = forwardRef(
2772
3348
  typeof value === 'string' ||
2773
3349
  typeof value === 'number'
2774
3350
  ) {
2775
- return <span>{value}</span>;
3351
+ return (
3352
+ <CellWithTooltip
3353
+ value={value}
3354
+ columnType="relation"
3355
+ >
3356
+ <span>{value}</span>
3357
+ </CellWithTooltip>
3358
+ );
2776
3359
  } else {
2777
3360
  return null;
2778
3361
  }
@@ -2923,7 +3506,14 @@ const DataGrid = forwardRef(
2923
3506
  result += values.join('<br />');
2924
3507
  }
2925
3508
 
2926
- return <span>{parse(result)}</span>;
3509
+ return (
3510
+ <CellWithTooltip
3511
+ value={result}
3512
+ columnType="relationArray"
3513
+ >
3514
+ <span>{parse(result)}</span>
3515
+ </CellWithTooltip>
3516
+ );
2927
3517
  // }
2928
3518
  } else {
2929
3519
  let result = '';
@@ -2954,7 +3544,14 @@ const DataGrid = forwardRef(
2954
3544
  : '';
2955
3545
  }
2956
3546
 
2957
- return <span>{parse(result)}</span>;
3547
+ return (
3548
+ <CellWithTooltip
3549
+ value={result}
3550
+ columnType="relationArray"
3551
+ >
3552
+ <span>{parse(result)}</span>
3553
+ </CellWithTooltip>
3554
+ );
2958
3555
  }
2959
3556
 
2960
3557
  return null;
@@ -2967,9 +3564,14 @@ const DataGrid = forwardRef(
2967
3564
  render: ({ data }) => {
2968
3565
  if (data && data[column.id]) {
2969
3566
  return (
2970
- <span>
2971
- {parse(data[column.id])}
2972
- </span>
3567
+ <CellWithTooltip
3568
+ value={data[column.id]}
3569
+ columnType="richtext"
3570
+ >
3571
+ <span>
3572
+ {parse(data[column.id])}
3573
+ </span>
3574
+ </CellWithTooltip>
2973
3575
  );
2974
3576
  } else {
2975
3577
  return null;
@@ -2990,7 +3592,14 @@ const DataGrid = forwardRef(
2990
3592
  }
2991
3593
  });
2992
3594
 
2993
- return <span>{stage}</span>;
3595
+ return (
3596
+ <CellWithTooltip
3597
+ value={stage}
3598
+ columnType="stage"
3599
+ >
3600
+ <span>{stage}</span>
3601
+ </CellWithTooltip>
3602
+ );
2994
3603
  },
2995
3604
  };
2996
3605
  case 'time':
@@ -3014,7 +3623,14 @@ const DataGrid = forwardRef(
3014
3623
  : 'hh:mm A'
3015
3624
  );
3016
3625
 
3017
- return <span>{data}</span>;
3626
+ return (
3627
+ <CellWithTooltip
3628
+ value={data[column.id]}
3629
+ columnType="time"
3630
+ >
3631
+ <span>{data}</span>
3632
+ </CellWithTooltip>
3633
+ );
3018
3634
  } else {
3019
3635
  return null;
3020
3636
  }
@@ -3041,7 +3657,14 @@ const DataGrid = forwardRef(
3041
3657
  : 'DD-MM-YYYY hh:mm A'
3042
3658
  );
3043
3659
 
3044
- return <span>{data}</span>;
3660
+ return (
3661
+ <CellWithTooltip
3662
+ value={data[column.id]}
3663
+ columnType="timer"
3664
+ >
3665
+ <span>{data}</span>
3666
+ </CellWithTooltip>
3667
+ );
3045
3668
  } else {
3046
3669
  let shouldRenderButton = false;
3047
3670
 
@@ -3166,7 +3789,7 @@ const DataGrid = forwardRef(
3166
3789
  data-tooltip-id="system-tooltip"
3167
3790
  data-tooltip-content={`${column.label} Timer`}
3168
3791
  strokeWidth={2}
3169
- size={18}
3792
+ size={14}
3170
3793
  style={{
3171
3794
  color: 'white',
3172
3795
  }}
@@ -3175,7 +3798,22 @@ const DataGrid = forwardRef(
3175
3798
  );
3176
3799
  }
3177
3800
 
3178
- return null;
3801
+ // Return a placeholder div to maintain consistent row height
3802
+ return (
3803
+ <div
3804
+ style={{
3805
+ width: '28px',
3806
+ height: '28px',
3807
+ display: 'flex',
3808
+ alignItems: 'center',
3809
+ justifyContent:
3810
+ 'center',
3811
+ margin: '0 auto',
3812
+ }}
3813
+ >
3814
+ {/* Empty placeholder to maintain layout */}
3815
+ </div>
3816
+ );
3179
3817
  }
3180
3818
  },
3181
3819
  };
@@ -3467,14 +4105,21 @@ const DataGrid = forwardRef(
3467
4105
  render: ({ data }) => {
3468
4106
  if (data && data[column.id]) {
3469
4107
  return (
3470
- <span>
3471
- <a
3472
- href={data[column.id]}
3473
- target="_blank"
3474
- >
3475
- Link
3476
- </a>
3477
- </span>
4108
+ <CellWithTooltip
4109
+ value={data[column.id]}
4110
+ columnType="url"
4111
+ >
4112
+ <span>
4113
+ <a
4114
+ href={
4115
+ data[column.id]
4116
+ }
4117
+ target="_blank"
4118
+ >
4119
+ Link
4120
+ </a>
4121
+ </span>
4122
+ </CellWithTooltip>
3478
4123
  );
3479
4124
  } else {
3480
4125
  return null;
@@ -3606,7 +4251,14 @@ const DataGrid = forwardRef(
3606
4251
  }
3607
4252
 
3608
4253
  return (
3609
- <span>{parse(content)}</span>
4254
+ <CellWithTooltip
4255
+ value={data[column.id]}
4256
+ columnType="text"
4257
+ >
4258
+ <span>
4259
+ {parse(content)}
4260
+ </span>
4261
+ </CellWithTooltip>
3610
4262
  );
3611
4263
  } else {
3612
4264
  return null;
@@ -3840,7 +4492,7 @@ const DataGrid = forwardRef(
3840
4492
  setLimit(ajaxSetting.take);
3841
4493
  } else {
3842
4494
  const calculateRows = (height) =>
3843
- Math.floor((height - 100) / 40);
4495
+ Math.floor((height - 100) / 32);
3844
4496
 
3845
4497
  const rowCount = calculateRows(minHeight);
3846
4498
  setLimit(rowCount);
@@ -3936,12 +4588,395 @@ const DataGrid = forwardRef(
3936
4588
  <ReactDataGrid
3937
4589
  key={`datagrid-${ajaxSetting?.url || ''}-${
3938
4590
  ajaxSetting?.groupBy ? 'grouped' : 'ungrouped'
3939
- }`}
4591
+ }-${groupRenderKey}`}
3940
4592
  {...tableSetting}
3941
4593
  columns={gridColumns}
3942
4594
  dataSource={dataSource}
3943
4595
  {...(ajaxSetting && ajaxSetting.groupBy
3944
- ? { defaultGroupBy: ajaxSetting.groupBy }
4596
+ ? {
4597
+ defaultGroupBy: ajaxSetting.groupBy,
4598
+ defaultCollapsed: true,
4599
+ collapsedGroups: collapsedGroups,
4600
+ onGroupToggle: (groupData) => {
4601
+ // Handle group expand/collapse events
4602
+ // Update collapsed groups state
4603
+ const groupValue =
4604
+ typeof groupData === 'string'
4605
+ ? groupData
4606
+ : groupData?.value ||
4607
+ groupData?.name ||
4608
+ '';
4609
+
4610
+ setCollapsedGroups((prev) => {
4611
+ const newState = { ...prev };
4612
+ if (newState[groupValue]) {
4613
+ delete newState[groupValue];
4614
+ } else {
4615
+ newState[groupValue] = true;
4616
+ }
4617
+ console.log(
4618
+ 'Updated collapsed groups:',
4619
+ newState
4620
+ );
4621
+ return newState;
4622
+ });
4623
+ },
4624
+ renderGroupTitle: (groupData) => {
4625
+ const groupValue =
4626
+ typeof groupData === 'string'
4627
+ ? groupData
4628
+ : groupData?.value ||
4629
+ groupData?.name ||
4630
+ '';
4631
+
4632
+ // Use ReactDataGrid's built-in toggle functionality
4633
+ const collapsed =
4634
+ collapsedGroups[groupValue] ||
4635
+ false;
4636
+ const toggleGroup = () => {
4637
+ // Update the collapsed state
4638
+ setCollapsedGroups((prev) => {
4639
+ const newState = { ...prev };
4640
+ if (newState[groupValue]) {
4641
+ delete newState[
4642
+ groupValue
4643
+ ];
4644
+ } else {
4645
+ newState[
4646
+ groupValue
4647
+ ] = true;
4648
+ }
4649
+ return newState;
4650
+ });
4651
+ };
4652
+ const groupCount =
4653
+ typeof groupData === 'object'
4654
+ ? groupData?.count || 0
4655
+ : 0;
4656
+ const hasCheckboxColumn =
4657
+ gridColumns.some(
4658
+ (col) =>
4659
+ col.id ===
4660
+ '__checkbox-column'
4661
+ );
4662
+
4663
+ // Handle group header click for selection
4664
+ const handleGroupHeaderClick = (
4665
+ e
4666
+ ) => {
4667
+ // Prevent rapid clicks
4668
+ if (
4669
+ groupClickTimeoutRef.current
4670
+ ) {
4671
+ console.log(
4672
+ '🖱️ Ignoring rapid click'
4673
+ );
4674
+ return;
4675
+ }
4676
+
4677
+ // Don't trigger if clicking on the expand button or checkbox
4678
+ if (
4679
+ e.target.closest(
4680
+ '.group-expand-btn'
4681
+ ) ||
4682
+ e.target.closest(
4683
+ 'input[type="checkbox"]'
4684
+ )
4685
+ ) {
4686
+ console.log(
4687
+ '🖱️ Ignoring click on expand button or checkbox'
4688
+ );
4689
+ return;
4690
+ }
4691
+
4692
+ // Set timeout to prevent rapid clicks
4693
+ groupClickTimeoutRef.current =
4694
+ setTimeout(() => {
4695
+ groupClickTimeoutRef.current =
4696
+ null;
4697
+ }, 300);
4698
+
4699
+ // Toggle selection of all items in this group
4700
+ const isCurrentlySelected =
4701
+ isGroupSelected(groupValue);
4702
+
4703
+ handleGroupSelectionChange(
4704
+ groupValue,
4705
+ !isCurrentlySelected
4706
+ );
4707
+ };
4708
+
4709
+ return (
4710
+ <div
4711
+ className="group-header-clean group-header-fixed-height"
4712
+ onClick={
4713
+ handleGroupHeaderClick
4714
+ }
4715
+ style={{
4716
+ display: 'flex',
4717
+ alignItems: 'center',
4718
+ justifyContent:
4719
+ 'space-between',
4720
+ width: '100%',
4721
+ height: '34px', // Compact height
4722
+ padding: '6px 12px', // Reduced padding for compact design
4723
+ background:
4724
+ 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
4725
+ borderLeft:
4726
+ '4px solid var(--primary-color, #3b82f6)',
4727
+ position: 'relative',
4728
+ overflow: 'hidden',
4729
+ boxShadow:
4730
+ '0 1px 3px rgba(0,0,0,0.1)',
4731
+ borderRadius:
4732
+ '0 4px 4px 0',
4733
+ cursor: 'pointer',
4734
+ boxSizing: 'border-box',
4735
+ lineHeight: '1.1', // Compact line height
4736
+ flexShrink: 0,
4737
+ transition:
4738
+ 'background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease',
4739
+ }}
4740
+ onMouseEnter={(e) => {
4741
+ e.currentTarget.style.background =
4742
+ 'linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%)';
4743
+ e.currentTarget.style.boxShadow =
4744
+ '0 2px 8px rgba(0,0,0,0.15)';
4745
+ e.currentTarget.style.transform =
4746
+ 'translateY(-1px)';
4747
+ }}
4748
+ onMouseLeave={(e) => {
4749
+ e.currentTarget.style.background =
4750
+ 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)';
4751
+ e.currentTarget.style.boxShadow =
4752
+ '0 1px 3px rgba(0,0,0,0.1)';
4753
+ e.currentTarget.style.transform =
4754
+ 'translateY(0)';
4755
+ }}
4756
+ >
4757
+ {/* Left side - Group info and expand/collapse */}
4758
+ <div
4759
+ style={{
4760
+ display: 'flex',
4761
+ alignItems: 'center',
4762
+ flex: 1,
4763
+ zIndex: 2,
4764
+ position: 'relative',
4765
+ }}
4766
+ >
4767
+ <button
4768
+ className="group-expand-btn"
4769
+ onClick={(e) => {
4770
+ e.stopPropagation();
4771
+ if (
4772
+ typeof toggleGroup ===
4773
+ 'function'
4774
+ ) {
4775
+ toggleGroup();
4776
+ } else {
4777
+ console.warn(
4778
+ 'toggleGroup is not a function:',
4779
+ toggleGroup
4780
+ );
4781
+ }
4782
+ }}
4783
+ style={{
4784
+ background:
4785
+ 'var(--primary-color, #3b82f6)',
4786
+ border: 'none',
4787
+ borderRadius:
4788
+ '3px',
4789
+ color: 'white',
4790
+ width: '20px', // More compact
4791
+ height: '20px', // More compact
4792
+ display: 'flex',
4793
+ alignItems:
4794
+ 'center',
4795
+ justifyContent:
4796
+ 'center',
4797
+ marginRight:
4798
+ '8px', // Reduced margin
4799
+ cursor: 'pointer',
4800
+ fontSize: '11px', // Smaller font
4801
+ fontWeight:
4802
+ 'bold',
4803
+ boxShadow:
4804
+ '0 1px 2px rgba(0,0,0,0.1)',
4805
+ transition:
4806
+ 'all 0.15s ease',
4807
+ }}
4808
+ onMouseEnter={(e) => {
4809
+ e.target.style.transform =
4810
+ 'scale(1.05)';
4811
+ e.target.style.background =
4812
+ 'var(--secondary-color, #2563eb)';
4813
+ }}
4814
+ onMouseLeave={(e) => {
4815
+ e.target.style.transform =
4816
+ 'scale(1)';
4817
+ e.target.style.background =
4818
+ 'var(--primary-color, #3b82f6)';
4819
+ }}
4820
+ >
4821
+ {collapsed
4822
+ ? '+'
4823
+ : '−'}
4824
+ </button>
4825
+ <div
4826
+ style={{
4827
+ display: 'flex',
4828
+ alignItems:
4829
+ 'center', // Changed to center for compact layout
4830
+ gap: '8px', // Add gap between elements
4831
+ }}
4832
+ >
4833
+ <strong
4834
+ style={{
4835
+ fontSize:
4836
+ '0.85rem', // Slightly smaller for compact design
4837
+ color: 'var(--secondary-color, #1f2937)',
4838
+ fontWeight:
4839
+ '600',
4840
+ textTransform:
4841
+ 'uppercase',
4842
+ letterSpacing:
4843
+ '0.025em',
4844
+ }}
4845
+ >
4846
+ {groupValue}
4847
+ </strong>
4848
+ {groupCount > 0 && (
4849
+ <span
4850
+ style={{
4851
+ color: '#6b7280',
4852
+ fontSize:
4853
+ '0.75rem', // Smaller for compact design
4854
+ fontWeight:
4855
+ '500',
4856
+ }}
4857
+ >
4858
+ ({groupCount}{' '}
4859
+ {groupCount ===
4860
+ 1
4861
+ ? 'item'
4862
+ : 'items'}
4863
+ )
4864
+ </span>
4865
+ )}
4866
+ {/* Click indicator */}
4867
+ <span
4868
+ style={{
4869
+ color: '#9ca3af',
4870
+ fontSize:
4871
+ '0.7rem',
4872
+ fontStyle:
4873
+ 'italic',
4874
+ opacity: 0.8,
4875
+ marginLeft:
4876
+ 'auto',
4877
+ userSelect:
4878
+ 'none',
4879
+ }}
4880
+ >
4881
+ Click to
4882
+ select/unselect
4883
+ all
4884
+ </span>
4885
+ </div>
4886
+ </div>
4887
+
4888
+ {/* Right side - Group selection */}
4889
+ {hasCheckboxColumn && (
4890
+ <div
4891
+ style={{
4892
+ display: 'flex',
4893
+ alignItems:
4894
+ 'center',
4895
+ gap: '4px', // Reduced gap for compact design
4896
+ fontSize:
4897
+ '0.75rem', // Smaller font for compact design
4898
+ background:
4899
+ 'rgba(255, 255, 255, 0.9)',
4900
+ padding:
4901
+ '3px 8px', // Reduced padding for compact design
4902
+ borderRadius:
4903
+ '12px', // Smaller border radius
4904
+ border: '1px solid rgba(59, 130, 246, 0.3)',
4905
+ zIndex: 2,
4906
+ position:
4907
+ 'relative',
4908
+ boxShadow:
4909
+ '0 1px 2px rgba(0,0,0,0.05)',
4910
+ }}
4911
+ >
4912
+ <input
4913
+ type="checkbox"
4914
+ onChange={(e) => {
4915
+ e.stopPropagation();
4916
+ handleGroupSelectionChange(
4917
+ groupValue,
4918
+ e.target
4919
+ .checked
4920
+ );
4921
+ }}
4922
+ checked={isGroupSelected(
4923
+ groupValue
4924
+ )}
4925
+ style={{
4926
+ width: '14px', // Smaller checkbox for compact design
4927
+ height: '14px',
4928
+ cursor: 'pointer',
4929
+ accentColor:
4930
+ 'var(--primary-color, #3b82f6)',
4931
+ }}
4932
+ />
4933
+ <span
4934
+ style={{
4935
+ cursor: 'pointer',
4936
+ userSelect:
4937
+ 'none',
4938
+ color: 'var(--secondary-color, #1f2937)',
4939
+ fontWeight:
4940
+ '500',
4941
+ fontSize:
4942
+ '0.7rem', // Smaller font for compact design
4943
+ }}
4944
+ onClick={(e) => {
4945
+ e.stopPropagation();
4946
+ const checkbox =
4947
+ e.target
4948
+ .previousElementSibling;
4949
+ checkbox.checked =
4950
+ !checkbox.checked;
4951
+ handleGroupSelectionChange(
4952
+ groupValue,
4953
+ checkbox.checked
4954
+ );
4955
+ }}
4956
+ >
4957
+ Select All
4958
+ </span>
4959
+ </div>
4960
+ )}
4961
+
4962
+ {/* Background pattern */}
4963
+ <div
4964
+ style={{
4965
+ position: 'absolute',
4966
+ top: 0,
4967
+ left: 0,
4968
+ right: 0,
4969
+ bottom: 0,
4970
+ background:
4971
+ 'repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(59, 130, 246, 0.03) 2px, rgba(59, 130, 246, 0.03) 4px)',
4972
+ zIndex: 1,
4973
+ pointerEvents: 'none',
4974
+ }}
4975
+ />
4976
+ </div>
4977
+ );
4978
+ },
4979
+ }
3945
4980
  : {})}
3946
4981
  filterValue={filterValue}
3947
4982
  limit={limit}
@@ -3952,7 +4987,7 @@ const DataGrid = forwardRef(
3952
4987
  }
3953
4988
  headerProps={headerProps}
3954
4989
  idProperty="id"
3955
- minRowHeight={40}
4990
+ minRowHeight={32}
3956
4991
  onFilterValueChange={(fv) => {
3957
4992
  handleFilterChange(fv, filterValue);
3958
4993
  }}
@@ -3986,6 +5021,11 @@ const DataGrid = forwardRef(
3986
5021
  borderRadius: 'var(--br, 4px)',
3987
5022
  overflow: 'hidden',
3988
5023
  }}
5024
+ className={
5025
+ ajaxSetting?.groupBy
5026
+ ? 'InovuaReactDataGrid--grouped datagrid-with-grouping datagrid-fixed-group-headers'
5027
+ : 'datagrid-without-grouping'
5028
+ }
3989
5029
  checkboxColumnProps={{
3990
5030
  width: 60,
3991
5031
  minWidth: 60,
@@ -3994,7 +5034,88 @@ const DataGrid = forwardRef(
3994
5034
  sortable: false,
3995
5035
  headerAlign: 'center',
3996
5036
  textAlign: 'center',
5037
+ renderHeader: () => {
5038
+ // Custom header checkbox that works with grouped data
5039
+ const allRowsSelected =
5040
+ dataRef.current &&
5041
+ dataRef.current.length > 0 &&
5042
+ dataRef.current.every(
5043
+ (row) =>
5044
+ row &&
5045
+ row.id !== undefined &&
5046
+ row.id !== null &&
5047
+ selected[row.id]
5048
+ );
5049
+ const someRowsSelected =
5050
+ dataRef.current &&
5051
+ dataRef.current.some(
5052
+ (row) =>
5053
+ row &&
5054
+ row.id !== undefined &&
5055
+ row.id !== null &&
5056
+ selected[row.id]
5057
+ );
5058
+
5059
+ return (
5060
+ <input
5061
+ type="checkbox"
5062
+ checked={allRowsSelected}
5063
+ ref={(checkbox) => {
5064
+ if (checkbox) {
5065
+ checkbox.indeterminate =
5066
+ someRowsSelected &&
5067
+ !allRowsSelected;
5068
+ }
5069
+ }}
5070
+ onChange={(e) => {
5071
+ if (e.target.checked) {
5072
+ // Select all rows
5073
+ const s = {};
5074
+ if (
5075
+ dataRef.current &&
5076
+ Array.isArray(
5077
+ dataRef.current
5078
+ )
5079
+ ) {
5080
+ dataRef.current.forEach(
5081
+ (obj) => {
5082
+ if (
5083
+ obj &&
5084
+ obj.id !==
5085
+ undefined &&
5086
+ obj.id !==
5087
+ null
5088
+ ) {
5089
+ s[obj.id] =
5090
+ obj;
5091
+ }
5092
+ }
5093
+ );
5094
+ }
5095
+ setSelected(s);
5096
+ if (setRowsSelected) {
5097
+ setRowsSelected(s);
5098
+ }
5099
+ } else {
5100
+ // Deselect all rows
5101
+ setSelected({});
5102
+ if (setRowsSelected) {
5103
+ setRowsSelected({});
5104
+ }
5105
+ }
5106
+ }}
5107
+ style={{
5108
+ width: '20px',
5109
+ height: '20px',
5110
+ cursor: 'pointer',
5111
+ accentColor:
5112
+ 'var(--primary-color, #3b82f6)',
5113
+ }}
5114
+ />
5115
+ );
5116
+ },
3997
5117
  }}
5118
+ checkboxOnlyRowSelect={true}
3998
5119
  />
3999
5120
  ) : null}
4000
5121
  </div>