trithuc-mvc-react 3.4.1 → 3.4.3
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/api/index.js
CHANGED
|
@@ -1,41 +1,87 @@
|
|
|
1
|
+
import qs from "qs"; // 👈 serialize query params
|
|
1
2
|
import { storeGetlanguage, storeGetToken } from "@/utils/storage";
|
|
2
3
|
import axios from "axios";
|
|
3
4
|
|
|
4
5
|
let baseUrl = "";
|
|
5
6
|
let apiUrl = "";
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Cấu hình base URL cho toàn ứng dụng
|
|
10
|
+
*/
|
|
7
11
|
export function configure({ customBaseUrl, customApiUrl }) {
|
|
8
12
|
if (customBaseUrl) {
|
|
9
13
|
baseUrl = customBaseUrl;
|
|
10
|
-
apiUrl = customApiUrl;
|
|
14
|
+
apiUrl = customApiUrl || customBaseUrl;
|
|
11
15
|
} else {
|
|
12
|
-
console.warn("baseUrl chưa được định cấu hình. Sử dụng giá trị mặc định.");
|
|
16
|
+
console.warn("⚠️ baseUrl chưa được định cấu hình. Sử dụng giá trị mặc định.");
|
|
13
17
|
}
|
|
14
18
|
}
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Lấy base URL hiện tại
|
|
22
|
+
*/
|
|
16
23
|
export function getBaseUrl() {
|
|
17
24
|
return baseUrl;
|
|
18
25
|
}
|
|
19
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Tạo instance axios chung
|
|
29
|
+
*/
|
|
20
30
|
const api = axios.create({
|
|
21
31
|
baseURL: getBaseUrl() ?? "/",
|
|
32
|
+
responseType: "json",
|
|
22
33
|
transitional: {
|
|
23
34
|
silentJSONParsing: false
|
|
24
35
|
},
|
|
25
|
-
responseType: "json"
|
|
26
|
-
});
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
// 👇 Tùy chỉnh serialize query params để gửi mảng đúng định dạng
|
|
38
|
+
paramsSerializer: (params) =>
|
|
39
|
+
qs.stringify(params, {
|
|
40
|
+
arrayFormat: "repeat", // => ?a=1&a=2
|
|
41
|
+
skipNulls: true // bỏ các giá trị null
|
|
42
|
+
})
|
|
43
|
+
});
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Interceptor REQUEST
|
|
47
|
+
*/
|
|
32
48
|
api.interceptors.request.use((config) => {
|
|
49
|
+
const token = storeGetToken();
|
|
33
50
|
const language = storeGetlanguage();
|
|
34
|
-
|
|
51
|
+
|
|
52
|
+
// 🔐 Thêm Authorization + ngôn ngữ
|
|
53
|
+
if (token) config.headers["Authorization"] = `Bearer ${token}`;
|
|
35
54
|
if (language) config.headers["Accept-Language"] = language;
|
|
55
|
+
|
|
56
|
+
// ⚙️ Nếu query GET quá dài, tự động chuyển sang POST
|
|
57
|
+
if (config.method?.toUpperCase() === "GET" && config.params) {
|
|
58
|
+
const queryString = qs.stringify(config.params, { arrayFormat: "repeat" });
|
|
59
|
+
|
|
60
|
+
if (queryString.length > 2000) {
|
|
61
|
+
config.method = "POST";
|
|
62
|
+
config.data = config.params;
|
|
63
|
+
delete config.params;
|
|
64
|
+
console.warn("⚙️ Query quá dài → tự động chuyển sang POST:", config.url);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
36
68
|
return config;
|
|
37
69
|
});
|
|
38
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Interceptor RESPONSE (có thể mở rộng sau này, ví dụ refresh token)
|
|
73
|
+
*/
|
|
74
|
+
api.interceptors.response.use(
|
|
75
|
+
(response) => response,
|
|
76
|
+
(error) => {
|
|
77
|
+
if (error.response?.status === 401) {
|
|
78
|
+
console.error("🚫 Token hết hạn hoặc không hợp lệ.");
|
|
79
|
+
// 👉 Có thể thêm xử lý refresh token ở đây sau này
|
|
80
|
+
}
|
|
81
|
+
return Promise.reject(error);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
39
85
|
export default api;
|
|
40
86
|
|
|
41
87
|
export const getDatasFromTable = async ({ tableName, page, pageSize, data }) => {
|
|
@@ -16,6 +16,7 @@ import TableToolbar from "./TableToolbar";
|
|
|
16
16
|
import { useDataTable } from "./hooks";
|
|
17
17
|
import DeleteConfirmationDialog from "./DeleteConfirmationDialog";
|
|
18
18
|
import DeleteMultipleConfirmationDialog from "./DeleteMultipleConfirmationDialog";
|
|
19
|
+
import { storeGetUser } from "../../utils/storage";
|
|
19
20
|
const defaultQueryOptions = {
|
|
20
21
|
staleTime: 1000 * 60 * 1, // Thời gian dữ liệu có thể sử dụng lại trước khi gọi lại API
|
|
21
22
|
cacheTime: 1000 * 60 * 30, // Thời gian dữ liệu được lưu trong cache trước khi bị xóa
|
|
@@ -36,7 +37,8 @@ const DataTable = ({ multipleActions = [], page, setPage = () => {}, disableEdit
|
|
|
36
37
|
hasTabpanel,
|
|
37
38
|
defaultRowsPerPage = 5
|
|
38
39
|
} = useDataTable();
|
|
39
|
-
|
|
40
|
+
const user = storeGetUser();
|
|
41
|
+
const userId = user?.UserId;
|
|
40
42
|
const { set: setPermission } = usePermission(tableName);
|
|
41
43
|
const queryClient = useQueryClient();
|
|
42
44
|
const confirm = useConfirm();
|
|
@@ -81,11 +83,19 @@ const DataTable = ({ multipleActions = [], page, setPage = () => {}, disableEdit
|
|
|
81
83
|
}),
|
|
82
84
|
defaultQueryOptions,
|
|
83
85
|
// keepPreviousData: true,
|
|
84
|
-
onSuccess: ({ PermissionModel, status }) => {
|
|
86
|
+
onSuccess: ({ PermissionModel, CountTrangThai, status }) => {
|
|
85
87
|
if (dataSearch?.TrangThaiXuLy !== undefined) {
|
|
86
88
|
PermissionModel.TrangThaiXuLy = dataSearch?.TrangThaiXuLy;
|
|
87
89
|
}
|
|
88
90
|
setPermission(PermissionModel);
|
|
91
|
+
|
|
92
|
+
if (CountTrangThai) {
|
|
93
|
+
const keyCounter = `${tableName}_${userId}_TrangThaiXuLyCounter`;
|
|
94
|
+
localStorage.setItem(keyCounter, JSON.stringify(CountTrangThai));
|
|
95
|
+
// 👉 Invalidate query để Tab reload ngay
|
|
96
|
+
queryClient.invalidateQueries({ queryKey: [tableName, "CountAllTrangThaiXuly"] });
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
if (status) {
|
|
90
100
|
// console.log("LOAD LAI PermissionModel");
|
|
91
101
|
// Cuộn lên đầu trang khi tải dữ liệu thành công
|
|
@@ -15,6 +15,7 @@ import TableToolbar from "./TableToolbar";
|
|
|
15
15
|
import { useDataTable } from "./hooks";
|
|
16
16
|
import DeleteConfirmationDialog from "./DeleteConfirmationDialog";
|
|
17
17
|
import DeleteMultipleConfirmationDialog from "./DeleteMultipleConfirmationDialog";
|
|
18
|
+
import { storeGetUser } from "../../utils/storage";
|
|
18
19
|
const defaultQueryOptions = {
|
|
19
20
|
staleTime: 1000 * 60 * 1, // Thời gian dữ liệu có thể sử dụng lại trước khi gọi lại API
|
|
20
21
|
cacheTime: 1000 * 60 * 30, // Thời gian dữ liệu được lưu trong cache trước khi bị xóa
|
|
@@ -35,7 +36,8 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
|
|
|
35
36
|
hasTabpanel,
|
|
36
37
|
defaultRowsPerPage = 5
|
|
37
38
|
} = useDataTable();
|
|
38
|
-
|
|
39
|
+
const user = storeGetUser();
|
|
40
|
+
const userId = user?.UserId;
|
|
39
41
|
const { set: setPermission } = usePermission(tableName);
|
|
40
42
|
const queryClient = useQueryClient();
|
|
41
43
|
const confirm = useConfirm();
|
|
@@ -80,11 +82,19 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
|
|
|
80
82
|
}),
|
|
81
83
|
defaultQueryOptions,
|
|
82
84
|
// keepPreviousData: true,
|
|
83
|
-
onSuccess: ({ PermissionModel, status }) => {
|
|
85
|
+
onSuccess: ({ PermissionModel, CountTrangThai, status }) => {
|
|
84
86
|
if (dataSearch?.TrangThaiXuLy !== undefined) {
|
|
85
87
|
PermissionModel.TrangThaiXuLy = dataSearch?.TrangThaiXuLy;
|
|
86
88
|
}
|
|
87
89
|
setPermission(PermissionModel);
|
|
90
|
+
|
|
91
|
+
if (CountTrangThai) {
|
|
92
|
+
const keyCounter = `${tableName}_${userId}_TrangThaiXuLyCounter`;
|
|
93
|
+
localStorage.setItem(keyCounter, JSON.stringify(CountTrangThai));
|
|
94
|
+
// 👉 Invalidate query để Tab reload ngay
|
|
95
|
+
queryClient.invalidateQueries({ queryKey: [tableName, "CountAllTrangThaiXuly"] });
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
if (status) {
|
|
89
99
|
// console.log("LOAD LAI PermissionModel");
|
|
90
100
|
// Cuộn lên đầu trang khi tải dữ liệu thành công
|
|
@@ -120,7 +120,7 @@ function DataManagement({
|
|
|
120
120
|
const [selectedEditItem, setSelectedEditItem] = useState(null);
|
|
121
121
|
const [openViewDialog, setOpenViewDialog] = useState(false);
|
|
122
122
|
|
|
123
|
-
const { permission, canCreate, canThongKe, canBieuDo } = usePermission(apiUrl || tableName);
|
|
123
|
+
const { permission, canCreate, canExportData, canThongKe, canBieuDo } = usePermission(apiUrl || tableName);
|
|
124
124
|
|
|
125
125
|
// console.log(">>> permission from hook:", permission);
|
|
126
126
|
// console.log(">>> canCreate:", canCreate);
|
|
@@ -389,7 +389,7 @@ function DataManagement({
|
|
|
389
389
|
<div key={index}>{button}</div>
|
|
390
390
|
))}
|
|
391
391
|
|
|
392
|
-
<ExportExcelButton tableName={tableName} data={dataSearch} size={elementSize} />
|
|
392
|
+
{canExportData && <ExportExcelButton tableName={tableName} data={dataSearch} size={elementSize} />}
|
|
393
393
|
|
|
394
394
|
{canCreate && !disableAdd && (
|
|
395
395
|
<Button
|
package/hooks/usePermission.js
CHANGED
|
@@ -51,6 +51,7 @@ const usePermission = (tableName) => {
|
|
|
51
51
|
canAction: false,
|
|
52
52
|
canView: false,
|
|
53
53
|
canImport: false,
|
|
54
|
+
canExportData: false,
|
|
54
55
|
canExportWord: false,
|
|
55
56
|
canPayment: false,
|
|
56
57
|
canThongKe: false,
|
|
@@ -86,6 +87,7 @@ const usePermission = (tableName) => {
|
|
|
86
87
|
canAction: source.Action ?? false,
|
|
87
88
|
canView: source.View ?? false,
|
|
88
89
|
canImport: source.ImportFile ?? false,
|
|
90
|
+
canExportData: source.ExportData ?? false,
|
|
89
91
|
canExportWord: source.ExportWord ?? false,
|
|
90
92
|
canPayment: source.Payment ?? false,
|
|
91
93
|
canThongKe: source.ThongKe ?? false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trithuc-mvc-react",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.3",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"lodash": "^4.17.21",
|
|
23
23
|
"moment": "^2.30.1",
|
|
24
24
|
"prop-types": "^15.8.1",
|
|
25
|
+
"qs": "^6.14.0",
|
|
25
26
|
"yup": "^1.6.1"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
@@ -35,18 +36,18 @@
|
|
|
35
36
|
"react-dom": "^18.3.1"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
39
|
+
"@mui/icons-material": "^7.0.2",
|
|
38
40
|
"@mui/lab": "^7.0.0-beta.11",
|
|
39
41
|
"@mui/material": "^7.0.2",
|
|
40
42
|
"@mui/system": "^5.17.1",
|
|
41
|
-
"@mui/icons-material": "^7.0.2",
|
|
42
|
-
"@mui/x-tree-view": "^7.28.1",
|
|
43
43
|
"@mui/x-date-pickers": "^7.28.3",
|
|
44
|
+
"@mui/x-tree-view": "^7.28.1",
|
|
45
|
+
"dayjs": "^1.11.13",
|
|
44
46
|
"material-ui-confirm": "^4.0.0",
|
|
45
47
|
"react": ">=16",
|
|
46
48
|
"react-dom": ">=16",
|
|
47
49
|
"react-hook-form": "^7.55.0",
|
|
48
50
|
"react-query": "^3.39.3",
|
|
49
|
-
"react-toastify": "^11.0.5"
|
|
50
|
-
"dayjs": "^1.11.13"
|
|
51
|
+
"react-toastify": "^11.0.5"
|
|
51
52
|
}
|
|
52
53
|
}
|
package/utils/storage.js
CHANGED
|
@@ -289,3 +289,26 @@ export const storeGetlanguageSystemKey = (TypeKey, SystemKey, TypeLanguage) => {
|
|
|
289
289
|
?.Description || ""
|
|
290
290
|
);
|
|
291
291
|
};
|
|
292
|
+
// ---------------- Storage helpers ----------------
|
|
293
|
+
export const storeSetKey = (Key, data) => {
|
|
294
|
+
if (!data) return;
|
|
295
|
+
const encryptedData = encryptData(data);
|
|
296
|
+
localStorage.setItem(Key, encryptedData);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export const storeGetKey = (key) => {
|
|
300
|
+
const encryptedData = localStorage.getItem(key);
|
|
301
|
+
if (!encryptedData) return null;
|
|
302
|
+
return decryptData(encryptedData);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// ---------------- Utility so sánh dữ liệu ----------------
|
|
306
|
+
export const isDataChanged = (oldData, newData) => {
|
|
307
|
+
if (!oldData) return true;
|
|
308
|
+
try {
|
|
309
|
+
return JSON.stringify(oldData) !== JSON.stringify(newData);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error("Error comparing data:", error);
|
|
312
|
+
return true; // nếu có lỗi thì coi như thay đổi
|
|
313
|
+
}
|
|
314
|
+
};
|