trithuc-mvc-react 3.4.6 → 3.4.8

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.
@@ -13,7 +13,7 @@ import { useEffect, useMemo, useState } from "react";
13
13
  import TablePaginationCustom from "../table/TablePagination";
14
14
 
15
15
  import { useConfirm } from "material-ui-confirm";
16
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
16
+ import { useMutation, useQuery, useQueryClient } from "react-query";
17
17
  import { toast } from "react-toastify";
18
18
  import {
19
19
  changeStatusDataToTable,
@@ -62,16 +62,16 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
62
62
  const [rowsPerPage, setRowsPerPage] = useState(defaultRowsPerPage);
63
63
  const [openDialog, setOpenDialog] = useState(false);
64
64
  const [deleteId, setDeleteId] = useState(null);
65
- const defaultSortColumn = columns.find((c) => c.defaultSort);
65
+ const [orderBy, setOrderBy] = useState("");
66
+ const [order, setOrder] = useState("asc");
66
67
 
67
- const [orderBy, setOrderBy] = useState(defaultSortColumn?.field || "");
68
- const [order, setOrder] = useState(defaultSortColumn?.defaultOrder || "asc");
69
68
  useEffect(() => {
70
- const defaultSortColumn = columns.find((c) => c.defaultSort);
71
- if (defaultSortColumn) {
72
- setOrderBy(defaultSortColumn.field);
73
- setOrder(defaultSortColumn.defaultOrder || "asc");
74
- }
69
+ if (!Array.isArray(columns) || columns.length === 0) return;
70
+
71
+ const defaultSortColumn = columns.find((c) => c.defaultSort) || columns[0];
72
+
73
+ setOrderBy(defaultSortColumn.field);
74
+ setOrder(defaultSortColumn.defaultOrder || "asc");
75
75
  }, [columns]);
76
76
 
77
77
  const handleRequestSort = (event, property) => {
@@ -80,12 +80,11 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
80
80
  setOrderBy(property);
81
81
  };
82
82
 
83
- // 1️⃣ Xoá defaultQueryOptions khỏi useQuery
84
83
  const { data, isLoading } = useQuery({
85
84
  queryKey: [tableName, page, rowsPerPage, dataSearch, order, orderBy],
86
85
  queryFn: () =>
87
86
  getDatasFromTable({
88
- tableName,
87
+ tableName: tableName,
89
88
  page: page + 1,
90
89
  pageSize: rowsPerPage,
91
90
  data: {
@@ -96,76 +95,107 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
96
95
  })
97
96
  }
98
97
  }),
99
- keepPreviousData: true,
100
- placeholderData: () => ({
101
- data: [],
102
- PermissionModel: {},
103
- CountTrangThai: null,
104
- status: false
105
- }),
106
- staleTime: 60_000,
107
- gcTime: 30 * 60_000, // ✅ v5 (cacheTime → gcTime)
108
- refetchOnWindowFocus: true,
109
- refetchInterval: 30_000,
110
- onSuccess: ({ PermissionModel = {}, CountTrangThai, status }) => {
98
+ defaultQueryOptions,
99
+ // keepPreviousData: true,
100
+ onSuccess: ({ PermissionModel, CountTrangThai, status }) => {
111
101
  if (dataSearch?.TrangThaiXuLy !== undefined) {
112
- PermissionModel.TrangThaiXuLy = dataSearch.TrangThaiXuLy;
102
+ PermissionModel.TrangThaiXuLy = dataSearch?.TrangThaiXuLy;
113
103
  }
114
-
115
104
  setPermission(PermissionModel);
116
105
 
117
106
  if (CountTrangThai) {
118
107
  const keyCounter = `${tableName}_${userId}_TrangThaiXuLyCounter`;
119
108
  localStorage.setItem(keyCounter, JSON.stringify(CountTrangThai));
120
- queryClient.invalidateQueries({
121
- queryKey: [tableName, "CountAllTrangThaiXuly"]
122
- });
109
+ // 👉 Invalidate query để Tab reload ngay
110
+ queryClient.invalidateQueries({ queryKey: [tableName, "CountAllTrangThaiXuly"] });
123
111
  }
124
112
 
125
113
  if (status) {
126
- window.scrollTo({ top: 0, behavior: "smooth" });
114
+ // console.log("LOAD LAI PermissionModel");
115
+ // Cuộn lên đầu trang khi tải dữ liệu thành công
116
+ window.scrollTo({
117
+ top: 0, // Vị trí pixel muốn cuộn tới
118
+ behavior: "smooth" // Tùy chọn cuộn mượt
119
+ });
127
120
  }
128
121
  }
129
122
  });
130
-
131
- // 2️⃣ Mutation cũng cần sửa, bỏ defaultMutationOptions nếu
132
- const changeStatusMutation = useMutation({
133
- mutationFn: changeStatusDataToTable,
134
- onSuccess: ({ status = false, message = "Có lỗi xảy ra !" }) => {
135
- toast.success(status ? message : "Thay đổi trạng thái thành công !");
123
+ const changeStatusMutation = useMutation(changeStatusDataToTable, {
124
+ onSuccess: ({ status = false, message = " Có lỗi xảy ra !" }) => {
125
+ if (URL_APPLICATION_API) {
126
+ toast.success(message);
127
+ } else {
128
+ toast.success("Thay đổi trạng thái thành công !");
129
+ }
136
130
  queryClient.invalidateQueries({ queryKey: [tableName] });
137
131
  queryClient.invalidateQueries({ queryKey: [tableName, "CountAllTrangThaiXuly"] });
138
132
  },
139
133
  onError: (error) => {
140
- const message = error?.response?.data?.message || "Đã xảy ra lỗi không mong muốn.";
141
- toast.error(message);
134
+ if (error.response && error.response.data && error.response.data.errors) {
135
+ const errors = error.response.data.errors;
136
+ // Chuyển đổi đối tượng lỗi thành chuỗi để hiển thị
137
+ const errorMessages = Object.entries(errors)
138
+ .map(([field, messages]) => `${field}: ${messages.join(", ")}`)
139
+ .join("\n");
140
+ toast.error(errorMessages);
141
+ } else {
142
+ // Nếu lỗi không theo định dạng mong đợi, hiển thị thông tin lỗi chung
143
+ toast.error("Đã xảy ra lỗi không mong muốn.");
144
+ }
142
145
  }
143
146
  });
147
+ const deleteMutation = useMutation(deleteDataFromTable, {
148
+ onSuccess: ({ status = false, message = " Có lỗi xảy ra !" }) => {
149
+ if (status) {
150
+ if (URL_APPLICATION_API) {
151
+ toast.success(message);
152
+ } else {
153
+ toast.success("Xóa thành công !");
154
+ }
155
+ } else {
156
+ toast.error(" Có lỗi xảy ra !");
157
+ }
144
158
 
145
- const deleteMutation = useMutation({
146
- mutationFn: deleteDataFromTable,
147
- onSuccess: ({ status = false, message = "Có lỗi xảy ra !" }) => {
148
- toast[status ? "success" : "error"](status ? message : "Có lỗi xảy ra !");
149
159
  queryClient.invalidateQueries({ queryKey: [tableName] });
150
160
  queryClient.invalidateQueries({ queryKey: [tableName, "CountAllTrangThaiXuly"] });
151
161
  },
152
162
  onError: (error) => {
153
- const message = error?.response?.data?.message || "Đã xảy ra lỗi không mong muốn.";
154
- toast.error(message);
163
+ if (error.response && error.response.data && error.response.data.errors) {
164
+ const errors = error.response.data.errors;
165
+ // Chuyển đổi đối tượng lỗi thành chuỗi để hiển thị
166
+ const errorMessages = Object.entries(errors)
167
+ .map(([field, messages]) => `${field}: ${messages.join(", ")}`)
168
+ .join("\n");
169
+ toast.error(errorMessages);
170
+ } else {
171
+ // Nếu lỗi không theo định dạng mong đợi, hiển thị thông tin lỗi chung
172
+ toast.error("Đã xảy ra lỗi không mong muốn.");
173
+ }
155
174
  }
156
175
  });
157
-
158
- const deleteMultipleMutation = useMutation({
159
- mutationFn: deleteMultipleDataFromTable,
160
- onSuccess: ({ status = false, message = "Có lỗi xảy ra !" }) => {
161
- toast[status ? "success" : "error"](status ? message : "Xóa thành công !");
176
+ const deleteMultipleMutation = useMutation(deleteMultipleDataFromTable, {
177
+ onSuccess: ({ status = false, message = " Có lỗi xảy ra !" }) => {
178
+ if (URL_APPLICATION_API) {
179
+ toast.success(message);
180
+ } else {
181
+ toast.success("Xóa thành công !");
182
+ }
162
183
  setSelectedItems([]);
163
184
  queryClient.invalidateQueries({ queryKey: [tableName] });
164
185
  queryClient.invalidateQueries({ queryKey: [tableName, "CountAllTrangThaiXuly"] });
165
186
  },
166
187
  onError: (error) => {
167
- const message = error?.response?.data?.message || "Đã xảy ra lỗi không mong muốn.";
168
- toast.error(message);
188
+ if (error.response && error.response.data && error.response.data.errors) {
189
+ const errors = error.response.data.errors;
190
+ // Chuyển đổi đối tượng lỗi thành chuỗi để hiển thị
191
+ const errorMessages = Object.entries(errors)
192
+ .map(([field, messages]) => `${field}: ${messages.join(", ")}`)
193
+ .join("\n");
194
+ toast.error(errorMessages);
195
+ } else {
196
+ // Nếu lỗi không theo định dạng mong đợi, hiển thị thông tin lỗi chung
197
+ toast.error("Đã xảy ra lỗi không mong muốn.");
198
+ }
169
199
  }
170
200
  });
171
201
  // Hàm gọi khi người dùng muốn xóa một bản ghi
@@ -349,7 +379,7 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
349
379
  /> */}
350
380
  {isLoading ? (
351
381
  <TableBody>
352
- <TableRowsLoader rowsNum={10} colsNum={columns.length + 3} />
382
+ <TableRowsLoader rowsNum={10} colsNum={1} />
353
383
  </TableBody>
354
384
  ) : (
355
385
  <TableBody>
@@ -8,7 +8,7 @@ import { useEffect } from "react";
8
8
 
9
9
  import { URL_APPLICATION, URL_APPLICATION_API } from "@/constants";
10
10
  import { yupResolver } from "@hookform/resolvers/yup";
11
- import { useMutation, useQueryClient } from "@tanstack/react-query";
11
+ import { useMutation, useQueryClient } from "react-query";
12
12
  import { toast } from "react-toastify";
13
13
  import { saveDataToTable } from "../../api";
14
14
  import FormField from "./FormField";
@@ -91,30 +91,35 @@ function EditorForm({ fields, submitRef }) {
91
91
  }
92
92
  }, [selectedEditItem]);
93
93
 
94
- const saveMutation = useMutation({
95
- mutationFn: saveDataToTable,
94
+ const saveMutation = useMutation(saveDataToTable, {
96
95
  onSuccess: ({ status = false, message = " Có lỗi xảy ra !" }, { data: { Id } }) => {
97
96
  if (status) {
98
- toast.success(URL_APPLICATION_API ? message : Id === 0 ? "Thêm thành công!" : "Cập nhật thành công!");
99
- queryClient.invalidateQueries({ queryKey: [tableName] });
97
+ if (URL_APPLICATION_API) {
98
+ toast.success(message);
99
+ } else {
100
+ toast.success(Id == 0 ? "Thêm thành công!" : "Cập nhật thành công!");
101
+ }
102
+ queryClient.invalidateQueries(tableName);
100
103
  setOpenEditorDialog(false);
101
104
  } else {
105
+ console.log(status, Id);
102
106
  toast.error(message);
103
107
  }
104
108
  },
105
109
  onError: (error) => {
106
- const errors = error?.response?.data?.errors;
107
- if (errors) {
110
+ if (error.response && error.response.data && error.response.data.errors) {
111
+ const errors = error.response.data.errors;
112
+ // Chuyển đổi đối tượng lỗi thành chuỗi để hiển thị
108
113
  const errorMessages = Object.entries(errors)
109
114
  .map(([field, messages]) => `${field}: ${messages.join(", ")}`)
110
115
  .join("\n");
111
116
  toast.error(errorMessages);
112
117
  } else {
118
+ // Nếu lỗi không theo định dạng mong đợi, hiển thị thông tin lỗi chung
113
119
  toast.error("Đã xảy ra lỗi không mong muốn.");
114
120
  }
115
121
  }
116
122
  });
117
-
118
123
  const onSubmit = (data) => {
119
124
  fields.reduce((data, { type, field, datas, keyValueLabel, keyValue, keyLabel }) => {
120
125
  if (type === "date") {
@@ -44,7 +44,7 @@ export const TableRowRenderSM = ({
44
44
 
45
45
  const theme = useTheme();
46
46
  const downXl = useMediaQuery(theme.breakpoints.down("xl"));
47
-
47
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
48
48
  return (
49
49
  <TableRow hover key={row[selectedField]} selected={selected}>
50
50
  <TableCell
@@ -77,17 +77,54 @@ export const TableRowRenderSM = ({
77
77
  >
78
78
  {/* Hiển thị các cột dữ liệu */}
79
79
  <Grid container spacing={1}>
80
- {columns.map(
81
- ({ field, label, type = "text", valueGetter = (row) => row[field], renderCell, valueFormat = (e) => e }) => {
82
- const value = renderCell ? renderCell(row) : valueFormat(valueGetter(row));
83
- return (
84
- <Grid size={12} key={`${row[selectedField]}-${field}`}>
85
- <strong>{label}: </strong> {value}
86
- </Grid>
87
- );
88
- }
89
- )}
80
+ {columns.map(({ field, label, valueGetter = (row) => row[field], renderCell, valueFormat = (e) => e }) => {
81
+ const rawValue = renderCell ? renderCell(row) : valueFormat(valueGetter(row));
82
+
83
+ return (
84
+ <Grid
85
+ key={`${row[selectedField]}-${field}`}
86
+ size={12}
87
+ sx={{
88
+ minWidth: 0,
89
+ maxWidth: "100%",
90
+ overflow: "hidden"
91
+ }}
92
+ >
93
+ <Typography
94
+ component="div"
95
+ variant="body2"
96
+ sx={{
97
+ lineHeight: 1.4,
98
+ wordBreak: "break-word",
99
+ overflowWrap: "anywhere",
100
+ whiteSpace: "normal"
101
+ }}
102
+ >
103
+ <Box component="span" fontWeight={600}>
104
+ {label}:
105
+ </Box>
106
+
107
+ <Box
108
+ sx={{
109
+ maxWidth: "100%",
110
+ overflow: "hidden",
111
+
112
+ // 🔥 ÉP CO TOÀN BỘ COMPONENT CON
113
+ "& *": {
114
+ maxWidth: "100%",
115
+ minWidth: 0,
116
+ boxSizing: "border-box"
117
+ }
118
+ }}
119
+ >
120
+ {rawValue}
121
+ </Box>
122
+ </Typography>
123
+ </Grid>
124
+ );
125
+ })}
90
126
  </Grid>
127
+
91
128
  {/* Công tắc trạng thái */}
92
129
  {!disableStatus && canEdit && (
93
130
  <Box sx={{ mt: 1 }}>
@@ -100,74 +137,138 @@ export const TableRowRenderSM = ({
100
137
  )}
101
138
 
102
139
  {/* Các nút thao tác */}
103
- <Box
104
- mt={2}
105
- pt={1}
106
- display="flex"
107
- gap={1}
108
- flexWrap="wrap"
109
- justifyContent="flex-end"
110
- borderTop="1px solid"
111
- borderColor="divider"
112
- >
140
+ <Box mt={2} pt={1} borderTop="1px solid" borderColor="divider" display="flex" flexDirection="column" gap={1}>
113
141
  {!disableCellThaoTac && (
114
142
  <>
115
- {!disableEdit && canEdit && (
116
- <Button
117
- size="small"
118
- variant="contained"
119
- color="primary"
120
- onClick={() => onEdit(row)}
121
- startIcon={<EditOutlined />}
122
- sx={modernButtonStyle}
123
- >
124
- Cập nhật
125
- </Button>
126
- )}
127
-
128
- {canView && !tableActions?.some(({ permissionType }) => permissionType === "view") && (
129
- <Button
130
- size="small"
131
- variant="outlined"
132
- color="info"
133
- onClick={() => onView(row)}
134
- startIcon={<RemoveRedEyeOutlinedIcon />}
135
- sx={modernButtonStyle}
136
- >
137
- Xem
138
- </Button>
139
- )}
140
-
141
- {!disableDelete && canDelete && (
142
- <Button
143
- size="small"
144
- variant="outlined"
145
- color="error"
146
- onClick={() => onDelete(row[selectedField])}
147
- startIcon={<DeleteOutlineIcon />}
148
- sx={modernButtonStyle}
149
- >
150
- Xóa
151
- </Button>
152
- )}
153
-
154
- {tableActionsOnTable.map(
155
- ({ title, size, onClick, element, visible = true }, idx) =>
156
- (typeof visible === "function" ? visible(row) : visible) && (
157
- <Button
158
- key={idx}
159
- size="small"
160
- variant="outlined"
161
- onClick={() => onClick(row)}
162
- startIcon={element}
163
- sx={modernButtonStyle}
164
- >
165
- {title === "xem chi tiết" ? "Xem" : title}
166
- </Button>
167
- )
168
- )}
169
-
170
- <MoreMenu actions={tableActionsOnMoreMenu} data={row} />
143
+ <Box display="grid" gridTemplateColumns={isMobile ? "repeat(3, 1fr)" : "auto"} gap={1}>
144
+ {!disableEdit && canEdit && (
145
+ <Button
146
+ fullWidth
147
+ size={isMobile ? "small" : "medium"}
148
+ variant="contained"
149
+ color="primary"
150
+ onClick={() => onEdit(row)}
151
+ startIcon={<EditOutlined />}
152
+ sx={{
153
+ ...modernButtonStyle,
154
+
155
+ // compact mobile
156
+ minHeight: 36,
157
+ paddingX: 1,
158
+
159
+ // icon - text spacing
160
+ gap: 0.5,
161
+ "& .MuiButton-startIcon": {
162
+ marginRight: 0.5,
163
+ marginLeft: 0
164
+ },
165
+ "& .MuiButton-startIcon svg": {
166
+ fontSize: 18
167
+ }
168
+ }}
169
+ >
170
+ Cập nhật
171
+ </Button>
172
+ )}
173
+
174
+ {canView && !tableActions?.some(({ permissionType }) => permissionType === "view") && (
175
+ <Button
176
+ fullWidth
177
+ size={isMobile ? "small" : "medium"}
178
+ variant="outlined"
179
+ color="info"
180
+ onClick={() => onView(row)}
181
+ startIcon={<RemoveRedEyeOutlinedIcon />}
182
+ sx={{
183
+ ...modernButtonStyle,
184
+
185
+ // compact mobile
186
+ minHeight: 36,
187
+ paddingX: 1,
188
+
189
+ // icon - text spacing
190
+ gap: 0.5,
191
+ "& .MuiButton-startIcon": {
192
+ marginRight: 0.5,
193
+ marginLeft: 0
194
+ },
195
+ "& .MuiButton-startIcon svg": {
196
+ fontSize: 18
197
+ }
198
+ }}
199
+ >
200
+ Xem
201
+ </Button>
202
+ )}
203
+
204
+ {!disableDelete && canDelete && (
205
+ <Button
206
+ fullWidth
207
+ size={isMobile ? "small" : "medium"}
208
+ variant="outlined"
209
+ color="error"
210
+ onClick={() => onDelete(row[selectedField])}
211
+ startIcon={<DeleteOutlineIcon />}
212
+ sx={{
213
+ ...modernButtonStyle,
214
+
215
+ // compact mobile
216
+ minHeight: 36,
217
+ paddingX: 1,
218
+
219
+ // icon - text spacing
220
+ gap: 0.5,
221
+ "& .MuiButton-startIcon": {
222
+ marginRight: 0.5,
223
+ marginLeft: 0
224
+ },
225
+ "& .MuiButton-startIcon svg": {
226
+ fontSize: 18
227
+ }
228
+ }}
229
+ >
230
+ Xóa
231
+ </Button>
232
+ )}
233
+
234
+ {tableActionsOnTable.map(
235
+ ({ title, onClick, element, visible = true }, idx) =>
236
+ (typeof visible === "function" ? visible(row) : visible) && (
237
+ <Button
238
+ key={idx}
239
+ fullWidth
240
+ size="small"
241
+ variant="outlined"
242
+ onClick={() => onClick(row)}
243
+ startIcon={element}
244
+ sx={{
245
+ ...modernButtonStyle,
246
+
247
+ // compact mobile
248
+ minHeight: 36,
249
+ paddingX: 1,
250
+
251
+ // icon - text spacing
252
+ gap: 0.5,
253
+ "& .MuiButton-startIcon": {
254
+ marginRight: 0.5,
255
+ marginLeft: 0
256
+ },
257
+ "& .MuiButton-startIcon svg": {
258
+ fontSize: 18
259
+ }
260
+ }}
261
+ >
262
+ {title === "xem chi tiết" ? "Xem" : title}
263
+ </Button>
264
+ )
265
+ )}
266
+ </Box>
267
+
268
+ {/* More menu luôn nằm riêng 1 hàng */}
269
+ <Box display="flex" justifyContent="flex-end">
270
+ <MoreMenu actions={tableActionsOnMoreMenu} data={row} />
271
+ </Box>
171
272
  </>
172
273
  )}
173
274
  </Box>
@@ -180,7 +281,7 @@ const modernButtonStyle = {
180
281
  fontWeight: 400,
181
282
  fontSize: "0.65rem",
182
283
  borderRadius: 2,
183
- minWidth: 90,
284
+ minWidth: 0,
184
285
  textTransform: "none",
185
286
  px: 1,
186
287
  py: 0.5,