diff --git a/src/Admin/Components/Components/Inputs/CreateQuestion.js b/src/Admin/Components/Components/Inputs/CreateQuestion.js new file mode 100644 index 0000000000000000000000000000000000000000..180deeb931bd37939c51ab1782b0a5c2261aabb7 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateQuestion.js @@ -0,0 +1,247 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useState } from 'react'; +//imports material ui componets +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import MenuItem from "@material-ui/core/MenuItem"; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +//imports local files +import { apiUrl } from '../../../../env'; +import SnackBar from '../../../../Components/SnackbarComponent'; +//imports services +import { Create } from '../../../Services'; +//router +import { Link } from 'react-router-dom'; + +const CreateQuestion = (props) => { + const [status, setStatus] = useState(''); + const [description, setDescription] = useState(''); + + const [isLoading, setIsLoading] = useState(false) + + // Handle error in name + const [errorInStatus, setErrorInStatus] = useState({ + error: false, + message: '', + }) + + // Handle error in Country + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const DescriptionHandler = (e) => { + if (errorInDescription.error) { + setErrorInDescription({ + error: false, + message: '' + }) + } + setDescription(e.target.value) + } + + // verify if the given text is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + //Handle submit + async function onSubmit() { + setIsLoading(true) + if (!isEmpty(status) && !isEmpty(description)) { + const api = apiUrl + '/questions' + const body = { + "question": { + 'status': status, + 'description': description, + } + } + Create(api, body).then(res => { + if (res) { + HandleSnack('A pergunta foi criada com sucesso', true, 'success', '#228B22') + } else { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + } + setIsLoading(false) + }) + } else { + HandleSnack('Você precisa preencher algumas informações obrigatórias', true, 'warning', '#FFC125') + if (isEmpty(status)) { + setErrorInStatus({ + error: true, + message: 'Esse campo está vazio' + }) + } + if (isEmpty(description)) { + setErrorInDescription({ + error: true, + message: 'Esse campo está vazio' + }) + } + setIsLoading(false) + } + } + + //handle change of staus + + const handleChange = (e) => { + const value = e.target.value; + setStatus(value); + console.log(status) + }; + + // Fields + const fields = [ + { + label: 'Descrição', + value: description, + required: true, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event) + } + ] + + const STATUS_OPTIONS = [ + { + value: "active", + label: "Ativo", + }, + { + value: "inactive", + label: "Inativo", + }, + ]; + + return ( + <Card variant='outlined'> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + <CardContent> + <Grid container direction='row' justify='space-between'> + <Typography variant='h4'> + Nova question + </Typography> + <Link to={'/admin/Questions'} style={{ textDecoration: 'none' }}> + <Button + onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + <> + <TextField + select + label="Status" + value={status ? status : ""} + error={errorInStatus.error} + style={{ width: '250px', marginBottom: '1em' }} + helperText={errorInStatus.error ? errorInStatus.errorMessage : ''} + onChange={handleChange} + > + {STATUS_OPTIONS.map((option, index) => ( + <MenuItem + key={option.value} + value={option.value} + style={option.value === status ? { color: 'blue' } : { color: 'black' }} + > + { + option.label + } + </MenuItem> + ))} + </TextField> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + error={field.error} + helperText={field.error ? field.errorMessage : ''} + style={{ width: '250px', marginBottom: '1em' }} + label={field.label} + value={field.value} + onChange={field.onChange} + type="search" + multiline={true} + /> + ))} + </> + </form> + </CardContent> + <CardAction> + <Button + onClick={() => { + onSubmit(); + }} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Adicionar' + } + </Button> + </CardAction> + </Card> + ); +} + +export default CreateQuestion; \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/Questions.js b/src/Admin/Pages/Pages/SubPages/Questions.js new file mode 100644 index 0000000000000000000000000000000000000000..4487d32cedfa49052e255075c89b4ef928657852 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Questions.js @@ -0,0 +1,359 @@ +/*Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre +Departamento de Informatica - Universidade Federal do Parana + +This file is part of Plataforma Integrada MEC. + +Plataforma Integrada MEC is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Plataforma Integrada MEC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with Plataforma Integrada MEC. If not, see <http://www.gnu.org/licenses/>.*/ + +import React, { useEffect, useState } from 'react' +//imports from local files +import TableData from '../../../Components/Components/Table'; +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Url, EditFilter } from '../../../Filters'; +import { GetFullList, Edit } from '../../../Services'; +//imports from material ui +import { withStyles } from '@material-ui/core/styles'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableRow from '@material-ui/core/TableRow'; +import { Button, Typography, Paper, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import AddRoundedIcon from '@material-ui/icons/AddRounded'; +import UpdateRoundedIcon from '@material-ui/icons/UpdateRounded'; +import CheckCircleRoundedIcon from '@material-ui/icons/CheckCircleRounded'; +import CancelRoundedIcon from '@material-ui/icons/CancelRounded'; +import Switch from '@material-ui/core/Switch'; +//router +import { Link } from 'react-router-dom'; + +let currPage = 0; +let transformListToAsc = false; + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + }, +}))(TableRow); + +const Questions = () => { + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = ['ID', 'CRIAÇÃO EM', 'DESCRIÇÃO', 'STATUS', 'ATUALIZAÇÃO EM'] //Labels from Table + + const [error, setError] = useState(null); //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false); //Necessary to consult the API, wait until complete + const [items, setItems] = useState([]); //Necessary to consult the API, data + + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false) //controlls the state of loadind more data + const [isUpdating, setIsUpdating] = useState(false); //controlls the state of updating data + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + //handle snack info + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + //handle load more items + const LoadMoreItens = async (api) => { + setIsLoadingMoreItems(true) + GetFullList(api).then(res => { + if (res.state) { + const arrData = [...res.data] + if (arrData.length === 0) { + HandleSnack('Não há mais dados para serem carregados', true, 'warning', '#FFC125') + } else { + const arrItems = [...items] + arrItems.pop(); //Deleting the last position, that was used to display the button of load more items + const arrResult = arrItems.concat(arrData) + setItems(arrResult.concat(ADD_ONE_LENGHT)) + } + + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsLoadingMoreItems(false) + }) + } + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true) + GetFullList(api).then(res => { + if (res.state) { + HandleSnack('A lista de dados foi atualizada', true, 'success', '#228B22') + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + setIsUpdating(false) + }) + } + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + GetFullList(Url('questions', '', `${currPage}`, 'ASC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + GetFullList(Url('questions', '', `${currPage}`, 'DESC')).then(res => { + if (res.state) { + const arrData = [...res.data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + } + + const handleChange = async (index, status) => { + const id = items[index].id; + const description = items[index].description; + if (status === 'active') { + const body = { + "question": { + "description": description, + "status": "inactive" + } + } + Edit(EditFilter('questions', id), body).then(res => { + if (res) { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('questions', '', `${currPage}`, 'DESC')) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } else { + const body = { + "question": { + "description": description, + "status": "active" + } + } + Edit(EditFilter('questions', id), body).then(res => { + if (res) { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('questions', '', `${currPage}`, 'DESC')) + } else { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + }) + } + + } + + //getting data from server + useEffect(() => { + fetch(Url('questions', '', `${currPage}`, 'DESC')) + .then(res => res.json()) + .then( + (result) => { + setIsLoaded(true); + setItems(result.concat(ADD_ONE_LENGHT)); + }, + (error) => { + setIsLoaded(true); + // HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + setError(error); + } + ) + }, []); + + + if (error) { + return <div>Error: {error.message}</div>; + } else if (!isLoaded) { + return <div>Loading...</div>; + } else { + return ( + <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => setSnackInfo({ + message: '', + icon: '', + open: false, + color: '' + })} + /> + + <Paper style={{ padding: '1em' }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4"> + Perguntas da curadoria + </Typography> + </Grid> + <Grid + item + xs={6} + alignItems="center" + justify="center" + direction="row" + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('questions', '', `${currPage}`, 'DESC')) + }} + startIcon={<UpdateRoundedIcon />} + > + { + isUpdating ? <CircularProgress size={24} /> : 'Atualizar' + } + </Button> + </Grid> + <Grid item> + + <Link style={{ textDecoration: 'none' }} to={'/admin/CreateQuestion'}> + <Button + variant="contained" + color="secondary" + startIcon={<AddRoundedIcon />} + > + Novo + </Button> + </Link> + </Grid> + </Grid> + </Grid> + </Grid> + </Paper> + + <div style={{ height: '2em' }}></div> + + <TableData + top={TOP_LABELS} + onIconPressed={InvertList} + > + <TableBody> + {items.map((row, index) => ( + index === items.length - 1 ? + <div style={{ padding: '1em' }}> + {/* Button to load more data */} + <Button + color='primary' + variant='text' + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('questions', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('questions', '', `${currPage}`, 'DESC')) + } + }} + > + { + isLoadingMoreItems ? <CircularProgress size={24} /> : 'Carregar mais itens' + } + </Button> + </div> + + : + + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row">{row.id}</StyledTableCell> + <StyledTableCell align="right">{row.created_at}</StyledTableCell> + <StyledTableCell align="right">{row.description}</StyledTableCell> + <StyledTableCell align="right"> + { + row.status === 'active' ? + <Grid container justify='flex-end' alignItems='center' direction='row'> + <Grid item> + <CheckCircleRoundedIcon style={{ fill: '#3CB371' }} /> + </Grid> + + <Grid item> + <Switch + checked={true} + onChange={() => handleChange(index, row.status)} + name="checkedB" + color="primary" + /> + </Grid> + </Grid> + + : + + <Grid container justify='flex-end' alignItems='center' direction='row'> + <Grid item> + <CancelRoundedIcon style={{ fill: '#FA8072' }} /> + </Grid> + + <Grid item> + <Switch + checked={false} + onChange={() => handleChange(index, row.status)} + name="checkedB" + color="primary" + /> + </Grid> + </Grid> + } + </StyledTableCell> + <StyledTableCell align="right">{row.updated_at}</StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </TableData> + </> + ); + } +} +export default Questions; \ No newline at end of file