@visns-studio/visns-components 5.8.19 → 5.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -86,7 +86,7 @@
86
86
  "react-dom": "^17.0.0 || ^18.0.0"
87
87
  },
88
88
  "name": "@visns-studio/visns-components",
89
- "version": "5.8.19",
89
+ "version": "5.8.20",
90
90
  "description": "Various packages to assist in the development of our Custom Applications.",
91
91
  "main": "src/index.js",
92
92
  "files": [
@@ -1,6 +1,24 @@
1
1
  import '../styles/global.css';
2
2
  import './styles/global-datagrid.css';
3
3
 
4
+ /**
5
+ * DataGrid Component
6
+ *
7
+ * This component provides a data grid with modal form editing capabilities.
8
+ *
9
+ * Modal Vertical Alignment:
10
+ * To position the modal toward the top of the viewport instead of center, add the `verticalAlign: "top"` property to the form object.
11
+ * Example:
12
+ * {
13
+ * form: {
14
+ * verticalAlign: "top",
15
+ * primaryKey: "id",
16
+ * url: "/ajax/jobs",
17
+ * // other form properties
18
+ * }
19
+ * }
20
+ */
21
+
4
22
  import React, {
5
23
  forwardRef,
6
24
  useCallback,
@@ -525,47 +543,33 @@ const DataGrid = forwardRef(
525
543
 
526
544
  const handleSelectionChange = useCallback(
527
545
  (param) => {
528
- // If this is a new selection (not a deselection)
529
- if (
530
- param.selected === true ||
546
+ // Handle the selection change based on the parameter type
547
+ if (param.selected === true) {
548
+ // Select all rows - this happens when header checkbox is clicked to select all
549
+ const s = {};
550
+ param.data.forEach((obj) => {
551
+ s[obj.id] = obj;
552
+ });
553
+ setSelected(s);
554
+ // Update parent component's rowsSelected state
555
+ if (setRowsSelected) {
556
+ setRowsSelected(s);
557
+ }
558
+ } else if (
559
+ param.selected === false ||
531
560
  (typeof param.selected === 'object' &&
532
- Object.keys(param.selected).length > 0)
561
+ Object.keys(param.selected).length === 0)
533
562
  ) {
534
- // Process the selection directly without confirmation
535
- if (param.selected === true) {
536
- const s = {};
537
- param.data.forEach((obj) => {
538
- s[obj.id] = obj;
539
- });
540
- setSelected(s);
541
- // Update parent component's rowsSelected state
542
- if (setRowsSelected) {
543
- setRowsSelected(s);
544
- }
545
- } else if (typeof param.selected === 'object') {
546
- const selectedKey = Object.keys(param.selected)[0];
547
- const isSelectedAlready =
548
- selected.hasOwnProperty(selectedKey);
549
-
550
- let newSelected;
551
- if (isSelectedAlready) {
552
- newSelected = {};
553
- setSelected(newSelected);
554
- } else {
555
- newSelected = {
556
- ...selected,
557
- [selectedKey]: param.selected[selectedKey],
558
- };
559
- setSelected(newSelected);
560
- }
561
-
562
- // Update parent component's rowsSelected state
563
- if (setRowsSelected) {
564
- setRowsSelected(newSelected);
565
- }
563
+ // Deselect all rows - this happens when header checkbox is clicked to deselect all
564
+ setSelected({});
565
+ // Update parent component's rowsSelected state
566
+ if (setRowsSelected) {
567
+ setRowsSelected({});
566
568
  }
567
- } else {
568
- // Handle deselection directly without confirmation
569
+ } else if (typeof param.selected === 'object') {
570
+ // Individual row selection/deselection - ReactDataGrid sends the complete new selection state
571
+ // 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
569
573
  setSelected(param.selected);
570
574
  // Update parent component's rowsSelected state
571
575
  if (setRowsSelected) {
@@ -574,7 +578,7 @@ const DataGrid = forwardRef(
574
578
  }
575
579
  },
576
580
  [selected, setRowsSelected]
577
- ); // Including selected and setRowsSelected in the dependency array
581
+ );
578
582
 
579
583
  const findUpdatedFilter = (filters, currentValues) => {
580
584
  for (const filter of filters) {
@@ -1141,35 +1145,85 @@ const DataGrid = forwardRef(
1141
1145
  }
1142
1146
  }, []);
1143
1147
 
1144
- const onRenderRow = useCallback((rowProps) => {
1145
- // save the original handlers to be called later
1146
- const { onClick } = rowProps;
1148
+ const onRenderRow = useCallback(
1149
+ (rowProps) => {
1150
+ // save the original handlers to be called later
1151
+ const { onClick } = rowProps;
1147
1152
 
1148
- rowProps.onClick = (event) => {
1149
- onRowClick(rowProps, event);
1153
+ rowProps.onClick = (event) => {
1154
+ const cellElement = event.target.closest(
1155
+ '.InovuaReactDataGrid__cell'
1156
+ );
1150
1157
 
1151
- const cellElement = event.target.closest(
1152
- '.InovuaReactDataGrid__cell'
1153
- );
1158
+ // Add null check to prevent errors when cellElement is null
1159
+ if (!cellElement) {
1160
+ return; // Exit early if no cell element was found
1161
+ }
1154
1162
 
1155
- // Add null check to prevent errors when cellElement is null
1156
- if (!cellElement) {
1157
- return; // Exit early if no cell element was found
1158
- }
1163
+ const columnIndex = Array.from(
1164
+ cellElement.parentNode.children
1165
+ ).indexOf(cellElement);
1159
1166
 
1160
- const columnIndex = Array.from(
1161
- cellElement.parentNode.children
1162
- ).indexOf(cellElement);
1167
+ const column = rowProps.columns[columnIndex];
1163
1168
 
1164
- const column = rowProps.columns[columnIndex];
1169
+ if (column.id === '__checkbox-column') {
1170
+ // For checkbox column clicks, we need to handle them specially
1171
+ // to ensure consistent behavior whether clicking checkbox or cell area
1172
+
1173
+ // Check if clicking directly on the checkbox input
1174
+ const isCheckboxInput = event.target.matches(
1175
+ 'input[type="checkbox"]'
1176
+ );
1177
+
1178
+ if (!isCheckboxInput) {
1179
+ // If clicking on empty space in checkbox cell, simulate checkbox click
1180
+ // by manually toggling the selection state
1181
+ event.preventDefault();
1182
+ event.stopPropagation();
1183
+
1184
+ const rowId =
1185
+ rowProps.data[rowProps.idProperty || 'id'];
1186
+ const isCurrentlySelected =
1187
+ selected.hasOwnProperty(rowId);
1188
+
1189
+ let newSelected;
1190
+ if (isCurrentlySelected) {
1191
+ // Remove from selection
1192
+ newSelected = { ...selected };
1193
+ delete newSelected[rowId];
1194
+ } else {
1195
+ // Add to selection
1196
+ newSelected = {
1197
+ ...selected,
1198
+ [rowId]: rowProps.data,
1199
+ };
1200
+ }
1201
+
1202
+ setSelected(newSelected);
1203
+ if (setRowsSelected) {
1204
+ setRowsSelected(newSelected);
1205
+ }
1206
+
1207
+ return; // Don't call the original onClick
1208
+ }
1165
1209
 
1166
- if (column.id === '__checkbox-column') {
1167
- if (onClick) {
1168
- onClick(event);
1210
+ // If clicking directly on checkbox input, let ReactDataGrid handle it
1211
+ if (onClick) {
1212
+ onClick(event);
1213
+ }
1214
+ } else {
1215
+ // For non-checkbox columns, handle normal row clicks
1216
+ onRowClick(rowProps, event);
1217
+
1218
+ // Call the original onClick handler
1219
+ if (onClick) {
1220
+ onClick(event);
1221
+ }
1169
1222
  }
1170
- }
1171
- };
1172
- }, []);
1223
+ };
1224
+ },
1225
+ [selected, setRowsSelected]
1226
+ );
1173
1227
 
1174
1228
  const exportTrigger = async () => {
1175
1229
  try {
@@ -1403,14 +1457,29 @@ const DataGrid = forwardRef(
1403
1457
  );
1404
1458
  };
1405
1459
 
1406
- const renderAutoRefreshControls = () => {
1460
+ // Helper function to determine if auto refresh should be relocated
1461
+ const shouldRelocateAutoRefresh = () => {
1462
+ const isSearchDisabled = form.searchDisable === true;
1463
+ const isCreateDisabled = form.createDisable === true;
1464
+ const hasAutoRefresh =
1465
+ ajaxSetting && ajaxSetting.autoRefresh !== undefined;
1466
+
1467
+ // Relocate only if auto refresh is the sole control (no search, no create button)
1468
+ return hasAutoRefresh && isSearchDisabled && isCreateDisabled;
1469
+ };
1470
+
1471
+ const renderAutoRefreshControls = (isRelocated = false) => {
1407
1472
  // Only show auto-refresh if the setting is present in ajaxSetting
1408
1473
  if (!ajaxSetting || ajaxSetting.autoRefresh === undefined) {
1409
1474
  return null;
1410
1475
  }
1411
1476
 
1477
+ const containerClass = isRelocated
1478
+ ? styles.autoRefreshContainerRelocated
1479
+ : styles.autoRefreshContainer;
1480
+
1412
1481
  return (
1413
- <div className={styles.autoRefreshContainer}>
1482
+ <div className={containerClass}>
1414
1483
  <label className={styles.autoRefreshLabel}>
1415
1484
  <input
1416
1485
  type="checkbox"
@@ -3846,14 +3915,20 @@ const DataGrid = forwardRef(
3846
3915
  }}
3847
3916
  >
3848
3917
  {/* Top container with search, action buttons, and auto-refresh */}
3849
- <div className={styles.dataGridTopContainer}>
3850
- <div className={styles.dataGridTopLeftContainer}>
3851
- {renderSearch()}
3852
- </div>
3853
- <div className={styles.dataGridTopRightContainer}>
3854
- {renderAutoRefreshControls()}
3918
+ {/* Only show top container if there are controls to display */}
3919
+ {(renderSearch() ||
3920
+ (!shouldRelocateAutoRefresh() &&
3921
+ renderAutoRefreshControls())) && (
3922
+ <div className={styles.dataGridTopContainer}>
3923
+ <div className={styles.dataGridTopLeftContainer}>
3924
+ {renderSearch()}
3925
+ </div>
3926
+ <div className={styles.dataGridTopRightContainer}>
3927
+ {!shouldRelocateAutoRefresh() &&
3928
+ renderAutoRefreshControls()}
3929
+ </div>
3855
3930
  </div>
3856
- </div>
3931
+ )}
3857
3932
 
3858
3933
  {/* DataGrid container with its own border */}
3859
3934
  <div className={styles.dataGridContainer}>
@@ -3911,9 +3986,26 @@ const DataGrid = forwardRef(
3911
3986
  borderRadius: 'var(--br, 4px)',
3912
3987
  overflow: 'hidden',
3913
3988
  }}
3989
+ checkboxColumnProps={{
3990
+ width: 60,
3991
+ minWidth: 60,
3992
+ maxWidth: 60,
3993
+ resizable: false,
3994
+ sortable: false,
3995
+ headerAlign: 'center',
3996
+ textAlign: 'center',
3997
+ }}
3914
3998
  />
3915
3999
  ) : null}
3916
4000
  </div>
4001
+
4002
+ {/* Relocated auto refresh control when it's the only control */}
4003
+ {shouldRelocateAutoRefresh() && (
4004
+ <div className={styles.dataGridBottomContainer}>
4005
+ {renderAutoRefreshControls(true)}
4006
+ </div>
4007
+ )}
4008
+
3917
4009
  {audioSource && <audio src={audioSource} ref={audioRef} />}
3918
4010
  <Popup
3919
4011
  open={modalShow}
@@ -3923,25 +4015,44 @@ const DataGrid = forwardRef(
3923
4015
  width: windowWidth < 768 ? '95vw' : '80vw',
3924
4016
  minWidth: windowWidth < 768 ? '95vw' : '600px',
3925
4017
  maxWidth: '1200px',
4018
+ ...(formData.verticalAlign === 'top' && {
4019
+ position: 'fixed',
4020
+ top: '5vh',
4021
+ left: '50%',
4022
+ transform: 'translateX(-50%)',
4023
+ margin: '0',
4024
+ }),
3926
4025
  }}
3927
4026
  nested
3928
4027
  >
3929
- <Form
3930
- ajaxSetting={ajaxSetting}
3931
- api={api}
3932
- closeModal={modalClose}
3933
- columnId={formId}
3934
- fetchTable={handleReload}
3935
- formSettings={formData}
3936
- formType={formType}
3937
- gridRef={gridRef}
3938
- mapbox={mapbox}
3939
- modalOpen={modalOpen}
3940
- paramValue={paramValue}
3941
- style={style}
3942
- updateForm={setFormData}
3943
- userProfile={userProfile}
3944
- />
4028
+ <div
4029
+ className={`${styles.modalwrap} ${
4030
+ styles['top--modal']
4031
+ } ${styles.modalWide} ${
4032
+ formData.verticalAlign === 'top'
4033
+ ? 'modal-top-aligned'
4034
+ : ''
4035
+ }`}
4036
+ >
4037
+ <div className={styles.modal}>
4038
+ <Form
4039
+ ajaxSetting={ajaxSetting}
4040
+ api={api}
4041
+ closeModal={modalClose}
4042
+ columnId={formId}
4043
+ fetchTable={handleReload}
4044
+ formSettings={formData}
4045
+ formType={formType}
4046
+ gridRef={gridRef}
4047
+ mapbox={mapbox}
4048
+ modalOpen={modalOpen}
4049
+ paramValue={paramValue}
4050
+ style={style}
4051
+ updateForm={setFormData}
4052
+ userProfile={userProfile}
4053
+ />
4054
+ </div>
4055
+ </div>
3945
4056
  </Popup>
3946
4057
  <Popup
3947
4058
  open={modalGalleryShow}
@@ -17,6 +17,20 @@ import '../../styles/global.css';
17
17
  * // other form properties
18
18
  * }
19
19
  * }
20
+ *
21
+ * Modal Vertical Alignment:
22
+ * To position the modal toward the top of the viewport instead of center, add the `verticalAlign: "top"` property to the form object.
23
+ * Example:
24
+ * {
25
+ * id: 'overview',
26
+ * type: 'overview',
27
+ * form: {
28
+ * verticalAlign: "top",
29
+ * primaryKey: "id",
30
+ * url: "/ajax/jobs",
31
+ * // other form properties
32
+ * }
33
+ * }
20
34
  */
21
35
 
22
36
  import React, { useEffect, useRef, useState } from 'react';
@@ -2753,9 +2767,25 @@ function GenericDetail({
2753
2767
  open={modalShow}
2754
2768
  onClose={modalClose}
2755
2769
  closeOnDocumentClick={false}
2756
- contentStyle={{ width: '80vw', maxWidth: '1200px' }}
2770
+ contentStyle={{
2771
+ width: '80vw',
2772
+ maxWidth: '1200px',
2773
+ ...(formData.verticalAlign === 'top' && {
2774
+ position: 'fixed',
2775
+ top: '5vh',
2776
+ left: '50%',
2777
+ transform: 'translateX(-50%)',
2778
+ margin: '0',
2779
+ }),
2780
+ }}
2757
2781
  >
2758
- <div className="modalwrap top--modal modalWide">
2782
+ <div
2783
+ className={`modalwrap top--modal modalWide ${
2784
+ formData.verticalAlign === 'top'
2785
+ ? 'modal-top-aligned'
2786
+ : ''
2787
+ }`}
2788
+ >
2759
2789
  <div className="modal">
2760
2790
  <Form
2761
2791
  closeModal={modalClose}
@@ -594,6 +594,19 @@
594
594
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
595
595
  }
596
596
 
597
+ /* Relocated auto-refresh controls (when placed at bottom) */
598
+ .autoRefreshContainerRelocated {
599
+ display: inline-flex;
600
+ align-items: center;
601
+ padding: 0 15px;
602
+ height: 38px;
603
+ background-color: #f9fafb;
604
+ border: 1px solid #d1d5db;
605
+ border-radius: var(--br, 4px);
606
+ margin: 0;
607
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
608
+ }
609
+
597
610
  .autoRefreshLabel {
598
611
  display: flex;
599
612
  align-items: center;
@@ -720,6 +733,15 @@
720
733
  }
721
734
  }
722
735
 
736
+ /* Bottom container for relocated controls */
737
+ .dataGridBottomContainer {
738
+ display: flex;
739
+ justify-content: flex-end;
740
+ align-items: center;
741
+ margin-top: 8px;
742
+ padding: 0 5px;
743
+ }
744
+
723
745
  /* Original filter input styles kept for backward compatibility */
724
746
  .filterInput {
725
747
  display: flex;
@@ -193,7 +193,38 @@
193
193
  }
194
194
 
195
195
  /* Media queries for responsive design */
196
- @media (max-width: 1024px) {
196
+
197
+ /* Tablet view (961px to 1024px) - Keep filter expanded in left container */
198
+ @media (max-width: 1024px) and (min-width: 961px) {
199
+ /* Ensure filter remains visible and properly styled for tablets */
200
+ .tableFilter {
201
+ display: block;
202
+ /* Maintain normal styling for tablet view */
203
+ }
204
+
205
+ .mobileToggle {
206
+ display: none; /* Hide hamburger menu on tablets */
207
+ }
208
+
209
+ /* Slightly adjust padding for better tablet experience */
210
+ .parentLink {
211
+ padding: 9px 16px;
212
+ font-size: 0.9rem;
213
+ }
214
+
215
+ .link {
216
+ padding: 9px 16px;
217
+ font-size: 0.85rem;
218
+ }
219
+
220
+ .childLink {
221
+ padding: 7px 16px 7px 26px;
222
+ font-size: 0.85rem;
223
+ }
224
+ }
225
+
226
+ /* Phone view (960px and below) - Use hamburger menu when navigation moves to top */
227
+ @media (max-width: 960px) {
197
228
  .mobileToggle {
198
229
  display: flex;
199
230
  }
@@ -32,3 +32,171 @@
32
32
  outline: none !important;
33
33
  box-shadow: 0 0 0 2px rgba(var(--primary-rgb, 59, 130, 246), 0.2) !important;
34
34
  }
35
+
36
+ /* Checkbox column improvements for touch devices */
37
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
38
+ padding: 8px 12px !important;
39
+ min-width: 60px !important;
40
+ width: 60px !important;
41
+ max-width: 60px !important;
42
+ position: relative !important;
43
+ cursor: pointer !important;
44
+ display: flex !important;
45
+ align-items: center !important;
46
+ justify-content: center !important;
47
+ /* Ensure the entire cell is clickable */
48
+ user-select: none !important;
49
+ -webkit-user-select: none !important;
50
+ -moz-user-select: none !important;
51
+ -ms-user-select: none !important;
52
+ }
53
+
54
+ /* Improve checkbox touch targets */
55
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
56
+ input[type='checkbox'] {
57
+ width: 20px !important;
58
+ height: 20px !important;
59
+ margin: 0 !important;
60
+ cursor: pointer !important;
61
+ position: relative !important;
62
+ z-index: 2 !important;
63
+ }
64
+
65
+ /* Create larger touch target area around checkbox */
66
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
67
+ .InovuaReactDataGrid__checkbox-wrapper,
68
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
69
+ .InovuaReactDataGrid__checkbox {
70
+ display: flex !important;
71
+ align-items: center !important;
72
+ justify-content: center !important;
73
+ min-height: 44px !important;
74
+ min-width: 44px !important;
75
+ padding: 12px !important;
76
+ margin: -12px !important;
77
+ cursor: pointer !important;
78
+ border-radius: 4px !important;
79
+ transition: background-color 0.2s ease !important;
80
+ }
81
+
82
+ /* Touch-friendly hover effect for the entire cell */
83
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']:hover {
84
+ background-color: rgba(var(--primary-rgb, 59, 130, 246), 0.05) !important;
85
+ }
86
+
87
+ /* Touch-friendly hover effect for checkbox wrapper */
88
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
89
+ .InovuaReactDataGrid__checkbox-wrapper:hover,
90
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
91
+ .InovuaReactDataGrid__checkbox:hover {
92
+ background-color: rgba(var(--primary-rgb, 59, 130, 246), 0.1) !important;
93
+ }
94
+
95
+ /* Header checkbox improvements */
96
+ .InovuaReactDataGrid__header-cell[data-column-id='__checkbox-column'] {
97
+ padding: 8px 12px !important;
98
+ min-width: 60px !important;
99
+ width: 60px !important;
100
+ max-width: 60px !important;
101
+ }
102
+
103
+ .InovuaReactDataGrid__header-cell[data-column-id='__checkbox-column']
104
+ input[type='checkbox'] {
105
+ width: 20px !important;
106
+ height: 20px !important;
107
+ margin: 0 !important;
108
+ cursor: pointer !important;
109
+ }
110
+
111
+ /* Prevent text selection in checkbox column */
112
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
113
+ user-select: none !important;
114
+ -webkit-user-select: none !important;
115
+ -moz-user-select: none !important;
116
+ -ms-user-select: none !important;
117
+ }
118
+
119
+ /* Additional checkbox styling for better UX */
120
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
121
+ .InovuaReactDataGrid__cell-content {
122
+ display: flex !important;
123
+ align-items: center !important;
124
+ justify-content: center !important;
125
+ width: 100% !important;
126
+ height: 100% !important;
127
+ }
128
+
129
+ /* Focus styles for accessibility */
130
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
131
+ input[type='checkbox']:focus {
132
+ outline: 2px solid rgba(var(--primary-rgb, 59, 130, 246), 0.5) !important;
133
+ outline-offset: 2px !important;
134
+ }
135
+
136
+ /* Ensure checkbox column doesn't interfere with row hover */
137
+ .InovuaReactDataGrid__row:hover
138
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
139
+ background-color: inherit !important;
140
+ }
141
+
142
+ /* Mobile-specific improvements */
143
+ @media (max-width: 768px) {
144
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
145
+ min-width: 70px !important;
146
+ width: 70px !important;
147
+ max-width: 70px !important;
148
+ padding: 12px 16px !important;
149
+ }
150
+
151
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
152
+ input[type='checkbox'] {
153
+ width: 24px !important;
154
+ height: 24px !important;
155
+ }
156
+
157
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
158
+ .InovuaReactDataGrid__checkbox-wrapper,
159
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
160
+ .InovuaReactDataGrid__checkbox {
161
+ min-height: 48px !important;
162
+ min-width: 48px !important;
163
+ padding: 16px !important;
164
+ margin: -16px !important;
165
+ }
166
+
167
+ .InovuaReactDataGrid__header-cell[data-column-id='__checkbox-column'] {
168
+ min-width: 70px !important;
169
+ width: 70px !important;
170
+ max-width: 70px !important;
171
+ padding: 12px 16px !important;
172
+ }
173
+
174
+ .InovuaReactDataGrid__header-cell[data-column-id='__checkbox-column']
175
+ input[type='checkbox'] {
176
+ width: 24px !important;
177
+ height: 24px !important;
178
+ }
179
+ }
180
+
181
+ /* Touch device specific improvements */
182
+ @media (hover: none) and (pointer: coarse) {
183
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column'] {
184
+ padding: 16px !important;
185
+ }
186
+
187
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
188
+ input[type='checkbox'] {
189
+ width: 28px !important;
190
+ height: 28px !important;
191
+ }
192
+
193
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
194
+ .InovuaReactDataGrid__checkbox-wrapper,
195
+ .InovuaReactDataGrid__cell[data-column-id='__checkbox-column']
196
+ .InovuaReactDataGrid__checkbox {
197
+ min-height: 52px !important;
198
+ min-width: 52px !important;
199
+ padding: 18px !important;
200
+ margin: -18px !important;
201
+ }
202
+ }
@@ -228,6 +228,35 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
228
228
  max-width: 1200px !important;
229
229
  }
230
230
 
231
+ /* Top-aligned modal styles */
232
+ .modal-top-aligned {
233
+ max-height: 90vh !important;
234
+ overflow-y: auto !important;
235
+ }
236
+
237
+ /* Ensure modal content is scrollable when top-aligned */
238
+ .modal-top-aligned .modal__content {
239
+ max-height: calc(90vh - 60px) !important;
240
+ overflow-y: auto !important;
241
+ }
242
+
243
+ /* Responsive adjustments for top-aligned modals */
244
+ @media (max-width: 768px) {
245
+ .popup-content[style*='top: 5vh'] {
246
+ top: 2vh !important;
247
+ width: 95vw !important;
248
+ max-width: none !important;
249
+ }
250
+
251
+ .modal-top-aligned {
252
+ max-height: 95vh !important;
253
+ }
254
+
255
+ .modal-top-aligned .modal__content {
256
+ max-height: calc(95vh - 60px) !important;
257
+ }
258
+ }
259
+
231
260
  .AutocompletePlace-results {
232
261
  z-index: 999;
233
262
  width: 500px;
@@ -0,0 +1,269 @@
1
+ import React, { useState } from 'react';
2
+ import DataGrid from '../components/crm/DataGrid';
3
+
4
+ /**
5
+ * Example demonstrating the improved checkbox column functionality in DataGrid
6
+ *
7
+ * Key improvements:
8
+ * 1. Larger touch targets for mobile devices
9
+ * 2. Fixed unintended "uncheck all" behavior when clicking empty space
10
+ * 3. Better visual feedback for clickable areas
11
+ * 4. Maintained existing selection functionality
12
+ */
13
+ const ImprovedCheckboxDataGrid = () => {
14
+ const [rowsSelected, setRowsSelected] = useState({});
15
+ const [tableData, setTableData] = useState([]);
16
+ const [total, setTotal] = useState(0);
17
+
18
+ // Example configuration with checkbox functionality enabled
19
+ const exampleConfig = {
20
+ ajaxSetting: {
21
+ url: '/api/example/data',
22
+ take: 25,
23
+ sortBy: 'id',
24
+ sort: 'desc',
25
+ where: [],
26
+ },
27
+ columns: [
28
+ {
29
+ id: 'id',
30
+ label: 'ID',
31
+ type: 'text',
32
+ minWidth: 80,
33
+ maxWidth: 80,
34
+ },
35
+ {
36
+ id: 'name',
37
+ label: 'Name',
38
+ type: 'text',
39
+ flex: 1,
40
+ },
41
+ {
42
+ id: 'email',
43
+ label: 'Email',
44
+ type: 'text',
45
+ flex: 1,
46
+ },
47
+ {
48
+ id: 'status',
49
+ label: 'Status',
50
+ type: 'text',
51
+ minWidth: 100,
52
+ maxWidth: 100,
53
+ },
54
+ ],
55
+ settings: [
56
+ {
57
+ id: 'edit',
58
+ label: 'Edit',
59
+ icon: 'edit',
60
+ url: '/edit/{id}',
61
+ },
62
+ {
63
+ id: 'delete',
64
+ label: 'Delete',
65
+ icon: 'delete',
66
+ url: '/delete/{id}',
67
+ method: 'DELETE',
68
+ },
69
+ ],
70
+ // Enable checkbox column functionality
71
+ tableSetting: {
72
+ checkboxColumn: true,
73
+ enableSelection: true,
74
+ },
75
+ functions: {
76
+ checkboxUpdate: {
77
+ url: '/api/bulk-update',
78
+ method: 'POST',
79
+ data: {},
80
+ message: {
81
+ warning: 'Please select at least one item to update.',
82
+ success: 'Items updated successfully!',
83
+ error: 'Failed to update items.',
84
+ },
85
+ label: 'Update Selected',
86
+ },
87
+ checkboxDelete: {
88
+ url: '/api/bulk-delete',
89
+ method: 'DELETE',
90
+ data: {},
91
+ message: {
92
+ warning: 'Please select at least one item to delete.',
93
+ success: 'Items deleted successfully!',
94
+ error: 'Failed to delete items.',
95
+ },
96
+ label: 'Delete Selected',
97
+ },
98
+ },
99
+ };
100
+
101
+ const handleBulkUpdate = () => {
102
+ const selectedCount = Object.keys(rowsSelected).length;
103
+ console.log(
104
+ `Bulk update triggered for ${selectedCount} items:`,
105
+ rowsSelected
106
+ );
107
+ // In a real implementation, this would call the API
108
+ };
109
+
110
+ const handleBulkDelete = () => {
111
+ const selectedCount = Object.keys(rowsSelected).length;
112
+ console.log(
113
+ `Bulk delete triggered for ${selectedCount} items:`,
114
+ rowsSelected
115
+ );
116
+ // In a real implementation, this would call the API
117
+ };
118
+
119
+ return (
120
+ <div style={{ padding: '20px' }}>
121
+ <h2>Improved Checkbox DataGrid Example</h2>
122
+
123
+ <div style={{ marginBottom: '20px' }}>
124
+ <h3>Key Improvements:</h3>
125
+ <ul>
126
+ <li>
127
+ <strong>Fixed checkbox cell clicks:</strong> Clicking
128
+ anywhere in a checkbox cell (including empty space) now
129
+ properly toggles ONLY that row
130
+ </li>
131
+ <li>
132
+ <strong>Eliminated "clear all" bug:</strong> No more
133
+ accidental clearing of all selections when clicking
134
+ checkbox cell areas
135
+ </li>
136
+ <li>
137
+ <strong>Consistent toggle behavior:</strong> Both
138
+ checkbox input clicks and cell area clicks now behave
139
+ identically
140
+ </li>
141
+ <li>
142
+ <strong>Multiple selection support:</strong> Can
143
+ properly select/deselect individual rows when multiple
144
+ rows are already selected
145
+ </li>
146
+ <li>
147
+ <strong>Header-only bulk actions:</strong>{' '}
148
+ Select/unselect all only works from header checkbox
149
+ </li>
150
+ <li>
151
+ <strong>Touch-friendly targets:</strong> Larger
152
+ clickable areas for mobile devices
153
+ </li>
154
+ </ul>
155
+ </div>
156
+
157
+ <div style={{ marginBottom: '20px' }}>
158
+ <p>
159
+ <strong>Selected items:</strong>{' '}
160
+ {Object.keys(rowsSelected).length}
161
+ </p>
162
+ {Object.keys(rowsSelected).length > 0 && (
163
+ <div style={{ display: 'flex', gap: '10px' }}>
164
+ <button
165
+ onClick={handleBulkUpdate}
166
+ style={{
167
+ padding: '8px 16px',
168
+ backgroundColor: '#3b82f6',
169
+ color: 'white',
170
+ border: 'none',
171
+ borderRadius: '4px',
172
+ cursor: 'pointer',
173
+ }}
174
+ >
175
+ Update Selected ({Object.keys(rowsSelected).length})
176
+ </button>
177
+ <button
178
+ onClick={handleBulkDelete}
179
+ style={{
180
+ padding: '8px 16px',
181
+ backgroundColor: '#ef4444',
182
+ color: 'white',
183
+ border: 'none',
184
+ borderRadius: '4px',
185
+ cursor: 'pointer',
186
+ }}
187
+ >
188
+ Delete Selected ({Object.keys(rowsSelected).length})
189
+ </button>
190
+ </div>
191
+ )}
192
+ </div>
193
+
194
+ <div
195
+ style={{
196
+ border: '1px solid #d1d5db',
197
+ borderRadius: '8px',
198
+ overflow: 'hidden',
199
+ }}
200
+ >
201
+ <DataGrid
202
+ {...exampleConfig}
203
+ setRowsSelected={setRowsSelected}
204
+ setTableData={setTableData}
205
+ setTotal={setTotal}
206
+ gridHeight={400}
207
+ />
208
+ </div>
209
+
210
+ <div
211
+ style={{
212
+ marginTop: '20px',
213
+ fontSize: '14px',
214
+ color: '#6b7280',
215
+ }}
216
+ >
217
+ <h4>Testing Instructions:</h4>
218
+ <ol>
219
+ <li>
220
+ <strong>Test checkbox input clicks:</strong> Click
221
+ directly on checkbox inputs - should toggle individual
222
+ rows
223
+ </li>
224
+ <li>
225
+ <strong>Test cell area clicks:</strong> Click on empty
226
+ space within checkbox cells - should behave identically
227
+ to checkbox clicks
228
+ </li>
229
+ <li>
230
+ <strong>Test multiple selections:</strong> Select 2-3
231
+ rows, then try to deselect individual rows - should work
232
+ properly
233
+ </li>
234
+ <li>
235
+ <strong>Test header checkbox:</strong> Click header
236
+ checkbox to select/unselect ALL rows
237
+ </li>
238
+ <li>
239
+ <strong>Test mixed interactions:</strong> Mix checkbox
240
+ input clicks and cell area clicks - should behave
241
+ consistently
242
+ </li>
243
+ <li>
244
+ <strong>Touch devices:</strong> Verify larger touch
245
+ targets work properly on mobile devices
246
+ </li>
247
+ </ol>
248
+
249
+ <h4>Expected Behavior:</h4>
250
+ <ul>
251
+ <li>
252
+ ✅ Clicking any part of a checkbox cell toggles that
253
+ specific row
254
+ </li>
255
+ <li>
256
+ ✅ Header checkbox controls select/unselect all
257
+ functionality
258
+ </li>
259
+ <li>✅ No more unexpected "uncheck all" behavior</li>
260
+ <li>
261
+ ✅ Consistent behavior across desktop and mobile devices
262
+ </li>
263
+ </ul>
264
+ </div>
265
+ </div>
266
+ );
267
+ };
268
+
269
+ export default ImprovedCheckboxDataGrid;