index.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import React, { useState, useEffect } from "react";
  2. import { makeStyles } from "@material-ui/core/styles";
  3. import Stepper from "@material-ui/core/Stepper";
  4. import Step from "@material-ui/core/Step";
  5. import StepLabel from "@material-ui/core/StepLabel";
  6. import Typography from "@material-ui/core/Typography";
  7. import { Button, IconButton, StepContent, TextField } from "@material-ui/core";
  8. import AddIcon from "@material-ui/icons/Add";
  9. import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
  10. import SaveIcon from "@material-ui/icons/Save";
  11. import EditIcon from "@material-ui/icons/Edit";
  12. import api from "../../services/api";
  13. import toastError from "../../errors/toastError";
  14. const useStyles = makeStyles((theme) => ({
  15. root: {
  16. width: "100%",
  17. //height: 400,
  18. [theme.breakpoints.down("sm")]: {
  19. maxHeight: "20vh",
  20. },
  21. },
  22. button: {
  23. marginRight: theme.spacing(1),
  24. },
  25. input: {
  26. marginTop: theme.spacing(1),
  27. marginBottom: theme.spacing(1),
  28. },
  29. addButton: {
  30. marginTop: theme.spacing(2),
  31. marginBottom: theme.spacing(2),
  32. },
  33. }));
  34. export function QueueOptionStepper({ queueId, options, updateOptions }) {
  35. const classes = useStyles();
  36. const [activeOption, setActiveOption] = useState(-1);
  37. const handleOption = (index) => async () => {
  38. setActiveOption(index);
  39. const option = options[index];
  40. if (option !== undefined && option.id !== undefined) {
  41. try {
  42. const { data } = await api.request({
  43. url: "/queue-options",
  44. method: "GET",
  45. params: { queueId, parentId: option.id },
  46. });
  47. const optionList = data.map((option) => {
  48. return {
  49. ...option,
  50. children: [],
  51. edition: false,
  52. };
  53. });
  54. option.children = optionList;
  55. updateOptions();
  56. } catch (e) {
  57. toastError(e);
  58. }
  59. }
  60. };
  61. const handleSave = async (option) => {
  62. try {
  63. if (option.id) {
  64. await api.request({
  65. url: `/queue-options/${option.id}`,
  66. method: "PUT",
  67. data: option,
  68. });
  69. } else {
  70. const { data } = await api.request({
  71. url: `/queue-options`,
  72. method: "POST",
  73. data: option,
  74. });
  75. option.id = data.id;
  76. }
  77. option.edition = false;
  78. updateOptions();
  79. } catch (e) {
  80. toastError(e);
  81. }
  82. };
  83. const handleEdition = (index) => {
  84. options[index].edition = !options[index].edition;
  85. updateOptions();
  86. };
  87. const handleDeleteOption = async (index) => {
  88. const option = options[index];
  89. if (option !== undefined && option.id !== undefined) {
  90. try {
  91. await api.request({
  92. url: `/queue-options/${option.id}`,
  93. method: "DELETE",
  94. });
  95. } catch (e) {
  96. toastError(e);
  97. }
  98. }
  99. options.splice(index, 1);
  100. options.forEach(async (option, order) => {
  101. option.option = order + 1;
  102. await handleSave(option);
  103. });
  104. updateOptions();
  105. };
  106. const handleOptionChangeTitle = (event, index) => {
  107. options[index].title = event.target.value;
  108. updateOptions();
  109. };
  110. const handleOptionChangeMessage = (event, index) => {
  111. options[index].message = event.target.value;
  112. updateOptions();
  113. };
  114. const renderTitle = (index) => {
  115. const option = options[index];
  116. if (option.edition) {
  117. return (
  118. <>
  119. <TextField
  120. value={option.title}
  121. onChange={(event) => handleOptionChangeTitle(event, index)}
  122. size="small"
  123. className={classes.input}
  124. placeholder="Título da opção"
  125. />
  126. {option.edition && (
  127. <>
  128. <IconButton
  129. color="primary"
  130. variant="outlined"
  131. size="small"
  132. className={classes.button}
  133. onClick={() => handleSave(option)}
  134. >
  135. <SaveIcon />
  136. </IconButton>
  137. <IconButton
  138. variant="outlined"
  139. color="secondary"
  140. size="small"
  141. className={classes.button}
  142. onClick={() => handleDeleteOption(index)}
  143. >
  144. <DeleteOutlineIcon />
  145. </IconButton>
  146. </>
  147. )}
  148. </>
  149. );
  150. }
  151. return (
  152. <>
  153. <Typography>
  154. {option.title !== "" ? option.title : "Título não definido"}
  155. <IconButton
  156. variant="outlined"
  157. size="small"
  158. className={classes.button}
  159. onClick={() => handleEdition(index)}
  160. >
  161. <EditIcon />
  162. </IconButton>
  163. </Typography>
  164. </>
  165. );
  166. };
  167. const renderMessage = (index) => {
  168. const option = options[index];
  169. if (option.edition) {
  170. return (
  171. <>
  172. <TextField
  173. style={{ width: "100%" }}
  174. multiline
  175. value={option.message}
  176. onChange={(event) => handleOptionChangeMessage(event, index)}
  177. size="small"
  178. className={classes.input}
  179. placeholder="Digite o texto da opção"
  180. />
  181. </>
  182. );
  183. }
  184. return (
  185. <>
  186. <Typography onClick={() => handleEdition(index)}>
  187. {option.message}
  188. </Typography>
  189. </>
  190. );
  191. };
  192. const handleAddOption = (index) => {
  193. const optionNumber = options[index].children.length + 1;
  194. options[index].children.push({
  195. title: "",
  196. message: "",
  197. edition: false,
  198. option: optionNumber,
  199. queueId,
  200. parentId: options[index].id,
  201. children: [],
  202. });
  203. updateOptions();
  204. };
  205. const renderStep = (option, index) => {
  206. return (
  207. <Step key={index}>
  208. <StepLabel style={{ cursor: "pointer" }} onClick={handleOption(index)}>
  209. {renderTitle(index)}
  210. </StepLabel>
  211. <StepContent>
  212. {renderMessage(index)}
  213. {option.id !== undefined && (
  214. <>
  215. <Button
  216. color="primary"
  217. size="small"
  218. onClick={() => handleAddOption(index)}
  219. startIcon={<AddIcon />}
  220. variant="outlined"
  221. className={classes.addButton}
  222. >
  223. Adicionar
  224. </Button>
  225. </>
  226. )}
  227. <QueueOptionStepper
  228. queueId={queueId}
  229. options={option.children}
  230. updateOptions={updateOptions}
  231. />
  232. </StepContent>
  233. </Step>
  234. );
  235. };
  236. const renderStepper = () => {
  237. return (
  238. <Stepper
  239. style={{ marginBottom: 0, paddingBottom: 0 }}
  240. nonLinear
  241. activeStep={activeOption}
  242. orientation="vertical"
  243. >
  244. {options.map((option, index) => renderStep(option, index))}
  245. </Stepper>
  246. );
  247. };
  248. return renderStepper();
  249. }
  250. export function QueueOptions({ queueId }) {
  251. const classes = useStyles();
  252. const [options, setOptions] = useState([]);
  253. useEffect(() => {
  254. if (queueId) {
  255. const fetchOptions = async () => {
  256. try {
  257. const { data } = await api.request({
  258. url: "/queue-options",
  259. method: "GET",
  260. params: { queueId, parentId: -1 },
  261. });
  262. const optionList = data.map((option) => {
  263. return {
  264. ...option,
  265. children: [],
  266. edition: false,
  267. };
  268. });
  269. setOptions(optionList);
  270. } catch (e) {
  271. toastError(e);
  272. }
  273. };
  274. fetchOptions();
  275. }
  276. // eslint-disable-next-line react-hooks/exhaustive-deps
  277. }, []);
  278. const renderStepper = () => {
  279. if (options.length > 0) {
  280. return (
  281. <QueueOptionStepper
  282. queueId={queueId}
  283. updateOptions={updateOptions}
  284. options={options}
  285. />
  286. );
  287. }
  288. };
  289. const updateOptions = () => {
  290. setOptions([...options]);
  291. };
  292. const addOption = () => {
  293. const newOption = {
  294. title: "",
  295. message: "",
  296. edition: false,
  297. option: options.length + 1,
  298. queueId,
  299. parentId: null,
  300. children: [],
  301. };
  302. setOptions([...options, newOption]);
  303. };
  304. return (
  305. <div className={classes.root}>
  306. <br />
  307. <Typography>
  308. Opções
  309. <Button
  310. color="primary"
  311. size="small"
  312. onClick={addOption}
  313. startIcon={<AddIcon />}
  314. style={{ marginLeft: 10 }}
  315. variant="outlined"
  316. >
  317. Adicionar
  318. </Button>
  319. </Typography>
  320. {renderStepper()}
  321. </div>
  322. );
  323. }