inboxlookup_screen 1.0.2

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.
Files changed (100) hide show
  1. package/README.md +70 -0
  2. package/dist/assets/09394b2afc53828fe342.otf +0 -0
  3. package/dist/assets/6537363fd5b802d5049c.woff2 +0 -0
  4. package/dist/assets/7287d1faf8f8e079aeab.ttf +0 -0
  5. package/dist/assets/9ac1f13f07c4503334c7.woff +0 -0
  6. package/dist/assets/ba20f6b82215cbe1b080.woff2 +0 -0
  7. package/dist/assets/c120fd44dc6e07624cd1.woff +0 -0
  8. package/dist/assets/icons/dxicons.ttf +0 -0
  9. package/dist/assets/icons/dxicons.woff +0 -0
  10. package/dist/assets/icons/dxicons.woff2 +0 -0
  11. package/dist/assets/icons/dxiconsfluent.ttf +0 -0
  12. package/dist/assets/icons/dxiconsfluent.woff +0 -0
  13. package/dist/assets/icons/dxiconsfluent.woff2 +0 -0
  14. package/dist/assets/icons/dxiconsmaterial.ttf +0 -0
  15. package/dist/assets/icons/dxiconsmaterial.woff +0 -0
  16. package/dist/assets/icons/dxiconsmaterial.woff2 +0 -0
  17. package/dist/index.html +1 -0
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.LICENSE.txt +14 -0
  20. package/package.json +55 -0
  21. package/src/App.css +38 -0
  22. package/src/App.js +33 -0
  23. package/src/App.test.js +8 -0
  24. package/src/Common/CustomLoadingPanel.js +43 -0
  25. package/src/assets/Desktop.png +0 -0
  26. package/src/assets/Excel.png +0 -0
  27. package/src/assets/Thumbs.db +0 -0
  28. package/src/assets/apps.png +0 -0
  29. package/src/assets/arc_flow.png +0 -0
  30. package/src/assets/circle_icons.png +0 -0
  31. package/src/assets/close.png +0 -0
  32. package/src/assets/connection.png +0 -0
  33. package/src/assets/csv.png +0 -0
  34. package/src/assets/data_preview.png +0 -0
  35. package/src/assets/database1.png +0 -0
  36. package/src/assets/date_refresh.png +0 -0
  37. package/src/assets/delete.png +0 -0
  38. package/src/assets/edit.png +0 -0
  39. package/src/assets/filter.png +0 -0
  40. package/src/assets/fonts/FontAwesome.otf +0 -0
  41. package/src/assets/fonts/Proxima-Nova-Bold.otf +0 -0
  42. package/src/assets/fonts/fa-brands-400.eot +0 -0
  43. package/src/assets/fonts/fa-brands-400.svg +1008 -0
  44. package/src/assets/fonts/fa-brands-400.ttf +0 -0
  45. package/src/assets/fonts/fa-brands-400.woff +0 -0
  46. package/src/assets/fonts/fa-brands-400.woff2 +0 -0
  47. package/src/assets/fonts/fa-regular-400.eot +0 -0
  48. package/src/assets/fonts/fa-regular-400.svg +366 -0
  49. package/src/assets/fonts/fa-regular-400.ttf +0 -0
  50. package/src/assets/fonts/fa-regular-400.woff +0 -0
  51. package/src/assets/fonts/fa-regular-400.woff2 +0 -0
  52. package/src/assets/fonts/fa-solid-900.eot +0 -0
  53. package/src/assets/fonts/fa-solid-900.svg +1518 -0
  54. package/src/assets/fonts/fa-solid-900.ttf +0 -0
  55. package/src/assets/fonts/fa-solid-900.woff +0 -0
  56. package/src/assets/fonts/fa-solid-900.woff2 +0 -0
  57. package/src/assets/fonts/fontawesome-webfont.eot +0 -0
  58. package/src/assets/fonts/fontawesome-webfont.svg +2671 -0
  59. package/src/assets/fonts/fontawesome-webfont.ttf +0 -0
  60. package/src/assets/fonts/fontawesome-webfont.woff +0 -0
  61. package/src/assets/fonts/fontawesome-webfont.woff2 +0 -0
  62. package/src/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  63. package/src/assets/fonts/glyphicons-halflings-regular.svg +288 -0
  64. package/src/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  65. package/src/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  66. package/src/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
  67. package/src/assets/fonts/proxima_nova_thin-webfont.woff +0 -0
  68. package/src/assets/fonts/proxima_nova_thin-webfont.woff2 +0 -0
  69. package/src/assets/fonts/proximanova-regular-webfont.woff +0 -0
  70. package/src/assets/fonts/proximanova-regular-webfont.woff2 +0 -0
  71. package/src/assets/logo_preview.png +0 -0
  72. package/src/assets/mail.png +0 -0
  73. package/src/assets/map_white 2.png +0 -0
  74. package/src/assets/map_white.png +0 -0
  75. package/src/assets/mapping.png +0 -0
  76. package/src/assets/notifications.png +0 -0
  77. package/src/assets/plus_add.png +0 -0
  78. package/src/assets/plusadd_white.png +0 -0
  79. package/src/assets/tick.png +0 -0
  80. package/src/assets/user.jpg +0 -0
  81. package/src/assets/user_setting.png +0 -0
  82. package/src/assets/wDesktop.png +0 -0
  83. package/src/assets/wconnction.png +0 -0
  84. package/src/assets/wdata_preview.png +0 -0
  85. package/src/assets/wdate_refresh.png +0 -0
  86. package/src/assets/wmapping.png +0 -0
  87. package/src/assets/wplus.png +0 -0
  88. package/src/components/Service/OrdersMasterDetail.css +427 -0
  89. package/src/components/Service/OrdersMasterDetail.js +1123 -0
  90. package/src/devextreme-license.js +1 -0
  91. package/src/index.css +13 -0
  92. package/src/index.js +261 -0
  93. package/src/logo.svg +1 -0
  94. package/src/reportWebVitals.js +13 -0
  95. package/src/routes/AppRoutes.js +18 -0
  96. package/src/setupTests.js +5 -0
  97. package/src/styles/UserDashBoard.css +1843 -0
  98. package/src/utils/ToastContext.js +34 -0
  99. package/src/utils/commonSchema.js +29 -0
  100. package/src/utils/helperFunctions.js +24 -0
@@ -0,0 +1,1123 @@
1
+ import List from "devextreme-react/list";
2
+ import "./OrdersMasterDetail.css";
3
+ import axios from "axios";
4
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
5
+ import DataGrid, {
6
+ Selection, Scrolling, Summary,
7
+ TotalItem, Grouping,
8
+ GroupPanel, Toolbar,
9
+ Item,
10
+ GroupItem, HeaderFilter, FilterRow,
11
+ } from 'devextreme-react/data-grid';
12
+ import Button from 'devextreme-react/button'; // <-- THIS import MUST be here
13
+ import CustomStore from 'devextreme/data/custom_store';
14
+ import Popup from 'devextreme-react/popup';
15
+ import 'devextreme/dist/css/dx.light.css';
16
+
17
+
18
+ const STORAGE_KEY = "ORDERS_MASTER_DETAIL_SEARCH_HISTORY";
19
+ const DEFAULT_SERVICE = {
20
+ CustomerId: 10,
21
+ projectId: 147,
22
+ id: 4, // any unique id for the list
23
+ status: "Active" // optional for left list rendering
24
+ };
25
+
26
+ const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9lNjZmMWU2Yi05Y2I5LTQyNTgtYTQyOS0xNzhjZWUzNTlhYTkvIiwiaWF0IjoxNzQ3NjI2MDM0LCJuYmYiOjE3NDc2MjYwMzQsImV4cCI6MTc0NzYyOTk1MCwiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsicDEiXSwiYWlvIjoiQVpRQWEvOFpBQUFBbGJ3R3dWeVZteXA3THJKZ0ZiRUFzTFhGQ3ZDRE9mOVBHRFZmVkhDTVIyTU9PVkUyaXNOdmtPVG9JWmdZSXg2Yi9tQkYyYnVTUmwrVW5jZVFkZ3E1RVlFSDNGWmFRYkxOMGFZK05YV2l2d2pLakdzcEJkQVREQTE3WDduQ0pmS0k5RUVtNVczTnFLNXY2QUdlZnpZRzBXMkp5d1pueVhwbVBSaEpZREpGbFdzaE9OWk1XSVNQaVdjN05Fb3gvc1IrIiwiYW1yIjpbInB3ZCIsIm1mYSJdLCJhcHBfZGlzcGxheW5hbWUiOiJmbG93LVNTTy1pbnRlZ3JhdGlvbiIsImFwcGlkIjoiOTAwN2UyYzQtM2VlOS00MWQ4LWJlNzEtM2FiZGQ0ZTc5MzIxIiwiYXBwaWRhY3IiOiIxIiwiZmFtaWx5X25hbWUiOiJNIiwiZ2l2ZW5fbmFtZSI6IkdhbnRoaW1hdGhpIiwiaWR0eXAiOiJ1c2VyIiwiaXBhZGRyIjoiMTU3LjUxLjEyNS43NSIsIm5hbWUiOiJHYW50aGltYXRoaSBNIiwib2lkIjoiNmJkNDZkNGEtNGM0ZS00Y2RlLWI5MDItNTJlN2FkYzE5NGRmIiwib25wcmVtX3NpZCI6IlMtMS01LTIxLTE3NzEzODE2ODEtMjQ0NzgzODAwMi0zNTE0ODIwOTgxLTE3MjA3OCIsInBsYXRmIjoiNSIsInB1aWQiOiIxMDAzMjAwMjJDMDlEQzgyIiwicmgiOiIxLkFWTUFheDV2NXJtY1dFS2tLUmVNN2pXYXFRTUFBQUFBQUFBQXdBQUFBQUFBQUFCVEFIeFRBQS4iLCJzY3AiOiJEaXJlY3RvcnkuUmVhZC5BbGwgVXNlci5SZWFkIFVzZXIuUmVhZC5BbGwgcHJvZmlsZSBvcGVuaWQgZW1haWwiLCJzaWQiOiIwMDRmMzk1OS0wYjE5LWZhMjUtNGUyNS0zNDI0MWU0NjljZTkiLCJzdWIiOiItV1gtSXQ5NGFDaUozN1l6UEszemdXYWxTYS1MMmJ4MDhsempxUzEyb1JRIiwidGVuYW50X3JlZ2lvbl9zY29wZSI6IkFTIiwidGlkIjoiZTY2ZjFlNmItOWNiOS00MjU4LWE0MjktMTc4Y2VlMzU5YWE5IiwidW5pcXVlX25hbWUiOiJnYW50aGltYXRoaS5tQGFjY2Vzc2hlYWx0aGNhcmUuY29tIiwidXBuIjoiZ2FudGhpbWF0aGkubUBhY2Nlc3NoZWFsdGhjYXJlLmNvbSIsInV0aSI6IjlfZDJSOGJjRFVlZERaeFdJaHdvQUEiLCJ2ZXIiOiIxLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiSVFudjZvNlNiS1FUbEpIT05mV1FXQWFxWW9XUHRnMmFoM19lX1VpbjlHa0JhMjl5WldGemIzVjBhQzFrYzIxeiIsInhtc19pZHJlbCI6IjEgMjIiLCJ4bXNfc3QiOnsic3ViIjoiQ2hwWms3bVpSX2N3UGdVUnV4ZndTb0tVQkVMNlJCaWZxMkJ5c1RhSTFyZyJ9LCJ4bXNfdGNkdCI6MTQwMTQ2MzYyNSwiVXNlclR5cGUiOjEsIlVzZXJJZCI6Nzc3NzIsIkVtcElkIjoiTTA5NDgwNTciLCJFbWFpbElkIjoiZ2FudGhpbWF0aGkubUBhY2Nlc3NoZWFsdGhjYXJlLmNvbSIsIk5hbWUiOiJHYW50aGltYXRoaSBNIiwiQXBwS2V5IjoiMjllMWEyYTAtOGU0My00NDNhLTk4MDQtMTM3MGIwN2I3ZWRkIiwiU2Vzc2lvbklkIjoiMGM5NzU3ZjktYTA1OC00MWE1LWE5M2YtOWY5MTZhMWU2ZmQxIiwiSWFtVVJMIjoiaHR0cHM6Ly91YXRfaWFtYXBpLmFjY2Vzc2hlYWx0aGNhcmUuY29tL2FwaSJ9.YPFSayPCfTBDaB2vEHhwR3TnvvstT5G3z3yIWYny_Sc'; // Replace with your token
27
+ const getFieldMetaAPI = (sheetId) =>
28
+ `http://localhost:5017/api/excel-sheets/${sheetId}/columns`;
29
+
30
+ let lastAppliedFilter = null;
31
+
32
+ const OrdersMasterDetail = ({ dataSource, keyExpr }) => {
33
+ const [orders, setOrders] = useState([]);
34
+ const [selectedOrder, setSelectedOrder] = useState(null);
35
+ const [selectedService, setSelectedService] = useState(DEFAULT_SERVICE);
36
+ const saveButtonRef = useRef(null);
37
+ const [rowDetailsVisible, setRowDetailsVisible] = useState(false);
38
+ const [selectedRow, setSelectedRow] = useState(null);
39
+ const [searchPopupVisible, setSearchPopupVisible] = useState(false);
40
+ const [searchText, setSearchText] = useState("");
41
+ const [category, setCategory] = useState(0);
42
+ const [status, setStatus] = useState(0);
43
+ const [filteredOrders, setFilteredOrders] = useState([]);
44
+ const [gridData, setGridData] = useState([]);
45
+ const searchTimeoutRef = useRef(null);
46
+ const [gridStore, setGridStore] = useState(null);
47
+
48
+ const gridRef = useRef(null);
49
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
50
+ const [selectAll, setSelectAll] = useState(false);
51
+ const [excludedKeys, setExcludedKeys] = useState([]);
52
+ const [columns, setColumns] = useState([]);
53
+ const [showCheckboxes, setShowCheckboxes] = useState(true);
54
+ const [finalCount, setFinalCount] = useState(0);
55
+ const [wasFullyDeselected, setWasFullyDeselected] = useState(false);
56
+ const [readOnlyFilters, setReadOnlyFilters] = useState(false);
57
+ const [gridInstance, setGridInstance] = useState(null);
58
+ const [autoExpandAll, setAutoExpandAll] = useState(true);
59
+ const [popupVisible, setPopupVisible] = useState(false);
60
+ const [actionValue, setActionValue] = useState(null);
61
+ const [assignPopupVisible, setAssignPopupVisible] = useState(false);
62
+ const [proceedEnabled, setProceedEnabled] = useState(false); // ✅ NEW: control button enabled state
63
+ const [popupSummary, setPopupSummary] = useState({
64
+ type: '',
65
+ count: 0,
66
+ ids: [],
67
+ filters: []
68
+ });
69
+
70
+ // ✅ Toast state
71
+ const [toastConfig, setToastConfig] = useState({
72
+ isVisible: false,
73
+ type: 'info',
74
+ message: '',
75
+ });
76
+
77
+ // RIGHT SIDE SEARCH
78
+ const [gridSearchText, setGridSearchText] = useState("");
79
+ const [searchHistory, setSearchHistory] = useState([]);
80
+ const searchHistoryRef = useRef([]);
81
+
82
+
83
+ useEffect(() => {
84
+ if (!selectedService) return; // do nothing if no service is selected
85
+
86
+ const { projectId } = selectedService;
87
+
88
+ axios.get(`http://localhost:5017/api/excel-sheets/latest-sheets/${projectId}`,)
89
+ .then(response => {
90
+ const data = response.data.map(item => ({
91
+ id: item.Id,
92
+ sheetName: item.SheetName,
93
+ fileId: item.FileId,
94
+ ProjectId: item.ProjectId,
95
+ status: "Active" // optional UI status
96
+ }));
97
+ setOrders(data);
98
+ setFilteredOrders(data); // ✅ show ALL by default
99
+ // setSelectedOrder(data.length ? data[0] : null);
100
+
101
+ })
102
+ .catch(error => {
103
+ console.error("Failed to load orders", error);
104
+ setOrders([]);
105
+ });
106
+
107
+ }, []);
108
+
109
+ useEffect(() => {
110
+ if (!orders) return;
111
+ const openLastViewedSheet = async () => {
112
+ try {
113
+ const res = await axios.get(
114
+ "http://localhost:5017/api/excel-sheets/last-viewed",
115
+ {
116
+ params: {
117
+ userId: 1,
118
+ projectId: 147 // ✅ FIXED (do NOT read from selectedService)
119
+ }
120
+ }
121
+ );
122
+
123
+ const lastViewedSheetId = res?.data;
124
+ if (!lastViewedSheetId) return;
125
+
126
+ const sheet = orders.find(o => o.id === lastViewedSheetId);
127
+
128
+ if (!sheet) return;
129
+ // ✅ EXACT SAME FLOW AS MANUAL CLICK
130
+ setSelectedOrder(sheet);
131
+ setSelectedService(sheet);
132
+
133
+ await loadColumns(sheet.id);
134
+
135
+ gridInstance.pageIndex(0);
136
+ gridInstance.clearSelection();
137
+ gridInstance.searchByText("");
138
+
139
+ setGridStore(createStore(sheet.id));
140
+ setSearchText("");
141
+
142
+ } catch (err) {
143
+ console.error("Auto open failed", err);
144
+ }
145
+ };
146
+
147
+ openLastViewedSheet();
148
+ }, [orders]);
149
+
150
+
151
+ const createStore = (sheetId) =>
152
+ new CustomStore({
153
+ key: "RowNumber",
154
+ async load(loadOptions) {
155
+ loadOptions.requireTotalCount = true;
156
+
157
+ const paramNames = [
158
+ "skip",
159
+ "take",
160
+ "requireTotalCount",
161
+ "requireGroupCount",
162
+ "sort",
163
+ "filter",
164
+ "searchValue",
165
+ "searchExpr",
166
+ "searchOperation",
167
+ "group",
168
+ "groupSummary",
169
+ "totalSummary"
170
+ ];
171
+
172
+ const queryString = paramNames
173
+ .filter(p => loadOptions[p] !== undefined && loadOptions[p] !== null)
174
+ .map(p => `${p}=${encodeURIComponent(JSON.stringify(loadOptions[p]))}`)
175
+ .join("&");
176
+
177
+ const requestUrl = `http://localhost:5017/api/excel-sheets/${sheetId}/data?${queryString}`;
178
+
179
+ const response = await fetch(requestUrl, {
180
+ method: "POST",
181
+ headers: {
182
+ Authorization: `Bearer ${token}`,
183
+ "Content-Type": "application/json"
184
+ },
185
+ body: JSON.stringify(loadOptions)
186
+ });
187
+
188
+ const result = await response.json();
189
+
190
+ return {
191
+ data: result?.data ?? [],
192
+ totalCount: result?.totalCount ?? 0,
193
+ groupCount: result?.groupCount ?? 0,
194
+ summary: result?.summary ?? []
195
+ };
196
+ }
197
+ });
198
+
199
+
200
+ const checkAvailability = useCallback(
201
+ (e) => {
202
+ const type = 'success';
203
+ const message = 'Filtering is disabled while selection mode is active. Please turn off selection mode to re-enable filters.';
204
+ setToastConfig({
205
+ ...toastConfig,
206
+ isVisible: true,
207
+ type,
208
+ message,
209
+ });
210
+ },
211
+ [toastConfig, setToastConfig],
212
+ );
213
+
214
+ const onHiding = useCallback(() => {
215
+ setToastConfig({
216
+ ...toastConfig,
217
+ isVisible: false,
218
+ });
219
+ }, [toastConfig, setToastConfig]);
220
+
221
+ const handleSearchChange = (value) => {
222
+ const safeValue = typeof value === "string" ? value : "";
223
+
224
+ setSearchText(safeValue);
225
+
226
+ if (!gridInstance) return;
227
+
228
+ if (searchTimeoutRef.current) {
229
+ clearTimeout(searchTimeoutRef.current);
230
+ }
231
+
232
+ searchTimeoutRef.current = setTimeout(() => {
233
+ const text = safeValue.trim();
234
+
235
+ if (text.length >= 3) {
236
+ gridInstance.searchByText(text);
237
+ } else if (text.length === 0) {
238
+ gridInstance.searchByText(""); // reset
239
+ }
240
+ }, 400);
241
+ };
242
+
243
+
244
+ const loadColumns = async (sheetId) => {
245
+ try {
246
+ const res = await fetch(getFieldMetaAPI(sheetId), {
247
+ headers: {
248
+ Authorization: `Bearer ${token}`,
249
+ },
250
+ });
251
+
252
+ const data = await res.json();
253
+
254
+ // Always keep FlowTaskId first
255
+ const flowTaskColumn = {
256
+ dataField: "RowNumber",
257
+ caption: "Sheet Row",
258
+ dataType: "number",
259
+ allowFiltering: true,
260
+ allowGrouping: true,
261
+ headerFilter: {
262
+ visible: true,
263
+ search: { enabled: true },
264
+ },
265
+ };
266
+
267
+ const metaColumns = (data ?? []).map(col => ({
268
+ dataField: col.FieldName,
269
+ caption: col.DisplayName,
270
+ dataType: col.FieldType,
271
+ allowFiltering: true,
272
+ allowReordering: true,
273
+ allowGrouping: true,
274
+ headerFilter: {
275
+ visible: true,
276
+ search: { enabled: true },
277
+ },
278
+ }));
279
+
280
+ setColumns([flowTaskColumn, ...metaColumns]);
281
+ } catch (err) {
282
+ console.error("Failed to load columns", err);
283
+ setColumns([]);
284
+ }
285
+ };
286
+
287
+
288
+ const onSelectionChanged = (e) => {
289
+ const visibleRowKeys = e.component.getVisibleRows().map(row => row.key);
290
+ const allVisibleSelected = visibleRowKeys.every(key => e.selectedRowKeys.includes(key));
291
+ const noneVisibleSelected = visibleRowKeys.every(key => !e.selectedRowKeys.includes(key));
292
+ const totalCount = e.component.totalCount();
293
+
294
+ if (!selectAll && allVisibleSelected) {
295
+ // ✅ First-time "Select All"
296
+ setSelectAll(true);
297
+ setWasFullyDeselected(false);
298
+ setExcludedKeys([]);
299
+ setSelectedRowKeys([]);
300
+ setFinalCount(totalCount);
301
+ }
302
+ else if (selectAll && allVisibleSelected) {
303
+ // ✅ Click "Select All" again (restore full selection)
304
+ const remainingExclusions = excludedKeys.filter(key => !visibleRowKeys.includes(key));
305
+ if (remainingExclusions.length !== excludedKeys.length) {
306
+ // User is undoing some previous deselections
307
+ setExcludedKeys([]);
308
+ setFinalCount(totalCount);
309
+ } else {
310
+ setExcludedKeys(remainingExclusions);
311
+ setFinalCount(totalCount - remainingExclusions.length);
312
+ }
313
+ setWasFullyDeselected(false);
314
+ setSelectedRowKeys([]);
315
+ }
316
+
317
+ else if (selectAll && !noneVisibleSelected) {
318
+ // ✅ Deselect few while in Select All mode
319
+ const newDeselections = visibleRowKeys.filter(key => !e.selectedRowKeys.includes(key));
320
+ const updatedExcluded = Array.from(new Set([...excludedKeys, ...newDeselections]));
321
+ setExcludedKeys(updatedExcluded);
322
+ setSelectedRowKeys([]);
323
+ setWasFullyDeselected(false);
324
+ setFinalCount(totalCount - updatedExcluded.length);
325
+ }
326
+
327
+
328
+ else if (selectAll && noneVisibleSelected) {
329
+ // ✅ "Deselect All"
330
+ e.component.clearSelection(); // ensures DevExtreme state resets
331
+ setSelectAll(false);
332
+ setWasFullyDeselected(true); // flag for Scenario 6
333
+ setExcludedKeys([]);
334
+ setSelectedRowKeys([]);
335
+ setFinalCount(0);
336
+ }
337
+
338
+ else if (!selectAll && wasFullyDeselected && e.selectedRowKeys.length > 0) {
339
+ // ✅ Scenario 6: manual selection after deselect all
340
+ const newKeys = e.selectedRowKeys.filter(key => visibleRowKeys.includes(key));
341
+ setWasFullyDeselected(false);
342
+ setSelectedRowKeys(newKeys);
343
+ setExcludedKeys([]);
344
+ setFinalCount(newKeys.length);
345
+ }
346
+
347
+ else {
348
+ // ✅ Manual few selection (fresh)
349
+ setSelectAll(false);
350
+ setWasFullyDeselected(false);
351
+ setExcludedKeys([]);
352
+ setSelectedRowKeys(e.selectedRowKeys);
353
+ setFinalCount(e.selectedRowKeys.length);
354
+ }
355
+ };
356
+
357
+ const updateHeaderCheckboxState = (gridInstance, selectAll, excludedKeys, selectedRowKeys) => {
358
+ if (!gridInstance) return;
359
+
360
+ const visibleRowKeys = gridInstance.getVisibleRows().map(row => row.key).filter(Boolean);
361
+
362
+ const selectedVisible = visibleRowKeys.filter(key =>
363
+ selectAll ? !excludedKeys.includes(key) : selectedRowKeys.includes(key)
364
+ );
365
+
366
+ const allVisibleSelected = selectedVisible.length === visibleRowKeys.length;
367
+ const noneVisibleSelected = selectedVisible.length === 0;
368
+
369
+ const isTrulyAllSelected = selectAll && excludedKeys.length === 0;
370
+ const isPartiallySelected = selectAll && excludedKeys.length > 0;
371
+ const isManualSomeSelected = !selectAll && selectedRowKeys.length > 0;
372
+
373
+ const headerCheckbox = gridInstance.element().querySelector('.dx-header-row .dx-checkbox');
374
+ if (!headerCheckbox) return;
375
+
376
+ headerCheckbox.classList.remove("dx-checkbox-checked", "dx-checkbox-indeterminate");
377
+
378
+ if (isTrulyAllSelected) {
379
+ headerCheckbox.classList.add("dx-checkbox-checked");
380
+ } else if (isPartiallySelected || (isManualSomeSelected && !allVisibleSelected)) {
381
+ headerCheckbox.classList.add("dx-checkbox-indeterminate");
382
+ } else if (isManualSomeSelected && allVisibleSelected) {
383
+ headerCheckbox.classList.add("dx-checkbox-checked");
384
+ }
385
+ };
386
+
387
+ useEffect(() => {
388
+ if (!gridInstance) return;
389
+
390
+ setTimeout(() => {
391
+ updateHeaderCheckboxState(gridInstance, selectAll, excludedKeys, selectedRowKeys);
392
+ }, 0);
393
+ }, [selectAll, excludedKeys, selectedRowKeys, gridInstance]);
394
+
395
+
396
+ const getFinalSelectedIds = async (extraPayload = {}) => {
397
+ const appliedFilters = Array.isArray(lastAppliedFilter) ? lastAppliedFilter : [];
398
+ const totalRows = gridInstance?.totalCount() || 0;
399
+
400
+ // Determine selectType and ids
401
+ let selectType = '';
402
+ let selectedIds = [];
403
+ let deselectedIds = [];
404
+ let selectionLabel = '';
405
+ let selectedCount = 0;
406
+
407
+ if (selectAll && excludedKeys.length === 0) {
408
+ selectType = 'ALL';
409
+ selectionLabel = 'All Rows Selected';
410
+ selectedCount = totalRows;
411
+ } else if (selectAll && excludedKeys.length > 0) {
412
+ selectType = 'EXCLUDE';
413
+ deselectedIds = excludedKeys;
414
+ selectionLabel = 'Deselected Rows';
415
+ selectedCount = excludedKeys.length;
416
+ } else {
417
+ selectType = 'INCLUDE';
418
+ selectedIds = selectedRowKeys;
419
+ selectionLabel = 'Selected Rows';
420
+ selectedCount = selectedRowKeys.length;
421
+ }
422
+
423
+ const payload = {
424
+ selectType,
425
+ selectedIds,
426
+ deselectedIds,
427
+ totalSelectedCount: selectedCount,
428
+ filters: appliedFilters.length > 0 ? appliedFilters : [],
429
+ ...extraPayload, // Inject workspaceId, userId, etc.
430
+ };
431
+
432
+ console.log("📦 Final Selection Payload:", payload);
433
+
434
+ setPopupSummary(payload);
435
+ setPopupVisible(true);
436
+ setActionValue(null);
437
+
438
+ // Reset selection state
439
+ setSelectAll(false);
440
+ setExcludedKeys([]);
441
+ setSelectedRowKeys([]);
442
+ setFinalCount(0);
443
+ getUnSelected();
444
+
445
+ return selectedIds;
446
+ };
447
+ const formatRawFilterToReadableText = (filter) => {
448
+ if (!filter) return '';
449
+
450
+ if (Array.isArray(filter[0])) {
451
+ // Composite condition like: [ [cond1], 'and', [cond2] ]
452
+ return formatRawFilterToReadableText(filter[0]) + ` ${filter[1].toUpperCase()} ` + formatRawFilterToReadableText(filter[2]);
453
+ }
454
+
455
+ if (typeof filter[0] === 'string') {
456
+ const [field, op, value] = filter;
457
+ return `• ${field} ${op} "${value}"`;
458
+ }
459
+
460
+ return '';
461
+ };
462
+
463
+
464
+ const onContentReady = (grid) => {
465
+ const visibleRowKeys = grid.getVisibleRows().map(row => row.key).filter(Boolean);
466
+
467
+ if (selectAll) {
468
+ const toSelect = visibleRowKeys.filter(key => !excludedKeys.includes(key));
469
+ grid.selectRows(toSelect, true);
470
+ } else {
471
+ grid.selectRows(selectedRowKeys, false);
472
+ }
473
+
474
+ // 🔁 Keep header checkbox patched
475
+ setTimeout(() => {
476
+ const headerCheckbox = grid.element().querySelector('.dx-header-row .dx-checkbox');
477
+ if (!headerCheckbox) return;
478
+
479
+ const selectedVisible = visibleRowKeys.filter(key =>
480
+ selectAll ? !excludedKeys.includes(key) : selectedRowKeys.includes(key)
481
+ );
482
+
483
+ const allVisibleSelected = selectedVisible.length === visibleRowKeys.length;
484
+ const noneVisibleSelected = selectedVisible.length === 0;
485
+
486
+ const isTrulyAllSelected = selectAll && excludedKeys.length === 0;
487
+ const isPartiallySelected = selectAll && excludedKeys.length > 0;
488
+ const isManualSomeSelected = !selectAll && selectedRowKeys.length > 0;
489
+
490
+ headerCheckbox.classList.remove("dx-checkbox-checked", "dx-checkbox-indeterminate");
491
+
492
+ if (isTrulyAllSelected) {
493
+ headerCheckbox.classList.add("dx-checkbox-checked");
494
+ } else if (isPartiallySelected) {
495
+ headerCheckbox.classList.add("dx-checkbox-indeterminate");
496
+ } else if (isManualSomeSelected) {
497
+ if (selectedVisible.length === 0) {
498
+ headerCheckbox.classList.add("dx-checkbox-indeterminate");
499
+ } else if (allVisibleSelected) {
500
+ headerCheckbox.classList.add("dx-checkbox-checked");
501
+ } else {
502
+ headerCheckbox.classList.add("dx-checkbox-indeterminate");
503
+ }
504
+ }
505
+ }, 0);
506
+ };
507
+
508
+ useEffect(() => {
509
+ if (!gridInstance || !selectAll || excludedKeys.length === 0) return;
510
+
511
+ const gridElement = gridInstance.element();
512
+
513
+ const observer = new MutationObserver(() => {
514
+ const checkboxWrapper = gridElement.querySelector('.dx-header-row .dx-checkbox');
515
+ if (!checkboxWrapper) return;
516
+
517
+ checkboxWrapper.addEventListener('click', handleHeaderCheckboxClick, { once: true });
518
+ });
519
+
520
+ observer.observe(gridElement, {
521
+ childList: true,
522
+ subtree: true,
523
+ });
524
+
525
+ return () => {
526
+ observer.disconnect();
527
+ };
528
+ }, [gridInstance, selectAll, excludedKeys]);
529
+
530
+
531
+ const handleHeaderCheckboxClick = () => {
532
+ if (!gridInstance) return;
533
+
534
+ const visibleRowKeys = gridInstance.getVisibleRows().map(r => r.key).filter(Boolean);
535
+ const selectedVisible = visibleRowKeys.filter(k => !excludedKeys.includes(k));
536
+ const allVisibleSelected = selectedVisible.length === visibleRowKeys.length;
537
+
538
+ if (allVisibleSelected) {
539
+
540
+ setExcludedKeys([]);
541
+ setSelectedRowKeys([]);
542
+ setFinalCount(gridInstance.totalCount());
543
+
544
+ gridInstance.selectRows(visibleRowKeys, true);
545
+
546
+ updateHeaderCheckboxState(gridInstance, true, [], []);
547
+ }
548
+ };
549
+
550
+ const getUnSelected = () => {
551
+ const gridInstance = gridRef.current?.instance?.();
552
+
553
+ if (!gridInstance) return;
554
+
555
+ // ✅ Clear selection only (do not clear filters)
556
+ gridInstance.deselectAll();
557
+
558
+ // ✅ Reset your internal state
559
+ setSelectAll(false);
560
+ setExcludedKeys([]);
561
+ setSelectedRowKeys([]);
562
+ setShowCheckboxes(false);
563
+ setFinalCount(0);
564
+ setReadOnlyFilters(false);
565
+ };
566
+
567
+ const getClearSelected = () => {
568
+ const gridInstance = gridRef.current?.instance?.();
569
+
570
+ if (!gridInstance) return;
571
+
572
+ // ✅ Clear selection
573
+ gridInstance.deselectAll();
574
+
575
+ // ✅ Clear all filters (FilterRow + HeaderFilter)
576
+ const columns = gridInstance.getVisibleColumns();
577
+ columns.forEach((col) => {
578
+ gridInstance.columnOption(col.dataField, 'filterValue', null); // FilterRow
579
+ gridInstance.columnOption(col.dataField, 'filterValues', null); // HeaderFilter
580
+ });
581
+
582
+ gridInstance.clearFilter(); // apply filter clearing
583
+
584
+ // ✅ Reset your internal state
585
+ setSelectAll(false);
586
+ setExcludedKeys([]);
587
+ setSelectedRowKeys([]);
588
+ // setShowCheckboxes(false);
589
+ setFinalCount(0);
590
+ setReadOnlyFilters(false);
591
+ setActionValue(null);
592
+
593
+ };
594
+
595
+ useEffect(() => {
596
+ if (readOnlyFilters) {
597
+ checkAvailability();
598
+ }
599
+ }, [readOnlyFilters]);
600
+
601
+ useEffect(() => {
602
+ if (!gridInstance) return;
603
+ gridInstance.refresh(); // rerender grid on filter lock toggle
604
+ }, [readOnlyFilters, gridInstance]);
605
+
606
+
607
+
608
+ useEffect(() => {
609
+ setProceedEnabled(selectAll || selectedRowKeys.length > 0);
610
+ }, [selectAll, selectedRowKeys]);
611
+
612
+ // ✅ SAVE LAST VIEWED SHEET (SEPARATE FUNCTION)
613
+ const saveLastViewedSheet = async (sheet) => {
614
+ try {
615
+ await axios.post(
616
+ "http://localhost:5017/api/excel-sheets/last-viewed",
617
+ {
618
+ userId: 1, // replace with logged-in user id later
619
+ projectId: 147,
620
+ sheetId: sheet.id
621
+ }
622
+ );
623
+ } catch (error) {
624
+ console.error("Failed to save last viewed sheet", error);
625
+ }
626
+ };
627
+ // ✅ GET LAST VIEWED SHEET
628
+ const getLastViewedSheet = async (projectId) => {
629
+ try {
630
+ const response = await axios.get(
631
+ "http://localhost:5017/api/excel-sheets/last-viewed",
632
+ {
633
+ params: {
634
+ userId: 1, // replace later with logged-in user
635
+ projectId: projectId
636
+ }
637
+ }
638
+ );
639
+ return response.data; // expected: { sheetId: number }
640
+ } catch (error) {
641
+ console.error("Failed to get last viewed sheet", error);
642
+ return null;
643
+ }
644
+ };
645
+
646
+
647
+ useEffect(() => {
648
+ fetch(`/api/search-history/1`)
649
+ .then(res => res.ok ? res.json() : [])
650
+ .then(data => {
651
+ if (data.length) {
652
+ setSearchHistory(data);
653
+ searchHistoryRef.current = data;
654
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data));
655
+ } else {
656
+ const stored = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) || [];
657
+ setSearchHistory(stored);
658
+ searchHistoryRef.current = stored;
659
+ }
660
+ })
661
+ .catch(() => {
662
+ const stored = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) || [];
663
+ setSearchHistory(stored);
664
+ searchHistoryRef.current = stored;
665
+ });
666
+ }, []);
667
+
668
+
669
+
670
+ useEffect(() => {
671
+ const handleStorageChange = (e) => {
672
+ if (e.key === STORAGE_KEY) {
673
+ const updated = JSON.parse(e.newValue) || [];
674
+ setSearchHistory(updated);
675
+ searchHistoryRef.current = updated;
676
+ }
677
+ };
678
+ window.addEventListener("storage", handleStorageChange);
679
+ return () => window.removeEventListener("storage", handleStorageChange);
680
+ }, []);
681
+
682
+
683
+
684
+
685
+ /* ----------------------------
686
+ API call (tab close / logout only)
687
+ ----------------------------- */
688
+ const updateSearchHistoryAPI = () => {
689
+ if (!searchHistoryRef.current.length) return;
690
+
691
+ navigator.sendBeacon(
692
+ "/api/search-history",
693
+ JSON.stringify({
694
+ userId: 1,
695
+ history: searchHistoryRef.current
696
+ })
697
+ );
698
+ };
699
+
700
+ /* ----------------------------
701
+ Tab switch & close
702
+ ----------------------------- */
703
+ useEffect(() => {
704
+ const handleVisibilityChange = () => {
705
+ if (document.visibilityState === "hidden") {
706
+ updateSearchHistoryAPI();
707
+ }
708
+ };
709
+
710
+ const handleBeforeUnload = () => {
711
+ updateSearchHistoryAPI();
712
+ };
713
+
714
+ document.addEventListener("visibilitychange", handleVisibilityChange);
715
+ window.addEventListener("beforeunload", handleBeforeUnload);
716
+
717
+ return () => {
718
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
719
+ window.removeEventListener("beforeunload", handleBeforeUnload);
720
+ };
721
+ }, []);
722
+
723
+ const applySearch = () => {
724
+ if (!gridInstance) return;
725
+
726
+ gridInstance.searchByText(searchText);
727
+ };
728
+ const clearSearch = () => {
729
+ if (!gridInstance) return;
730
+ setSearchText("");
731
+ gridInstance.searchByText("");
732
+ };
733
+
734
+ const gridDataWithId = gridData.map((row, index) => ({
735
+ ...row,
736
+ rowId: index + 1
737
+ }));
738
+
739
+ const handleColumnReorder = (e) => {
740
+ alert("bsvdchg")
741
+ console.log("selectedOrder", selectedOrder)
742
+ if (!selectedOrder) return;
743
+
744
+ const payload = {
745
+ sheetId: selectedOrder.id,
746
+ fieldName: e.column.dataField,
747
+ fromIndex: e.fromIndex,
748
+ toIndex: e.toIndex
749
+ };
750
+
751
+ axios.post(
752
+ "http://localhost:5017/api/excel-sheets/columns/reorder",
753
+ payload
754
+ ).catch(err => console.error("Column reorder save failed", err));
755
+ };
756
+
757
+
758
+ useEffect(() => {
759
+ // If all filters are cleared → show all records
760
+ if (
761
+ searchText.trim() === "" &&
762
+ category === 0 &&
763
+ status === 0
764
+ ) {
765
+ setFilteredOrders(orders);
766
+ }
767
+ }, [searchText, category, status, orders]);
768
+
769
+ const truncate = (text, max = 30) =>
770
+ text.length > max ? text.substring(0, max) + "..." : text;
771
+
772
+ const formatLabel = (key) => {
773
+ if (!key) return "";
774
+
775
+ return key
776
+ // Handle snake_case first
777
+ .replace(/_/g, " ")
778
+ // Add space between lowerCase -> UpperCase
779
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
780
+ // Add space before last capital in acronyms (ID, URL, etc.)
781
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
782
+ // Capitalize first letter of each word
783
+ .replace(/\b\w/g, (c) => c.toUpperCase());
784
+ };
785
+
786
+
787
+ return (
788
+ <div className="orders-container">
789
+ {/* LEFT PANEL (NO SEARCH) */}
790
+ <div className="orders-left">
791
+ <h3 className="panel-title">Service List</h3>
792
+ <List
793
+ dataSource={orders}
794
+ keyExpr="id"
795
+ selectionMode="single"
796
+ selectedItem={selectedOrder}
797
+ onItemClick={async (e) => {
798
+ const sheet = e.itemData;
799
+ saveLastViewedSheet(sheet);
800
+
801
+ // ✅ Update selected service
802
+ setSelectedOrder(sheet);
803
+ setSelectedService(sheet);
804
+ await loadColumns(sheet.id);
805
+
806
+ // ✅ Reset grid state BEFORE assigning new store
807
+ if (gridInstance) {
808
+ gridInstance.pageIndex(0); // 🔥 reset pagination
809
+ gridInstance.clearSelection(); // optional but recommended
810
+ gridInstance.searchByText(""); // reset server search
811
+ }
812
+
813
+ // ✅ Create & assign new datasource
814
+ const newStore = createStore(sheet.id);
815
+ setGridStore(newStore);
816
+
817
+ // ✅ Reset search textbox
818
+ setSearchText("");
819
+ }}
820
+ itemRender={(item) => (
821
+ <div
822
+ className={`order-item ${item.id === selectedOrder?.id ? "active" : ""
823
+ }`}
824
+ >
825
+ <div className="order-id">
826
+ {truncate(item.sheetName, 25)}
827
+ </div>
828
+ </div>
829
+ )}
830
+ />
831
+
832
+
833
+ </div>
834
+
835
+ {/* RIGHT PANEL (SEARCH + HISTORY) */}
836
+ <div className="orders-right">
837
+ {selectedOrder && (
838
+ <div className="excel-card">
839
+ {/* Header */}
840
+ <div className="excel-header">
841
+ <h2>Sheet Name: {selectedOrder.sheetName}</h2>
842
+ </div>
843
+
844
+ <div className="ordermasterbox">
845
+ <div className="Ordermaster">
846
+
847
+ {/* SEARCH TEXT */}
848
+ <div className="levellist first">
849
+ <label>What are you looking for?</label>
850
+ <input
851
+ type="text"
852
+ className="form-control"
853
+ placeholder="Search here..."
854
+ value={searchText}
855
+ onChange={(e) => {
856
+ setSearchText(e.target.value)
857
+ handleSearchChange(e.target.value)
858
+ }}
859
+ />
860
+
861
+ </div>
862
+
863
+
864
+
865
+ {/* SEARCH BUTTON */}
866
+ <div className="levellist action" onClick={applySearch}>
867
+ <button className="orderbutton">SEARCH</button>
868
+
869
+ </div>
870
+ {/* <div ref={saveButtonRef} >
871
+
872
+ <Button
873
+ icon="clock"
874
+ stylingMode="text"
875
+ hint="Search history"
876
+ onClick={() => setSearchPopupVisible(true)}
877
+ />
878
+
879
+ </div> */}
880
+ </div>
881
+ </div>
882
+
883
+ <DataGrid
884
+ ref={gridRef}
885
+ dataSource={gridStore}
886
+ keyExpr={keyExpr}
887
+ onInitialized={(e) => setGridInstance(e.component)}
888
+ remoteOperations={true} // KEEP TRUE for remote paging, etc.
889
+ height="80%"
890
+ width="100%" // ✅ Set width to allow layout
891
+ columnAutoWidth={true} // ✅ Adjust columns automatically
892
+ showBorders={true}
893
+ allowColumnReordering={true}
894
+ onColumnReordered={(e) => {
895
+ console.log("REORDER TRIGGERED");
896
+ console.log("From:", e.fromIndex);
897
+ console.log("To:", e.toIndex);
898
+ console.log("Field:", e.column.dataField);
899
+ }}
900
+ // onColumnReordered={handleColumnReorder}
901
+ columnHidingEnabled={false}
902
+ onSelectionChanged={onSelectionChanged}
903
+ repaintChangesOnly={true}
904
+ columns={columns}
905
+ onOptionChanged={(e) => {
906
+ if (e.name === "grouping" && e.fullName === "grouping.groupedColumns") {
907
+ setAutoExpandAll(false);
908
+ if (gridInstance) {
909
+ gridInstance.collapseAll(-1); // 👈 collapses all groups
910
+ }
911
+ }
912
+ }}
913
+ onRowClick={(e) => {
914
+ setSelectedRow(e.data); // 🔥 full row object
915
+ setRowDetailsVisible(true); // 🔥 open popup
916
+ }}
917
+ onContentReady={(e) => {
918
+ onContentReady(e.component)
919
+ // setGridInstance(e.component); // ensure grid instance is always updated
920
+
921
+ if (!readOnlyFilters) return;
922
+ const rootElement = e.component.element();
923
+
924
+ // Disable filter row inputs
925
+ const filterRowInputs = rootElement.querySelectorAll(".dx-datagrid-filter-row input");
926
+ filterRowInputs.forEach((input) => {
927
+ input.readOnly = true;
928
+ input.style.backgroundColor = "#f5f5f5";
929
+
930
+ });
931
+ const headerIcons = rootElement.querySelectorAll(".dx-header-filter");
932
+ headerIcons.forEach((icon) => {
933
+ icon.style.pointerEvents = "none";
934
+ icon.style.opacity = "0.5";
935
+ icon.setAttribute("aria-disabled", "true");
936
+ icon.setAttribute("tabindex", "-1");
937
+
938
+ });
939
+ }}
940
+ >
941
+ <Selection
942
+ mode="multiple"
943
+ showCheckBoxesMode={showCheckboxes ? 'always' : 'none'} // toggle here
944
+ selectAllMode="page" // This shows the checkbox in header
945
+ />
946
+ <GroupPanel visible={true}
947
+ emptyPanelText="Use the context menu of header columns to group data"
948
+ />
949
+ {/* <SearchPanel visible={true} highlightCaseSensitive={false} /> */}
950
+
951
+ <Grouping autoExpandAll={false} contextMenuEnabled={true} expandMode="rowClick"
952
+ />
953
+ <FilterRow visible={true} />
954
+ <HeaderFilter visible={true} />
955
+
956
+ <Scrolling mode="virtual" useNative={false} />
957
+ <Summary>
958
+ <TotalItem column="sheetId" summaryType="count" displayFormat="Total: {0}" />
959
+ <GroupItem column="sheetId" summaryType="count" displayFormat="Count: {0}" />
960
+ </Summary>
961
+
962
+ <Toolbar>
963
+ <Item name="groupPanel" location="before" />
964
+
965
+ <Item
966
+ location="after"
967
+ render={() => (
968
+ <Button
969
+ icon="arrowright"
970
+ text={`(${finalCount})`}
971
+ onClick={() => {
972
+ if (actionValue === "Assigned") {
973
+ setAssignPopupVisible(true);
974
+ } else {
975
+ getFinalSelectedIds();
976
+ setPopupVisible(true);
977
+ }
978
+ }}
979
+ disabled={!proceedEnabled}
980
+ />
981
+ )}
982
+ />
983
+
984
+ <Item
985
+ location="after"
986
+ render={() => (
987
+ <Button
988
+ icon="clearformat"
989
+ text="Clear"
990
+ onClick={getClearSelected}
991
+ />
992
+ )}
993
+ />
994
+ </Toolbar>
995
+ </DataGrid>
996
+
997
+
998
+ {selectedRow && (
999
+ <Popup
1000
+ visible={rowDetailsVisible}
1001
+ onHiding={() => setRowDetailsVisible(false)}
1002
+ showTitle
1003
+ title={`Row Details`}
1004
+ dragEnabled={false}
1005
+ closeOnOutsideClick
1006
+ showCloseButton
1007
+ width={420}
1008
+ height="100%"
1009
+ position={{
1010
+ my: "right top",
1011
+ at: "right top",
1012
+ of: window,
1013
+ }}
1014
+ wrapperAttr={{ class: "right-side-popup" }}
1015
+ >
1016
+ <div className="popup-content">
1017
+ {Object.entries(selectedRow).map(([key, value]) => (
1018
+ <div className="form-group" key={key}>
1019
+ <label>{formatLabel(key)}</label>
1020
+ <input
1021
+ type="text"
1022
+ value={value ?? ""}
1023
+ readOnly
1024
+ />
1025
+ </div>
1026
+ ))}
1027
+ </div>
1028
+ </Popup>
1029
+ )}
1030
+
1031
+
1032
+ <Popup
1033
+ visible={searchPopupVisible}
1034
+ onHiding={() => setSearchPopupVisible(false)}
1035
+ showTitle
1036
+ title="Search History"
1037
+ width={360}
1038
+ height="100%"
1039
+ dragEnabled={false}
1040
+ closeOnOutsideClick
1041
+ showCloseButton
1042
+ position={{
1043
+ my: "right top",
1044
+ at: "right top",
1045
+ of: window
1046
+ }}
1047
+ wrapperAttr={{ class: "right-search-popup" }}
1048
+ >
1049
+ <div style={{ padding: 16, height: "100%" }}>
1050
+ {searchHistory.length > 0 ? (
1051
+ <ul
1052
+ style={{
1053
+ listStyle: "none",
1054
+ padding: 0,
1055
+ margin: 0,
1056
+ height: "100%",
1057
+ overflowY: "auto"
1058
+ }}
1059
+ >
1060
+ {searchHistory.map((s, i) => (
1061
+ <li
1062
+ key={i}
1063
+ onClick={() => {
1064
+ setGridSearchText(s);
1065
+ setSearchPopupVisible(false);
1066
+ }}
1067
+ style={{
1068
+ display: "flex",
1069
+ alignItems: "center",
1070
+ gap: 10,
1071
+ padding: "10px 12px",
1072
+ marginBottom: 8,
1073
+ borderRadius: 8,
1074
+ cursor: "pointer",
1075
+ backgroundColor: "#f5f7fa",
1076
+ transition: "all 0.2s ease"
1077
+ }}
1078
+ onMouseEnter={e => e.currentTarget.style.background = "#e6f0ff"}
1079
+ onMouseLeave={e => e.currentTarget.style.background = "#f5f7fa"}
1080
+ >
1081
+ <span style={{ fontSize: 16, color: "#1976d2" }}>🔍</span>
1082
+ <span
1083
+ style={{
1084
+ fontSize: 14,
1085
+ color: "#333",
1086
+ overflow: "hidden",
1087
+ textOverflow: "ellipsis",
1088
+ whiteSpace: "nowrap"
1089
+ }}
1090
+ title={s}
1091
+ >
1092
+ {s}
1093
+ </span>
1094
+ </li>
1095
+ ))}
1096
+ </ul>
1097
+ ) : (
1098
+ <div
1099
+ style={{
1100
+ height: "100%",
1101
+ display: "flex",
1102
+ alignItems: "center",
1103
+ justifyContent: "center",
1104
+ color: "#999",
1105
+ fontSize: 14
1106
+ }}
1107
+ >
1108
+ 🔍 No search history available
1109
+ </div>
1110
+ )}
1111
+ </div>
1112
+ </Popup>
1113
+
1114
+
1115
+
1116
+ </div>
1117
+ )}
1118
+ </div>
1119
+ </div>
1120
+ );
1121
+ };
1122
+
1123
+ export default OrdersMasterDetail;