GoodForm.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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.setTouched({ ...formik.touched, ...{ amount: true, description: true, name: true, price: true } });
  125. formik.validateForm();
  126. }, [good.categories, good.name, good.description, good.amount, good.price]);
  127. useEffect(() => {
  128. return () => {
  129. onClose && onClose();
  130. };
  131. }, []);
  132. return (
  133. <Box className="GoodForm" component="form" onSubmit={formik.handleSubmit}>
  134. <TextField
  135. id="name"
  136. name="name"
  137. variant="outlined"
  138. label="Назва"
  139. size="small"
  140. error={formik.touched.name && Boolean(formik.errors.name)}
  141. value={formik.values.name}
  142. onBlur={formik.handleBlur}
  143. onChange={formik.handleChange}
  144. helperText={formik.touched.name && formik.errors.name}
  145. multiline
  146. fullWidth
  147. sx={{ mt: 2 }}
  148. />
  149. <Box sx={{ mt: 3 }}>
  150. <InputLabel>Зображення</InputLabel>
  151. <CGoodEditor onImagesSave={(images) => setInputImages(images)} />
  152. </Box>
  153. <TextField
  154. variant="outlined"
  155. id="description"
  156. name="description"
  157. label="Опис"
  158. size="small"
  159. error={formik.touched.description && Boolean(formik.errors.description)}
  160. value={formik.values.description}
  161. onBlur={formik.handleBlur}
  162. onChange={formik.handleChange}
  163. helperText={formik.touched.description && formik.errors.description}
  164. multiline
  165. fullWidth
  166. sx={{ mt: 2 }}
  167. />
  168. <Box sx={{ mt: 3 }}>
  169. <TextField
  170. variant="outlined"
  171. id="price"
  172. name="price"
  173. label="Ціна"
  174. size="small"
  175. error={formik.touched.price && Boolean(formik.errors.price)}
  176. value={formik.values.price}
  177. onBlur={formik.handleBlur}
  178. onChange={formik.handleChange}
  179. helperText={formik.touched.price && formik.errors.price}
  180. multiline
  181. fullWidth
  182. sx={{ mt: 2 }}
  183. />
  184. </Box>
  185. <Box sx={{ mt: 3 }}>
  186. <TextField
  187. variant="outlined"
  188. id="amount"
  189. name="amount"
  190. label="Кількість"
  191. size="small"
  192. error={formik.touched.amount && Boolean(formik.errors.amount)}
  193. value={formik.values.amount}
  194. onBlur={formik.handleBlur}
  195. onChange={formik.handleChange}
  196. helperText={formik.touched.amount && formik.errors.amount}
  197. multiline
  198. fullWidth
  199. sx={{ mt: 2 }}
  200. />
  201. </Box>
  202. <Box sx={{ mt: 3 }}>
  203. <InputLabel>Категорії</InputLabel>
  204. <Select
  205. placeholder="Обрати категорії"
  206. value={inputCategories.map(({ _id, name }) => ({ value: _id, label: name }))}
  207. closeMenuOnSelect={false}
  208. onChange={(e) => setInputCategories(e.map(({ label, value }) => ({ _id: value, name: label })))}
  209. options={catList?.map(({ _id, name }) => ({ value: _id, label: name }))}
  210. isMulti={true}
  211. />
  212. </Box>
  213. <Stack direction="row" sx={{ mt: 3 }} justifyContent="flex-end" spacing={1}>
  214. {!!good._id && (
  215. <>
  216. <Button variant="contained" onClick={() => setIsDeleteModalOpen(true)} disabled={formik.isSubmitting} color="error">
  217. Видалити
  218. </Button>
  219. <Button
  220. variant="contained"
  221. onClick={() => setIsNew(true)}
  222. disabled={!formik.isValid || formik.isSubmitting}
  223. type="submit"
  224. >
  225. Зберегти як новий
  226. </Button>
  227. </>
  228. )}
  229. <Button variant="contained" onClick={() => setIsNew(false)} disabled={!formik.isValid || formik.isSubmitting} type="submit">
  230. Зберегти
  231. </Button>
  232. </Stack>
  233. {!!good._id && (
  234. <ConfirmModal
  235. open={isDeleteModalOpen}
  236. text="Видалити товар?"
  237. onClose={() => setIsDeleteModalOpen(false)}
  238. onNO={() => setIsDeleteModalOpen(false)}
  239. onYES={() => {
  240. onDelete(good);
  241. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  242. }}
  243. />
  244. )}
  245. </Box>
  246. );
  247. };
  248. export const CGoodForm = connect(
  249. (state) => ({
  250. catList: state.promise.catAll?.payload || [],
  251. promiseStatus: state.promise.goodUpsert?.status || null,
  252. deletePromiseStatus: state.promise.goodDelete?.status || null,
  253. good: state.promise?.adminGoodById?.payload || {},
  254. serverErrors: state.promise?.goodUpsert?.error || [],
  255. }),
  256. {
  257. onSave: (good) => actionGoodUpdate(good),
  258. onClose: () => actionPromiseClear("goodUpsert"),
  259. onDelete: (good) => actionGoodDelete({ good }),
  260. }
  261. )(GoodForm);