From 75e13330a7641d92500c78a6998a303d3b86c371 Mon Sep 17 00:00:00 2001 From: Luis Felipe Risch <lfr20@inf.ufpr.br> Date: Thu, 1 Apr 2021 11:38:38 -0300 Subject: [PATCH] Finish pages of requirements --- .../Components/DataCards/RequirementCard.js | 288 +++++++++ .../Components/Inputs/CreateRequirement.js | 382 ++++++++++++ .../Components/Inputs/EditRequirements.js | 413 +++++++++++++ .../Pages/SubPages/GameficationRequires.js | 545 ++++++++++++++++++ 4 files changed, 1628 insertions(+) create mode 100644 src/Admin/Components/Components/DataCards/RequirementCard.js create mode 100644 src/Admin/Components/Components/Inputs/CreateRequirement.js create mode 100644 src/Admin/Components/Components/Inputs/EditRequirements.js create mode 100644 src/Admin/Pages/Pages/SubPages/GameficationRequires.js diff --git a/src/Admin/Components/Components/DataCards/RequirementCard.js b/src/Admin/Components/Components/DataCards/RequirementCard.js new file mode 100644 index 00000000..68b264c6 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/RequirementCard.js @@ -0,0 +1,288 @@ +/*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, useEffect } from "react"; +import moment from "moment"; +// Maerial ui components +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import ListRoundedIcon from "@material-ui/icons/ListRounded"; +import { useStyles } from "../../Styles/DataCard"; +import Grid from "@material-ui/core/Grid"; +import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded'; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +//imports from local files +import { GetAData, DeleteFilter } from "../../../Filters"; +import { Link, useHistory } from "react-router-dom"; +import LoadingSpinner from "../../../../Components/LoadingSpinner"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import { + getRequest, + deleteRequest, +} from "../../../../Components/HelperFunctions/getAxiosConfig"; +import styled from 'styled-components' +import { DiRequirejs } from 'react-icons/di' + +const AchievementCard = ({ match }) => { + let history = useHistory(); + const classes = useStyles(); + const WINDOW_WIDTH = window.innerWidth + const [error, setError] = useState(false) //Necessary to consult the API, catch errors + const [isLoaded, setIsLoaded] = useState(false) //Necessary to consult the API, wait until complete + const [item, setItem] = useState() + const [reloadPage, setReloadPage] = useState(false); + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color, + }); + }; + + const deleteHandler = () => { + deleteRequest( + DeleteFilter("requirements", item.id), + (data) => { + if (data.errors) + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + else { + HandleSnack( + "O item foi deletada com sucesso", + true, + "success", + "#228B22" + ); + } + history.goBack() + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + ) + } + + useEffect(() => { + setIsLoaded(false) + getRequest( + GetAData("requirements", match.params.id), + (data, header) => { + setItem(data); + setIsLoaded(true); + setError(false); + }, + (error) => { + setIsLoaded(true); + setError(true); + } + ); + }, [reloadPage]); + + if (error) { + return <div>Houve um erro</div>; + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." />; + } else { + return ( + <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + <Grid container direction="row" spacing={1}> + <Grid item xs={12}> + <Card> + <CardContent> + <Grid + xs={12} + justify="space-between" + alignItems="center" + container + > + <Grid item> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Informações do requisito + </Typography> + </Grid> + <Grid item> + <Link + style={{ textDecoration: "none" }} + to={`/admin/requirements`} + > + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + <Link + to={`/admin/EditRequirement/${match.params.id}`} + style={{ textDecoration: "none" }} + > + <Button + startIcon={<EditRoundedIcon />} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + <Button + startIcon={<DeleteRoundedIcon />} + color="secondary" + variant="outlined" + onClick={deleteHandler} + > + Deletar + </Button> + </Grid> + </Grid> + <div style={{ height: "1em" }} /> + <Grid container direction="row" spacing={3}> + <Grid item sm={5} xs={12} style={WINDOW_WIDTH >= 600 ? { + borderRight: "solid #d4d4d4 1px" + } : { + borderBottom: "solid #d4d4d4 1px" + }}> + <ImgDiv> + <DiRequirejs size={200} color="#673ab7" /> + </ImgDiv> + </Grid> + <Grid item sm={7} xs={12}> + <Grid container direction="column" spacing={1}> + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + > + Nome + </Typography> + <Link to={`/admin/action/${item.action.id}`}> + <a style={{ textDecoration: 'none', color: "#673ab7" }}> + {item.action.name} + </a> + </Link> + </Grid> + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + > + Criado em + </Typography> + <Typography color="textSecondary"> + {DisplayDate(item.created_at)} + </Typography> + </Grid> + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + > + Atualizado em + </Typography> + <Typography color="textSecondary"> + {DisplayDate(item.updated_at)} + </Typography> + </Grid> + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Descrição + </Typography> + <Typography color="textSecondary"> + {item.description} + </Typography> + </Grid> + </Grid> + </Grid> + </Grid> + <Grid container direction="column" spacing={1}> + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Achievements que dependem desse requisito + </Typography> + <Typography color="textSecondary"> + <ul> + { + item.achievements.map((achieve) => { + return ( + <li key={achieve.created_at}> + <Link to={`/admin/achievement/${achieve.id}`}> + <a style={{ textDecoration: 'none', color: "#673ab7" }}> + {achieve.name} + </a> + </Link> + </li> + ) + }) + } + </ul> + </Typography> + </Grid> + </Grid> + </CardContent> + </Card> + </Grid> + </Grid> + </> + ); + } +}; + +export default AchievementCard; + +const ImgDiv = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; +` diff --git a/src/Admin/Components/Components/Inputs/CreateRequirement.js b/src/Admin/Components/Components/Inputs/CreateRequirement.js new file mode 100644 index 00000000..e7d51d25 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateRequirement.js @@ -0,0 +1,382 @@ +/*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, useContext, useEffect } 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 ListRoundedIcon from '@material-ui/icons/ListRounded' +import Chip from '@material-ui/core/Chip'; +import MenuItem from "@material-ui/core/MenuItem" +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent' +import { Store } from '../../../../Store' +import Unauthorized from '../Unauthorized' +import { postRequest } from "../../../../Components/HelperFunctions/getAxiosConfig" +//router +import { Link } from 'react-router-dom' + + +const CreateRequirement = () => { + const { state } = useContext(Store) + + const [description, setDescription] = useState('') + const [goal, setGoal] = useState('') + const [resettable, setResettable] = useState('') + const [actionId, setActionId] = useState('') + const [achievements, setAchievements] = useState([]) + const [stringReq, setStringReq] = useState("") + + const [isLoading, setIsLoading] = useState(false) + const [isLoaded, setIsLoaded] = useState(false) + const [error, setError] = useState(false) + + const [errorInGoal, setErrorInGoal] = useState({ + error: false, + message: '', + }) + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '', + }) + const [errorInActionId, setErrorInActionId] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const resettableOptions = [ + { name: 1, value: "Sim" }, + { name: 0, value: "Não" }, + + ] + + const DescriptionHandler = (e) => { + if (errorInDescription.error) + setErrorInDescription({ + error: false, + message: '' + }) + setDescription(e.target.value) + } + const goalHandler = (e) => { + if (errorInGoal.error) + setErrorInGoal({ + error: false, + message: '' + }) + setGoal(e.target.value) + } + const actionIdHandler = (e) => { + if (errorInActionId.error) + setErrorInActionId({ + error: false, + message: '' + }) + setActionId(e.target.value) + }; + + const resettableHandler = (e) => { + setResettable(e.target.value) + } + const stringReqHandler = (e) => { + setStringReq(e.target.value) + }; + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles] + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true + } + else { + canUserEdit = false + } + + return canUserEdit + } + + //Handle submit + async function onSubmit() { + setIsLoading(true) + const api = `/requirements` + const body = { + "requirement": { + "description": description, + "goal": goal, + "repeatable": resettable, + "action_id": parseInt(actionId), + "achievements": achievements + } + } + postRequest( + api, + body, + (data) => { + if (data.id) + HandleSnack('O requisito foi criado com sucesso!', true, 'success', '#228B22') + else { + if (data.errors) { + HandleSnack(`${data.errors[0]}`, true, 'warning', '#FA8072') + } + if (data.goal) { + let goalError = "" + data.goal.map((msg) => ( + goalError = goalError + msg + " e " + )) + setErrorInGoal({ + error: true, + message: goalError + }) + } + if (data.description) { + let descriptionError = "" + data.description.map((msg) => ( + descriptionError = descriptionError + msg + " e " + )) + setErrorInDescription({ + error: true, + message: descriptionError + }) + } + if (data.action) { + let actionError = "" + data.action.map((msg) => ( + actionError = actionError + msg + " e " + )) + setErrorInActionId({ + error: true, + message: actionError + }) + } + } + setIsLoading(false) + }, + (error) => { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + setIsLoading(false) + } + ) + } + + const HandleDelete = (i) => { + const copyReq = [...achievements]; + copyReq.splice(i, 1); + setAchievements(copyReq); + }; + + const OnKeyPressHandler = (key) => { + if (key === 13) { + const copyReq = [...achievements]; + copyReq.push(parseInt(stringReq)); + setAchievements(copyReq); + setStringReq(""); + } + }; + + // Fields + const fields = [ + { + select: false, + label: 'Descrição', + value: description, + required: false, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event) + }, + { + select: false, + label: 'Meta', + value: goal, + error: errorInGoal.error, + errorMessage: errorInGoal.message, + required: false, + onChange: (event) => goalHandler(event) + }, + { + select: false, + label: 'ID da ação', + value: actionId, + error: errorInActionId.error, + errorMessage: errorInActionId.message, + required: false, + onChange: (event) => actionIdHandler(event) + }, + { + select: true, + label: 'Repetível', + value: resettable, + required: false, + options: [...resettableOptions], + onChange: (event) => resettableHandler(event) + }, + ] + + if (CheckUserPermission()) { + return ( + <Card> + <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' alignContent="center" alignItems="center" xs={12}> + <Grid item> + <Typography variant='h4'> + Criar requisito + </Typography> + </Grid> + <Grid item> + <Link style={{ textDecoration: 'none' }} to={'/admin/requirements'}> + <Button + // onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + field.select ? + <TextField + select + 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} + > + {field.options.map((option, index) => ( + <MenuItem + key={option.value} + value={option.name} + name={option.value} + > + {option.value} + </MenuItem> + ))} + </TextField> + : + <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} + /> + ))} + <> + <div + style={{ + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + marginBottom: "1em" + }} + > + {achievements.map((req, index) => ( + <li key={req.id} style={{ listStyleType: "none", marginBottom: "0.5em", marginRight: "0.5em" }}> + <Chip + label={req} + onDelete={() => HandleDelete(index)} + /> + </li> + ))} + </div> + + <TextField + id="outlined-input" + label="Requisitos" + rows={1} + value={stringReq} + onKeyPress={(key) => OnKeyPressHandler(key.which)} + onChange={stringReqHandler} + // onBlur={ShowEmails} + helperText="Digite o ID do requisito, um por vez, e pressione Enter" + style={{ marginBottom: "1em", width: '250px' }} + /> + </> + </form> + </CardContent> + <CardAction> + <Button + onClick={onSubmit} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Criar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized /> +} + +export default CreateRequirement \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/EditRequirements.js b/src/Admin/Components/Components/Inputs/EditRequirements.js new file mode 100644 index 00000000..db4b459a --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditRequirements.js @@ -0,0 +1,413 @@ +/*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, useContext, useEffect } 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 ListRoundedIcon from '@material-ui/icons/ListRounded' +import Chip from '@material-ui/core/Chip'; +import MenuItem from "@material-ui/core/MenuItem" +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent' +import { Store } from '../../../../Store' +import Unauthorized from '../Unauthorized' +import { getRequest, putRequest } from "../../../../Components/HelperFunctions/getAxiosConfig" +import LoadingSpinner from '../../../../Components/LoadingSpinner' +import { GetAData } from '../../../Filters' +//router +import { Link } from 'react-router-dom' + + +const EditRequirements = ({ match }) => { + const { state } = useContext(Store) + + const [description, setDescription] = useState('') + const [goal, setGoal] = useState('') + const [resettable, setResettable] = useState('') + const [actionId, setActionId] = useState('') + const [achievements, setAchievements] = useState([]) + const [stringReq, setStringReq] = useState("") + + const [isLoading, setIsLoading] = useState(false) + const [isLoaded, setIsLoaded] = useState(false) + const [error, setError] = useState(false) + + const [errorInGoal, setErrorInGoal] = useState({ + error: false, + message: '', + }) + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '', + }) + const [errorInActionId, setErrorInActionId] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const resettableOptions = [ + { name: 1, value: "Sim" }, + { name: 0, value: "Não" }, + + ] + + const DescriptionHandler = (e) => { + if (errorInDescription.error) + setErrorInDescription({ + error: false, + message: '' + }) + setDescription(e.target.value) + } + const goalHandler = (e) => { + if (errorInGoal.error) + setErrorInGoal({ + error: false, + message: '' + }) + setGoal(e.target.value) + } + const actionIdHandler = (e) => { + if (errorInActionId.error) + setErrorInActionId({ + error: false, + message: '' + }) + setActionId(e.target.value) + }; + + const resettableHandler = (e) => { + setResettable(e.target.value) + } + const stringReqHandler = (e) => { + setStringReq(e.target.value) + }; + + // Handle snack infos + const HandleSnack = (message, state, icon, color) => { + setSnackInfo({ + message: message, + icon: icon, + open: state, + color: color + }) + } + + const CheckUserPermission = () => { + let canUserEdit = false + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles] + for (let i = 0; i < roles.length; i++) + if (roles[i].name === 'admin' || roles[i].name === 'editor') + canUserEdit = true + } + else { + canUserEdit = false + } + + return canUserEdit + } + + //Handle submit + async function onSubmit() { + setIsLoading(true) + const api = `/requirements/${match.params.id}` + const body = { + "requirement": { + "description": description, + "goal": goal, + "repeatable": resettable, + "action_id" : parseInt(actionId), + "achievements": achievements + } + } + putRequest( + api, + body, + (data) => { + if (data.id) + HandleSnack('O item foi alterado com sucesso!', true, 'success', '#228B22') + else { + if (data.errors) { + HandleSnack(`${data.errors[0]}`, true, 'warning', '#FA8072') + } + if (data.goal) { + let goalError = "" + data.goal.map((msg) => ( + goalError = goalError + msg + " e " + )) + setErrorInGoal({ + error: true, + message: goalError + }) + } + if (data.description) { + let descriptionError = "" + data.description.map((msg) => ( + descriptionError = descriptionError + msg + " e " + )) + setErrorInDescription({ + error: true, + message: descriptionError + }) + } + if (data.action) { + let actionError = "" + data.action.map((msg) => ( + actionError = actionError + msg + " e " + )) + setErrorInActionId({ + error: true, + message: actionError + }) + } + } + setIsLoading(false) + }, + (error) => { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + setIsLoading(false) + } + ) + } + + const HandleDelete = (i) => { + const copyReq = [...achievements]; + copyReq.splice(i, 1); + setAchievements(copyReq); + }; + + const OnKeyPressHandler = (key) => { + if (key === 13) { + const copyReq = [...achievements]; + copyReq.push(parseInt(stringReq)); + setAchievements(copyReq); + setStringReq(""); + } + }; + + // Fields + const fields = [ + { + select: false, + label: 'Descrição', + value: description, + required: false, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event) + }, + { + select: false, + label: 'Meta', + value: goal, + error: errorInGoal.error, + errorMessage: errorInGoal.message, + required: false, + onChange: (event) => goalHandler(event) + }, + { + select: false, + label: 'ID da ação', + value: actionId, + error: errorInActionId.error, + errorMessage: errorInActionId.message, + required: false, + onChange: (event) => actionIdHandler(event) + }, + { + select: true, + label: 'Repetível', + value: resettable, + required: false, + options: [...resettableOptions], + onChange: (event) => resettableHandler(event) + }, + ] + + useEffect(() => { + getRequest( + GetAData("requirements", match.params.id), + (data, header) => { + const achievementsId = [] + for (let index = 0; index < data.achievements.length; index++) { + const element = data.achievements[index]; + achievementsId.push(element.id) + } + setAchievements(achievementsId) + setDescription(data.description) + setGoal(data.goal) + setResettable(data.repeatable) + setActionId(data.action.id) + + setError(false) + setIsLoaded(true) + }, + (error) => { + setIsLoaded(true) + setError(true) + } + ) + }, []) + + if (error) { + return <div> Error... </div> + } else if (!isLoaded) { + return <LoadingSpinner text="Carregando..." /> + } else if (CheckUserPermission()) { + return ( + <Card> + <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' alignContent="center" alignItems="center" xs={12}> + <Grid item> + <Typography variant='h4'> + Editar requisito + </Typography> + </Grid> + <Grid item> + <Link style={{ textDecoration: 'none' }} to={'/admin/requirements'}> + <Button + // onClick={props.BackToList} + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + </Grid> + + <div style={{ height: '1em' }}></div> + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + field.select ? + <TextField + select + 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} + > + {field.options.map((option, index) => ( + <MenuItem + key={option.value} + value={option.name} + name={option.value} + > + {option.value} + </MenuItem> + ))} + </TextField> + : + <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} + /> + ))} + <> + <div + style={{ + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + marginBottom: "1em" + }} + > + {achievements.map((req, index) => ( + <li key={req.id} style={{ listStyleType: "none", marginBottom: "0.5em", marginRight: "0.5em" }}> + <Chip + label={req} + onDelete={() => HandleDelete(index)} + /> + </li> + ))} + </div> + + <TextField + id="outlined-input" + label="Requisitos" + rows={1} + value={stringReq} + onKeyPress={(key) => OnKeyPressHandler(key.which)} + onChange={stringReqHandler} + // onBlur={ShowEmails} + helperText="Digite o ID do requisito, um por vez, e pressione Enter" + style={{ marginBottom: "1em", width: '250px' }} + /> + </> + </form> + </CardContent> + <CardAction> + <Button + onClick={onSubmit} + variant="contained" + color="primary" + disabled={isLoading} + startIcon={isLoading ? null : <AddRoundedIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Editar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized /> +} + +export default EditRequirements \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/GameficationRequires.js b/src/Admin/Pages/Pages/SubPages/GameficationRequires.js new file mode 100644 index 00000000..64099c70 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/GameficationRequires.js @@ -0,0 +1,545 @@ +/*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, { useContext, useEffect, useState } from "react"; +import moment from "moment"; +import Unauthorized from "../../../Components/Components/Unauthorized"; +import { Store } from "../../../../Store"; +import { Link, useHistory } from "react-router-dom"; +import { getRequest, deleteRequest, putRequest } from "../../../../Components/HelperFunctions/getAxiosConfig"; +import { Url, DeleteFilter, EditFilter } from "../../../Filters"; +import UpdateRoundedIcon from "@material-ui/icons/UpdateRounded"; +import { withStyles } from "@material-ui/core/styles"; +import AddRoundedIcon from "@material-ui/icons/AddRounded"; +import FilterListRoundedIcon from "@material-ui/icons/FilterListRounded"; +import VisibilityIcon from "@material-ui/icons/Visibility"; +import LoadingSpinner from "../../../../Components/LoadingSpinner"; +import PageHeader from "../../../Components/Components/PageHeader"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import TableData from "../../../Components/Components/Table"; +import TextField from "@material-ui/core/TextField"; +import MenuItem from "@material-ui/core/MenuItem"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import Button from "@material-ui/core/Button"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Paper from "@material-ui/core/Paper"; +import MobilePageHeader from "../../../Components/Components/MobileComponents/MobilePageHeader" +import styled from 'styled-components' +import MobileList from "../../../Components/Components/MobileComponents/SimpleList" +import { Grid } from "@material-ui/core"; +import AlertDialog from "../../../Components/Components/AlertDialog" +import { DiRequirejs } from 'react-icons/di' +import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded'; + +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 GameficationRequires = () => { + const history = useHistory() + const WINDOW_WIDTH = window.innerWidth + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = [ + "ID", + "NAME", + "DESCRIÇÃO", + "META", + "REPETIVEL", + "CRIADO EM", + "ATUALIZADO EM", + "AÇÕES" + ]; //Labels from Table + const { state } = useContext(Store); + const [currPage, setcurrPage] = useState(0); + const [error, setError] = useState(false); + const [loaded, setLoaded] = useState(true); + const [isLoadingMoreItems, setIsLoadingMoreItems] = useState(false); + const [showFilter, setShowFilter] = useState(false); + const [items, setItems] = useState([]); + const [stateOpt, setStateOpt] = useState(1) + const [descricao, setDescricao] = useState("") + const [valueDescricaoField, setValueDescricaoField] = useState("") + const [open, setOpen] = useState(false) + const [deleteItem, setDeleteItem] = useState({}) + 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, + }); + }; + + const CheckUserPermission = () => { + let canUserEdit = false; + + if (state.userIsLoggedIn) { + const roles = [...state.currentUser.roles]; + for (let i = 0; i < roles.length; i++) + if (roles[i].name === "admin" || roles[i].name === "editor") + canUserEdit = true; + } else { + canUserEdit = false; + } + + return canUserEdit; + }; + + const DisplayDate = (date) => { + const convertedData = moment.utc(date); + return moment(convertedData) + .format("LLL") + .toString(); + }; + + const handleAlertDialog = (item) => { + setOpen(true) + setDeleteItem(item) + } + + const findIndexOfWantedItem = (item) => { + const index = items.findIndex((item) => item.id === deleteItem.id) + return index; + } + + const deleteHandler = () => { + deleteRequest( + DeleteFilter("requirements", deleteItem.id), + (data) => { + if (data.errors) + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + else { + HandleSnack( + "O achievement foi deletada com sucesso", + true, + "success", + "#228B22" + ); + handleChangeStateItem(findIndexOfWantedItem(deleteItem), "deleted") + } + setcurrPage(0) + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + ) + } + + const handleChangeStateItem = (index, state) => { + const currItems = [...items] + currItems.splice(index, 1) + setItems(currItems) + } + + const buildUrl = (description) => { + if (description) + return Url("/requirements", `"description" : "${description}"`, currPage, "DESC") + else + return Url("/requirements", "", currPage, "DESC") + } + + useEffect(() => { + if (currPage === 0) + setLoaded(false) + else + setIsLoadingMoreItems(true) + getRequest( + buildUrl(descricao), + (data, header) => { + const arrData = [...data] + if (arrData.length === 0) { + HandleSnack('Não há mais dados para serem carregados', true, 'warning', '#FFC125') + } else { + const arrItems = [...items] + if (currPage === 0) { + setItems(arrData.concat(ADD_ONE_LENGHT)) + } + else { + 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)) + } + } + setLoaded(true) + setIsLoadingMoreItems(false) + }, + (error) => { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + setIsLoadingMoreItems(false) + } + ) + }, [currPage, descricao, stateOpt]) + + useEffect(() => { + setDescricao("") + setStateOpt(1) + }, [showFilter]) + + if (error) { + return <div>Error</div>; + } else if (!loaded) { + return <LoadingSpinner text="Carregando..." />; + } else { + if (CheckUserPermission()) { + if (WINDOW_WIDTH <= 950) { + return ( + <> + <AlertDialog + open={open} + OnDelete={deleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpen(false) + }} + /> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + <MobilePageHeader + title="Requisitos" + actions={[ + { + name: "Atualizar", + isLoading: false, + func: () => { + setcurrPage(0); + }, + icon: <UpdateRoundedIcon />, + }, + { + name: "Filtrar", + isLoading: false, + func: () => { + setShowFilter(!showFilter); + }, + icon: <FilterListRoundedIcon />, + }, + { + name: "Novo", + isLoading: false, + func: () => { + history.push("/admin/CreateRequirement") + }, + icon: <AddRoundedIcon />, + }, + ]} + > + {showFilter ? ( + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + alignContent="flex-end" + spacing={3} + xs={12} + > + <Grid item> + <TextField + label="Descrição" + value={valueDescricaoField} + onChange={(e) => { setValueDescricaoField(e.target.value) }} + onBlur={(e) => { setDescricao(e.target.value) }} + helperText="Por favor, ao digitar o descrição que você quer filtar, retire o foco do campo de texto" + /> + </Grid> + </Grid> + ) : null} + </MobilePageHeader> + <div style={{ height: '2em' }}></div> + + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledDivButton> + <Button + key={index} + color="primary" + variant="text" + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + setcurrPage(currPage + 1) + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledDivButton> + ) : ( + <> + <MobileList + key={index} + title={row.action.name} + subtitle={row.id} + backColor={"#673ab7"} + avatar={ + <DiRequirejs size={20} color="white" /> + } + href={`/admin/requirement/${row.id}`} + reset={() => { }} + data={[ + { + title: "Criado em", + subtitle: DisplayDate(row.created_at) + }, + { + title: "Atualizado em", + subtitle: DisplayDate(row.updated_at) + }, + { + title: "Descrição", + subtitle: row.description + }, + { + title: "Achivements que depende desse requisito", + subtitle: <ul> + { + row.achievements.map((achieve) => { + return ( + <li key={achieve.created_at}> + <Link to={`/admin/achievement/${achieve.id}`}> + <a style={{textDecoration: "none", color: '#673ab7'}}> + {achieve.name} + </a> + </Link> + </li> + ) + }) + } + </ul> + }, + { + title: "Ações", + subtitle: <Button + variant="contained" + color="secondary" + startIcon={<DeleteRoundedIcon />} + onClick={() => handleAlertDialog(row)} + > + Deletar + </Button> + }, + ]} + /> + <div style={{ height: "0.5em" }} /> + </> + ) + )} + </> + ) + } else { + return ( + <> + <SnackBar + severity={snackInfo.icon} + text={snackInfo.message} + snackbarOpen={snackInfo.open} + color={snackInfo.color} + handleClose={() => + setSnackInfo({ + message: "", + icon: "", + open: false, + color: "", + }) + } + /> + <PageHeader + title="Requisitos" + actions={[ + { + name: "Atualizar", + isLoading: false, + func: () => { + setcurrPage(0); + }, + icon: <UpdateRoundedIcon />, + }, + { + name: "Filtrar", + isLoading: false, + func: () => { + setShowFilter(!showFilter); + }, + icon: <FilterListRoundedIcon />, + }, + { + name: "Novo", + isLoading: false, + func: () => { + history.push("/admin/CreateRequirement") + }, + icon: <AddRoundedIcon />, + }, + ]} + > + {showFilter ? ( + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + alignContent="flex-end" + spacing={3} + xs={12} + > + <Grid item> + <TextField + label="Descrição" + value={valueDescricaoField} + onChange={(e) => { setValueDescricaoField(e.target.value) }} + onBlur={(e) => { setDescricao(e.target.value) }} + helperText="Por favor, ao digitar o descrição que você quer filtar, retire o foco do campo de texto" + /> + </Grid> + </Grid> + ) : null} + </PageHeader> + + <div style={{ height: "2em" }}></div> + + <TableData top={TOP_LABELS}> + <TableBody> + {items.map((row, index) => + index === items.length - 1 ? ( + <StyledTableRow key={index} style={{ padding: "1em" }}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color="primary" + variant="text" + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + setcurrPage(currPage + 1); + }} + > + {isLoadingMoreItems ? ( + <CircularProgress size={24} /> + ) : ( + "Carregar mais itens" + )} + </Button> + </StyledTableCell> + </StyledTableRow> + ) : ( + <StyledTableRow key={index}> + <StyledTableCell component="th" scope="row"> + {row.id} + </StyledTableCell> + <StyledTableCell align="right">{row.action.name}</StyledTableCell> + <StyledTableCell align="right">{row.description}</StyledTableCell> + <StyledTableCell align="right">{row.goal}</StyledTableCell> + <StyledTableCell align="right">{row.repeatable}</StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.updated_at)} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/requirement/${row.id}`}> + <Button + style={{ width: "100%", marginBottom: "0.5em" }} + variant="contained" + color="primary" + startIcon={<VisibilityIcon />} + > + Visualizar + </Button> + </Link> + + <Button + style={{ width: "100%" }} + variant="contained" + color="secondary" + startIcon={<DeleteRoundedIcon />} + onClick={() => handleAlertDialog(row)} + > + Deletar + </Button> + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + <AlertDialog + open={open} + OnDelete={deleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpen(false) + }} + /> + </> + ); + } + } else return <Unauthorized />; + } +}; +export default GameficationRequires; + +const StyledDivButton = styled(Paper)` + width : 100%; + display : flex; + justify-content : center; + align-items : center; +` + + -- GitLab