index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. import React, { useState, useEffect, useContext } from "react";
  2. import * as Yup from "yup";
  3. import {
  4. Formik,
  5. Form,
  6. Field,
  7. FieldArray
  8. } from "formik";
  9. import { toast } from "react-toastify";
  10. import {
  11. Box,
  12. Button,
  13. CircularProgress,
  14. Dialog,
  15. DialogActions,
  16. DialogContent,
  17. DialogTitle,
  18. Divider,
  19. Grid,
  20. makeStyles,
  21. TextField
  22. } from "@material-ui/core";
  23. import IconButton from "@material-ui/core/IconButton";
  24. import Typography from "@material-ui/core/Typography";
  25. import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
  26. import AttachFileIcon from "@material-ui/icons/AttachFile";
  27. import { green } from "@material-ui/core/colors";
  28. import { i18n } from "../../translate/i18n";
  29. import api from "../../services/api";
  30. import toastError from "../../errors/toastError";
  31. import { AuthContext } from "../../context/Auth/AuthContext";
  32. const useStyles = makeStyles(theme => ({
  33. root: {
  34. display: "flex",
  35. flexWrap: "wrap",
  36. gap: 4
  37. },
  38. multFieldLine: {
  39. display: "flex",
  40. "& > *:not(:last-child)": {
  41. marginRight: theme.spacing(1),
  42. },
  43. },
  44. textField: {
  45. marginRight: theme.spacing(1),
  46. flex: 1,
  47. },
  48. extraAttr: {
  49. display: "flex",
  50. justifyContent: "center",
  51. alignItems: "center",
  52. },
  53. btnWrapper: {
  54. position: "relative",
  55. },
  56. buttonProgress: {
  57. color: green[500],
  58. position: "absolute",
  59. top: "50%",
  60. left: "50%",
  61. marginTop: -12,
  62. marginLeft: -12,
  63. },
  64. formControl: {
  65. margin: theme.spacing(1),
  66. minWidth: 2000,
  67. },
  68. colorAdorment: {
  69. width: 20,
  70. height: 20,
  71. },
  72. }));
  73. const FileListSchema = Yup.object().shape({
  74. name: Yup.string()
  75. .min(3, i18n.t("fileModal.formErrors.name.short"))
  76. .required(i18n.t("fileModal.formErrors.name.required")),
  77. message: Yup.string()
  78. .required(i18n.t("fileModal.formErrors.message.required"))
  79. });
  80. const FilesModal = ({ open, onClose, fileListId, reload }) => {
  81. const classes = useStyles();
  82. const { user } = useContext(AuthContext);
  83. const [ files, setFiles ] = useState([]);
  84. const [selectedFileNames, setSelectedFileNames] = useState([]);
  85. const initialState = {
  86. name: "",
  87. message: "",
  88. options: [{ name: "", path:"", mediaType:"" }],
  89. };
  90. const [fileList, setFileList] = useState(initialState);
  91. useEffect(() => {
  92. try {
  93. (async () => {
  94. if (!fileListId) return;
  95. const { data } = await api.get(`/files/${fileListId}`);
  96. setFileList(data);
  97. })()
  98. } catch (err) {
  99. toastError(err);
  100. }
  101. }, [fileListId, open]);
  102. const handleClose = () => {
  103. setFileList(initialState);
  104. setFiles([]);
  105. onClose();
  106. };
  107. const handleSaveFileList = async (values) => {
  108. const uploadFiles = async (options, filesOptions, id) => {
  109. const formData = new FormData();
  110. formData.append("fileId", id);
  111. formData.append("typeArch", "fileList")
  112. filesOptions.forEach((fileOption, index) => {
  113. if (fileOption.file) {
  114. formData.append("files", fileOption.file);
  115. formData.append("mediaType", fileOption.file.type)
  116. formData.append("name", options[index].name);
  117. formData.append("id", options[index].id);
  118. }
  119. });
  120. try {
  121. const { data } = await api.post(`/files/uploadList/${id}`, formData);
  122. setFiles([]);
  123. return data;
  124. } catch (err) {
  125. toastError(err);
  126. }
  127. return null;
  128. }
  129. const fileData = { ...values, userId: user.id };
  130. try {
  131. if (fileListId) {
  132. const { data } = await api.put(`/files/${fileListId}`, fileData)
  133. if (data.options.length > 0)
  134. uploadFiles(data.options, values.options, fileListId)
  135. } else {
  136. const { data } = await api.post("/files", fileData);
  137. if (data.options.length > 0)
  138. uploadFiles(data.options, values.options, data.id)
  139. }
  140. toast.success(i18n.t("fileModal.success"));
  141. if (typeof reload == 'function') {
  142. reload();
  143. }
  144. } catch (err) {
  145. toastError(err);
  146. }
  147. handleClose();
  148. };
  149. return (
  150. <div className={classes.root}>
  151. <Dialog
  152. open={open}
  153. onClose={handleClose}
  154. maxWidth="md"
  155. fullWidth
  156. scroll="paper">
  157. <DialogTitle id="form-dialog-title">
  158. {(fileListId ? `${i18n.t("fileModal.title.edit")}` : `${i18n.t("fileModal.title.add")}`)}
  159. </DialogTitle>
  160. <Formik
  161. initialValues={fileList}
  162. enableReinitialize={true}
  163. validationSchema={FileListSchema}
  164. onSubmit={(values, actions) => {
  165. setTimeout(() => {
  166. handleSaveFileList(values);
  167. actions.setSubmitting(false);
  168. }, 400);
  169. }}
  170. >
  171. {({ touched, errors, isSubmitting, values }) => (
  172. <Form>
  173. <DialogContent dividers>
  174. <div className={classes.multFieldLine}>
  175. <Field
  176. as={TextField}
  177. label={i18n.t("fileModal.form.name")}
  178. name="name"
  179. error={touched.name && Boolean(errors.name)}
  180. helperText={touched.name && errors.name}
  181. variant="outlined"
  182. margin="dense"
  183. fullWidth
  184. />
  185. </div>
  186. <br />
  187. <div className={classes.multFieldLine}>
  188. <Field
  189. as={TextField}
  190. label={i18n.t("fileModal.form.message")}
  191. type="message"
  192. multiline
  193. minRows={5}
  194. fullWidth
  195. name="message"
  196. error={
  197. touched.message && Boolean(errors.message)
  198. }
  199. helperText={
  200. touched.message && errors.message
  201. }
  202. variant="outlined"
  203. margin="dense"
  204. />
  205. </div>
  206. <Typography
  207. style={{ marginBottom: 8, marginTop: 12 }}
  208. variant="subtitle1"
  209. >
  210. {i18n.t("fileModal.form.fileOptions")}
  211. </Typography>
  212. <FieldArray name="options">
  213. {({ push, remove }) => (
  214. <>
  215. {values.options &&
  216. values.options.length > 0 &&
  217. values.options.map((info, index) => (
  218. <div
  219. className={classes.extraAttr}
  220. key={`${index}-info`}
  221. >
  222. <Grid container spacing={0}>
  223. <Grid xs={6} md={10} item>
  224. <Field
  225. as={TextField}
  226. label={i18n.t("fileModal.form.extraName")}
  227. name={`options[${index}].name`}
  228. variant="outlined"
  229. margin="dense"
  230. multiline
  231. fullWidth
  232. minRows={2}
  233. className={classes.textField}
  234. />
  235. </Grid>
  236. <Grid xs={2} md={2} item style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
  237. <input
  238. type="file"
  239. onChange={(e) => {
  240. const selectedFile = e.target.files[0];
  241. const updatedOptions = [...values.options];
  242. updatedOptions[index].file = selectedFile;
  243. setFiles('options', updatedOptions);
  244. // Atualize a lista selectedFileNames para o campo específico
  245. const updatedFileNames = [...selectedFileNames];
  246. updatedFileNames[index] = selectedFile ? selectedFile.name : '';
  247. setSelectedFileNames(updatedFileNames);
  248. }}
  249. style={{ display: 'none' }}
  250. name={`options[${index}].file`}
  251. id={`file-upload-${index}`}
  252. />
  253. <label htmlFor={`file-upload-${index}`}>
  254. <IconButton component="span">
  255. <AttachFileIcon />
  256. </IconButton>
  257. </label>
  258. <IconButton
  259. size="small"
  260. onClick={() => remove(index)}
  261. >
  262. <DeleteOutlineIcon />
  263. </IconButton>
  264. </Grid>
  265. <Grid xs={12} md={12} item>
  266. {info.path? info.path : selectedFileNames[index]}
  267. </Grid>
  268. </Grid>
  269. </div>
  270. ))}
  271. <div className={classes.extraAttr}>
  272. <Button
  273. style={{ flex: 1, marginTop: 8 }}
  274. variant="outlined"
  275. color="primary"
  276. onClick={() => {push({ name: "", path: ""});
  277. setSelectedFileNames([...selectedFileNames, ""]);
  278. }}
  279. >
  280. {`+ ${i18n.t("fileModal.buttons.fileOptions")}`}
  281. </Button>
  282. </div>
  283. </>
  284. )}
  285. </FieldArray>
  286. </DialogContent>
  287. <DialogActions>
  288. <Button
  289. onClick={handleClose}
  290. color="secondary"
  291. disabled={isSubmitting}
  292. variant="outlined"
  293. >
  294. {i18n.t("fileModal.buttons.cancel")}
  295. </Button>
  296. <Button
  297. type="submit"
  298. color="primary"
  299. disabled={isSubmitting}
  300. variant="contained"
  301. className={classes.btnWrapper}
  302. >
  303. {fileListId
  304. ? `${i18n.t("fileModal.buttons.okEdit")}`
  305. : `${i18n.t("fileModal.buttons.okAdd")}`}
  306. {isSubmitting && (
  307. <CircularProgress
  308. size={24}
  309. className={classes.buttonProgress}
  310. />
  311. )}
  312. </Button>
  313. </DialogActions>
  314. </Form>
  315. )}
  316. </Formik>
  317. </Dialog>
  318. </div>
  319. );
  320. };
  321. export default FilesModal;