trithuc-mvc-react 1.0.0
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 +57 -0
- package/components/DataManagement/DataTable.jsx +186 -0
- package/components/DataManagement/EditorDialog.jsx +64 -0
- package/components/DataManagement/EditorForm.jsx +147 -0
- package/components/DataManagement/ExportExcelButton.jsx +27 -0
- package/components/DataManagement/FilterElement.jsx +102 -0
- package/components/DataManagement/FilterGod.jsx +83 -0
- package/components/DataManagement/FormField.jsx +240 -0
- package/components/DataManagement/TableHead.jsx +26 -0
- package/components/DataManagement/TableRowRender.jsx +82 -0
- package/components/DataManagement/TableToolbar.jsx +57 -0
- package/components/DataManagement/context.js +4 -0
- package/components/DataManagement/hooks.js +19 -0
- package/components/DataManagement/index.jsx +107 -0
- package/components/date/DateRangePicker.jsx +143 -0
- package/components/date/StaticDateRangePicker.jsx +498 -0
- package/components/date/index.js +1 -0
- package/components/index.js +4 -0
- package/components/table/TablePagination.jsx +34 -0
- package/components/table/TableRowsLoader.jsx +15 -0
- package/components/table/index.js +2 -0
- package/constants/index.js +1 -0
- package/helpers/data-table.js +23 -0
- package/helpers/index.js +1 -0
- package/index.js +3 -0
- package/package.json +15 -0
package/api/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
const api = axios.create({
|
|
3
|
+
baseURL: "/",
|
|
4
|
+
transitional: {
|
|
5
|
+
silentJSONParsing: false
|
|
6
|
+
},
|
|
7
|
+
responseType: "json"
|
|
8
|
+
});
|
|
9
|
+
export default api;
|
|
10
|
+
|
|
11
|
+
export const getDatasFromTable = async ({ tableName, page, pageSize, data }) => {
|
|
12
|
+
const res = await api.get(`/Admin/${tableName}/LoadData`, {
|
|
13
|
+
params: {
|
|
14
|
+
json: JSON.stringify(data),
|
|
15
|
+
page,
|
|
16
|
+
pageSize
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return res.data;
|
|
20
|
+
};
|
|
21
|
+
export const getDataFromTable = async ({ tableName, id }) => {
|
|
22
|
+
const res = await api.get(`/Admin/${tableName}/GetDetail`, {
|
|
23
|
+
params: {
|
|
24
|
+
id
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return res.data;
|
|
28
|
+
};
|
|
29
|
+
export const deleteDataFromTable = async ({ tableName, id }) => {
|
|
30
|
+
const res = await api.post(`/Admin/${tableName}/Delete`, {
|
|
31
|
+
id
|
|
32
|
+
});
|
|
33
|
+
return res.data;
|
|
34
|
+
};
|
|
35
|
+
export const deleteMultipleDataFromTable = async ({ tableName, ids }) => {
|
|
36
|
+
const res = await api.post(`/Admin/${tableName}/DeleteMulti`, {
|
|
37
|
+
ids
|
|
38
|
+
});
|
|
39
|
+
return res.data;
|
|
40
|
+
};
|
|
41
|
+
export const saveDataToTable = async ({ tableName, data }) => {
|
|
42
|
+
const res = await api.post(`/Admin/${tableName}/SaveData`, {
|
|
43
|
+
json: JSON.stringify({ ...data })
|
|
44
|
+
});
|
|
45
|
+
return res.data;
|
|
46
|
+
};
|
|
47
|
+
export const changeStatusDataToTable = async ({ tableName, id }) => {
|
|
48
|
+
const res = await api.post(`/Admin/${tableName}/ChangeStatus`, {
|
|
49
|
+
id
|
|
50
|
+
});
|
|
51
|
+
return res.data;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const exportExcel = async ({ tableName }) => {
|
|
55
|
+
const res = await api.get(`/Admin/${tableName}/ExportData`);
|
|
56
|
+
return res.data;
|
|
57
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Table, TableBody, TableContainer } from "@mui/material";
|
|
2
|
+
import TablePaginationCustom from "../table/TablePagination";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { TableHead } from "./TableHead";
|
|
6
|
+
import { useMutation, useQuery, useQueryClient } from "react-query";
|
|
7
|
+
import { changeStatusDataToTable, deleteDataFromTable, deleteMultipleDataFromTable, getDatasFromTable } from "../../api";
|
|
8
|
+
import TableRowsLoader from "../table/TableRowsLoader";
|
|
9
|
+
import { toast } from "react-toastify";
|
|
10
|
+
import { useConfirm } from "material-ui-confirm";
|
|
11
|
+
|
|
12
|
+
import { TableRowRender } from "./TableRowRender";
|
|
13
|
+
import { TableToolbar } from "./TableToolbar";
|
|
14
|
+
import { useDataTable, usePermission } from "./hooks";
|
|
15
|
+
|
|
16
|
+
const DataTable = () => {
|
|
17
|
+
const { tableName, selectedField, columns, dataSearch, setOpenEditorDialog, setSelectedEditItem } = useDataTable();
|
|
18
|
+
const { setPermission, Permission } = usePermission();
|
|
19
|
+
const queryClient = useQueryClient();
|
|
20
|
+
const confirm = useConfirm();
|
|
21
|
+
const [selected, setSelected] = useState([]);
|
|
22
|
+
const [page, setPage] = useState(0);
|
|
23
|
+
const [rowsPerPage, setRowsPerPage] = useState(5);
|
|
24
|
+
const { data, isLoading } = useQuery({
|
|
25
|
+
queryKey: [tableName, page, rowsPerPage, dataSearch],
|
|
26
|
+
queryFn: () =>
|
|
27
|
+
getDatasFromTable({
|
|
28
|
+
tableName: tableName,
|
|
29
|
+
page: page + 1,
|
|
30
|
+
pageSize: rowsPerPage,
|
|
31
|
+
data: dataSearch
|
|
32
|
+
}),
|
|
33
|
+
keepPreviousData: true,
|
|
34
|
+
onSuccess: ({ PermissionModel, status }) => {
|
|
35
|
+
if (!Permission && status) {
|
|
36
|
+
setPermission(PermissionModel);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const changeStatusMutation = useMutation(changeStatusDataToTable, {
|
|
41
|
+
onSuccess: () => {
|
|
42
|
+
toast.success("Thay đổi trạng thái thành công !");
|
|
43
|
+
queryClient.invalidateQueries({ queryKey: [tableName] });
|
|
44
|
+
},
|
|
45
|
+
onError: () => {
|
|
46
|
+
toast.error(" Có lỗi xảy ra !");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const deleteMutation = useMutation(deleteDataFromTable, {
|
|
50
|
+
onSuccess: ({ status }) => {
|
|
51
|
+
if (status) {
|
|
52
|
+
toast.success("Xóa thành công !");
|
|
53
|
+
} else {
|
|
54
|
+
toast.error(" Có lỗi xảy ra !");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
queryClient.invalidateQueries({ queryKey: [tableName] });
|
|
58
|
+
},
|
|
59
|
+
onError: () => {
|
|
60
|
+
toast.error(" Có lỗi xảy ra !");
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const deleteMultipleMutation = useMutation(deleteMultipleDataFromTable, {
|
|
64
|
+
onSuccess: () => {
|
|
65
|
+
toast.success("Xóa thành công !");
|
|
66
|
+
setSelected([]);
|
|
67
|
+
queryClient.invalidateQueries({ queryKey: [tableName] });
|
|
68
|
+
},
|
|
69
|
+
onError: () => {
|
|
70
|
+
toast.error(" Có lỗi xảy ra !");
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const handleDelete = (id) => {
|
|
75
|
+
confirm({ description: "Bạn có chắc chắn muốn xóa bản ghi này không?", title: "Xác nhận" })
|
|
76
|
+
.then(() => {
|
|
77
|
+
deleteMutation.mutate({
|
|
78
|
+
id,
|
|
79
|
+
tableName
|
|
80
|
+
});
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {});
|
|
83
|
+
};
|
|
84
|
+
const handleChangeStatus = (Id) => {
|
|
85
|
+
changeStatusMutation.mutate({
|
|
86
|
+
tableName,
|
|
87
|
+
id: Id
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const handlEdit = (item) => {
|
|
91
|
+
setOpenEditorDialog(true);
|
|
92
|
+
setSelectedEditItem(item);
|
|
93
|
+
};
|
|
94
|
+
const { rows, total } = useMemo(() => {
|
|
95
|
+
let rows = data?.data ?? [];
|
|
96
|
+
let total = data?.total ?? 0;
|
|
97
|
+
return {
|
|
98
|
+
rows: rows,
|
|
99
|
+
total
|
|
100
|
+
};
|
|
101
|
+
}, [data]);
|
|
102
|
+
|
|
103
|
+
const handleChangePage = (event, newPage) => {
|
|
104
|
+
setPage(newPage);
|
|
105
|
+
};
|
|
106
|
+
const isSelected = (Id) => selected.indexOf(Id) !== -1;
|
|
107
|
+
const handleSelect = (event, Id) => {
|
|
108
|
+
const selectedIndex = selected.indexOf(Id);
|
|
109
|
+
let newSelected = [];
|
|
110
|
+
|
|
111
|
+
if (selectedIndex === -1) {
|
|
112
|
+
newSelected = newSelected.concat(selected, Id);
|
|
113
|
+
} else if (selectedIndex === 0) {
|
|
114
|
+
newSelected = newSelected.concat(selected.slice(1));
|
|
115
|
+
} else if (selectedIndex === selected.length - 1) {
|
|
116
|
+
newSelected = newSelected.concat(selected.slice(0, -1));
|
|
117
|
+
} else if (selectedIndex > 0) {
|
|
118
|
+
newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setSelected(newSelected);
|
|
122
|
+
};
|
|
123
|
+
const handleSelectAllClick = (event) => {
|
|
124
|
+
if (event.target.checked) {
|
|
125
|
+
const newSelected = rows.map((n) => n[selectedField]);
|
|
126
|
+
setSelected(newSelected);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
setSelected([]);
|
|
130
|
+
};
|
|
131
|
+
const handleChangeRowsPerPage = (event) => {
|
|
132
|
+
setRowsPerPage(parseInt(event.target.value, 10));
|
|
133
|
+
setPage(0);
|
|
134
|
+
};
|
|
135
|
+
const handleDeleteMultiple = () => {
|
|
136
|
+
confirm({ description: `Bạn có chắc chắn muốn xóa ${selected?.length} bản ghi này không?`, title: "Xác nhận" })
|
|
137
|
+
.then(() => {
|
|
138
|
+
deleteMultipleMutation.mutate({
|
|
139
|
+
tableName,
|
|
140
|
+
ids: selected
|
|
141
|
+
});
|
|
142
|
+
})
|
|
143
|
+
.catch(() => {});
|
|
144
|
+
};
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<TableContainer sx={{ position: "relative" }}>
|
|
148
|
+
<TableToolbar
|
|
149
|
+
onSelectAllClick={handleSelectAllClick}
|
|
150
|
+
numSelected={selected?.length}
|
|
151
|
+
rowCount={rows.length}
|
|
152
|
+
onDeleteMultiple={handleDeleteMultiple}
|
|
153
|
+
/>
|
|
154
|
+
<Table className="border">
|
|
155
|
+
<TableHead headLabel={columns} onSelectAllClick={handleSelectAllClick} numSelected={selected?.length} rowCount={rows.length} />
|
|
156
|
+
{isLoading ? (
|
|
157
|
+
<TableRowsLoader rowsNum={5} colsNum={columns.length + 4} />
|
|
158
|
+
) : (
|
|
159
|
+
<TableBody>
|
|
160
|
+
{[...rows].map((row, index) => (
|
|
161
|
+
<TableRowRender
|
|
162
|
+
key={row.Id}
|
|
163
|
+
index={index}
|
|
164
|
+
row={row}
|
|
165
|
+
selected={isSelected(row[selectedField])}
|
|
166
|
+
onSelect={handleSelect}
|
|
167
|
+
onEdit={handlEdit}
|
|
168
|
+
onChangeStatus={handleChangeStatus}
|
|
169
|
+
onDelete={handleDelete}
|
|
170
|
+
/>
|
|
171
|
+
))}
|
|
172
|
+
</TableBody>
|
|
173
|
+
)}
|
|
174
|
+
</Table>
|
|
175
|
+
</TableContainer>
|
|
176
|
+
<TablePaginationCustom
|
|
177
|
+
count={total}
|
|
178
|
+
rowsPerPage={rowsPerPage}
|
|
179
|
+
page={page}
|
|
180
|
+
onPageChange={handleChangePage}
|
|
181
|
+
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
182
|
+
/>
|
|
183
|
+
</>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
export default DataTable;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Button from "@mui/material/Button";
|
|
2
|
+
|
|
3
|
+
import Dialog from "@mui/material/Dialog";
|
|
4
|
+
import DialogActions from "@mui/material/DialogActions";
|
|
5
|
+
import DialogContent from "@mui/material/DialogContent";
|
|
6
|
+
import DialogTitle from "@mui/material/DialogTitle";
|
|
7
|
+
import IconButton from "@mui/material/IconButton";
|
|
8
|
+
import CloseIcon from "@mui/icons-material/Close";
|
|
9
|
+
|
|
10
|
+
import { useEffect, useRef, useState } from "react";
|
|
11
|
+
import PropTypes from "prop-types";
|
|
12
|
+
|
|
13
|
+
import EditorForm from "./EditorForm";
|
|
14
|
+
import { usePermission } from "./hooks";
|
|
15
|
+
EditorDialog.propTypes = {
|
|
16
|
+
open: PropTypes.bool,
|
|
17
|
+
onClose: PropTypes.func,
|
|
18
|
+
defaultValues: PropTypes.objectOf({})
|
|
19
|
+
};
|
|
20
|
+
function EditorDialog({ open, onClose = () => {}, defaultValues = {}, fields = [] }) {
|
|
21
|
+
const [isDisableBtnSave, setIsDisableBtnSave] = useState(false);
|
|
22
|
+
const { canSave } = usePermission();
|
|
23
|
+
|
|
24
|
+
useEffect(() => {}, [defaultValues]);
|
|
25
|
+
|
|
26
|
+
const handleSave = async () => {
|
|
27
|
+
submitRef.current.click();
|
|
28
|
+
setIsDisableBtnSave(true);
|
|
29
|
+
|
|
30
|
+
setIsDisableBtnSave(false);
|
|
31
|
+
};
|
|
32
|
+
const submitRef = useRef();
|
|
33
|
+
return (
|
|
34
|
+
<Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth={true} scroll={'body'}>
|
|
35
|
+
<DialogTitle>
|
|
36
|
+
{defaultValues?.Id ? "Cập nhật" : "Thêm mới"}
|
|
37
|
+
<IconButton
|
|
38
|
+
aria-label="close"
|
|
39
|
+
onClick={onClose}
|
|
40
|
+
sx={{
|
|
41
|
+
position: "absolute",
|
|
42
|
+
right: 8,
|
|
43
|
+
top: 8,
|
|
44
|
+
color: (theme) => theme.palette.grey[500]
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<CloseIcon />
|
|
48
|
+
</IconButton>
|
|
49
|
+
</DialogTitle>
|
|
50
|
+
<DialogContent dividers={true}>
|
|
51
|
+
<EditorForm fields={fields} submitRef={submitRef} />
|
|
52
|
+
</DialogContent>
|
|
53
|
+
<DialogActions>
|
|
54
|
+
<Button onClick={onClose}>Đóng</Button>
|
|
55
|
+
{canSave && (
|
|
56
|
+
<Button onClick={handleSave} disabled={isDisableBtnSave}>
|
|
57
|
+
Lưu
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
</DialogActions>
|
|
61
|
+
</Dialog>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
export default EditorDialog;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Box } from "@mui/material";
|
|
2
|
+
import { FormProvider, useForm } from "react-hook-form";
|
|
3
|
+
import Grid from "@mui/material/Unstable_Grid2"; // Grid version 2
|
|
4
|
+
import PropTypes from "prop-types";
|
|
5
|
+
|
|
6
|
+
import { useEffect } from "react";
|
|
7
|
+
import moment from "moment/moment";
|
|
8
|
+
|
|
9
|
+
import { useMutation, useQueryClient } from "react-query";
|
|
10
|
+
import { saveDataToTable } from "../../api";
|
|
11
|
+
import { toast } from "react-toastify";
|
|
12
|
+
import { useDataTable } from "./hooks";
|
|
13
|
+
import FormField from "./FormField";
|
|
14
|
+
import { yupResolver } from "@hookform/resolvers/yup";
|
|
15
|
+
EditorForm.propTypes = {
|
|
16
|
+
fields: PropTypes.array
|
|
17
|
+
};
|
|
18
|
+
function EditorForm({ fields, elementSize = "medium", submitRef }) {
|
|
19
|
+
const queryClient = useQueryClient();
|
|
20
|
+
const { tableName, selectedEditItem, setOpenEditorDialog, validationSchema } = useDataTable();
|
|
21
|
+
|
|
22
|
+
const methods = useForm({ defaultValues: {}, resolver: yupResolver(validationSchema) });
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (selectedEditItem) {
|
|
26
|
+
methods.setValue("Id", selectedEditItem.Id);
|
|
27
|
+
|
|
28
|
+
fields.forEach(({ field, onChange, type, keyValue, keyValueLabel, defaultValue }) => {
|
|
29
|
+
if (type == "autocomplete") {
|
|
30
|
+
onChange?.({
|
|
31
|
+
[keyValue]: selectedEditItem[field]
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
methods.setValue(field, selectedEditItem ? selectedEditItem[field] : defaultValue );
|
|
35
|
+
methods.setValue(keyValueLabel, selectedEditItem[keyValueLabel]);
|
|
36
|
+
}else if(type === 'date'){
|
|
37
|
+
methods.setValue(field, selectedEditItem[field]);
|
|
38
|
+
} else {
|
|
39
|
+
methods.setValue(field, selectedEditItem[field]);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
}else{
|
|
44
|
+
fields.forEach(({ field, defaultValue }) => {
|
|
45
|
+
methods.setValue(field, defaultValue );
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}, [selectedEditItem]);
|
|
49
|
+
|
|
50
|
+
const saveMutation = useMutation(saveDataToTable, {
|
|
51
|
+
onSuccess: ({ status = false }, { data: { Id } }) => {
|
|
52
|
+
if (status) {
|
|
53
|
+
toast.success(Id == 0 ? "Thêm thành công!" : "Cập nhật thành công!");
|
|
54
|
+
queryClient.invalidateQueries(tableName);
|
|
55
|
+
setOpenEditorDialog(false);
|
|
56
|
+
} else {
|
|
57
|
+
toast.error(" Có lỗi xảy ra !");
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
onError: () => {
|
|
61
|
+
toast.error(" Có lỗi xảy ra !");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const onSubmit = (data) => {
|
|
65
|
+
|
|
66
|
+
fields
|
|
67
|
+
.filter(({ type }) => type === "date")
|
|
68
|
+
.forEach(({ field }) => {
|
|
69
|
+
if (data[field]) {
|
|
70
|
+
data[field] = moment(data[field]).toDate();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
fields
|
|
74
|
+
.filter(({ type }) => type === "autocomplete")
|
|
75
|
+
.forEach(({ field, datas, keyValueLabel, keyValue, keyLabel }) => {
|
|
76
|
+
if (data[field] && !data[keyValueLabel] && keyValueLabel) {
|
|
77
|
+
data[keyValueLabel] = datas.find((item) => item[keyValue] == data[field])?.[keyLabel];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
saveMutation.mutate({
|
|
82
|
+
tableName,
|
|
83
|
+
data: {
|
|
84
|
+
Id: 0,
|
|
85
|
+
...data
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
return (
|
|
90
|
+
<FormProvider {...methods}>
|
|
91
|
+
<Box component={"form"} sx={{ pt: 2 }} onSubmit={methods.handleSubmit(onSubmit)}>
|
|
92
|
+
<Grid container spacing={2}>
|
|
93
|
+
{fields.map(
|
|
94
|
+
({
|
|
95
|
+
field,
|
|
96
|
+
type = "text",
|
|
97
|
+
label,
|
|
98
|
+
childrenFields,
|
|
99
|
+
datas,
|
|
100
|
+
loading = false,
|
|
101
|
+
onChange = () => { },
|
|
102
|
+
keyLabel,
|
|
103
|
+
keyValue,
|
|
104
|
+
keyValueLabel,
|
|
105
|
+
required
|
|
106
|
+
}) => {
|
|
107
|
+
let sizes = {
|
|
108
|
+
xs: 12,
|
|
109
|
+
sm: 6,
|
|
110
|
+
md: 4
|
|
111
|
+
};
|
|
112
|
+
if (type == 'textarea') {
|
|
113
|
+
sizes = {
|
|
114
|
+
xs: 12,
|
|
115
|
+
sm: 12,
|
|
116
|
+
md: 12
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return (
|
|
120
|
+
<Grid {...sizes} key={field}>
|
|
121
|
+
<FormField
|
|
122
|
+
type={type}
|
|
123
|
+
label={label}
|
|
124
|
+
control={methods.control}
|
|
125
|
+
name={field}
|
|
126
|
+
loading={loading}
|
|
127
|
+
onChange={onChange}
|
|
128
|
+
keyLabel={keyLabel}
|
|
129
|
+
keyValueLabel={keyValueLabel}
|
|
130
|
+
datas={datas}
|
|
131
|
+
childrenFields={childrenFields}
|
|
132
|
+
keyValue={keyValue}
|
|
133
|
+
size={elementSize}
|
|
134
|
+
required={required}
|
|
135
|
+
/>
|
|
136
|
+
</Grid>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
)}
|
|
140
|
+
</Grid>
|
|
141
|
+
<button ref={submitRef} type="submit" style={{ display: "none" }} />
|
|
142
|
+
</Box>
|
|
143
|
+
</FormProvider>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default EditorForm;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { toast } from "react-toastify";
|
|
2
|
+
import { exportExcel } from "../../api";
|
|
3
|
+
import { Button } from "@mui/material";
|
|
4
|
+
import { Download } from "@mui/icons-material";
|
|
5
|
+
|
|
6
|
+
const ExportExcelButton = ({ tableName })=>{
|
|
7
|
+
const handleExportExcel = async (tableName) => {
|
|
8
|
+
const data = await exportExcel({ tableName });
|
|
9
|
+
if (data.status) {
|
|
10
|
+
window.open(data.url, "_blank").focus();
|
|
11
|
+
} else {
|
|
12
|
+
toast.error("Xuất file thất bại!");
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return (
|
|
16
|
+
<Button
|
|
17
|
+
variant="outlined"
|
|
18
|
+
startIcon={<Download />}
|
|
19
|
+
onClick={() => {
|
|
20
|
+
handleExportExcel(tableName);
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
Excel
|
|
24
|
+
</Button>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
export default ExportExcelButton;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Autocomplete, FormControl, InputAdornment,
|
|
3
|
+
InputLabel,
|
|
4
|
+
MenuItem,
|
|
5
|
+
OutlinedInput,
|
|
6
|
+
Select, TextField
|
|
7
|
+
} from "@mui/material";
|
|
8
|
+
import { useCallback } from "react";
|
|
9
|
+
import { SearchOutlined } from "@mui/icons-material";
|
|
10
|
+
import { useController, useFormContext } from "react-hook-form";
|
|
11
|
+
import { debounce } from "lodash";
|
|
12
|
+
import { useDataTable } from "./hooks";
|
|
13
|
+
|
|
14
|
+
export function FilterElement({ name, type, label, keyValue, keyLabel, childrenFields, datas, loading = false, onChange = () => { } }) {
|
|
15
|
+
const { control, setValue } = useFormContext();
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
field: { value, onChange: onFieldChange, ...rest }
|
|
19
|
+
} = useController({ control, name });
|
|
20
|
+
|
|
21
|
+
const { dataSearch, setDataSearch } = useDataTable();
|
|
22
|
+
|
|
23
|
+
const handleFilterChangeDebounce = useCallback(
|
|
24
|
+
debounce((name, value) => {
|
|
25
|
+
setDataSearch({ ...dataSearch, [name]: value });
|
|
26
|
+
}, 500),
|
|
27
|
+
[dataSearch]
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
switch (type) {
|
|
31
|
+
case "search":
|
|
32
|
+
return (
|
|
33
|
+
<OutlinedInput
|
|
34
|
+
{...rest}
|
|
35
|
+
fullWidth
|
|
36
|
+
value={value ?? ""}
|
|
37
|
+
onChange={(e) => {
|
|
38
|
+
onFieldChange(e);
|
|
39
|
+
handleFilterChangeDebounce(name, e.target.value);
|
|
40
|
+
}}
|
|
41
|
+
size="small"
|
|
42
|
+
placeholder={label}
|
|
43
|
+
startAdornment={<InputAdornment position="start">
|
|
44
|
+
<SearchOutlined />
|
|
45
|
+
</InputAdornment>} />
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
case "autocomplete":
|
|
49
|
+
return (
|
|
50
|
+
<Autocomplete
|
|
51
|
+
{...name}
|
|
52
|
+
disablePortal
|
|
53
|
+
loading={loading}
|
|
54
|
+
size="small"
|
|
55
|
+
fullWidth
|
|
56
|
+
options={datas ?? []}
|
|
57
|
+
onChange={(event, newValue) => {
|
|
58
|
+
let updateObject = { [name]: newValue?.[keyValue] };
|
|
59
|
+
|
|
60
|
+
onFieldChange(newValue);
|
|
61
|
+
onChange(newValue);
|
|
62
|
+
childrenFields?.forEach((childrenField) => {
|
|
63
|
+
setValue(childrenField, null, { shouldTouch: true });
|
|
64
|
+
updateObject[childrenField] = null;
|
|
65
|
+
});
|
|
66
|
+
setDataSearch({ ...dataSearch, ...updateObject });
|
|
67
|
+
}}
|
|
68
|
+
value={value || null}
|
|
69
|
+
getOptionLabel={(option) => option?.[keyLabel]}
|
|
70
|
+
renderInput={(params) => <TextField {...params} label={label} />} />
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
case "select":
|
|
74
|
+
return (
|
|
75
|
+
<FormControl sx={{ minWidth: 160 }} size="small" fullWidth>
|
|
76
|
+
<InputLabel id="demo-simple-select-label">{label}</InputLabel>
|
|
77
|
+
<Select
|
|
78
|
+
{...rest}
|
|
79
|
+
labelId="demo-simple-select-label"
|
|
80
|
+
id="demo-simple-select"
|
|
81
|
+
name={name}
|
|
82
|
+
fullWidth
|
|
83
|
+
label={label}
|
|
84
|
+
value={value ?? ""}
|
|
85
|
+
onChange={(e) => {
|
|
86
|
+
onFieldChange(e);
|
|
87
|
+
setDataSearch({ ...dataSearch, [name]: e.target.value });
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{[...datas].map(({ label, value }) => (
|
|
91
|
+
<MenuItem key={value} value={value}>
|
|
92
|
+
{label}
|
|
93
|
+
</MenuItem>
|
|
94
|
+
))}
|
|
95
|
+
</Select>
|
|
96
|
+
</FormControl>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
default:
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Slider, Toolbar } from "@mui/material";
|
|
2
|
+
import { useFormContext } from "react-hook-form";
|
|
3
|
+
import Grid from "@mui/material/Unstable_Grid2";
|
|
4
|
+
import DateRangePicker from "../date/DateRangePicker";
|
|
5
|
+
import { FilterElement } from "./FilterElement";
|
|
6
|
+
import { useDataTable } from "./hooks";
|
|
7
|
+
|
|
8
|
+
export const FilterGod = ({ filters }) => {
|
|
9
|
+
const { handleSubmit } = useFormContext();
|
|
10
|
+
const onSubmit = (data) => console.log(data);
|
|
11
|
+
const { setDataSearch, dataSearch } = useDataTable();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Toolbar
|
|
15
|
+
component={"form"}
|
|
16
|
+
disableGutters
|
|
17
|
+
onSubmit={handleSubmit(onSubmit)}
|
|
18
|
+
sx={{
|
|
19
|
+
px: 1,
|
|
20
|
+
my: 2,
|
|
21
|
+
display: "block"
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
<Grid
|
|
25
|
+
container
|
|
26
|
+
rowSpacing={2}
|
|
27
|
+
columnSpacing={{ xs: 2 }}
|
|
28
|
+
sx={{
|
|
29
|
+
px: 1
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{filters.map(({ field, ...rest }) => {
|
|
33
|
+
if (rest.type === "date-range") {
|
|
34
|
+
return (
|
|
35
|
+
<Grid key={field.toString()} xs={12} sm={6} md={4} xl={3}>
|
|
36
|
+
<DateRangePicker
|
|
37
|
+
onChange={(value) => {
|
|
38
|
+
setDataSearch(({ previousState }) => ({
|
|
39
|
+
...previousState,
|
|
40
|
+
[field[0]]: value[0],
|
|
41
|
+
[field[1]]: value[1]
|
|
42
|
+
}));
|
|
43
|
+
}}
|
|
44
|
+
size="small"
|
|
45
|
+
value={[dataSearch?.[field[0]], dataSearch?.[field[1]]]}
|
|
46
|
+
/>
|
|
47
|
+
</Grid>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (rest.type === "slider-range") {
|
|
52
|
+
return (
|
|
53
|
+
<Grid key={field.toString()} xs={12} sm={6} md={4} xl={3} sx={{
|
|
54
|
+
px:4
|
|
55
|
+
}}>
|
|
56
|
+
<Slider
|
|
57
|
+
onChange={(e, value) => {
|
|
58
|
+
setDataSearch({
|
|
59
|
+
...dataSearch,
|
|
60
|
+
[field[0]]: value[0],
|
|
61
|
+
[field[1]]: value[1]
|
|
62
|
+
});
|
|
63
|
+
}}
|
|
64
|
+
size="small"
|
|
65
|
+
marks={rest.marks}
|
|
66
|
+
defaultValue={rest.defaultValue}
|
|
67
|
+
valueLabelDisplay="auto"
|
|
68
|
+
max={rest.marks[rest.marks.length - 1]?.value}
|
|
69
|
+
step={null}
|
|
70
|
+
/>
|
|
71
|
+
</Grid>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return (
|
|
75
|
+
<Grid key={field} xs={12} sm={6} md={4} xl={3}>
|
|
76
|
+
<FilterElement name={field} {...rest} />
|
|
77
|
+
</Grid>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</Grid>
|
|
81
|
+
</Toolbar>
|
|
82
|
+
);
|
|
83
|
+
};
|