diff --git a/src/App.js b/src/App.js index 64dc8a2841c712c81c924629dfe0e992e30699c5..be756c9983346e16424539128e0c21d3ded3871d 100644 --- a/src/App.js +++ b/src/App.js @@ -10,10 +10,12 @@ import SignIn from "./pages/SignIn"; import ListForms from "./pages/ListForms"; import EditForm from "./pages/EditForm"; import GetForm from "./pages/GetForm"; +import VisualizeForm from "./pages/VisualizeForm"; function App() { return ( <HashRouter> <Header /> + <Route path="/visualize/:id" component={VisualizeForm} /> <Route path="/create" component={CreateForm} /> <Route path="/answer/:id" component={AnswerForm} /> <Route path="/edit/:id" component={EditForm} /> diff --git a/src/components/fieldsListForms/CardForm.jsx b/src/components/fieldsListForms/CardForm.jsx index 40fc1f864a0643ec015b56cdb82b408cde260864..102d08e22288ef5c7db8b0a67abccec9316f2bf3 100644 --- a/src/components/fieldsListForms/CardForm.jsx +++ b/src/components/fieldsListForms/CardForm.jsx @@ -7,7 +7,7 @@ import IconButton from "@material-ui/core/IconButton"; import Tooltip from "@material-ui/core/Tooltip"; import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/core/styles"; - +import { useHistory } from "react-router-dom"; // Icons import MoreVertOutlinedIcon from "@material-ui/icons/MoreVertOutlined"; import DeleteButton from "./DeleteButton"; @@ -56,6 +56,13 @@ const useStyles = makeStyles(theme => ({ color: "#667079" }, + icons: { + marginLeft: "20%", + ["@media (max-width: 370px)"]: { + marginLeft: "10%" + } + }, + numberOfAnswers: { fontSize: 18, textAlign: "left", @@ -65,25 +72,36 @@ const useStyles = makeStyles(theme => ({ function CardForm(props) { const classes = useStyles(); + const history = useHistory(); const handleAnswer = () => { //redirecionar para /answer/props.id + let path = `/form/${props.id}`; + history.push(path); }; const handleVisualize = () => { //redirecionar para /visual/props.id + let path = `/visualize/${props.id}`; + history.push(path); }; const handleEdit = () => { //redirecionar para /edit/props.id + let path = `/edit/${props.id}`; + history.push(path); }; function handleDelete(value) { if (value) { - //deletar o form + // deleteForm(); } } + async function deleteForm() { + // const res = await api + } + function manageDate(date) { if (date === "") { return ""; @@ -97,12 +115,10 @@ function CardForm(props) { return ( <ExpansionPanel> <ExpansionPanelSummary expandIcon={<MoreVertOutlinedIcon />}> - <Typography className={classes.title}> + <Typography noWrap className={classes.title}> {props.title} <br /> - <div className={classes.create} noWrap> - {props.description} - </div> + <div className={classes.create}>{props.description}</div> <Divider /> <div className={classes.numberOfAnswers}> {props.numberOfAnswers} Respostas @@ -113,7 +129,7 @@ function CardForm(props) { <Typography className={classes.date}> {props.date ? "Data de modificação: " + manageDate(props.date) : ""} </Typography> - <Tooltip title="Editar" arrow> + <Tooltip className={classes.icons} title="Editar" arrow> <IconButton onClick={handleEdit}> <EditOutlinedIcon /> </IconButton> @@ -133,10 +149,10 @@ function CardForm(props) { </IconButton> </Tooltip> - <DeleteButton handleDelete={handleDelete} /> + {/*<DeleteButton handleDelete={handleDelete} />*/} </ExpansionPanelDetails> </ExpansionPanel> ); } -export default CardForm; \ No newline at end of file +export default CardForm; diff --git a/src/components/fieldsListForms/ShareButton.jsx b/src/components/fieldsListForms/ShareButton.jsx index 7ef16fcd8561b2cd32f29d8bf93da0acf5a8abc5..c93a55e85c55ef6eb0704ec88e92d1884814a17b 100644 --- a/src/components/fieldsListForms/ShareButton.jsx +++ b/src/components/fieldsListForms/ShareButton.jsx @@ -35,7 +35,7 @@ function ShareButton(props) { > <DialogTitle id="responsive-dialog-title"> { - "Aqui está a URL do seu formulário, copie-a e envie-a para ser respondida: " + "Aqui está a URL do seu formulário, copie-a e envie-a para ser respondido: " } </DialogTitle> <DialogContent> @@ -48,4 +48,4 @@ function ShareButton(props) { ); } -export default ShareButton; \ No newline at end of file +export default ShareButton; diff --git a/src/components/fieldsListForms/Tab.jsx b/src/components/fieldsListForms/Tab.jsx index 50a2fecdc9e69437f941e7de72609692c30c7f1e..fdcc5dcd3df99be4bfd23677235803ba21b2d78e 100644 --- a/src/components/fieldsListForms/Tab.jsx +++ b/src/components/fieldsListForms/Tab.jsx @@ -77,6 +77,11 @@ function Tab(props) { setseletectedValue(event.target.value); }; + const handleClick = () => { + let path = `/create`; + history.push(path); + }; + return ( <Grid container className={classes.container}> <Grid item className={classes.gridMenu}> @@ -103,7 +108,11 @@ function Tab(props) { <SearchBar searching={props.searching} /> </Grid> <Grid item className={classes.gridButton}> - <Button className={classes.button} variant="contained"> + <Button + className={classes.button} + onClick={handleClick} + variant="contained" + > CRIAR NOVO FORMULÁRIO </Button> </Grid> diff --git a/src/components/fieldsVisualizeForm/FieldFooterOptions.js b/src/components/fieldsVisualizeForm/FieldFooterOptions.js new file mode 100644 index 0000000000000000000000000000000000000000..f6446f0b9fbd69735a1c055d79c59f301da37250 --- /dev/null +++ b/src/components/fieldsVisualizeForm/FieldFooterOptions.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Grid from '@material-ui/core/Grid'; +import Paper from '@material-ui/core/Paper'; +import TextField from '@material-ui/core/TextField'; +import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined'; +import IconButton from '@material-ui/core/IconButton'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import AddCircleIcon from '@material-ui/icons/AddCircle'; +import CloseIcon from '@material-ui/icons/Close'; +import Switch from '@material-ui/core/Switch'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; + +function FieldFooterOptions(props) { + + return ( + <> + <FormControlLabel + control={ + <Switch + onChange={e => props.setRequiredField(props.idq) } + value="required" + color="primary" + checked={props.required} + /> + } + label="Obrigatória" + /> + <IconButton aria-label="delete" onClick={() => { props.deleteFromForm(props.idq) } }> + <DeleteOutlinedIcon /> + </IconButton> + </> + ); + + +} + + +export default FieldFooterOptions; \ No newline at end of file diff --git a/src/components/fieldsVisualizeForm/FormFieldCheckbox.js b/src/components/fieldsVisualizeForm/FormFieldCheckbox.js new file mode 100644 index 0000000000000000000000000000000000000000..2ff9e25d13fc6646748f91fcfab838a08188e96a --- /dev/null +++ b/src/components/fieldsVisualizeForm/FormFieldCheckbox.js @@ -0,0 +1,85 @@ +import React, { useEffect } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import Typography from "@material-ui/core/Typography"; +import Checkbox from "@material-ui/core/Checkbox"; + +import FieldFooterOptions from "./FieldFooterOptions"; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(3), + width: theme.spacing(100), + minheight: theme.spacing(18), + margin: theme.spacing(2), + ["@media (max-width:827px)"]: { + width: theme.spacing(70) + }, + ["@media (max-width:590px)"]: { + width: theme.spacing(40) + } + }, + questionsGrid: { + marginBottom: "20px" + }, + text: { + color: "black" + }, + validation: { + fontSize: "14px", + color: "red" + } +})); + +function FormFieldCheckbox(props) { + const classes = useStyles(); + + /** HTML to be displayed. */ + const options = props.options.map((x, index) => { + return ( + <span> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.text} + variant="h7" + > + {x.value} + </Typography> + <Checkbox disabled /> + </span> + ); + }); + + return ( + <Paper className={classes.paper}> + <Grid container> + <Grid item xs={12} className={classes.questionsGrid}> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.text} + variant="h6" + > + {props.question} + </Typography> + <Typography style={{ wordWrap: "break-word" }} variant="h8"> + {props.description} + </Typography> + </Grid> + <Grid + item + container + direction="column" + justify="flex-start" + alignItems="flex-start" + xs={5} + className={classes.questionsGrid} + > + {options} + </Grid> + </Grid> + </Paper> + ); +} + +export default FormFieldCheckbox; diff --git a/src/components/fieldsVisualizeForm/FormFieldRadio.js b/src/components/fieldsVisualizeForm/FormFieldRadio.js new file mode 100644 index 0000000000000000000000000000000000000000..c470b27e1e73c7d2c1ee513f797a522b2be38518 --- /dev/null +++ b/src/components/fieldsVisualizeForm/FormFieldRadio.js @@ -0,0 +1,86 @@ +import React, { useState, useEffect } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import Typography from "@material-ui/core/Typography"; +import Radio from "@material-ui/core/Radio"; +import RadioGroup from "@material-ui/core/RadioGroup"; + +import FieldFooterOptions from "./FieldFooterOptions"; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(3), + width: theme.spacing(100), + minheight: theme.spacing(18), + margin: theme.spacing(2), + ["@media (max-width:827px)"]: { + width: theme.spacing(70) + }, + ["@media (max-width:590px)"]: { + width: theme.spacing(40) + } + }, + questionsGrid: { + marginBottom: "20px" + }, + text: { + color: "black" + }, + validation: { + fontSize: "14px", + color: "red" + } +})); + +function FormFieldRadio(props) { + const classes = useStyles(); + + /** HTML object to be displayed on component return. */ + const options = props.options.map(function(x, index) { + return ( + <span> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.text} + variant="h7" + > + {x.value} + </Typography> + <Radio disabled value={x.value} /> + </span> + ); + }); + + return ( + <Paper className={classes.paper}> + <Grid container> + <Grid item xs={12} className={classes.questionsGrid}> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.text} + variant="h6" + > + {props.question} + </Typography> + <Typography style={{ wordWrap: "break-word" }} variant="h8"> + {props.description} + </Typography> + </Grid> + <Grid + item + container + direction="column" + justify="flex-start" + alignItems="flex-start" + xs={5} + className={classes.questionsGrid} + > + <RadioGroup>{options}</RadioGroup> + </Grid> + </Grid> + </Paper> + ); +} + +export default FormFieldRadio; diff --git a/src/components/fieldsVisualizeForm/FormFieldSelect.js b/src/components/fieldsVisualizeForm/FormFieldSelect.js new file mode 100644 index 0000000000000000000000000000000000000000..a52bb580f98d6308310fb11130c907eb38cebb19 --- /dev/null +++ b/src/components/fieldsVisualizeForm/FormFieldSelect.js @@ -0,0 +1,85 @@ +import React, { useState, useEffect } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import Select from "@material-ui/core/Select"; +import MenuItem from "@material-ui/core/MenuItem"; +import Typography from "@material-ui/core/Typography"; + +import FieldFooterOptions from "./FieldFooterOptions"; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(3), + width: theme.spacing(100), + minheight: theme.spacing(18), + margin: theme.spacing(2), + ["@media (max-width:827px)"]: { + width: theme.spacing(70) + }, + ["@media (max-width:590px)"]: { + width: theme.spacing(40) + } + }, + questionsGrid: { + marginBottom: "20px" + }, + text: { + color: "black" + }, + validation: { + fontSize: "14px", + color: "red" + } +})); + +function FormFieldSelect(props) { + const classes = useStyles(); + + /** HTML object to be displayed on component return. */ + const options = props.options.map(function(x) { + return <MenuItem value={x.placement}>{x.value}</MenuItem>; + }); + + return ( + <Paper className={classes.paper}> + <Grid container> + <Grid item xs={12} className={classes.questionsGrid}> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.text} + variant="h6" + > + {props.question} + </Typography> + <Typography + style={{ wordWrap: "break-word" }} + variant="h8" + gutterBottom + > + {props.description} + </Typography> + </Grid> + <Grid + item + container + direction="column" + justify="flex-start" + alignItems="flex-start" + xs={5} + className={classes.questionsGrid} + > + <Select + labelId="demo-simple-select-label" + id="demo-simple-select" + disabled + > + {options} + </Select> + </Grid> + </Grid> + </Paper> + ); +} + +export default FormFieldSelect; diff --git a/src/components/fieldsVisualizeForm/FormFieldSubform.js b/src/components/fieldsVisualizeForm/FormFieldSubform.js new file mode 100644 index 0000000000000000000000000000000000000000..f87e04a9ed5c9f398b2d2f8709093b792f52878f --- /dev/null +++ b/src/components/fieldsVisualizeForm/FormFieldSubform.js @@ -0,0 +1,110 @@ +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Button from "@material-ui/core/Button"; +import api from "../../api"; + +import FormFieldText from "./FormFieldText"; +import FormFieldSelect from "./FormFieldSelect"; +import FormFieldRadio from "./FormFieldRadio"; +import FormFieldCheckbox from "./FormFieldCheckbox"; + +const useStyles = makeStyles(theme => ({ + menu: { + width: theme.spacing(6), + minheight: theme.spacing(15), + position: "fixed", + top: theme.spacing(10), + left: "90%", + padding: theme.spacing(1) + } +})); + +function FormFieldSubform(props) { + const classes = useStyles(); + + /** Subform id */ + const id = props.id; + + /** Maped subform */ + const [formData, setFormData] = useState(0); + + /** Get subform */ + async function getForm(id) { + const res = await api + .get(`/form/${id}`) + .then(function(res) { + setFormData(res.data); + }) + .catch(error => { + alert("Um erro inesperado ocorreu ao tentar obter o subform."); + }); + } + + /** First gets info from the backend */ + useEffect(() => { + getForm(id); + }, []); + + return ( + <div> + <Grid container direction="column" alignItems="center" justify="center"> + {formData ? ( + <div> + {formData.inputs.map((input, index) => { + if (input.type === 0) + return ( + <FormFieldText + question={input.question} + description={input.description} + id={input.id} + /> + ); + else if (input.type === 3) + return ( + <FormFieldSelect + question={input.question} + id={input.id} + description={input.description} + options={input.sugestions} + /> + ); + else if (input.type === 2) + return ( + <FormFieldRadio + question={input.question} + description={input.description} + id={input.id} + options={input.sugestions} + /> + ); + else if (input.type === 1) + return ( + <FormFieldCheckbox + question={input.question} + description={input.description} + options={input.sugestions} + id={input.id} + /> + ); + else if (input.type === 4) + return ( + <FormFieldSubform + question={input.question} + description={input.description} + options={input.sugestions} + id={input.subForm.contentFormId} + /> + ); + })} + </div> + ) : ( + <p>Loading...</p> + )} + </Grid> + </div> + ); +} + +export default FormFieldSubform; diff --git a/src/components/fieldsVisualizeForm/FormFieldText.js b/src/components/fieldsVisualizeForm/FormFieldText.js new file mode 100644 index 0000000000000000000000000000000000000000..92d7cc0ef7a60ee74bc5e5bcc467a23a03fb29c5 --- /dev/null +++ b/src/components/fieldsVisualizeForm/FormFieldText.js @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(3), + width: theme.spacing(100), + height: "15%", + margin: theme.spacing(2), + ["@media (max-width:827px)"]: { + width: theme.spacing(70) + }, + ["@media (max-width:590px)"]: { + width: theme.spacing(40) + } + }, + questionsGrid: { + marginBottom: "20px" + }, + text: { + color: "black" + }, + validation: { + fontSize: "14px", + color: "red" + } +})); + +function FormFieldText(props) { + const classes = useStyles(); + + return ( + <Paper className={classes.paper}> + <Grid container> + <Grid item xs={12} className={classes.questionsGrid}> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.text} + variant="h6" + > + {props.question} + </Typography> + <Typography style={{ wordWrap: "break-word" }} variant="h8"> + {props.description} + </Typography> + </Grid> + <Grid item xs={9} className={classes.questionsGrid}> + <TextField + multiline + id="outlined-disabled" + label="" + placeholder="Resposta" + disabled + /> + </Grid> + <Grid + item + container + direction="row" + justify="flex-end" + alignItems="flex-end" + xs={3} + ></Grid> + </Grid> + </Paper> + ); +} + +export default FormFieldText; diff --git a/src/components/fieldsVisualizeForm/FormFieldTitle.js b/src/components/fieldsVisualizeForm/FormFieldTitle.js new file mode 100644 index 0000000000000000000000000000000000000000..530c7f38df1e8ec194044afddf2f6cc45456e5ad --- /dev/null +++ b/src/components/fieldsVisualizeForm/FormFieldTitle.js @@ -0,0 +1,93 @@ +import React from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import Typography from "@material-ui/core/Typography"; + +import FieldFooterOptions from "./FieldFooterOptions"; + +const useStyles = makeStyles(theme => ({ + paper: { + padding: theme.spacing(3), + width: theme.spacing(100), + height: "40%", + margin: theme.spacing(2), + color: "#000000", + ["@media (max-width:827px)"]: { + width: theme.spacing(70) + }, + ["@media (max-width:590px)"]: { + width: theme.spacing(40) + } + }, + questionsGrid: { + marginBottom: "20px", + color: "#000000", + ["@media (max-width:827px)"]: { + width: theme.spacing(70) + } + }, + title: { + fontSize: "45px", + color: "#000000", + ["@media (max-width:827px)"]: { + fontSize: "35px" + }, + ["@media (max-width:590px)"]: { + fontSize: "25px" + } + }, + description: { + fontSize: "30px", + color: "#000000", + ["@media (max-width:827px)"]: { + fontSize: "25px" + }, + ["@media (max-width:590px)"]: { + fontSize: "15px" + } + } +})); + +function FormFieldText(props) { + const classes = useStyles(); + + return ( + <Grid> + <Paper className={classes.paper}> + <Grid container> + <Grid item xs={12} className={classes.questionsGrid}> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.title} + variant="h3" + gutterBottom + > + {props.title} + </Typography> + </Grid> + <Grid item xs={9} className={classes.questionsGrid}> + <Typography + style={{ wordWrap: "break-word" }} + className={classes.description} + variant="h4" + gutterBottom + > + {props.description} + </Typography> + </Grid> + <Grid + item + container + direction="row" + justify="flex-end" + alignItems="flex-end" + xs={3} + ></Grid> + </Grid> + </Paper> + </Grid> + ); +} + +export default FormFieldText; diff --git a/src/components/footer/footer.js b/src/components/footer/footer.js index 07c7650130b2cc749f22218fc93cfabcc54421cf..f538dcad5f03a43d5d5374b24feb33e102d51dca 100644 --- a/src/components/footer/footer.js +++ b/src/components/footer/footer.js @@ -23,15 +23,18 @@ const useStyles = makeStyles(theme => ({ marginLeft: "auto", marginRight: "auto", ["@media (min-width: 960px)"]: { - marginLeft: "5px", + marginLeft: "5px" } }, item: { display: "flex", justifyContent: "space-evenly", - flexDirection: "column", - alignItems: "center" + flexDirection: "column" + }, + + link: { + textDecoration: "none" }, text: { @@ -65,7 +68,13 @@ function Footer() { return ( <Grid container className={classes.footer}> <Grid item xs={12} sm={12} md={3} lg={2} className={classes.item}> - <img src={Logo} className={classes.img}></img> + <a + href="https://www.c3sl.ufpr.br/" + title="Ir para a página inicial do C3SL" + className={classes.link} + > + <img src={Logo} className={classes.img} /> + </a> </Grid> <Grid item md={6} lg={8} className={classes.item}> @@ -89,8 +98,6 @@ function Footer() { Telefone: 61 2027-6000 </Typography> </Grid> - - </Grid> ); } diff --git a/src/components/header/header.jsx b/src/components/header/header.jsx index d71425821f96c7defe162221ef425cddcc9f150f..8a52a8c84e822ae87d57ffcdeb7c4fbe641f62e0 100644 --- a/src/components/header/header.jsx +++ b/src/components/header/header.jsx @@ -4,14 +4,14 @@ import logo from "./header_imgs/imgsimmc-01.png"; import { makeStyles } from "@material-ui/core"; import MenuListComposition from "./header_components/MenuList"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ header: { background: "#05a5dd", width: "auto", display: "flex", flexDirection: "column", justifyContent: "center", - height: "13%", + height: "13%" }, simmc: { marginTop: "5%", @@ -19,8 +19,8 @@ const useStyles = makeStyles((theme) => ({ color: "#ffffff", marginLeft: "2%", ["@media (max-width:1040px)"]: { - display: "none", - }, + display: "none" + } }, form_creator: { color: "#ffffff", @@ -29,15 +29,15 @@ const useStyles = makeStyles((theme) => ({ fontSize: "21px", ["@media (max-width:525px)"]: { marginTop: "5%", - marginLeft: "10%", + marginLeft: "10%" }, ["@media (max-width:337px)"]: { - fontSize: "19px", - }, - }, + fontSize: "19px" + } + } }, link: { - textDecoration: "none", + textDecoration: "none" }, logo: { marginLeft: "2.5%", @@ -46,18 +46,27 @@ const useStyles = makeStyles((theme) => ({ ["@media (max-width:600px)"]: { width: "65px", ["@media (max-width:338px)"]: { - marginTop: "3%", - }, - }, + marginTop: "3%" + } + } }, userImgContainer: { display: "flex", - flexDirection: "column", - }, + flexDirection: "column" + } })); export default function Header() { const classes = useStyles(); + const [isLoged, setIsLoged] = React.useState(checkLoged()); + + function checkLoged() { + if (window.sessionStorage.getItem("token")) { + return true; + } + + return false; + } return ( <Grid> @@ -98,15 +107,27 @@ export default function Header() { </Grid> </Grid> <Grid container item xs={6} sm={6} md={4} justify="center"> - <a - href="https://google.com.br" // mudar para a main page quando estiver feita - title="Ir para a página inicial do Gerenciador de Formulários" - className={classes.link} - > - <h2 className={classes.form_creator}> - Gerenciador de Formulários - </h2> - </a> + {isLoged ? ( + <a + href="http://localhost:3000/#/signup" + title="Ir para a página de SignUp" + className={classes.link} + > + <h2 className={classes.form_creator}> + Gerenciador de Formulários + </h2> + </a> + ) : ( + <a + href="http://localhost:3000/#/signin" + title="Ir para a página de SignIn" + className={classes.link} + > + <h2 className={classes.form_creator}> + Gerenciador de Formulários + </h2> + </a> + )} </Grid> <Grid container @@ -118,7 +139,7 @@ export default function Header() { justify="center" alignContent="flex-end" > - <MenuListComposition /> + <MenuListComposition isLoged={isLoged} checkLoged={checkLoged} /> </Grid> </Grid> </header> diff --git a/src/components/header/header_components/MenuList.jsx b/src/components/header/header_components/MenuList.jsx index e4491ce3ea454e29e49c014b5e38ab3434a49be4..21bd7014f6e172f29f16b71a9878ab867ee1f81d 100644 --- a/src/components/header/header_components/MenuList.jsx +++ b/src/components/header/header_components/MenuList.jsx @@ -5,34 +5,35 @@ import Grow from "@material-ui/core/Grow"; import Paper from "@material-ui/core/Paper"; import Popper from "@material-ui/core/Popper"; import MenuItem from "@material-ui/core/MenuItem"; +import { useHistory } from "react-router-dom"; import MenuList from "@material-ui/core/MenuList"; import { makeStyles } from "@material-ui/core/styles"; import UserImg from "./../header_imgs/user.png"; import { Avatar } from "@material-ui/core"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ menuPopUp: { alignContent: "start", ["@media (max-width:346px)"]: { - width: "23%", - }, + width: "23%" + } }, menuPopUpText: { ["@media (max-width:525px)"]: { - fontSize: "13px", - }, + fontSize: "13px" + } }, popUpPaper: { marginRight: "14%", ["@media (max-width:525px)"]: { - marginRight: "0", - }, + marginRight: "0" + } }, menuList: { alignItems: "flex-start", - color: "grey", - }, + color: "grey" + } })); /** @@ -42,14 +43,35 @@ const useStyles = makeStyles((theme) => ({ function MenuListComposition(props) { const [open, setOpen] = React.useState(false); + const history = useHistory(); + + const handleLogin = () => { + let path = `/signin`; + history.push(path); + }; const prevOpen = React.useRef(open); const anchorRef = React.useRef(null); const handleToggle = () => { - setOpen((prevOpen) => !prevOpen); + setOpen(prevOpen => !prevOpen); + }; + const handleProfile = event => { + if (window.sessionStorage.getItem("userId")) { + let path = `/list/${window.sessionStorage.getItem("userId")}`; + history.push(path); + } + + alert("Você não está logado."); + }; + const handleLogOut = event => { + window.sessionStorage.removeItem("token"); + window.sessionStorage.removeItem("userId"); + props.checkLoged(); + let path = `/signin`; + history.push(path); }; - const handleClose = (event) => { + const handleClose = event => { if (anchorRef.current && anchorRef.current.contains(event.target)) { return; } @@ -64,6 +86,7 @@ function MenuListComposition(props) { React.useEffect(() => { if (prevOpen.current === true && open === false) { anchorRef.current.focus(); + props.checkLoged(); } prevOpen.current = open; @@ -84,6 +107,7 @@ function MenuListComposition(props) { </Button> <Popper open={open} + placement="left" anchorEl={anchorRef.current} role={undefined} transition @@ -95,32 +119,51 @@ function MenuListComposition(props) { {...TransitionProps} style={{ transformOrigin: - placement === "bottom" ? "center top" : "center bottom", + placement === "bottom" ? "center top" : "center bottom" }} > <Paper className={classes.popUpPaper}> - <ClickAwayListener onClickAway={handleClose}> - <MenuList - className={classes.menuList} - autoFocusItem={open} - id="menu-list-grow" - onKeyDown={handleListKeyDown} - alignContent="flex-start" - > - <MenuItem - onClick={handleClose} - className={classes.menuPopUpText} + {props.isLoged ? ( + <ClickAwayListener onClickAway={handleClose}> + <MenuList + className={classes.menuList} + autoFocusItem={open} + id="menu-list-grow" + onKeyDown={handleListKeyDown} + alignContent="flex-start" > - Perfil - </MenuItem> - <MenuItem - onClick={handleClose} - className={classes.menuPopUpText} + <MenuItem + onClick={handleProfile} + className={classes.menuPopUpText} + > + Perfil + </MenuItem> + <MenuItem + onClick={handleLogOut} + className={classes.menuPopUpText} + > + Logout + </MenuItem> + </MenuList> + </ClickAwayListener> + ) : ( + <ClickAwayListener onClickAway={handleClose}> + <MenuList + className={classes.menuList} + autoFocusItem={open} + id="menu-list-grow" + onKeyDown={handleListKeyDown} + alignContent="flex-start" > - Logout - </MenuItem> - </MenuList> - </ClickAwayListener> + <MenuItem + onClick={handleLogin} + className={classes.menuPopUpText} + > + Login + </MenuItem> + </MenuList> + </ClickAwayListener> + )} </Paper> </Grow> )} diff --git a/src/contexts/FormContext.js b/src/contexts/FormContext.js index bd70c897fa6cfd32e53ba4859f7e64562a6ccdf6..015c9e922fef2e709ce809241ce311a825ffb3f1 100644 --- a/src/contexts/FormContext.js +++ b/src/contexts/FormContext.js @@ -2,9 +2,11 @@ import React, { useState, createContext, useEffect } from "react"; import { useParams } from "react-router-dom"; import api from "../api"; import { createFrontendForm } from "../components/fieldsDisplayForm/utils/FrontEndTranslation"; +import { useHistory } from "react-router-dom"; export const FormEditionContext = createContext(); -const FormProvider = (props) => { +const FormProvider = props => { + const history = useHistory(); /** Getting the id from the route information */ const { id } = useParams(); /** Form state being started with the title in the case of the creation or 0 when editing (to be overrided by the backend data afterwards) @@ -20,19 +22,28 @@ const FormProvider = (props) => { error: { errorMsg: { question: "Este campo é obrigatório!", - description: "", - }, - }, - }, + description: "" + } + } + } ] ); /** Hook to access the API in the case a edition is being done */ useEffect(() => { const fetchData = async () => { - api.get(`/form/${id}`).then(async function (res) { - setForm(await createFrontendForm(res.data)); - }); + api + .get(`/form/${id}`) + .then(async function(res) { + setForm(await createFrontendForm(res.data)); + }) + .catch(error => { + if (error.response.status === 401) { + let path = `/signin`; + history.push(path); + return; + } + }); }; if (id) fetchData(); }, []); diff --git a/src/contexts/useForm.js b/src/contexts/useForm.js index 71fc9266bbb6a4e62637a9dc617357ce2ada0c15..d7b17c235c1c1d4421f0157d9fcc4c4a3c572f23 100644 --- a/src/contexts/useForm.js +++ b/src/contexts/useForm.js @@ -1,13 +1,14 @@ import { useContext, useEffect } from "react"; import { FormEditionContext } from "./FormContext"; import uuid from "uuid/v4"; +import { useHistory } from "react-router-dom"; import { testQuestionTextSchema, testDescriptionTextSchema, selectOptionsTesting, testSubformSchema, selectOptionTextTesting, - testTextValidation, + testTextValidation } from "../components/fieldsDisplayForm/utils/schemas"; import { pushTitle, @@ -15,7 +16,7 @@ import { pushSelect, pushRadio, pushCheckbox, - pushSubform, + pushSubform } from "../components/fieldsDisplayForm/utils/FormComposition"; import api from "../api"; @@ -237,7 +238,7 @@ const useForm = () => { */ async function setId() { const fetchData = async () => { - await api.get(`/form/${routeId}`).then(async function (res) { + await api.get(`/form/${routeId}`).then(async function(res) { let backForm = createFrontendForm(res.data); for (let i = 1; i < backForm.length; i++) { for (let j = 1; j < form.length; j++) { @@ -247,13 +248,13 @@ const useForm = () => { "question", "description", "options", - "subformId", + "subformId" ]) !== JSON.stringify(form[j], [ "question", "description", "options", - "subformId", + "subformId" ]) || differentValidation(backForm[i], form[j]) ) { @@ -266,6 +267,7 @@ const useForm = () => { }; await fetchData(); } + const history = useHistory(); /** The submit function. It's triggered when the submit button is pressed on the interface. * Its api call may be to create or to edit a form. */ @@ -277,14 +279,19 @@ const useForm = () => { const post_response = await api .put(`/form/${routeId}`, data, { headers: { - authorization: `bearer ${window.sessionStorage.getItem("token")}`, - }, + authorization: `bearer ${window.sessionStorage.getItem("token")}` + } }) - .then(function (error) { + .then(function(error) { if (!error.response) alert("Seu formulário foi atualizado com sucesso."); }) - .catch(function (error) { + .catch(function(error) { + if (error.response.status === 401) { + let path = `/signin`; + history.push(path); + return; + } if (error.response.data.error === "User dont own this form.") alert("Você não tem permissão para alterar este formulário"); else if (error.response.data.error === "Found a subform loop") @@ -297,14 +304,14 @@ const useForm = () => { const post_response = await api .post(`/form`, await createBackendForm(form), { headers: { - authorization: `bearer ${window.sessionStorage.getItem("token")}`, - }, + authorization: `bearer ${window.sessionStorage.getItem("token")}` + } }) - .then(function (error) { + .then(function(error) { if (!error.response) alert("Seu formulário foi criado com sucesso."); else console.log("ERROR NO POST_RESPONSE", error); }) - .catch(function (error) { + .catch(function(error) { console.log("ERROR NO POST RESPONSE", error.response); alert("Um erro ocorreu."); }); @@ -331,7 +338,7 @@ const useForm = () => { setValidationValue, removeValidation, onDragEnd, - submit, + submit }; }; diff --git a/src/pages/AnswerForm.js b/src/pages/AnswerForm.js index 47fc79143e39e76841bba393be7ab22d87bea88e..1f46fe8fdbfb4d7fcc7e5f756d0e3a8fea1bc22d 100644 --- a/src/pages/AnswerForm.js +++ b/src/pages/AnswerForm.js @@ -5,6 +5,7 @@ import Grid from "@material-ui/core/Grid"; import api from "../api"; import Button from "@material-ui/core/Button"; import { createMuiTheme, MuiThemeProvider } from "@material-ui/core"; +import { useHistory } from "react-router-dom"; import FormFieldText from "../components/fieldsAnswerForm/FormFieldText"; import FormFieldSelect from "../components/fieldsAnswerForm/FormFieldSelect"; @@ -62,6 +63,7 @@ const theme = createMuiTheme({ function AnwserForm() { const classes = useStyles(); + const history = useHistory(); /** Array of answers created when form is maped from the api */ const [answerArray, setanswerArray] = React.useState([]); @@ -212,7 +214,8 @@ function AnwserForm() { }) .catch(error => { if (error.response.status === 401) { - alert("Você não está logado."); + let path = `/signin`; + history.push(path); return; } if (error.response.status === 500) { diff --git a/src/pages/ListForms.js b/src/pages/ListForms.js index a09fc5bbad9fcf1cc648c447590d96e364deac5f..2c97add4d596d51aed0a295100f47411ff6e054f 100644 --- a/src/pages/ListForms.js +++ b/src/pages/ListForms.js @@ -4,6 +4,7 @@ import { useParams } from "react-router-dom"; import Grid from "@material-ui/core/Grid"; import Container from "@material-ui/core/Container"; import { makeStyles } from "@material-ui/core/styles"; +import { useHistory } from "react-router-dom"; // Components import CardForm from "../components/fieldsListForms/CardForm.jsx"; @@ -15,6 +16,7 @@ const useStyles = makeStyles(theme => ({ })); export default function ListForms() { const classes = useStyles(); + const history = useHistory(); // Get the ID from the URL const { id } = useParams(); @@ -74,11 +76,20 @@ export default function ListForms() { * @param id - the user's id to have the forms listed. */ async function fetchData(id) { - const res = await api.get(`/user/list/${id}`).then(function(res) { - setForms(res.data.sort((a, b) => a.id > b.id)); - setAuxForms(res.data.sort((a, b) => a.id > b.id)); - setisLoaded(true); - }); + const res = await api + .get(`/user/list/${id}`) + .then(function(res) { + setForms(res.data.sort((a, b) => a.id > b.id)); + setAuxForms(res.data.sort((a, b) => a.id > b.id)); + setisLoaded(true); + }) + .catch(error => { + if (error.response.status === 401) { + let path = `/signin`; + history.push(path); + return; + } + }); } useEffect(() => { diff --git a/src/pages/SignIn.js b/src/pages/SignIn.js index 4e6b3a5870907e1cfca9806b7c39aa18fbc37d81..c3c924465b6161aa82e649bdf5917fd8a6955d81 100644 --- a/src/pages/SignIn.js +++ b/src/pages/SignIn.js @@ -1,4 +1,5 @@ import React from "react"; +import { useHistory } from "react-router-dom"; import Grid from "@material-ui/core/Grid"; import { createMuiTheme, MuiThemeProvider } from "@material-ui/core"; import IconButton from "@material-ui/core/IconButton"; @@ -8,7 +9,7 @@ import FormInput from "../components/fieldsSignUp/FormInput"; import Paper from "@material-ui/core/Paper"; import api from "../api"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ register: { maxWidth: "1000px", background: "#ffffff", @@ -16,22 +17,22 @@ const useStyles = makeStyles((theme) => ({ padding: "2% 1%", margin: "0 auto", marginTop: "9%", - width: "95%", + width: "95%" }, custom_strong: { fontSize: "25px", textAlign: "center", display: "block", - color: "#46525d", + color: "#46525d" }, strong_description: { fontSize: "14px", - color: "#c2c6ca", + color: "#c2c6ca" }, form: { marginTop: "3%", alignItems: "center", - textAlign: "center", + textAlign: "center" }, button: { type: "submit", @@ -42,25 +43,26 @@ const useStyles = makeStyles((theme) => ({ padding: "10px 20px", fontSize: "18px", "&:hover": { - backgroundColor: "rgb(25, 109, 23)", + backgroundColor: "rgb(25, 109, 23)" }, ["@media (max-width:550px)"]: { - width: "55%", - }, - }, + width: "55%" + } + } })); export default function SignIn() { const classes = useStyles(); + const history = useHistory(); const [values, setValues] = React.useState({ email: "", password: "", - emailError: false, + emailError: false }); async function update(prop, event) { await setValues({ ...values, [prop]: event.target.value }); } - const handleChange = (prop) => (event) => { + const handleChange = prop => event => { if (!checkEmail()) { values.emailError = true; } else { @@ -93,17 +95,17 @@ export default function SignIn() { const response = await api .post(`/user/signIn`, { email: values.email, - hash: values.password, + hash: values.password }) - .then(function (response) { + .then(function(response) { if (!response.data.error) { - alert("Você logou com sucesso."); window.sessionStorage.setItem("token", response.data.token); window.sessionStorage.setItem("userId", response.data.id); - // redirecionar para a main page + let path = `list/${response.data.id}`; + history.push(path); } }) - .catch(function (error) { + .catch(function(error) { if (error.response) { alert( "Falha de autenticação. Certifique-se que email e senha estão corretos." @@ -121,19 +123,19 @@ export default function SignIn() { const theme = createMuiTheme({ overrides: { root: { - color: "white", + color: "white" }, MuiInput: { underline: { "&:before": { - borderBottom: "1px solid #35c7fc", + borderBottom: "1px solid #35c7fc" }, "&:after": { - borderBottom: "1px solid #3f51b5", - }, - }, - }, - }, + borderBottom: "1px solid #3f51b5" + } + } + } + } }); return ( <MuiThemeProvider theme={theme}> diff --git a/src/pages/SignUp.js b/src/pages/SignUp.js index e9a0af57cf96be76601136b7283508883bb66a29..97472a802c5cad74ce30ab64f5f2b94e4c631da2 100644 --- a/src/pages/SignUp.js +++ b/src/pages/SignUp.js @@ -2,13 +2,14 @@ import React from "react"; import Grid from "@material-ui/core/Grid"; import { createMuiTheme, MuiThemeProvider } from "@material-ui/core"; import IconButton from "@material-ui/core/IconButton"; +import { useHistory } from "react-router-dom"; import { makeStyles } from "@material-ui/core/styles"; import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight"; import FormInput from "../components/fieldsSignUp/FormInput"; import Paper from "@material-ui/core/Paper"; import api from "../api"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ register: { maxWidth: "1000px", background: "#ffffff", @@ -16,22 +17,22 @@ const useStyles = makeStyles((theme) => ({ padding: "2% 1%", margin: "0 auto", marginTop: "9%", - width: "95%", + width: "95%" }, custom_strong: { fontSize: "25px", textAlign: "center", display: "block", - color: "#46525d", + color: "#46525d" }, strong_description: { fontSize: "14px", - color: "#c2c6ca", + color: "#c2c6ca" }, form: { marginTop: "3%", alignItems: "center", - textAlign: "center", + textAlign: "center" }, button: { type: "submit", @@ -42,14 +43,15 @@ const useStyles = makeStyles((theme) => ({ padding: "10px 20px", fontSize: "18px", "&:hover": { - backgroundColor: "rgb(25, 109, 23)", + backgroundColor: "rgb(25, 109, 23)" }, ["@media (max-width:550px)"]: { - width: "55%", - }, - }, + width: "55%" + } + } })); export default function SignUp() { + const history = useHistory(); const classes = useStyles(); const [values, setValues] = React.useState({ name: "", @@ -57,13 +59,13 @@ export default function SignUp() { password: "", password_confirm: "", nameError: false, - emailError: false, + emailError: false }); async function update(prop, event) { await setValues({ ...values, [prop]: event.target.value }); } - const handleChange = (prop) => (event) => { + const handleChange = prop => event => { switch (prop) { case "name": if (!checkName()) { @@ -134,15 +136,15 @@ export default function SignUp() { .post(`/user/signUp`, { email: values.email, name: values.name, - hash: values.password, + hash: values.password }) - .then(function (error) { + .then(function(error) { if (!error.response) { - alert("Usuário criado com sucesso"); - // redirecionar para a main page + let path = `signin`; + history.push(path); } }) - .catch(function (error) { + .catch(function(error) { if (error.response) { switch (error.response.data.error) { case 'duplicate key value violates unique constraint "form_user_name_key"': @@ -172,19 +174,19 @@ export default function SignUp() { const theme = createMuiTheme({ overrides: { root: { - color: "white", + color: "white" }, MuiInput: { underline: { "&:before": { - borderBottom: "1px solid #35c7fc", + borderBottom: "1px solid #35c7fc" }, "&:after": { - borderBottom: "1px solid #3f51b5", - }, - }, - }, - }, + borderBottom: "1px solid #3f51b5" + } + } + } + } }); return ( <MuiThemeProvider theme={theme}> diff --git a/src/pages/VisualizeForm.js b/src/pages/VisualizeForm.js new file mode 100644 index 0000000000000000000000000000000000000000..a161ada03e35ab56f9dcb7a708aa7531ad3fd078 --- /dev/null +++ b/src/pages/VisualizeForm.js @@ -0,0 +1,171 @@ +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { makeStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import api from "../api"; +import { createMuiTheme, MuiThemeProvider } from "@material-ui/core"; +import { useHistory } from "react-router-dom"; + +import FormFieldText from "../components/fieldsVisualizeForm/FormFieldText"; +import FormFieldSelect from "../components/fieldsVisualizeForm/FormFieldSelect"; +import FormFieldRadio from "../components/fieldsVisualizeForm/FormFieldRadio"; +import FormFieldCheckbox from "../components/fieldsVisualizeForm/FormFieldCheckbox"; +import FormFieldTitle from "../components/fieldsVisualizeForm/FormFieldTitle"; +import FormFieldSubform from "../components/fieldsVisualizeForm/FormFieldSubform"; + +const useStyles = makeStyles(theme => ({ + menu: { + width: theme.spacing(6), + minheight: theme.spacing(15), + position: "fixed", + top: theme.spacing(10), + left: "90%", + padding: theme.spacing(1) + }, + button: { + type: "submit", + width: "100%", + background: "#6ec46c", + borderRadius: "2px", + padding: "10px 20px", + fontSize: "18px", + "&:hover": { + backgroundColor: "rgb(25, 109, 23)" + } + }, + pageBody: { + minHeight: "calc(100vh - 92.4px - 78px)", + paddingBottom: "78px" + } +})); + +const theme = createMuiTheme({ + overrides: { + MuiInput: { + underline: { + "&:before": { + borderBottom: "1px solid #35c7fc" + }, + "&:after": { + borderBottom: "1px solid #3f51b5" + } + } + }, + MuiButton: { + label: { + color: "black" + } + } + } +}); + +function VisualizeForm() { + const classes = useStyles(); + const history = useHistory(); + + /** Form id got from the browser's URL */ + const { id } = useParams(); + + /** Maped form from backend */ + const [formData, setFormData] = useState(0); + + /** + * Function to get form object from the API. + * @param id - Form id got from the broswer's URL + */ + async function getForm(id) { + const res = await api + .get(`/form/${id}`) + .then(function(res) { + setFormData(res.data); + }) + .catch(error => { + if (error.response.status === 401) { + let path = `/signin`; + history.push(path); + return; + } + + if (error.response.status === 500) { + if (error.response.data.error === "User dont own this form.") { + alert("Você não é o dono deste formulário."); + } else { + alert("Ocorreu um erro inesperado. Tente novamente mais tarde."); + } + } + return; + }); + } + + /** First thing the page does is getting the form from the API. */ + useEffect(() => { + getForm(id); + }, []); + + return ( + <MuiThemeProvider theme={theme}> + <div className={classes.pageBody}> + <Grid container direction="column" alignItems="center" justify="center"> + {formData ? ( + <div> + <FormFieldTitle + title={formData.title} + description={formData.description} + /> + {formData.inputs.map((input, index) => { + if (input.type === 0) + return ( + <FormFieldText + question={input.question} + description={input.description} + id={input.id} + /> + ); + else if (input.type === 3) + return ( + <FormFieldSelect + question={input.question} + id={input.id} + description={input.description} + options={input.sugestions} + /> + ); + else if (input.type === 2) + return ( + <FormFieldRadio + question={input.question} + description={input.description} + id={input.id} + options={input.sugestions} + /> + ); + else if (input.type === 1) + return ( + <FormFieldCheckbox + question={input.question} + description={input.description} + options={input.sugestions} + id={input.id} + /> + ); + else if (input.type === 4) + return ( + <FormFieldSubform + question={input.question} + description={input.description} + options={input.sugestions} + id={input.subForm.contentFormId} + /> + ); + })} + </div> + ) : ( + <p>Loading...</p> + )} + </Grid> + </div> + </MuiThemeProvider> + ); +} + +export default VisualizeForm;