diff --git a/src/Admin/Components/Components/Inputs/CreateRole.js b/src/Admin/Components/Components/Inputs/CreateRole.js new file mode 100644 index 0000000000000000000000000000000000000000..7f8349de07438e026d6173d93e147ecf79780496 --- /dev/null +++ b/src/Admin/Components/Components/Inputs/CreateRole.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, useContext } 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'; +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import { postRequest } from '../../../../Components/HelperFunctions/getAxiosConfig' +//router +import { Link } from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const CreateRole = (props) => { + const { state, dispatch } = useContext(Store); + + const [name, setName] = useState('Nova role'); + const [desc, setDesc] = useState(''); + + const [isLoading, setIsLoading] = useState(false) + + // Handle error in name + const [errorInName, setErrorInName] = useState({ + error: false, + message: '', + }) + + // Handle error in Country + const [errorInDesc, setErrorInDesc] = useState({ + error: false, + message: '', + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + setName(e.target.value) + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + } + + const DescHandler = (e) => { + if (errorInDesc.error) { + setErrorInDesc({ + error: false, + message: '' + }) + } + setDesc(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 + }) + } + + 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 = '/roles' + const body = { + "role": { + 'name': name, + 'description': desc, + } + } + postRequest( + api, + body, + (data) => { + if (data.id) + HandleSnack('A role foi criada com sucesso!', true, 'success', '#228B22') + else { + if (data.name) { + let errorName = ""; + data.name.map((err) => ( + errorName = errorName + err + " e " + )) + setErrorInName({ + error: true, + message: errorName + }) + } + } + setIsLoading(false) + }, + (error) => { + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + setIsLoading(false) + } + ) + } + + // Fields + const fields = [ + { + label: 'Nome', + value: name, + required: true, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event) + }, + { + label: 'Descrição', + value: desc, + required: true, + error: errorInDesc.error, + errorMessage: errorInDesc.message, + onChange: (event) => DescHandler(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' alignItems="center" alignContent="center" xs={12}> + <Grid item> + <Typography variant='h4'> + {name} + </Typography> + </Grid> + <Grid item> + <Link to={'/admin/permissions'} style={{ textDecoration: 'none' }}> + <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) => ( + <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> + ) + } else return <Unauthorized /> +} + +export default CreateRole; \ No newline at end of file diff --git a/src/Admin/Components/Components/Inputs/EditRoles.js b/src/Admin/Components/Components/Inputs/EditRoles.js new file mode 100644 index 0000000000000000000000000000000000000000..55e4aa00cb7fc030b291ef40ec7d7c51568d19ec --- /dev/null +++ b/src/Admin/Components/Components/Inputs/EditRoles.js @@ -0,0 +1,275 @@ +/*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, useContext } from 'react'; +//imports material ui components +import { Typography, TextField, Button, Grid } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import CardAction from '@material-ui/core/CardActions'; +import ListRoundedIcon from '@material-ui/icons/ListRounded'; +import SaveIcon from '@material-ui/icons/Save'; +//imports local files +import SnackBar from '../../../../Components/SnackbarComponent'; +import { Store } from '../../../../Store'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +//imports services +import { getRequest, putRequest } from '../../../../Components/HelperFunctions/getAxiosConfig' +import { EditFilter, GetAData } from '../../../Filters'; +//routers +import { Link } from 'react-router-dom'; +import Unauthorized from '../Unauthorized'; + +const EditLanguage = ({ match }) => { + const { state, dispatch } = useContext(Store); + + 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 [isLoading, setIsLoading] = useState(false); + const id = match.params.id + const [name, setName] = useState() + const [desc, setDesc] = useState() + + const [errorInName, setErrorInName] = useState({ + error: false, + message: '' + }) + + const [errorInDesc, setErrorInDesc] = useState({ + error: false, + message: '' + }) + + const [snackInfo, setSnackInfo] = useState({ + message: '', + icon: '', + open: false, + color: '', + }) + + const NameHandler = (e) => { + if (errorInName.error) { + setErrorInName({ + error: false, + message: '' + }) + } + setName(e.target.value) + } + + const DescHandler = (e) => { + if (errorInDesc.error) { + setErrorInDesc({ + error: false, + message: '' + }) + } + setDesc(e.target.value) + } + + + //Verify if the string is empty + const isEmpty = (text) => { + return text.length === 0 ? true : false; + } + + // Fields + const fields = [ + { + label: 'ID', + value: id, + default: true, + type: 'text' + }, + { + label: 'Nome', + value: name, + error: errorInName.error, + errorMessage: errorInName.message, + onChange: (event) => NameHandler(event), + default: false, + type: 'text', + }, + { + label: 'Descrição', + value: desc, + error: errorInDesc.error, + errorMessage: errorInDesc.message, + onChange: (event) => DescHandler(event), + default: false, + type: 'text' + }, + ] + + // 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; + } + + + const onSubmit = async () => { + setIsLoading(true) + const api = EditFilter('roles', id) + const body = { + "role": { + 'name': name, + 'description': desc, + } + } + putRequest( + api, + body, + (data) => { + if (data.id) + HandleSnack('A role foi alterada com sucesso', true, 'success', '#228B22') + else { + if (data.name) { + let errorName = ""; + data.name.map((err) => ( + errorName = errorName + err + " e " + )) + setErrorInName({ + error: true, + message: errorName + }) + } + } + setIsLoading(false) + }, + (error) => { + console.log(error) + HandleSnack('Ocorreu algum erro', true, 'warning', '#FA8072') + setIsLoading(false) + } + ) + } + + useEffect(() => { + getRequest( + GetAData("roles", match.params.id), + (data, header) => { + setIsLoaded(true); + setName(data.name) + setDesc(data.description) + }, + (error) => { + setIsLoaded(true); + setError(error); + } + ) + }, []); + + if (error) { + return <div> Houve um erro... </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"> + <Typography variant='h4'> + {name} + </Typography> + <Link style={{ textDecoration: 'none' }} to={'/admin/permissions'}> + <Button + startIcon={<ListRoundedIcon />} + variant='outlined' + color='primary' + > + Listar + </Button> + </Link> + </Grid> + + <div style={{ height: '1em' }}></div> + + <form style={{ display: 'flex', flexDirection: 'column' }}> + {fields.map((field, index) => ( + <TextField + key={index} + required={field.required} + disabled={field.default} + 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 : <SaveIcon />} + > + { + isLoading ? <CircularProgress size={24} /> : 'Salvar' + } + </Button> + </CardAction> + </Card> + ) + } else return <Unauthorized /> +} + +export default EditLanguage; \ No newline at end of file diff --git a/src/Admin/Pages/Pages/SubPages/Permissions.js b/src/Admin/Pages/Pages/SubPages/Permissions.js new file mode 100644 index 0000000000000000000000000000000000000000..b0a927e2fa822f04eb9dda4cd42479364ba4bb7c --- /dev/null +++ b/src/Admin/Pages/Pages/SubPages/Permissions.js @@ -0,0 +1,373 @@ +/*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 AlertDialog from "../../../Components/Components/AlertDialog"; +import { Url } from '../../../Filters'; +import LoadingSpinner from '../../../../Components/LoadingSpinner'; +import { DeleteFilter } from '../../../Filters'; +import { getRequest, deleteRequest } from '../../../../Components/HelperFunctions/getAxiosConfig' +//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 IconButton from '@material-ui/core/IconButton'; +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 EditRoundedIcon from '@material-ui/icons/EditRounded'; +import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded'; +//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 UserPermissions = () => { + const ADD_ONE_LENGHT = [""]; + const TOP_LABELS = ['ID', 'NOME', 'DESCRIÇÃO', 'AÇÕES'] //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 [openAlertDialog, setOpenAlertDialog] = useState(false); //controlls the state od alert dialog + + const [deleteItem, setDeleteItem] = useState({}); //Delete Item + const [isLoadingToDelete, setIsLoadingToDelete] = useState(null); + + 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) + getRequest( + api, + (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] + 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)) + } + setIsLoadingMoreItems(false) + }, + (error) => { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + setIsLoadingMoreItems(false) + } + ) + } + + // handle update list data + const UpdateHandler = async (api) => { + setIsUpdating(true) + getRequest( + api, + (data, header) => { + HandleSnack('A lista de dados foi atualizada', true, 'success', '#228B22') + const arrData = [...data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + setIsUpdating(false) + }, + (error) => { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + setIsUpdating(false) + } + ) + } + + //handle Delete + async function DeleteHandler() { + const id = deleteItem.id; + HandleStateAlertDialog(null); + deleteRequest( + DeleteFilter("roles", id), + (data) => { + HandleSnack( + "A lÃngua foi deletada com sucesso", + true, + "success", + "#228B22" + ); + currPage = 0; + transformListToAsc = false + UpdateHandler(Url("roles", "", `${currPage}`, "DESC")); + HandleStateCircularProgress(null); + }, + (error) => { + HandleSnack("Ocorreu algum erro", true, "warning", "#FA8072"); + HandleStateCircularProgress(null); + } + ) + } + + const HandleStateCircularProgress = (i) => { + setIsLoadingToDelete(i); + }; + + const HandleStateAlertDialog = (i) => { + const obj = { ...items[i] }; + setDeleteItem(obj); + setOpenAlertDialog(!openAlertDialog); + }; + + const InvertList = async () => { + transformListToAsc = !transformListToAsc + currPage = 0 + if (transformListToAsc) { + getRequest( + Url('roles', '', `${currPage}`, 'ASC'), + (data, header) => { + const arrData = [...data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + }, + (error) => { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + ) + } else { + getRequest( + Url('roles', '', `${currPage}`, 'DESC'), + (data, header) => { + const arrData = [...data] + setItems(arrData.concat(ADD_ONE_LENGHT)) + }, + (error) => { + HandleSnack('Erro ao carregar os dados', true, 'warning', '#FA8072') + } + ) + } + } + + //getting data from server + useEffect(() => { + getRequest( + Url('roles', '', `${currPage}`, 'DESC'), + (data, header) => { + setIsLoaded(true); + setItems(data.concat(ADD_ONE_LENGHT)); + }, + (error) => { + setError(true); + } + ) + }, []); + + + if (error) { + return <div>Error: {error.message}</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: '' + })} + /> + + <Paper style={{ padding: '1em' }}> + <Grid container spacing={3} direction="row" alignItems="center"> + <Grid item xs={6}> + <Typography variant="h4"> + Lista de permissões de usuário + </Typography> + </Grid> + <Grid + item + xs={6} + > + <Grid container justify="flex-end" spacing={3}> + <Grid item> + <Button + variant="contained" + color="secondary" + disabled={isUpdating} + onClick={() => { + currPage = 0 + transformListToAsc = false + UpdateHandler(Url('roles', '', `${currPage}`, 'DESC')) + }} + startIcon={<UpdateRoundedIcon />} + > + { + isUpdating ? <CircularProgress size={24} /> : 'Atualizar' + } + </Button> + </Grid> + <Grid item> + + <Link style={{ textDecoration: 'none' }} to={'/admin/CreateRole'}> + <Button + variant="contained" + color="secondary" + startIcon={<AddRoundedIcon />} + onClick={() => {currPage = 0}} + > + 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 ? + <StyledTableRow key={index}> + {/* Button to load more data */} + <StyledTableCell> + <Button + color='primary' + variant='text' + // disabled={isLoadingMoreItems} + startIcon={<AddRoundedIcon />} + disabled={isLoadingMoreItems} + onClick={() => { + currPage++ + if (transformListToAsc) { + LoadMoreItens(Url('roles', '', `${currPage}`, 'ASC')) + } else { + LoadMoreItens(Url('roles', '', `${currPage}`, 'DESC')) + } + }} + > + { + 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.name}</StyledTableCell> + <StyledTableCell align="right">{row.description}</StyledTableCell> + <StyledTableCell align="right"> + <Link to={`/admin/EditPermissions/${row.id}`}> + <Button + style={{ width: "100%", marginBottom: "0.5em" }} + variant="contained" + color="primary" + onClick={() => {currPage = 0}} + startIcon={<EditRoundedIcon />} + > + Editar + </Button> + </Link> + + {isLoadingToDelete === index ? ( + <CircularProgress size={24} color="primary" /> + ) : ( + <Button + style={{ width: "100%" }} + variant="contained" + color="secondary" + onClick={() => { + HandleStateAlertDialog(index) + HandleStateCircularProgress(index) + }} + startIcon={<DeleteRoundedIcon />} + > + Deletar + </Button> + )} + + </StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </TableData> + + {/* This alert will be displayed if the user click to delete an institution */ } + <AlertDialog + open={openAlertDialog} + OnDelete={DeleteHandler} + deleteItem={deleteItem} + HandleClose={() => { + setOpenAlertDialog(false); + HandleStateCircularProgress(null); + }} + /> + </> + ); + } +} +export default UserPermissions; \ No newline at end of file