diff --git a/src/Admin/Components/Components/DataCards/AchievementsCard.js b/src/Admin/Components/Components/DataCards/AchievementsCard.js new file mode 100644 index 0000000000000000000000000000000000000000..3babb97d83395904d23b759ee1314612cbc436b9 --- /dev/null +++ b/src/Admin/Components/Components/DataCards/AchievementsCard.js @@ -0,0 +1,555 @@ +/*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"; +// Icons +import EditRoundedIcon from "@material-ui/icons/EditRounded"; +//imports from local files +import { GetAData, DeleteFilter, EditFilter } from "../../../Filters"; +import { Link, useHistory } from "react-router-dom"; +import LoadingSpinner from "../../../../Components/LoadingSpinner"; +import SnackBar from "../../../../Components/SnackbarComponent"; +import { + getRequest, + deleteRequest, + putRequest +} from "../../../../Components/HelperFunctions/getAxiosConfig"; +import Paper from "@material-ui/core/Paper"; +import styled from 'styled-components' +import { GiAchievement } from 'react-icons/gi' +import ItemStoreContainer from "../../../../Pages/ItemStore"; + + +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 inactiveItem = (item) => { + const body = { + "achievement": { + "state": "inactive" + } + } + putRequest( + EditFilter("achievements", item.id), + body, + (data) => { + if (data.errors) + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + else { + HandleSnack( + "O achievement foi inativado com sucesso", + true, + "success", + "#228B22" + ); + } + setReloadPage(!reloadPage) + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + ) + } + + const activeItem = (item) => { + const body = { + "achievement": { + "state": "active" + } + } + putRequest( + EditFilter("achievements", item.id), + body, + (data) => { + if (data.errors) + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + else { + HandleSnack( + "O achievement foi ativado com sucesso", + true, + "success", + "#228B22" + ); + } + setReloadPage(!reloadPage) + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + ) + } + + const deleteHandler = () => { + deleteRequest( + DeleteFilter("achievements", 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"); + } + ) + } + + + const FastActions = (item) => { + switch (item.state) { + case "deleted": + return ( + <Grid container alignItems="center" spacing={1}> + <Grid item> + <Button + color="primary" + variant="contained" + style={{ backgroundColor: "#FF8C00", color: "#FFFAFA", width: "150px" }} + onClick={() => inactiveItem(item)} + > + Inativar + </Button> + </Grid> + <Grid item> + <Button + color="primary" + style={{ backgroundColor: "#228B22", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => activeItem(item)} + > + Ativar + </Button> + </Grid> + </Grid> + ); + case "inactive": + return ( + <Grid container alignItems="center" spacing={1}> + <Grid item> + <Button + color="primary" + variant="contained" + style={{ backgroundColor: "#FA8072", color: "#FFFAFA", width: "150px" }} + onClick={deleteHandler} + > + Remover + </Button> + </Grid> + <Grid item> + <Button + color="primary" + style={{ backgroundColor: "#228B22", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => activeItem(item)} + > + Ativar + </Button> + </Grid> + </Grid> + ); + case "active": + return ( + <Grid container alignItems="center" spacing={1}> + <Grid item> + <Button + style={{ backgroundColor: "#FA8072", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={deleteHandler} + > + Remover + </Button> + </Grid> + <Grid item> + <Button + style={{ backgroundColor: "#FF8C00", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => inactiveItem(item)} + > + Inativar + </Button> + </Grid> + </Grid> + ); + default: + return "NOTHING"; + } + }; + + const StateItem = (status) => { + switch (status) { + case "deleted": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FA8072", + fontWeight: "500", + color: "#FFFAFA", + }} + > + REMOVIDO + </Paper> + ); + case "inactive": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FF8C00", + fontWeight: "500", + color: "#FFFAFA", + }} + > + INATIVO + </Paper> + ); + case "active": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#228B22", + fontWeight: "500", + color: "#FFFAFA", + }} + > + ATIVO + </Paper> + ); + default: + return "NOTHING"; + } + }; + + useEffect(() => { + setIsLoaded(false) + getRequest( + GetAData("achievements", 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} md={6}> + <Card> + <CardContent> + <Grid + xs={12} + justify="space-between" + alignItems="center" + container + > + <Grid item> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Informações do item + </Typography> + </Grid> + <Grid item> + <Link + style={{ textDecoration: "none" }} + to={`/admin/gamefication`} + > + <Button + startIcon={<ListRoundedIcon />} + color="primary" + variant="outlined" + > + Listar + </Button> + </Link> + <Link + to={`/admin/EditAchievement/${match.params.id}`} + style={{ textDecoration: "none" }} + > + <Button + startIcon={<EditRoundedIcon />} + color="primary" + variant="outlined" + > + Editar + </Button> + </Link> + </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> + <GiAchievement size={200} color="#e81f4f" /> + </ImgDiv> + </Grid> + <Grid item sm={7} xs={12}> + <Grid container direction="column" spacing={1}> + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + > + Nome + </Typography> + <Typography color="textSecondary"> + {item.name} + </Typography> + </Grid> + <Grid item> + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + spacing={1} + > + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + > + Experiência ganha + </Typography> + <Typography style={{ color: "#228B22" }}> + {"+" + (item.reward_experience) + " experiência"} + </Typography> + </Grid> + <Grid item> + <Typography + color="initial" + className={classes.subTitle} + > + Points ganhos + </Typography> + <Typography style={{ color: "#228B22" }}> + {"+" + (item.reward_points) + " points"} + </Typography> + </Grid> + </Grid> + </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> + </Grid> + </Grid> + <Grid container direction="column" spacing={1}> + { + item.achievement ? + <> + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Como desbloquear + </Typography> + <Typography color="textSecondary"> + {item.achievement.description} + </Typography> + </Grid> + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Recompensa de xp + </Typography> + <Typography style={{ color: "#228B22" }}> + {"+" + item.achievement.reward_experience + " xp"} + </Typography> + </Grid> + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Recompensa de points + </Typography> + <Typography style={{ color: "#228B22" }}> + {"+" + item.achievement.reward_points + " points"} + </Typography> + </Grid> + </> + : null + } + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Descrição + </Typography> + <Typography color="textSecondary"> + {item.description} + </Typography> + </Grid> + <Grid item> + <Typography color="initial" className={classes.subTitle}> + Requisitos + </Typography> + <Typography color="textSecondary"> + <ul> + { + item.requirements.map((req) => { + return ( + <li key={req.created_at}> + {req.description} + </li> + ) + }) + } + </ul> + </Typography> + </Grid> + </Grid> + </CardContent> + </Card> + </Grid> + <Grid item xs={12} md={6}> + <Card> + <CardContent> + <div className={classes.displayColumn}> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Estado + </Typography> + <Typography color="textSecondary"> + {StateItem(item.state)} + </Typography> + </div> + <div className={classes.displayColumn}> + <Typography + className={classes.title} + color="inherit" + gutterBottom + > + Ações + </Typography> + <Typography color="textSecondary"> + {FastActions(item)} + </Typography> + </div> + </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/EditAchievement.js b/src/Admin/Components/Components/Inputs/EditAchievement.js new file mode 100644 index 0000000000000000000000000000000000000000..c598c16473cfa1bcb5cdaccdc424dbf9445dcb43 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditAchievement.js @@ -0,0 +1,448 @@ +/*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 EditAchievement = ({ match }) => { + const { state } = useContext(Store) + + const [name, setName] = useState('Editar Item') + const [description, setDescription] = useState('') + const [rewardXP, setRewardXP] = useState('') + const [rewardPT, setRewardPT] = useState('') + const [itemState, setItemState] = useState('') + const [repeatable, setRepeatable] = useState('') + const [resettable, setResettable] = useState('') + const [requirements, setRequirements] = useState([]) + const [stringReq, setStringReq] = useState("") + + const [isLoading, setIsLoading] = useState(false) + const [isLoaded, setIsLoaded] = useState(false) + const [error, setError] = useState(false) + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '', + }) + const [errorInDescription, setErrorInDescription] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const stateOptions = [ + { name: "active", value: "Ativo" }, + { name: "inactive", value: "Inativo" }, + { name: "deleted", value: "Removido" }, + ] + + const repeatableOptions = [ + { name: "never", value: "Nunca" }, + { name: "daily", value: "Diariamente" }, + { name: "weekly", value: "Semanalmente" }, + { name: "monthly", value: "Mensalmente" }, + { name: "yearly", value: "Anualmente" }, + ] + const resettableOptions = [ + { name: "true", value: "Sim" }, + { name: "false", value: "Não" }, + + ] + + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + const DescriptionHandler = (e) => { + if (errorInDescription.error) + setErrorInDescription({ + error: false, + message: '' + }) + setDescription(e.target.value) + } + const rewardXPHandler = (e) => { + setRewardXP(e.target.value) + } + const repeatableHandler = (e) => { + setRepeatable(e.target.value) + } + const rewardPTHandler = (e) => { + setRewardPT(e.target.value) + } + const itemStateHandler = (e) => { + setItemState(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 = `/achievements/${match.params.id}` + const body = { + "achievement": { + "name": name, + "description": description, + "reward_experience ": parseInt(rewardXP), + "reward_points": parseInt(rewardPT), + "state": itemState, + "repeatable": repeatable, + "resettable": resettable, + "requirements": requirements + } + } + console.log(requirements) + 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.name) { + let nameError = "" + data.name.map((msg) => ( + nameError = nameError + msg + " e " + )) + setErrorInName({ + error: true, + message: nameError + }) + } + if (data.description) { + let descriptionError = "" + data.description.map((msg) => ( + descriptionError = descriptionError + msg + " e " + )) + setErrorInDescription({ + error: true, + message: descriptionError + }) + } + } + setIsLoading(false) + }, + (error) => { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + setIsLoading(false) + } + ) + } + + const HandleDelete = (i) => { + const copyReq = [...requirements]; + copyReq.splice(i, 1); + setRequirements(copyReq); + }; + + const OnKeyPressHandler = (key) => { + if (key === 13) { + const copyReq = [...requirements]; + copyReq.push(parseInt(stringReq)); + setRequirements(copyReq); + setStringReq(""); + } + }; + + // Fields + const fields = [ + { + select: false, + label: 'Nome', + value: name, + required: true, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event) + }, + { + select: false, + label: 'Descrição', + value: description, + required: false, + error: errorInDescription.error, + errorMessage: errorInDescription.message, + onChange: (event) => DescriptionHandler(event) + }, + { + select: false, + label: 'Experiência ganha', + value: rewardXP, + required: false, + onChange: (event) => rewardXPHandler(event) + }, + { + select: false, + label: 'Points ganhos', + value: rewardPT, + required: false, + onChange: (event) => rewardPTHandler(event) + }, + { + select: true, + label: 'Estado', + value: itemState, + options: [...stateOptions], + required: false, + onChange: (event) => itemStateHandler(event) + }, + { + select: true, + label: 'Repetível', + value: repeatable, + required: false, + options: [...repeatableOptions], + onChange: (event) => repeatableHandler(event) + }, + { + select: true, + label: 'Reajustável', + value: resettable, + required: false, + options: [...resettableOptions], + onChange: (event) => resettableHandler(event) + }, + ] + + useEffect(() => { + getRequest( + GetAData("achievements", match.params.id), + (data, header) => { + const requirementsID = [] + setName(data.name) + setDescription(data.description) + setRewardXP(data.reward_experience) + setRewardPT(data.reward_points) + setItemState(data.state) + setRepeatable(data.repeatable) + setResettable(data.resettable) + for (let index = 0; index < data.requirements.length; index++) { + const element = data.requirements[index]; + requirementsID.push(element.id) + } + setRequirements(requirementsID) + 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'> + {name} + </Typography> + </Grid> + <Grid item> + <Link style={{ textDecoration: 'none' }} to={'/admin/achievements'}> + <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" + }} + > + {requirements.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} + placeholder="Digite um requisito 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 EditAchievement \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/GameficationAchieves.js b/src/Admin/Pages/Pages/SubPages/GameficationAchieves.js new file mode 100644 index 0000000000000000000000000000000000000000..a58bd59fae6557507e5fe1eab40dc0205d087b89 --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/GameficationAchieves.js @@ -0,0 +1,801 @@ +/*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 IconButton from "@material-ui/core/IconButton"; +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, Typography } from "@material-ui/core"; +import AlertDialog from "../../../Components/Components/AlertDialog" +import { GiAchievement } from 'react-icons/gi' + +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 GameficationAchieves = () => { + const history = useHistory() + const WINDOW_WIDTH = window.innerWidth + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = [ + "ESTADO", + "ID", + "NAME", + "TIPO", + "CRIADO EM", + "ATUALIZADO EM", + "AÇÕES", + "VISUALIZAR" + ]; //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 [typeOpt, setTypeOpt] = useState("") + const [open, setOpen] = useState(false) + const [deleteItem, setDeleteItem] = useState({}) + const [snackInfo, setSnackInfo] = useState({ + message: "", + icon: "", + open: false, + color: "", + }); + + const stateOptions = [ + { name: 0, value: "Inativo" }, + { name: 1, value: "Ativo" }, + { name: 2, value: "Removido" }, + ]; + + const typesOptions = [ + { name: "avatar_frame", value: "Moldura de avatar" }, + { name: "badge", value: "Insígnia" }, + { name: "card_frame", value: "Moldura de card do usuário" }, + { name: "cover_frame", value: "Moldura de capa" }, + + ]; + + //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 FastActions = (item, index) => { + switch (item.state) { + case "deleted": + return ( + <Grid container alignItems="center" justify={WINDOW_WIDTH <= 950 ? "flex-start" : "flex-end"} spacing={1}> + <Grid item> + <Button + color="primary" + variant="contained" + style={{ backgroundColor: "#FF8C00", color: "#FFFAFA", width: "150px" }} + onClick={() => inactiveItem(item, index)} + > + Inativar + </Button> + </Grid> + <Grid item> + <Button + color="primary" + style={{ backgroundColor: "#228B22", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => activeItem(item, index)} + > + Ativar + </Button> + </Grid> + </Grid> + ); + case "inactive": + return ( + <Grid container alignItems="center" justify={WINDOW_WIDTH <= 950 ? "flex-start" : "flex-end"} spacing={1}> + <Grid item> + <Button + color="primary" + variant="contained" + style={{ backgroundColor: "#FA8072", color: "#FFFAFA", width: "150px" }} + onClick={() => handleAlertDialog(item)} + > + Remover + </Button> + </Grid> + <Grid item> + <Button + color="primary" + style={{ backgroundColor: "#228B22", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => activeItem(item, index)} + > + Ativar + </Button> + </Grid> + </Grid> + ); + case "active": + return ( + <Grid container alignItems="center" justify={WINDOW_WIDTH <= 950 ? "flex-start" : "flex-end"} spacing={1}> + <Grid item> + <Button + style={{ backgroundColor: "#FA8072", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => handleAlertDialog(item)} + > + Remover + </Button> + </Grid> + <Grid item> + <Button + style={{ backgroundColor: "#FF8C00", color: "#FFFAFA", width: "150px" }} + variant="contained" + onClick={() => inactiveItem(item, index)} + > + Inativar + </Button> + </Grid> + </Grid> + ); + default: + return "NOTHING"; + } + }; + + const StateItem = (state) => { + switch (state) { + case "deleted": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FA8072", + fontWeight: "500", + color: "#FFFAFA", + }} + > + REMOVIDO + </Paper> + ); + case "inactive": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#FF8C00", + fontWeight: "500", + color: "#FFFAFA", + }} + > + INATIVO + </Paper> + ); + case "active": + return ( + <Paper + style={{ + textAlign: "center", + padding: "0.5em", + backgroundColor: "#228B22", + fontWeight: "500", + color: "#FFFAFA", + }} + > + ATIVO + </Paper> + ); + default: + return "NOTHING"; + } + }; + + 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("achievements", 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 inactiveItem = (item, index) => { + const body = { + "achievement": { + "state": "inactive" + } + } + putRequest( + EditFilter("achievements", item.id), + body, + (data) => { + if (data.errors) + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + else { + HandleSnack( + "O achievement foi inativado com sucesso", + true, + "success", + "#228B22" + ); + handleChangeStateItem(index, "inactive") + } + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + ) + } + + const activeItem = (item, index) => { + const body = { + "achievement": { + "state": "active" + } + } + putRequest( + EditFilter("achievements", item.id), + body, + (data) => { + if (data.errors) + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + else { + HandleSnack( + "O achievement foi ativado com sucesso", + true, + "success", + "#228B22" + ); + handleChangeStateItem(index, "active") + } + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + } + ) + } + + const handleChangeStateItem = (index, state) => { + const currItems = [...items] + currItems.splice(index, 1) + setItems(currItems) + } + + const buildUrl = (objType, state) => { + if (objType && (state >= 0 && state <= 2)) + return Url("/achievements", `"state" : ${stateOpt}, "item_type" : "${typeOpt}"`, currPage, "DESC") + else if (objType) + return Url("/achievements", `"item_type" : "${typeOpt}"`, currPage, "DESC") + else if (state >= 0 && state <= 2) + return Url("/achievements", `"state" : ${stateOpt}`, currPage, "DESC") + else + return Url("/achievements", "", currPage, "DESC") + } + + useEffect(() => { + if (currPage === 0) + setLoaded(false) + else + setIsLoadingMoreItems(true) + getRequest( + buildUrl(typeOpt, stateOpt), + (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, typeOpt, stateOpt]) + + useEffect(() => { + setTypeOpt("") + 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="Conquistas" + 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/createItem") + }, + icon: <AddRoundedIcon />, + }, + ]} + > + {showFilter ? ( + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + alignContent="flex-end" + spacing={3} + xs={12} + > + <Grid item> + <TextField + select + label="Estado" + value={stateOpt} + onChange={(e) => { setStateOpt(e.target.value) }} + helperText="Por favor, selecione uma das opções" + > + {stateOptions.map((option, index) => ( + <MenuItem + key={option.value} + value={option.name} + name={option.value} + > + {option.value} + </MenuItem> + ))} + </TextField> + </Grid> + <Grid item> + <TextField + select + label="Tipo" + value={typeOpt} + onChange={(e) => { setTypeOpt(e.target.value) }} + helperText="Por favor, selecione uma das opções" + > + {typesOptions.map((option, index) => ( + <MenuItem + key={option.value} + value={option.name} + name={option.value} + > + {option.value} + </MenuItem> + ))} + </TextField> + </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.name} + subtitle={row.id} + backColor={"#e81f4f"} + avatar={ + <GiAchievement size={20} color="white" /> + + } + href={`/admin/achievement/${row.id}`} + reset={() => { }} + data={[ + + { + title: "Criado em", + subtitle: DisplayDate(row.created_at) + }, + { + title: "Atualizado em", + subtitle: DisplayDate(row.updated_at) + }, + { + title: "Experiência ganha", + subtitle: <Typography style={{ color: "#228B22" }}> + {"+" + (row.reward_experience) + " experiência"} + </Typography> + }, + { + title: "Points ganho", + subtitle: <Typography style={{ color: "#228B22" }}> + {"+" + (row.reward_points) + " points"} + </Typography> + }, + { + title: "Descrição", + subtitle: row.description + }, + { + title: "Requisitos", + subtitle: <ul> + { + row.requirements.map((req) => { + return ( + <li key={req.created_at}> + {req.description} + </li> + ) + }) + } + </ul> + }, + { + title: "Estado", + subtitle: StateItem(row.state) + }, + { + title: "Ações rapidos", + subtitle: FastActions(row, index) + }, + ]} + /> + <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="Conquistas" + 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/createItem") + }, + icon: <AddRoundedIcon />, + }, + ]} + > + {showFilter ? ( + <Grid + container + direction="row" + justify="space-between" + alignItems="center" + alignContent="flex-end" + spacing={3} + xs={12} + > + <Grid item> + <TextField + select + label="Estado" + value={stateOpt} + onChange={(e) => { setStateOpt(e.target.value) }} + helperText="Por favor, selecione uma das opções" + > + {stateOptions.map((option, index) => ( + <MenuItem + key={option.value} + value={option.name} + name={option.value} + > + {option.value} + </MenuItem> + ))} + </TextField> + </Grid> + <Grid item> + <TextField + select + label="Tipo" + value={typeOpt} + onChange={(e) => { setTypeOpt(e.target.value) }} + helperText="Por favor, selecione uma das opções" + > + {typesOptions.map((option, index) => ( + <MenuItem + key={option.value} + value={option.name} + name={option.value} + > + {option.value} + </MenuItem> + ))} + </TextField> + </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"> + {StateItem(row.state)} + </StyledTableCell> + <StyledTableCell align="right">{row.id}</StyledTableCell> + <StyledTableCell align="right">{row.name}</StyledTableCell> + <StyledTableCell align="right"> + {row.item_type} + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.created_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {DisplayDate(row.updated_at)} + </StyledTableCell> + <StyledTableCell align="right"> + {FastActions(row, index)} + </StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/achievement/${row.id}`}> + <IconButton> + <VisibilityIcon style={{ fill: "#00bcd4" }} /> + </IconButton> + </Link> + </StyledTableCell> + </StyledTableRow> + ) + )} + </TableBody> + </TableData> + <AlertDialog + open={open} + OnDelete={deleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpen(false) + }} + /> + </> + ); + } + } else return <Unauthorized />; + } +}; +export default GameficationAchieves; + +const StyledDivButton = styled(Paper)` + width : 100%; + display : flex; + justify-content : center; + align-items : center; +` + +