GoodForm.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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 [isNew, setIsNew] = useState(false);
  45. const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
  46. const [promiseTimeOut, setPromiseTimeOut] = useState(null);
  47. const navigate = useNavigate();
  48. const formik = useFormik({
  49. initialValues: {
  50. name: "",
  51. description: "",
  52. price: 0,
  53. amount: 0,
  54. },
  55. validationSchema: goodSchema,
  56. validateOnChange: true,
  57. onSubmit: () => {
  58. let goodToSave = {};
  59. !isNew && good?._id && (goodToSave._id = good._id);
  60. goodToSave.name = formik.values.name;
  61. goodToSave.description = formik.values.description;
  62. goodToSave.price = +formik.values.price;
  63. goodToSave.amount = +formik.values.amount;
  64. goodToSave.categories = inputCategories;
  65. goodToSave.images = inputImages?.map(({ _id }) => ({ _id })) || [];
  66. onSaveClick && onSaveClick();
  67. onSave(goodToSave);
  68. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  69. },
  70. });
  71. useEffect(() => {
  72. return () => {
  73. promiseTimeOut && clearTimeout(promiseTimeOut);
  74. setPromiseTimeOut(null);
  75. };
  76. }, []);
  77. useEffect(() => {
  78. if (promiseStatus === "FULFILLED") {
  79. formik.setSubmitting(false);
  80. promiseTimeOut && clearTimeout(promiseTimeOut);
  81. setPromiseTimeOut(null);
  82. setAlert({
  83. show: true,
  84. severity: "success",
  85. message: "Готово",
  86. });
  87. }
  88. if (promiseStatus === "REJECTED") {
  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. }, [good.categories, good.name, good.description, good.amount, good.price]);
  125. useEffect(() => {
  126. return () => {
  127. onClose && onClose();
  128. };
  129. }, []);
  130. return (
  131. <Box className="GoodForm" component="form" onSubmit={formik.handleSubmit}>
  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 sx={{ mt: 3 }}>
  148. <InputLabel>Зображення</InputLabel>
  149. <CGoodEditor onImagesSave={(images) => setInputImages(images)} />
  150. </Box>
  151. <TextField
  152. variant="outlined"
  153. id="description"
  154. name="description"
  155. label="Опис"
  156. size="small"
  157. error={formik.touched.description && Boolean(formik.errors.description)}
  158. value={formik.values.description}
  159. onBlur={formik.handleBlur}
  160. onChange={formik.handleChange}
  161. helperText={formik.touched.description && formik.errors.description}
  162. multiline
  163. fullWidth
  164. sx={{ mt: 2 }}
  165. />
  166. <Box sx={{ mt: 3 }}>
  167. <TextField
  168. variant="outlined"
  169. id="price"
  170. name="price"
  171. label="Ціна"
  172. size="small"
  173. error={formik.touched.price && Boolean(formik.errors.price)}
  174. value={formik.values.price}
  175. onBlur={formik.handleBlur}
  176. onChange={formik.handleChange}
  177. helperText={formik.touched.price && formik.errors.price}
  178. multiline
  179. fullWidth
  180. sx={{ mt: 2 }}
  181. />
  182. </Box>
  183. <Box sx={{ mt: 3 }}>
  184. <TextField
  185. variant="outlined"
  186. id="amount"
  187. name="amount"
  188. label="Кількість"
  189. size="small"
  190. error={formik.touched.amount && Boolean(formik.errors.amount)}
  191. value={formik.values.amount}
  192. onBlur={formik.handleBlur}
  193. onChange={formik.handleChange}
  194. helperText={formik.touched.amount && formik.errors.amount}
  195. multiline
  196. fullWidth
  197. sx={{ mt: 2 }}
  198. />
  199. </Box>
  200. <Box sx={{ mt: 3 }}>
  201. <InputLabel>Категорії</InputLabel>
  202. <Select
  203. placeholder="Обрати категорії"
  204. value={inputCategories.map(({ _id, name }) => ({ value: _id, label: name }))}
  205. closeMenuOnSelect={false}
  206. onChange={(e) => setInputCategories(e.map(({ label, value }) => ({ _id: value, name: label })))}
  207. options={catList?.map(({ _id, name }) => ({ value: _id, label: name }))}
  208. isMulti={true}
  209. />
  210. </Box>
  211. <Stack direction="row" sx={{ mt: 3 }} justifyContent="flex-end" spacing={1}>
  212. {!!good._id && (
  213. <>
  214. <Button variant="contained" onClick={() => setIsDeleteModalOpen(true)} disabled={formik.isSubmitting} color="error">
  215. Видалити
  216. </Button>
  217. <Button variant="contained" onClick={() => setIsNew(true)} disabled={formik.isSubmitting} type="submit">
  218. Зберегти як новий
  219. </Button>
  220. </>
  221. )}
  222. <Button variant="contained" onClick={() => setIsNew(false)} disabled={formik.isSubmitting} type="submit">
  223. Зберегти
  224. </Button>
  225. </Stack>
  226. {!!good._id && (
  227. <ConfirmModal
  228. open={isDeleteModalOpen}
  229. text="Видалити товар?"
  230. onClose={() => setIsDeleteModalOpen(false)}
  231. onNO={() => setIsDeleteModalOpen(false)}
  232. onYES={() => {
  233. onDelete(good);
  234. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  235. }}
  236. />
  237. )}
  238. </Box>
  239. );
  240. };
  241. export const CGoodForm = connect(
  242. (state) => ({
  243. catList: state.promise.catAll?.payload || [],
  244. promiseStatus: state.promise.goodUpsert?.status || null,
  245. deletePromiseStatus: state.promise.goodDelete?.status || null,
  246. good: state.promise?.adminGoodById?.payload || {},
  247. serverErrors: state.promise?.goodUpsert?.error || [],
  248. }),
  249. {
  250. onSave: (good) => actionGoodUpdate(good),
  251. onClose: () => actionPromiseClear("goodUpsert"),
  252. onDelete: (good) => actionGoodDelete({ good }),
  253. }
  254. )(GoodForm);