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.
- package/README.md +70 -0
- package/dist/assets/09394b2afc53828fe342.otf +0 -0
- package/dist/assets/6537363fd5b802d5049c.woff2 +0 -0
- package/dist/assets/7287d1faf8f8e079aeab.ttf +0 -0
- package/dist/assets/9ac1f13f07c4503334c7.woff +0 -0
- package/dist/assets/ba20f6b82215cbe1b080.woff2 +0 -0
- package/dist/assets/c120fd44dc6e07624cd1.woff +0 -0
- package/dist/assets/icons/dxicons.ttf +0 -0
- package/dist/assets/icons/dxicons.woff +0 -0
- package/dist/assets/icons/dxicons.woff2 +0 -0
- package/dist/assets/icons/dxiconsfluent.ttf +0 -0
- package/dist/assets/icons/dxiconsfluent.woff +0 -0
- package/dist/assets/icons/dxiconsfluent.woff2 +0 -0
- package/dist/assets/icons/dxiconsmaterial.ttf +0 -0
- package/dist/assets/icons/dxiconsmaterial.woff +0 -0
- package/dist/assets/icons/dxiconsmaterial.woff2 +0 -0
- package/dist/index.html +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +14 -0
- package/package.json +55 -0
- package/src/App.css +38 -0
- package/src/App.js +33 -0
- package/src/App.test.js +8 -0
- package/src/Common/CustomLoadingPanel.js +43 -0
- package/src/assets/Desktop.png +0 -0
- package/src/assets/Excel.png +0 -0
- package/src/assets/Thumbs.db +0 -0
- package/src/assets/apps.png +0 -0
- package/src/assets/arc_flow.png +0 -0
- package/src/assets/circle_icons.png +0 -0
- package/src/assets/close.png +0 -0
- package/src/assets/connection.png +0 -0
- package/src/assets/csv.png +0 -0
- package/src/assets/data_preview.png +0 -0
- package/src/assets/database1.png +0 -0
- package/src/assets/date_refresh.png +0 -0
- package/src/assets/delete.png +0 -0
- package/src/assets/edit.png +0 -0
- package/src/assets/filter.png +0 -0
- package/src/assets/fonts/FontAwesome.otf +0 -0
- package/src/assets/fonts/Proxima-Nova-Bold.otf +0 -0
- package/src/assets/fonts/fa-brands-400.eot +0 -0
- package/src/assets/fonts/fa-brands-400.svg +1008 -0
- package/src/assets/fonts/fa-brands-400.ttf +0 -0
- package/src/assets/fonts/fa-brands-400.woff +0 -0
- package/src/assets/fonts/fa-brands-400.woff2 +0 -0
- package/src/assets/fonts/fa-regular-400.eot +0 -0
- package/src/assets/fonts/fa-regular-400.svg +366 -0
- package/src/assets/fonts/fa-regular-400.ttf +0 -0
- package/src/assets/fonts/fa-regular-400.woff +0 -0
- package/src/assets/fonts/fa-regular-400.woff2 +0 -0
- package/src/assets/fonts/fa-solid-900.eot +0 -0
- package/src/assets/fonts/fa-solid-900.svg +1518 -0
- package/src/assets/fonts/fa-solid-900.ttf +0 -0
- package/src/assets/fonts/fa-solid-900.woff +0 -0
- package/src/assets/fonts/fa-solid-900.woff2 +0 -0
- package/src/assets/fonts/fontawesome-webfont.eot +0 -0
- package/src/assets/fonts/fontawesome-webfont.svg +2671 -0
- package/src/assets/fonts/fontawesome-webfont.ttf +0 -0
- package/src/assets/fonts/fontawesome-webfont.woff +0 -0
- package/src/assets/fonts/fontawesome-webfont.woff2 +0 -0
- package/src/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- package/src/assets/fonts/glyphicons-halflings-regular.svg +288 -0
- package/src/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- package/src/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- package/src/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
- package/src/assets/fonts/proxima_nova_thin-webfont.woff +0 -0
- package/src/assets/fonts/proxima_nova_thin-webfont.woff2 +0 -0
- package/src/assets/fonts/proximanova-regular-webfont.woff +0 -0
- package/src/assets/fonts/proximanova-regular-webfont.woff2 +0 -0
- package/src/assets/logo_preview.png +0 -0
- package/src/assets/mail.png +0 -0
- package/src/assets/map_white 2.png +0 -0
- package/src/assets/map_white.png +0 -0
- package/src/assets/mapping.png +0 -0
- package/src/assets/notifications.png +0 -0
- package/src/assets/plus_add.png +0 -0
- package/src/assets/plusadd_white.png +0 -0
- package/src/assets/tick.png +0 -0
- package/src/assets/user.jpg +0 -0
- package/src/assets/user_setting.png +0 -0
- package/src/assets/wDesktop.png +0 -0
- package/src/assets/wconnction.png +0 -0
- package/src/assets/wdata_preview.png +0 -0
- package/src/assets/wdate_refresh.png +0 -0
- package/src/assets/wmapping.png +0 -0
- package/src/assets/wplus.png +0 -0
- package/src/components/Service/OrdersMasterDetail.css +427 -0
- package/src/components/Service/OrdersMasterDetail.js +1123 -0
- package/src/devextreme-license.js +1 -0
- package/src/index.css +13 -0
- package/src/index.js +261 -0
- package/src/logo.svg +1 -0
- package/src/reportWebVitals.js +13 -0
- package/src/routes/AppRoutes.js +18 -0
- package/src/setupTests.js +5 -0
- package/src/styles/UserDashBoard.css +1843 -0
- package/src/utils/ToastContext.js +34 -0
- package/src/utils/commonSchema.js +29 -0
- 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;
|