CategoryForm.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import { connect } from "react-redux";
  2. import { useState, useEffect, useContext } from "react";
  3. import Select from "react-select";
  4. import { actionCategoryUpdate } from "../../../actions/actionCategoryUpdate";
  5. import { Box, Button, InputLabel, Stack, TextField } from "@mui/material";
  6. import { UIContext } from "../../UIContext";
  7. import { useFormik } from "formik";
  8. import * as Yup from "yup";
  9. import { ConfirmModal } from "../../common/ConfirmModal";
  10. import { useNavigate } from "react-router-dom";
  11. import { actionCategoryDelete } from "../../../actions/actionCategoryDelete";
  12. import { actionPromisesClear } from "../../../actions/actionPromisesClear";
  13. const categorySchema = Yup.object().shape({
  14. name: Yup.string().required("Обов'язкове"),
  15. });
  16. const CategoryForm = ({
  17. serverErrors = [],
  18. onSaveClick,
  19. onSave,
  20. onClose,
  21. onDelete,
  22. promiseStatus,
  23. deletePromiseStatus,
  24. catList: initialCatList = [],
  25. goodList = [],
  26. category = {},
  27. } = {}) => {
  28. const [inputSubcategories, setInputSubcategories] = useState([]);
  29. const [inputGoods, setInputGoods] = useState([]);
  30. const [inputParent, setInputParent] = useState({});
  31. const [subCatList, setSubCatList] = useState([]);
  32. const [parentList, setParentList] = useState([]);
  33. const { setAlert } = useContext(UIContext);
  34. const [isNew, setIsNew] = useState(false);
  35. const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
  36. const [promiseTimeOut, setPromiseTimeOut] = useState(null);
  37. const navigate = useNavigate();
  38. const formik = useFormik({
  39. initialValues: {
  40. name: category?.name || "",
  41. },
  42. validationSchema: categorySchema,
  43. validateOnChange: true,
  44. validateOnMount: true,
  45. onSubmit: () => {
  46. let categoryToSave = {};
  47. !isNew && category?._id && (categoryToSave._id = category?._id);
  48. categoryToSave.name = formik.values.name;
  49. inputGoods && (categoryToSave.goods = inputGoods);
  50. inputParent && (categoryToSave.parent = inputParent._id ? inputParent : null);
  51. categoryToSave.subcategories = inputSubcategories;
  52. onSaveClick && onSaveClick();
  53. onSave(categoryToSave);
  54. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  55. },
  56. });
  57. useEffect(() => {
  58. return () => {
  59. promiseTimeOut && clearTimeout(promiseTimeOut);
  60. setPromiseTimeOut(null);
  61. };
  62. }, []);
  63. useEffect(() => {
  64. formik.setFieldValue("name", category.name || "");
  65. setInputParent(category?.parent || null);
  66. setInputGoods(category?.goods || []);
  67. setInputSubcategories(category?.subcategories || []);
  68. }, [category]);
  69. useEffect(() => {
  70. if (promiseStatus === "FULFILLED") {
  71. formik.setSubmitting(false);
  72. promiseTimeOut && clearTimeout(promiseTimeOut);
  73. setPromiseTimeOut(null);
  74. setAlert({
  75. show: true,
  76. severity: "success",
  77. message: "Готово",
  78. });
  79. }
  80. if (promiseStatus === "REJECTED") {
  81. const errorMessage = (serverErrors ? [].concat(serverErrors) : []).reduce((prev, curr) => prev + "\n" + curr.message, "");
  82. formik.setSubmitting(false);
  83. promiseTimeOut && clearTimeout(promiseTimeOut);
  84. setPromiseTimeOut(null);
  85. setAlert({
  86. show: true,
  87. severity: "error",
  88. message: errorMessage,
  89. });
  90. }
  91. }, [promiseStatus]);
  92. useEffect(() => {
  93. if (deletePromiseStatus === "FULFILLED") {
  94. promiseTimeOut && clearTimeout(promiseTimeOut);
  95. setPromiseTimeOut(null);
  96. navigate("/admin/categories/");
  97. }
  98. if (deletePromiseStatus === "REJECTED") {
  99. promiseTimeOut && clearTimeout(promiseTimeOut);
  100. setPromiseTimeOut(null);
  101. setAlert({
  102. show: true,
  103. severity: "error",
  104. message: "Помилка",
  105. });
  106. }
  107. }, [deletePromiseStatus]);
  108. useEffect(() => {
  109. let parentList = initialCatList.filter(
  110. ({ _id }) =>
  111. !category?.subCatergories?.find((subCat) => _id === subCat._id) &&
  112. _id !== category?._id &&
  113. !inputSubcategories?.find((subCat) => _id === subCat._id)
  114. );
  115. parentList = [...[{ _id: null, name: "null" }], ...parentList];
  116. setParentList(parentList);
  117. }, [inputSubcategories, initialCatList]);
  118. useEffect(() => {
  119. let subCatList = initialCatList.filter(
  120. ({ _id }) => _id !== category?.parent?._id && _id !== category?._id && inputParent?._id !== _id
  121. );
  122. setSubCatList(subCatList);
  123. }, [inputParent, initialCatList]);
  124. useEffect(() => {
  125. return () => {
  126. onClose && onClose();
  127. };
  128. }, []);
  129. return (
  130. <Box className="CategoryForm" component="form" onSubmit={formik.handleSubmit}>
  131. <Box>
  132. <TextField
  133. id="name"
  134. name="name"
  135. variant="outlined"
  136. label="Назва"
  137. size="small"
  138. error={formik.touched.name && Boolean(formik.errors.name)}
  139. value={formik.values.name}
  140. onBlur={formik.handleBlur}
  141. onChange={formik.handleChange}
  142. helperText={formik.touched.name && formik.errors.name}
  143. multiline
  144. fullWidth
  145. sx={{ mt: 2 }}
  146. />
  147. </Box>
  148. <Box sx={{ mt: 3 }}>
  149. <InputLabel className="form-label">Батьківська категорія</InputLabel>
  150. <Select
  151. value={{ value: inputParent?._id || null, label: inputParent?.name || "null" }}
  152. onChange={(e) => setInputParent({ _id: e.value, name: e.label })}
  153. options={parentList.map(({ _id, name }) => ({ value: _id, label: name }))}
  154. />
  155. </Box>
  156. <Box sx={{ mt: 3 }}>
  157. <InputLabel className="form-label">Підкатегорії</InputLabel>
  158. <Select
  159. value={inputSubcategories?.map(({ _id, name }) => ({ value: _id, label: name }))}
  160. closeMenuOnSelect={false}
  161. onChange={(e) => setInputSubcategories(e.map(({ value, label }) => ({ _id: value, name: label })))}
  162. options={subCatList?.map(({ _id, name }) => ({ value: _id, label: name }))}
  163. isMulti={true}
  164. />
  165. </Box>
  166. <Box sx={{ mt: 3 }}>
  167. <InputLabel className="form-label">Товари</InputLabel>
  168. <Select
  169. value={inputGoods?.map(({ _id, name }) => ({ value: _id, label: name }))}
  170. closeMenuOnSelect={false}
  171. onChange={(e) => setInputGoods(e.map(({ value, label }) => ({ _id: value, name: label })))}
  172. options={goodList?.map(({ _id, name }) => ({ value: _id, label: name }))}
  173. isMulti={true}
  174. />
  175. </Box>
  176. <Stack direction="row" sx={{ mt: 3 }} justifyContent="flex-end" spacing={1}>
  177. {!!category._id && (
  178. <>
  179. <Button variant="contained" onClick={() => setIsDeleteModalOpen(true)} disabled={formik.isSubmitting} color="error">
  180. Видалити
  181. </Button>
  182. <Button variant="contained" onClick={() => setIsNew(true)} disabled={formik.isSubmitting} type="submit">
  183. Зберегти як новий
  184. </Button>
  185. </>
  186. )}
  187. <Button variant="contained" onClick={() => setIsNew(false)} disabled={formik.isSubmitting} type="submit">
  188. Зберегти
  189. </Button>
  190. </Stack>
  191. {!!category._id && (
  192. <ConfirmModal
  193. open={isDeleteModalOpen}
  194. text="Видалити категорію?"
  195. onClose={() => setIsDeleteModalOpen(false)}
  196. onNO={() => setIsDeleteModalOpen(false)}
  197. onYES={() => {
  198. onDelete(category);
  199. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  200. }}
  201. />
  202. )}
  203. </Box>
  204. );
  205. };
  206. export const CCategoryForm = connect(
  207. (state) => ({
  208. catList: state.promise.catAll?.payload || [],
  209. promiseStatus: state.promise.categoryUpsert?.status || null,
  210. serverErrors: state.promise.categoryUpsert?.error || null,
  211. goodList: state.promise.goodsAll?.payload || [],
  212. deletePromiseStatus: state.promise.categoryDelete?.status || null,
  213. }),
  214. {
  215. onSave: (cat) => actionCategoryUpdate(cat),
  216. onClose: () => actionPromisesClear(["categoryUpsert", "categoryDelete"]),
  217. onDelete: (category) => actionCategoryDelete({ category }),
  218. }
  219. )(CategoryForm);