GoodForm.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import { connect } from "react-redux";
  2. import { useState, useEffect, useContext } from "react";
  3. import { actionPromiseClear } from "../../../reducers";
  4. import Select from "react-select";
  5. import { actionGoodUpdate } from "../../../actions/actionGoodUpdate";
  6. import { EntityEditor } from "../../common/EntityEditor";
  7. import { actionUploadFiles } from "../../../actions/actionUploadFiles";
  8. import { UIContext } from "../../UIContext";
  9. import { Box, Button, InputLabel, Stack, TextField } from "@mui/material";
  10. import { useFormik } from "formik";
  11. import * as Yup from "yup";
  12. import { ConfirmModal } from "../../common/ConfirmModal";
  13. import { actionGoodDelete } from "../../../actions/actionGoodDelete";
  14. import { useNavigate } from "react-router-dom";
  15. const goodSchema = Yup.object().shape({
  16. name: Yup.string().required("Обов'язкове"),
  17. description: Yup.string().required("Обов'язкове"),
  18. price: Yup.number().min(0, "більше або равно 0").required("Обов'язкове"),
  19. amount: Yup.number().min(0, "більше або равно 0").required("Обов'язкове"),
  20. });
  21. const CGoodEditor = connect(
  22. (state) => ({
  23. entity: state.promise?.adminGoodById?.payload || {},
  24. uploadFiles: state.promise?.uploadFiles,
  25. }),
  26. {
  27. onFileDrop: (files) => actionUploadFiles(files),
  28. }
  29. )(EntityEditor);
  30. export const GoodForm = ({
  31. serverErrors = [],
  32. onSaveClick,
  33. onSave,
  34. onClose,
  35. onDelete,
  36. promiseStatus,
  37. deletePromiseStatus,
  38. catList = [],
  39. good = {},
  40. } = {}) => {
  41. const [inputCategories, setInputCategories] = useState([]);
  42. const [inputImages, setInputImages] = useState([]);
  43. const { setAlert } = useContext(UIContext);
  44. const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
  45. const [promiseTimeOut, setPromiseTimeOut] = useState(null);
  46. const navigate = useNavigate();
  47. const formik = useFormik({
  48. initialValues: {
  49. name: "",
  50. description: "",
  51. price: 0,
  52. amount: 0,
  53. },
  54. validationSchema: goodSchema,
  55. validateOnChange: true,
  56. onSubmit: () => {
  57. let goodToSave = {};
  58. good?._id && (goodToSave._id = good._id);
  59. goodToSave.name = formik.values.name;
  60. goodToSave.description = formik.values.description;
  61. goodToSave.price = +formik.values.price;
  62. goodToSave.amount = +formik.values.amount;
  63. goodToSave.categories = inputCategories;
  64. goodToSave.images = inputImages?.map(({ _id }) => ({ _id })) || [];
  65. onSaveClick && onSaveClick();
  66. onSave(goodToSave);
  67. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  68. },
  69. });
  70. useEffect(() => {
  71. return () => {
  72. promiseTimeOut && clearTimeout(promiseTimeOut);
  73. setPromiseTimeOut(null);
  74. };
  75. }, []);
  76. useEffect(() => {
  77. if (promiseStatus === "FULFILLED") {
  78. formik.setSubmitting(false);
  79. promiseTimeOut && clearTimeout(promiseTimeOut);
  80. setPromiseTimeOut(null);
  81. setAlert({
  82. show: true,
  83. severity: "success",
  84. message: "Готово",
  85. });
  86. }
  87. if (promiseStatus === "REJECTED") {
  88. console.log(serverErrors);
  89. const errorMessage = (serverErrors ? [].concat(serverErrors) : []).reduce((prev, curr) => prev + "\n" + curr.message, "");
  90. formik.setSubmitting(false);
  91. promiseTimeOut && clearTimeout(promiseTimeOut);
  92. setPromiseTimeOut(null);
  93. setAlert({
  94. show: true,
  95. severity: "error",
  96. message: errorMessage,
  97. });
  98. }
  99. }, [promiseStatus]);
  100. useEffect(() => {
  101. if (deletePromiseStatus === "FULFILLED") {
  102. promiseTimeOut && clearTimeout(promiseTimeOut);
  103. setPromiseTimeOut(null);
  104. navigate("/admin/goods/");
  105. }
  106. if (deletePromiseStatus === "REJECTED") {
  107. promiseTimeOut && clearTimeout(promiseTimeOut);
  108. setPromiseTimeOut(null);
  109. setAlert({
  110. show: true,
  111. severity: "error",
  112. message: "Помилка",
  113. });
  114. }
  115. return () => {};
  116. }, [deletePromiseStatus]);
  117. useEffect(() => {
  118. setInputCategories(good?.categories || []);
  119. setInputImages(good?.images || []);
  120. formik.setFieldValue("name", good.name || "");
  121. formik.setFieldValue("description", good.description || "");
  122. formik.setFieldValue("amount", good.amount || 0);
  123. formik.setFieldValue("price", good.price || 0);
  124. formik.validateForm();
  125. }, [good.categories, good.name, good.description, good.amount, good.price]);
  126. useEffect(() => {
  127. return () => {
  128. onClose && onClose();
  129. };
  130. }, []);
  131. return (
  132. <Box className="GoodForm" component="form" onSubmit={formik.handleSubmit}>
  133. <TextField
  134. id="name"
  135. name="name"
  136. variant="outlined"
  137. label="Назва"
  138. size="small"
  139. error={formik.touched.name && Boolean(formik.errors.name)}
  140. value={formik.values.name}
  141. onBlur={formik.handleBlur}
  142. onChange={formik.handleChange}
  143. helperText={formik.touched.name && formik.errors.name}
  144. multiline
  145. fullWidth
  146. sx={{ mt: 2 }}
  147. />
  148. <Box sx={{ mt: 3 }}>
  149. <InputLabel>Зображення</InputLabel>
  150. <CGoodEditor onImagesSave={(images) => setInputImages(images)} />
  151. </Box>
  152. <TextField
  153. variant="outlined"
  154. id="description"
  155. name="description"
  156. label="Опис"
  157. size="small"
  158. error={formik.touched.description && Boolean(formik.errors.description)}
  159. value={formik.values.description}
  160. onBlur={formik.handleBlur}
  161. onChange={formik.handleChange}
  162. helperText={formik.touched.description && formik.errors.description}
  163. multiline
  164. fullWidth
  165. sx={{ mt: 2 }}
  166. />
  167. <Box sx={{ mt: 3 }}>
  168. <TextField
  169. variant="outlined"
  170. id="price"
  171. name="price"
  172. label="Ціна"
  173. size="small"
  174. error={formik.touched.price && Boolean(formik.errors.price)}
  175. value={formik.values.price}
  176. onBlur={formik.handleBlur}
  177. onChange={formik.handleChange}
  178. helperText={formik.touched.price && formik.errors.price}
  179. multiline
  180. fullWidth
  181. sx={{ mt: 2 }}
  182. />
  183. </Box>
  184. <Box sx={{ mt: 3 }}>
  185. <TextField
  186. variant="outlined"
  187. id="amount"
  188. name="amount"
  189. label="Кількість"
  190. size="small"
  191. error={formik.touched.amount && Boolean(formik.errors.amount)}
  192. value={formik.values.amount}
  193. onBlur={formik.handleBlur}
  194. onChange={formik.handleChange}
  195. helperText={formik.touched.amount && formik.errors.amount}
  196. multiline
  197. fullWidth
  198. sx={{ mt: 2 }}
  199. />
  200. </Box>
  201. <Box sx={{ mt: 3 }}>
  202. <InputLabel>Категорії</InputLabel>
  203. <Select
  204. placeholder="Обрати категорії"
  205. value={inputCategories.map(({ _id, name }) => ({ value: _id, label: name }))}
  206. closeMenuOnSelect={false}
  207. onChange={(e) => setInputCategories(e.map(({ label, value }) => ({ _id: value, name: label })))}
  208. options={catList?.map(({ _id, name }) => ({ value: _id, label: name }))}
  209. isMulti={true}
  210. />
  211. </Box>
  212. <Stack direction="row" sx={{ mt: 3 }} justifyContent="flex-end" spacing={1}>
  213. {!!good._id && (
  214. <Button variant="contained" onClick={() => setIsDeleteModalOpen(true)} color="error">
  215. Видалити
  216. </Button>
  217. )}
  218. <Button variant="contained" disabled={!formik.isValid || formik.isSubmitting} type="submit">
  219. Зберегти
  220. </Button>
  221. </Stack>
  222. {!!good._id && (
  223. <ConfirmModal
  224. open={isDeleteModalOpen}
  225. text="Видалити товар?"
  226. onClose={() => setIsDeleteModalOpen(false)}
  227. onNO={() => setIsDeleteModalOpen(false)}
  228. onYES={() => {
  229. onDelete(good);
  230. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  231. }}
  232. />
  233. )}
  234. </Box>
  235. );
  236. };
  237. export const CGoodForm = connect(
  238. (state) => ({
  239. catList: state.promise.catAll?.payload || [],
  240. promiseStatus: state.promise.goodUpsert?.status || null,
  241. deletePromiseStatus: state.promise.goodDelete?.status || null,
  242. good: state.promise?.adminGoodById?.payload || {},
  243. serverErrors: state.promise?.goodUpsert?.error || [],
  244. }),
  245. {
  246. onSave: (good) => actionGoodUpdate(good),
  247. onClose: () => actionPromiseClear("goodUpsert"),
  248. onDelete: (good) => actionGoodDelete({ good }),
  249. }
  250. )(GoodForm);