trithuc-mvc-react 3.3.7 → 3.3.9

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
@@ -235,6 +235,17 @@ export const exportExcel = async ({ tableName, data }) => {
235
235
  };
236
236
 
237
237
  export const uploadFile = async (formData) => {
238
+ // 1. Lấy URL hiện tại
239
+ const currentUrl = window.location.href;
240
+
241
+ // 2. Append vào formData
242
+ formData.append("currentUrl", currentUrl);
243
+
244
+ // 3. Log tất cả key-value trong FormData để kiểm tra
245
+ // console.log("===== FormData Contents =====");
246
+ // formData.forEach((value, key) => {
247
+ // console.log(key, value);
248
+ // });
238
249
  if (!apiUrl) {
239
250
  const res = await api.post(`${getBaseUrl()}/Handler/fileUploader.ashx`, formData, {
240
251
  headers: {
@@ -46,15 +46,38 @@ const DataTable = ({ multipleActions = [], page, setPage = () => {}, disableEdit
46
46
  const [rowsPerPage, setRowsPerPage] = useState(defaultRowsPerPage);
47
47
  const [openDialog, setOpenDialog] = useState(false);
48
48
  const [deleteId, setDeleteId] = useState(null);
49
+ const defaultSortColumn = columns.find((c) => c.defaultSort);
50
+
51
+ const [orderBy, setOrderBy] = useState(defaultSortColumn?.field || "");
52
+ const [order, setOrder] = useState(defaultSortColumn?.defaultOrder || "asc");
53
+ useEffect(() => {
54
+ const defaultSortColumn = columns.find((c) => c.defaultSort);
55
+ if (defaultSortColumn) {
56
+ setOrderBy(defaultSortColumn.field);
57
+ setOrder(defaultSortColumn.defaultOrder || "asc");
58
+ }
59
+ }, [columns]);
60
+
61
+ const handleRequestSort = (event, property) => {
62
+ const isAsc = orderBy === property && order === "asc";
63
+ setOrder(isAsc ? "desc" : "asc");
64
+ setOrderBy(property);
65
+ };
49
66
 
50
67
  const { data, isLoading } = useQuery({
51
- queryKey: [tableName, page, rowsPerPage, dataSearch],
68
+ queryKey: [tableName, page, rowsPerPage, dataSearch, order, orderBy],
52
69
  queryFn: () =>
53
70
  getDatasFromTable({
54
71
  tableName: tableName,
55
72
  page: page + 1,
56
73
  pageSize: rowsPerPage,
57
- data: dataSearch
74
+ data: {
75
+ ...dataSearch,
76
+ ...(orderBy && {
77
+ SortBy: orderBy,
78
+ IsDescending: order === "desc"
79
+ })
80
+ }
58
81
  }),
59
82
  defaultQueryOptions,
60
83
  // keepPreviousData: true,
@@ -73,6 +96,7 @@ const DataTable = ({ multipleActions = [], page, setPage = () => {}, disableEdit
73
96
  }
74
97
  }
75
98
  });
99
+
76
100
  const changeStatusMutation = useMutation(changeStatusDataToTable, {
77
101
  onSuccess: ({ status = false, message = " Có lỗi xảy ra !" }) => {
78
102
  if (URL_APPLICATION_API) {
@@ -253,6 +277,7 @@ const DataTable = ({ multipleActions = [], page, setPage = () => {}, disableEdit
253
277
  }
254
278
  setOpenDeleteMultipleDialog(false); // Đóng Dialog sau khi chọn
255
279
  };
280
+
256
281
  const theme = useTheme();
257
282
  const downXL = useMediaQuery(theme.breakpoints.down("xl"));
258
283
 
@@ -283,7 +308,11 @@ const DataTable = ({ multipleActions = [], page, setPage = () => {}, disableEdit
283
308
  onSelectAllClick={handleSelectAllClick}
284
309
  numSelected={selectedItems?.length}
285
310
  rowCount={rows.length}
311
+ order={order}
312
+ orderBy={orderBy}
313
+ onRequestSort={handleRequestSort}
286
314
  />
315
+
287
316
  {isLoading ? (
288
317
  <TableBody>
289
318
  <TableRowsLoader rowsNum={15} colsNum={columns.length + 3} />
@@ -45,15 +45,38 @@ const DataTableSM = ({ multipleActions = [], page, setPage = () => {}, disableEd
45
45
  const [rowsPerPage, setRowsPerPage] = useState(defaultRowsPerPage);
46
46
  const [openDialog, setOpenDialog] = useState(false);
47
47
  const [deleteId, setDeleteId] = useState(null);
48
+ const defaultSortColumn = columns.find((c) => c.defaultSort);
49
+
50
+ const [orderBy, setOrderBy] = useState(defaultSortColumn?.field || "");
51
+ const [order, setOrder] = useState(defaultSortColumn?.defaultOrder || "asc");
52
+ useEffect(() => {
53
+ const defaultSortColumn = columns.find((c) => c.defaultSort);
54
+ if (defaultSortColumn) {
55
+ setOrderBy(defaultSortColumn.field);
56
+ setOrder(defaultSortColumn.defaultOrder || "asc");
57
+ }
58
+ }, [columns]);
59
+
60
+ const handleRequestSort = (event, property) => {
61
+ const isAsc = orderBy === property && order === "asc";
62
+ setOrder(isAsc ? "desc" : "asc");
63
+ setOrderBy(property);
64
+ };
48
65
 
49
66
  const { data, isLoading } = useQuery({
50
- queryKey: [tableName, page, rowsPerPage, dataSearch],
67
+ queryKey: [tableName, page, rowsPerPage, dataSearch, order, orderBy],
51
68
  queryFn: () =>
52
69
  getDatasFromTable({
53
70
  tableName: tableName,
54
71
  page: page + 1,
55
72
  pageSize: rowsPerPage,
56
- data: dataSearch
73
+ data: {
74
+ ...dataSearch,
75
+ ...(orderBy && {
76
+ SortBy: orderBy,
77
+ IsDescending: order === "desc"
78
+ })
79
+ }
57
80
  }),
58
81
  defaultQueryOptions,
59
82
  // keepPreviousData: true,
@@ -1,15 +1,22 @@
1
- import { Checkbox, TableHead as MuiTableHead, TableCell, TableRow, useMediaQuery, useTheme } from "@mui/material";
1
+ import { Checkbox, TableHead as MuiTableHead, TableCell, TableRow, TableSortLabel, useMediaQuery, useTheme } from "@mui/material";
2
2
  import { usePermission } from "../../hooks";
3
3
  import { useDataTable } from "./hooks";
4
- export function TableHead({ numSelected, rowCount, onSelectAllClick, headLabel }) {
4
+
5
+ export function TableHead({ numSelected, rowCount, onSelectAllClick, headLabel, order, orderBy, onRequestSort }) {
5
6
  const { disableStatus, disableCellThaoTac, tableName } = useDataTable();
6
7
  const { canEdit } = usePermission(tableName);
7
8
 
8
9
  const theme = useTheme();
9
10
  const downXl = useMediaQuery(theme.breakpoints.down("xl"));
11
+
12
+ const createSortHandler = (property) => (event) => {
13
+ onRequestSort(event, property);
14
+ };
15
+
10
16
  return (
11
17
  <MuiTableHead sx={{ height: 56, visibility: numSelected > 0 ? "hidden" : "visible" }}>
12
18
  <TableRow>
19
+ {/* Checkbox chọn tất cả */}
13
20
  <TableCell padding="checkbox">
14
21
  <Checkbox
15
22
  indeterminate={numSelected > 0 && numSelected < rowCount}
@@ -18,13 +25,35 @@ export function TableHead({ numSelected, rowCount, onSelectAllClick, headLabel }
18
25
  onChange={onSelectAllClick}
19
26
  />
20
27
  </TableCell>
28
+
29
+ {/* STT không sort */}
21
30
  <TableCell sx={{ textAlign: "center" }}>STT</TableCell>
31
+
32
+ {/* Các cột động */}
22
33
  {headLabel.map((headCell) => (
23
- <TableCell key={headCell.field} align={headCell.alignRight ? "right" : "left"} sx={{ textAlign: "center" }}>
24
- {headCell.label}
34
+ <TableCell
35
+ key={headCell.field}
36
+ align={headCell.alignRight ? "right" : "left"}
37
+ sortDirection={orderBy === headCell.field ? order : false}
38
+ >
39
+ {headCell.sortable ? (
40
+ <TableSortLabel
41
+ active={orderBy === headCell.field}
42
+ direction={orderBy === headCell.field ? order : "asc"}
43
+ onClick={createSortHandler(headCell.field)}
44
+ >
45
+ {headCell.label}
46
+ </TableSortLabel>
47
+ ) : (
48
+ headCell.label
49
+ )}
25
50
  </TableCell>
26
51
  ))}
52
+
53
+ {/* Kích hoạt */}
27
54
  {!disableStatus && canEdit && <TableCell sx={{ textAlign: "center" }}>Kích hoạt</TableCell>}
55
+
56
+ {/* Thao tác */}
28
57
  {!disableCellThaoTac && <TableCell sx={{ minWidth: "136px", textAlign: "center" }}>Thao tác</TableCell>}
29
58
  </TableRow>
30
59
  </MuiTableHead>
@@ -1,12 +1,13 @@
1
1
  import { EditOutlined } from "@mui/icons-material";
2
2
  import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
3
3
  import RemoveRedEyeOutlinedIcon from "@mui/icons-material/RemoveRedEyeOutlined";
4
- import { Box, Button, Grid, Switch, TableCell, TableRow, Toolbar, useMediaQuery } from "@mui/material";
4
+ import { Box, Button, Grid, Switch, TableCell, TableRow, Toolbar, Typography, useMediaQuery } from "@mui/material";
5
5
  import { styled, useTheme } from "@mui/material/styles";
6
6
  import { useMemo } from "react";
7
7
  import { usePermission } from "../../hooks";
8
8
  import MoreMenu from "../MoreMenu";
9
9
  import { useDataTable } from "./hooks";
10
+ import { fontSize } from "@mui/system";
10
11
 
11
12
  export const TableRowRenderSM = ({
12
13
  index,
@@ -56,105 +57,139 @@ export const TableRowRenderSM = ({
56
57
  overflowWrap: "break-word"
57
58
  }}
58
59
  >
59
- {/* Hiển thị các cột dữ liệu */}
60
- {columns.map(({ field, label, type = "text", valueGetter = (row) => row[field], renderCell, valueFormat = (e) => e }) => {
61
- const value = renderCell ? renderCell(row) : valueFormat(valueGetter(row));
62
- return (
63
- <div key={`${row[selectedField]}-${field}`}>
64
- <strong>{label}: </strong>
65
- {value}
66
- </div>
67
- );
68
- })}
69
-
70
- {/* Công tắc trạng thái */}
71
- {!disableStatus && canEdit && (
72
- <Box sx={{ mt: 1 }}>
73
- <Switch
74
- checked={row[statusKey]}
75
- onChange={() => onChangeStatus(row[selectedField])}
76
- inputProps={{ "aria-label": "controlled" }}
77
- />
78
- </Box>
79
- )}
60
+ <Box
61
+ display="flex"
62
+ flexDirection="column"
63
+ gap={1}
64
+ border="1px solid"
65
+ borderColor="divider"
66
+ borderRadius={2}
67
+ boxShadow={1}
68
+ p={2}
69
+ bgcolor="background.paper"
70
+ sx={{
71
+ transition: "all 0.2s ease",
72
+ "&:hover": {
73
+ boxShadow: 3,
74
+ borderColor: "primary.light"
75
+ }
76
+ }}
77
+ >
78
+ {/* Hiển thị các cột dữ liệu */}
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
+ )}
90
+ </Grid>
91
+ {/* Công tắc trạng thái */}
92
+ {!disableStatus && canEdit && (
93
+ <Box sx={{ mt: 1 }}>
94
+ <Switch
95
+ checked={row[statusKey]}
96
+ onChange={() => onChangeStatus(row[selectedField])}
97
+ inputProps={{ "aria-label": "controlled" }}
98
+ />
99
+ </Box>
100
+ )}
80
101
 
81
- {/* Các nút thao tác */}
82
- <Grid container spacing={0.5} sx={{ width: "100%", pt: 2, pb: 2 }}>
83
- {!disableCellThaoTac && (
84
- <>
85
- {!disableEdit && canEdit && (
86
- <Grid size={4}>
102
+ {/* 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
+ >
113
+ {!disableCellThaoTac && (
114
+ <>
115
+ {!disableEdit && canEdit && (
87
116
  <Button
88
- fullWidth
89
117
  size="small"
90
- variant="outlined"
118
+ variant="contained"
91
119
  color="primary"
92
120
  onClick={() => onEdit(row)}
93
- startIcon={<EditOutlined fontSize="inherit" />}
94
- sx={buttonStyle}
121
+ startIcon={<EditOutlined />}
122
+ sx={modernButtonStyle}
95
123
  >
96
124
  Cập nhật
97
125
  </Button>
98
- </Grid>
99
- )}
100
- {canView && !tableActions?.some(({ permissionType }) => permissionType === "view") && (
101
- <Grid size={4}>
126
+ )}
127
+
128
+ {canView && !tableActions?.some(({ permissionType }) => permissionType === "view") && (
102
129
  <Button
103
- fullWidth
104
130
  size="small"
105
131
  variant="outlined"
106
132
  color="info"
107
133
  onClick={() => onView(row)}
108
134
  startIcon={<RemoveRedEyeOutlinedIcon />}
109
- sx={buttonStyle}
135
+ sx={modernButtonStyle}
110
136
  >
111
137
  Xem
112
138
  </Button>
113
- </Grid>
114
- )}
115
- {!disableDelete && canDelete && (
116
- <Grid size={4}>
139
+ )}
140
+
141
+ {!disableDelete && canDelete && (
117
142
  <Button
118
- fullWidth
119
143
  size="small"
120
144
  variant="outlined"
121
145
  color="error"
122
146
  onClick={() => onDelete(row[selectedField])}
123
- startIcon={<DeleteOutlineIcon fontSize="inherit" />}
124
- sx={buttonStyle}
147
+ startIcon={<DeleteOutlineIcon />}
148
+ sx={modernButtonStyle}
125
149
  >
126
150
  Xóa
127
151
  </Button>
128
- </Grid>
129
- )}
130
- {tableActionsOnTable.map(
131
- ({ title, size, onClick, element, visible = true }, idx) =>
132
- (typeof visible === "function" ? visible(row) : visible) && (
133
- <Grid key={row.Id} size={4}>
152
+ )}
153
+
154
+ {tableActionsOnTable.map(
155
+ ({ title, size, onClick, element, visible = true }, idx) =>
156
+ (typeof visible === "function" ? visible(row) : visible) && (
134
157
  <Button
135
- fullWidth
136
158
  key={idx}
137
159
  size="small"
138
160
  variant="outlined"
139
161
  onClick={() => onClick(row)}
140
162
  startIcon={element}
141
- sx={{
142
- ...buttonStyle
143
- }}
163
+ sx={modernButtonStyle}
144
164
  >
145
165
  {title === "xem chi tiết" ? "Xem" : title}
146
166
  </Button>
147
- </Grid>
148
- )
149
- )}
150
- <MoreMenu actions={tableActionsOnMoreMenu} data={row} />
151
- </>
152
- )}
153
- </Grid>
167
+ )
168
+ )}
169
+
170
+ <MoreMenu actions={tableActionsOnMoreMenu} data={row} />
171
+ </>
172
+ )}
173
+ </Box>
174
+ </Box>
154
175
  </TableCell>
155
176
  </TableRow>
156
177
  );
157
178
  };
179
+ const modernButtonStyle = {
180
+ fontWeight: 400,
181
+ fontSize: "0.7rem",
182
+ borderRadius: 2,
183
+ minWidth: 90,
184
+ textTransform: "none",
185
+ px: 1,
186
+ py: 0.5,
187
+ boxShadow: "none",
188
+ transition: "all 0.2s ease-in-out",
189
+ "&:hover": {
190
+ boxShadow: "0px 2px 6px rgba(0,0,0,0.1)"
191
+ }
192
+ };
158
193
 
159
194
  // Button style dùng chung
160
195
  const buttonStyle = {
@@ -67,7 +67,8 @@ DataManagement.propTypes = {
67
67
  titleAddButton: PropTypes.string,
68
68
  thongKe: PropTypes.object,
69
69
  bieuDo: PropTypes.object,
70
- disableHead: PropTypes.bool
70
+ disableHead: PropTypes.bool,
71
+ disableSearch: PropTypes.bool
71
72
  };
72
73
  const getDefaultValues = (filters = []) => {
73
74
  const defaultValues = {};
@@ -112,7 +113,8 @@ function DataManagement({
112
113
  titleAddButton = "Thêm mới",
113
114
  thongKe,
114
115
  bieuDo,
115
- disableHead = false
116
+ disableHead = false,
117
+ disableSearch = false
116
118
  }) {
117
119
  const [openEditorDialog, setOpenEditorDialog] = useState(false);
118
120
  const [selectedEditItem, setSelectedEditItem] = useState(null);
@@ -189,7 +191,8 @@ function DataManagement({
189
191
  titleAddButton,
190
192
  thongKe,
191
193
  bieuDo,
192
- disableHead
194
+ disableHead,
195
+ disableSearch
193
196
  };
194
197
  }, [
195
198
  reserPage,
@@ -211,7 +214,8 @@ function DataManagement({
211
214
  defaultRowsPerPage,
212
215
  thongKe,
213
216
  bieuDo,
214
- disableHead
217
+ disableHead,
218
+ disableSearch
215
219
  ]);
216
220
  const methods = useForm({ defaultValues: getDefaultValues(filters) });
217
221
  const { reset, setValue } = methods;
@@ -426,13 +430,15 @@ function DataManagement({
426
430
  {tabPanel}
427
431
  {viewMode === "table" && (
428
432
  <>
429
- <FilterGod
430
- tableName={tableName}
431
- filters={filters}
432
- filterButtons={filterButtons}
433
- elementSize={elementSize}
434
- setPage={setPage}
435
- />
433
+ {!disableSearch && (
434
+ <FilterGod
435
+ tableName={tableName}
436
+ filters={filters}
437
+ filterButtons={filterButtons}
438
+ elementSize={elementSize}
439
+ setPage={setPage}
440
+ />
441
+ )}
436
442
  {backParentNavigator}
437
443
  {!isSmallScreen ? (
438
444
  <DataTable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trithuc-mvc-react",
3
- "version": "3.3.7",
3
+ "version": "3.3.9",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
package/utils/storage.js CHANGED
@@ -142,11 +142,9 @@ export const storeSetCurrentInfor = (data) => {
142
142
  const storedThamSo = storedUserInfo?.ThamSoHeThong?.find((item) => item.TenThamSo === "DEFAULT_ROWS_PER_PAGE");
143
143
  const newThamSo = data?.ThamSoHeThong?.find((item) => item.TenThamSo === "DEFAULT_ROWS_PER_PAGE");
144
144
 
145
- // Kiểm tra và cập nhật nếu thông tin mới từ API
146
145
  if (newThamSo) {
147
146
  const isUpdated = !storedThamSo || new Date(newThamSo.ModifiedDate) > new Date(storedThamSo?.ModifiedDate);
148
147
  if (isUpdated) {
149
- // Xóa dữ liệu `rowsPerPage` cũ từ localStorage nếu cần
150
148
  Object.keys(localStorage).forEach((key) => {
151
149
  if (key.endsWith("rowsPerPage")) {
152
150
  localStorage.removeItem(key);
@@ -155,10 +153,18 @@ export const storeSetCurrentInfor = (data) => {
155
153
  }
156
154
  }
157
155
 
158
- // Mã hóa dữ liệu và lưu vào localStorage
159
156
  try {
160
157
  const encryptedData = encryptData(data);
161
158
  localStorage.setItem("GetCurrentInfor", encryptedData);
159
+
160
+ // 🔹 Bắn event cho từng tham số hệ thống
161
+ data?.ThamSoHeThong?.forEach((item) => {
162
+ window.dispatchEvent(
163
+ new CustomEvent("GetCurrentInfor-changed", {
164
+ detail: { key: item.TenThamSo, value: item.GiaTri }
165
+ })
166
+ );
167
+ });
162
168
  } catch (error) {
163
169
  console.error("Error encrypting or saving data to localStorage:", error);
164
170
  }