diff --git a/src/Components/CollectionCommentSection.js b/src/Components/CollectionCommentSection.js index 280e749a626e31e164b58404d48e32f2825ce14f..df6ead8183a7b462c0f588d6415bccd9317b2255 100644 --- a/src/Components/CollectionCommentSection.js +++ b/src/Components/CollectionCommentSection.js @@ -16,9 +16,11 @@ 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 React, { useRef, useState, useEffect } from 'react'; import { Grid } from '@material-ui/core'; import Card from '@material-ui/core/Card'; +import Button from '@material-ui/core/Button'; +import EditIcon from '@material-ui/icons/Edit'; import styled from 'styled-components'; import axios from 'axios'; import { apiUrl } from '../env'; @@ -26,12 +28,14 @@ import CommentForm from './ResourcePageComponents/CommentForm.js'; import Comment from './Comment.js'; import Snackbar from '@material-ui/core/Snackbar'; import MuiAlert from '@material-ui/lab/Alert'; +import Comentario from '../img/comentarios.png'; export default function CollectionCommentSection(props) { const [post_snack_open, setPostSnackOpen] = useState(false); const [delete_snack_open, setDeleteSnackOpen] = useState(false); const [render_state, setRenderState] = useState(false); const [reviews, setReviews] = useState([]); + const comment_ref = useRef(null); const forceUpdate = () => { setRenderState(!render_state); } @@ -43,10 +47,72 @@ export default function CollectionCommentSection(props) { setDeleteSnackOpen(!delete_snack_open); } + const handleScrollToCommentForm = () => { + window.scrollTo(0, comment_ref.current.offsetTop); + } + function Alert(props) { return <MuiAlert elevation={6} variant="filled" {...props} />; } + const NoCommentsMessage = () => { + const NoCommentsContainer=styled.div` + text-align: center; + margin-left: 9vw; + margin-right: 9vw; + ` + const BlueTitle=styled.h2` + color: #673ab7; + ` + const Secondary=styled.h3` + font-weight: 100; + ` + const ButtonText=styled.span` + font-weight: 900; + ` + const Image=styled.img` + ` + return ( + <NoCommentsContainer> + <Image src={Comentario} /> + <BlueTitle>Compartilhe sua opinião com a rede!</BlueTitle> + <Secondary>Gostou desta coleção? Comente e compartilhe com a rede sua opinião. Interagindo com a rede, você contribui para que mais coleções como esta sejam criadas.</Secondary> + <Button + variant="contained" + color="primary" + startIcon={<EditIcon />} + onClick={handleScrollToCommentForm} + > + <ButtonText>Relatar experiência</ButtonText> + </Button> + </NoCommentsContainer> + ); + } + const CollectionComments = () => { + return ( + <div> + <Title>{reviews.length} {reviews.length == 1 ? "Relato" : "Relatos"} sobre a Coleção</Title> + {reviews.map(r => { + return ( + <Comment + rerenderCallback={forceUpdate} + objectID={props.id} + reviewID={r.id} + reviewRatings={r.review_ratings} + authorID={r.user.id} + rating={r.rating_average} + authorName={r.user.name} + authorAvatar={r.user.avatar} + description={r.description} + createdAt={r.created_at} + handleSnackbar={handleDeleteSnackbar} + recurso={false} + /> + );})} + </div> + ); + } + useEffect(() => { axios.get(apiUrl+'/collections/'+props.id+'/reviews') .then(res => { @@ -56,31 +122,14 @@ export default function CollectionCommentSection(props) { return ( <CommentAreaContainer container xs={12} direction="row" justify="center" alignItems="center"> - <Grid item xs={12}> + <Grid item xs={12} ref={comment_ref}> <CommentAreaCard> <Title>Conte sua experiência com a coleção</Title> <CommentForm colecao recursoId={props.id} handleSnackbar={handlePostSnackbar} - rerenderCallback={forceUpdate}/> - <Title>{reviews.length} {reviews.length == 1 ? "Relato" : "Relatos"} sobre a Coleção</Title> - {reviews.map(r => { - return ( - <Comment - rerenderCallback={forceUpdate} - objectID={props.id} - reviewID={r.id} - reviewRatings={r.review_ratings} - authorID={r.user.id} - rating={r.rating_average} - authorName={r.user.name} - authorAvatar={r.user.avatar} - description={r.description} - createdAt={r.created_at} - handleSnackbar={handleDeleteSnackbar} - recurso={false} - /> - ); - })} + rerenderCallback={forceUpdate} + /> + {reviews.length ? CollectionComments() : NoCommentsMessage()} </CommentAreaCard> </Grid> <Snackbar @@ -107,6 +156,7 @@ export default function CollectionCommentSection(props) { ); } + const CommentAreaContainer=styled(Grid)` margin-left: 10%; margin-right: 10%; diff --git a/src/Components/ModalExcluirComentario.js b/src/Components/ModalExcluirComentario.js new file mode 100644 index 0000000000000000000000000000000000000000..419b621fbda711314c24e44ce95192d57895f6f3 --- /dev/null +++ b/src/Components/ModalExcluirComentario.js @@ -0,0 +1,104 @@ +import React, {useContext} from 'react'; +import Modal from '@material-ui/core/Modal'; +import Fade from '@material-ui/core/Fade'; +import styled from 'styled-components' +import { Button } from '@material-ui/core'; +import Backdrop from '@material-ui/core/Backdrop'; +import { Store } from '../Store.js'; +import axios from 'axios' +import {apiUrl} from '../env'; + +const StyledModal = styled(Modal)` + margin : 0 !important; + margin-left : 0 !important; + display : flex; + align-items: center; + justify-content : center; + text-align : center; + padding : 10px !important; + border-radius : 4px; + max-width : none; + max-height : none; + min-width: 150px; + min-height: 150px !important; +` + +const HeaderDiv = styled.div` + width : 100%; + max-width : 100%; + display : flex; + align-items : center; + align-content : center; +` +const ContentContainer = styled.div` + box-sizing : border-box; + background-color : white; + max-width : none; + min-wdith : 240px; + align : center; + border-radius : 4px; + font-family : 'Roboto', sans serif; + box-shadow : 0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12)!important; +` +const ButtonCancelar = styled(Button)` + background-color : rgba(158,158,158,0.2) !important; + color : #666 !important; + text-decoration : none !important; + outline : none !important; + text-align : center !important; + margin : 0 8px !important; + font-weight : 600 !important; +` + +const ButtonConfirmar = styled(Button)` + background-color : #ff7f00 !important; + color : #fff !important; + border-radius : 3px !important; + margin : 0 8px !important; + font-weight : 600 !important; +` + +export default function ModalExcluir (props) { + const text = { + header : "Tem certeza que deseja excluir este comentário?", + explanation : "A exclusão de um comentário é permanente. Não é possÃvel desfazer." + } + + return ( + <StyledModal + aria-labelledby="transition-modal-title" + aria-describedby="transition-modal-description" + open={props.open} + + centered="true" + onClose={props.handleClose} + closeAfterTransition + BackdropComponent={Backdrop} + BackdropProps={{ + timeout: 500, + }} + > + <Fade in={props.open}> + <ContentContainer> + <HeaderDiv> + <h3 style={{fontSize : "24px", margin : "20px 15px 10px", fontWeight : "normal"}}> + {text.header} + </h3> + </HeaderDiv> + <div style={{display : "flex", flexDirection : "column", padding : "20px 30px"}}> + <div style={{marginTop : "0", textAlign : "center", marginBottom : "20px"}}> + <span style={{fontSize : "14px", color : "#666"}}>{text.explanation}</span> + </div> + <div style={{display : "flex", flexDirection : "row", justifyContent: "center"}}> + <ButtonCancelar onClick={props.handleClose}>CANCELAR</ButtonCancelar> + <ButtonConfirmar onClick={props.handleConfirm}> EXCLUIR </ButtonConfirmar> + </div> + </div> + </ContentContainer> + </Fade> + + </StyledModal> + ) + +} + diff --git a/src/Components/ResourcePageComponents/CommentForm.js b/src/Components/ResourcePageComponents/CommentForm.js new file mode 100644 index 0000000000000000000000000000000000000000..0b13b1479b9329bf1f31228575d829fb60f27f2a --- /dev/null +++ b/src/Components/ResourcePageComponents/CommentForm.js @@ -0,0 +1,211 @@ +import React, {useState} from 'react' +import styled from 'styled-components' +import Rating from '@material-ui/lab/Rating'; +import StarBorderIcon from '@material-ui/icons/StarBorder'; +import StarIcon from '@material-ui/icons/Star'; +import TextField from "@material-ui/core/TextField"; +import { Button } from '@material-ui/core'; +import axios from 'axios' +import {apiUrl} from '../../env'; +import EditIcon from '@material-ui/icons/Edit'; +import Grid from '@material-ui/core/Grid'; + +export default function CommentForm (props) { + const [rating, setRating] = useState({ + error : true, + value : 0 + }) + const [comment, setComment] = useState({ + error : false, + value : '' + }) + + const handleChange = (e) => { + const userInput = e.target.value + const flag = (userInput.length === 0 ? true : false); + setComment({...comment, error : flag, value : userInput}) + } + + const [attemptedSubmit, setAttempt] = useState(false) + + const handleSubmit = (e) => { + e.preventDefault() + const finalRating = rating + const finalComment = comment + + + if (!(finalRating.error || finalComment.error)) { + let config = { + headers : { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Access-Token': sessionStorage.getItem('@portalmec/accessToken'), + 'Client': sessionStorage.getItem('@portalmec/clientToken'), + 'Uid': sessionStorage.getItem('@portalmec/uid'), + } + } + let payload = { + "review" : { + "description" : finalComment.value, + "review_ratings_attributes" : [ + { + "rating_id" : 1, + "value" : finalRating.value + } + ] + } + } + let type = props.recurso ? 'learning_objects/' : 'collections/' + console.log(payload) + axios.post( (`${apiUrl}/` + type + props.recursoId + '/reviews'), payload, config + ).then((response) => { + if ( response.headers['access-token'] ) { + sessionStorage.setItem('@portalmec/accessToken', response.headers['access-token']) + } + console.log(response.data); + props.handleSnackbar(1); + props.rerenderCallback()}, + (error) => {console.log(error)}) + } + else { + setAttempt(true) + } + } + + return ( + <StyledForm onSubmit={handleSubmit}> + <label htmlFor="avaliacao-estrelas" className="start-label"> + {props.recurso ? "Este recurso foi útil?*" : "Esta coleção foi útil?*"} + </label> + <div className="stars-container"> + <Rating + name="avaliacao-estrelas" + value={rating.value} + precision={0.5} + style={{color:"#ff9226"}} + onChange = {(e, newValue) => {setRating({...rating, error : newValue === null ? true : false, value : newValue})}} + emptyIcon={<StarIcon fontSize="inherit" style={{color : "#666"}} />} + getLabelText={(value) => {return(value + ' Estrela' + (value !== 1 ? 's' : ''))}} + /> + </div> + <div className="star-alert" style={attemptedSubmit ? {visibility : "visible"} : {visibility : "hidden" }}>{props.recurso ? "Avalie se o recurso foi útil." : "Avalie se esta coleção foi útil."}</div> + + <Grid container> + <Grid item xs={10}> + <StyledTextField + colecao={!props.recurso} + value={comment.value} + multiline + rows="5" + error={comment.error} + label={props.recurso ? "Escreva aqui a sua experiência com este Recurso" : "Escreva aqui a sua experiência com esta Coleção"} + onChange={e => handleChange(e)} + required={true} + help = {comment.error ? (props.recurso ? "Escreva aqui a sua experiência com este Recurso" : "Escreva aqui a sua experiência com esta Coleção") : ''} + /> + </Grid> + <Grid item xs={2}> + <div style={{height : "100%", display : "flex", flexDirection : "column", justifyContent : "flex-end"}}> + { + props.recurso ? + ( + <OrangeButton type="submit">Publicar</OrangeButton> + ) + : + ( + <PurpleButton type="submit"><EditIcon/>Enviar</PurpleButton> + ) + } + </div> + </Grid> + + <div className="campos-obrigatorios">* Campos obrigatórios.</div> + </Grid > + </StyledForm> + ) +} + +const PurpleButton = styled(Button)` + background-color : #673ab7 !important; + box-shadow : 0 2px 5px 0 rgba(0,0,0,.26) !important; + font-weight : 600 !important; + color : #fff !important; + .icon { + vertical-align : middle !important; + font-weight : normal !important; + font-style : normal !important; + font-size : 24px !important; + line-height : 1 !important; + letter-spacing : normal !important; + text-transform : none !important; + display : inline-block !important; + white-space : nowrap !important; + word-wrap : normal !important; + direction : ltr !important; + padding-right : 2px; + } +` + +const OrangeButton = styled(Button)` + color : rgba(255,255,255,0.87) !important; + box-shadow : 0 2px 5px 0 rgba(0,0,0,.26) !important; + font-weight : 600 !important; + background-color : #ff7f00 !important; +` + +const StyledTextField = styled(TextField)` + .MuiInputBase-root { + margin-bottom : 5px; + } + + label.Mui-focused { + color : ${props => props.colecao ? "#673ab7" : "rgb(255,127,0)"} + } + + .MuiInput-underline::after { + border-bottom: ${props => props.colecao ? "2px solid #673ab7" : "2px solid rgb(255,127,0)" }; + } + + label.Mui-focused.Mui-error { + color : red; + } + + width: 95%; +` + +const StyledForm = styled.form` + display : flex; + flex-direction : column; + text-align : start; + + .start-label { + font-size : 14px; + max-width : 100%; + display : inline-block; + margin-bottom : 0; + color : #a5a5a5; + font-weight : 400; + } + + .stars-container { + padding-top : 10px; + padding-bottom : 5px; + display : flex; + flex-direction : row; + align-self : flex-start; + margin-bottom : 5px; + color : #a5a5a5; + } + + .star-alert { + color : #666; + text-align : start; + } + + .campos-obrigatorios { + padding-top : 18px; + font-weight : 400; + font-size : 12px; + color :#a5a5a5; + } +` diff --git a/src/Components/ResourcePageComponents/CommentsArea.js b/src/Components/ResourcePageComponents/CommentsArea.js new file mode 100644 index 0000000000000000000000000000000000000000..afebc50bc5a4d2a912eeee03bfdef8517843e070 --- /dev/null +++ b/src/Components/ResourcePageComponents/CommentsArea.js @@ -0,0 +1,211 @@ +/*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' +import {Store} from '../../Store.js' +import styled from 'styled-components' +import Grid from '@material-ui/core/Grid'; +import { Button } from '@material-ui/core'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import Comentarios from '../../img/comentarios.png' +import {apiUrl, apiDomain} from '../../env'; +import CommentForm from './CommentForm.js' +import axios from 'axios' +import Comment from '../Comment.js' + +export default function CommentsArea (props) { + const {state} = useContext(Store) + const [comentarios, setComentarios] = useState([]) + const [gambiarra, setState] = useState(0) + const forceUpdate = () => {setState(gambiarra + 1)} + + useEffect( () => { + axios.get( (`${apiUrl}/learning_objects/` + props.recursoId + '/reviews') + ).then( (response) => { + if ( response.headers['access-token'] ) { + sessionStorage.setItem('@portalmec/accessToken', response.headers['access-token']) + } + console.log(response) + setComentarios(response.data.sort((a, b) => a.updated_at > b.updated_at ? -1 : 1)) + }, (error) => {console.log(error)}) + }, [gambiarra]) + + return ( + <Grid container spacing={2} style={{padding : "10px"}}> + { + (state.currentUser.id !== '') ? + ( + <Grid item xs={12} > + <GrayContainer> + <h3>Conte sua experiência com o Recurso</h3> + <Grid container style={{paddingTop : "20px"}}> + <Grid item xs={2} style={{paddingLeft : "15px", paddingRight : "15px"}}> + <img src={apiDomain + state.currentUser.userAvatar} className="minha-imagem" alt="user avatar"/> + </Grid> + <Grid item xs={10}> + <CommentForm + recursoId={props.recursoId} + handleSnackbar={props.handleSnackbar} + rerenderCallback={forceUpdate} + recurso={true} + /> + </Grid> + </Grid> + </GrayContainer> + </Grid> + ) + : + ( + <Grid item xs={12}> + <LogInToComment> + <span className="span-laranja">Você precisa entrar para comentar</span> + {/*adicionar funcionalidade ao botao de entrar*/} + <Button style={{textTransform : "uppercase", color : "#666", fontWeight : "700"}}> + <ExitToAppIcon/>ENTRAR + </Button> + </LogInToComment> + </Grid> + ) + } + { + comentarios.length !== 0 ? + ( + <ComentariosBox> + <h3>{comentarios.length} {comentarios.length != 1 ? 'Relatos' : 'Relato'} sobre o uso do Recurso</h3> + { + comentarios.map( comentario => + <div className="comentario-template" key={comentario.id}> + <Comment + authorID={comentario.user ? comentario.user.id : null} + authorAvatar={comentario.user ? comentario.user.avatar : null} + authorName={comentario.user ? comentario.user.name : null} + name={comentario.name} + rating={comentario.rating_average} + reviewRatings = {comentario.review_ratings} + description={comentario.description} + createdAt={comentario.created_at} + recurso={true} + reviewID={comentario.id} + objectID={props.recursoId} + rerenderCallback={forceUpdate} + handleSnackbar={props.handleSnackbar} + /> + </div> + ) + } + </ComentariosBox> + ) + : + ( + <Grid item xs={12}> + <LogInToComment> + <img src={Comentarios} /> + <span className="span-laranja">Compartilhe sua experiência com a Rede!</span> + <AoRelatar> + Ao relatar sua experiência de uso do Recurso você estará auxiliando professores de todo paÃs. + </AoRelatar> + </LogInToComment> + </Grid> + ) + } + </Grid> + ) +} + + +const ComentariosBox = styled.div` + display : flex; + flex-direction : column; + padding : 20px; + width : 100%; + + h3 { + font-family: 'Roboto Light','Roboto Regular',Roboto; + font-weight: 300; + font-style: normal; + color:#666; + font-size: 1.857em; + margin: 15px 2%; + text-align : flex-start; + } + + .comentario-template { + padding : 20px 0; + border-bottom : 1px solid #f4f4f4; + } +` +const AoRelatar = styled.div` + width : 70%; + font-size : 20px; + font-weight : 300; + text-align : center; + padding-bottom : 20px; +` + +const LogInToComment = styled.div` + display : flex; + flex-direction : column; + text-align : center; + padding : 20px; + align-items : center; + + .span-laranja { + font-size : 24px; + font-weight : 700; + padding-bottom : 5px; + color : #ff7f00; + } + + img { + object-fit : contain !important; + background-color : transparent !important; + } +` + +const GrayContainer = styled.div` + background-color : #fafafa; + font-weight : 400; + display : flex; + flex-direction : column; + justify-content : space-between; + padding-right : 15px; + padding-left : 15px; + padding-bottom : 20px; + + h3 { + font-family : 'Roboto Light','Roboto Regular',Roboto; + font-weight: 300; + font-style: normal; + color: #666; + font-size: 1.857em; + margin-bottom : 10px; + margin-left : 2%; + margin-top : 2%; + } + + .minha-imagem { + height: 60px; + width: 60px; + border-radius: 50%; + margin-left: 2%; + margin-top: 5%; + } + img { + vertical-align :middle; + } +`