GoodForm.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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. 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. <>
  215. <Button variant="contained" onClick={() => setIsDeleteModalOpen(true)} disabled={formik.isSubmitting} color="error">
  216. Видалити
  217. </Button>
  218. <Button
  219. variant="contained"
  220. onClick={() => setIsNew(true)}
  221. disabled={!formik.isValid || formik.isSubmitting}
  222. type="submit"
  223. >
  224. Зберегти як новий
  225. </Button>
  226. </>
  227. )}
  228. <Button variant="contained" onClick={() => setIsNew(false)} disabled={!formik.isValid || formik.isSubmitting} type="submit">
  229. Зберегти
  230. </Button>
  231. </Stack>
  232. {!!good._id && (
  233. <ConfirmModal
  234. open={isDeleteModalOpen}
  235. text="Видалити товар?"
  236. onClose={() => setIsDeleteModalOpen(false)}
  237. onNO={() => setIsDeleteModalOpen(false)}
  238. onYES={() => {
  239. onDelete(good);
  240. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  241. }}
  242. />
  243. )}
  244. </Box>
  245. );
  246. };
  247. export const CGoodForm = connect(
  248. (state) => ({
  249. catList: state.promise.catAll?.payload || [],
  250. promiseStatus: state.promise.goodUpsert?.status || null,
  251. deletePromiseStatus: state.promise.goodDelete?.status || null,
  252. good: state.promise?.adminGoodById?.payload || {},
  253. serverErrors: state.promise?.goodUpsert?.error || [],
  254. }),
  255. {
  256. onSave: (good) => actionGoodUpdate(good),
  257. onClose: () => actionPromiseClear("goodUpsert"),
  258. onDelete: (good) => actionGoodDelete({ good }),
  259. }
  260. )(GoodForm);