From bbba7f3186ef45341d577079db22773c76087d64 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 28 Apr 2021 06:20:46 +0000 Subject: [PATCH] Resolve "Finish judge view" --- client/src/actions/presentation.ts | 7 ++- client/src/interfaces/ApiModels.ts | 1 - .../PresentationEditorPage.tsx | 20 +------ .../components/SlideDisplay.tsx | 2 +- client/src/pages/views/JudgeViewPage.test.tsx | 2 +- client/src/pages/views/JudgeViewPage.tsx | 24 ++++++-- .../views/components/JudgeScoreDisplay.tsx | 56 +++++++++++++------ .../components/JudgeScoringInstructions.tsx | 28 ++++++++++ .../components/PresentationComponent.tsx | 1 + client/src/pages/views/components/styled.tsx | 12 +++- client/src/pages/views/styled.tsx | 25 ++++++++- .../src/reducers/presentationReducer.test.ts | 8 ++- client/src/reducers/presentationReducer.ts | 5 +- client/src/utils/renderSlideIcon.tsx | 21 +++++++ server/app/apis/answers.py | 1 - 15 files changed, 159 insertions(+), 54 deletions(-) create mode 100644 client/src/pages/views/components/JudgeScoringInstructions.tsx create mode 100644 client/src/utils/renderSlideIcon.tsx diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts index 223b802d..90b728c5 100644 --- a/client/src/actions/presentation.ts +++ b/client/src/actions/presentation.ts @@ -5,11 +5,11 @@ This file handles actions for the presentation redux state import axios from 'axios' import { Slide } from '../interfaces/ApiModels' import { Timer } from '../interfaces/Timer' -import store, { AppDispatch } from './../store' +import store, { AppDispatch, RootState } from './../store' import Types from './types' // Save competition in presentation state from input id -export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch) => { +export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch, getState: () => RootState) => { await axios .get(`/api/competitions/${id}`) .then((res) => { @@ -17,6 +17,9 @@ export const getPresentationCompetition = (id: string) => async (dispatch: AppDi type: Types.SET_PRESENTATION_COMPETITION, payload: res.data, }) + if (getState().presentation.slide.id === -1 && res.data.slides[0]) { + setCurrentSlideByOrder(0)(dispatch) + } }) .catch((err) => { console.log(err) diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts index 4de675fb..06fdee0a 100644 --- a/client/src/interfaces/ApiModels.ts +++ b/client/src/interfaces/ApiModels.ts @@ -54,7 +54,6 @@ export interface Team extends NameID { export interface Question extends NameID { slide_id: number - title: string total_score: number type_id: number } diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx index 3c86afe9..cd9d22c2 100644 --- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx +++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx @@ -6,10 +6,6 @@ import Drawer from '@material-ui/core/Drawer' import ListItemText from '@material-ui/core/ListItemText' import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles' import AddOutlinedIcon from '@material-ui/icons/AddOutlined' -import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined' -import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined' -import DnsOutlinedIcon from '@material-ui/icons/DnsOutlined' -import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined' import axios from 'axios' import React, { useEffect, useState } from 'react' import { Link, useParams } from 'react-router-dom' @@ -18,6 +14,7 @@ import { getEditorCompetition, setEditorSlideId } from '../../actions/editor' import { getTypes } from '../../actions/typesAction' import { useAppDispatch, useAppSelector } from '../../hooks' import { RichSlide } from '../../interfaces/ApiRichModels' +import { renderSlideIcon } from '../../utils/renderSlideIcon' import { RemoveMenuItem } from '../admin/styledComp' import { Content, InnerContent } from '../views/styled' import SettingsPanel from './components/SettingsPanel' @@ -139,21 +136,6 @@ const PresentationEditorPage: React.FC = () => { setContextState(initialState) } - const renderSlideIcon = (slide: RichSlide) => { - if (slide.questions && slide.questions[0] && slide.questions[0].type_id) { - switch (slide.questions[0].type_id) { - case 1: - return <CreateOutlinedIcon /> // text question - case 2: - return <BuildOutlinedIcon /> // practical qustion - case 3: - return <DnsOutlinedIcon /> // multiple choice question - } - } else { - return <InfoOutlinedIcon /> // information slide - } - } - const GreenCheckbox = withStyles({ root: { color: '#FFFFFF', diff --git a/client/src/pages/presentationEditor/components/SlideDisplay.tsx b/client/src/pages/presentationEditor/components/SlideDisplay.tsx index 6ddb38ef..f134d0a8 100644 --- a/client/src/pages/presentationEditor/components/SlideDisplay.tsx +++ b/client/src/pages/presentationEditor/components/SlideDisplay.tsx @@ -15,7 +15,7 @@ const SlideDisplay = ({ editor }: SlideDisplayProps) => { const components = useAppSelector((state) => { if (editor) return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)?.components - return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide.id)?.components + return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide?.id)?.components }) const dispatch = useAppDispatch() const editorPaperRef = useRef<HTMLDivElement>(null) diff --git a/client/src/pages/views/JudgeViewPage.test.tsx b/client/src/pages/views/JudgeViewPage.test.tsx index 29de4d12..2e15f090 100644 --- a/client/src/pages/views/JudgeViewPage.test.tsx +++ b/client/src/pages/views/JudgeViewPage.test.tsx @@ -9,7 +9,7 @@ import JudgeViewPage from './JudgeViewPage' it('renders judge view page', () => { const compRes: any = { data: { - slides: [{ id: 0, title: '' }], + slides: [{ id: 0, title: '', questions: [{ id: 0 }] }], }, } const teamsRes: any = { diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index de5abe40..5a806d23 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,4 +1,4 @@ -import { Divider, List, ListItemText, Typography } from '@material-ui/core' +import { Card, Divider, List, ListItem, ListItemText, Paper, Typography } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import React, { useEffect, useState } from 'react' import { getPresentationCompetition, setCurrentSlide, setPresentationCode } from '../../actions/presentation' @@ -18,8 +18,13 @@ import { JudgeToolbar, LeftDrawer, RightDrawer, + ScoreHeaderPadding, + ScoreHeaderPaper, + ScoreFooterPadding, } from './styled' import SlideDisplay from '../presentationEditor/components/SlideDisplay' +import JudgeScoringInstructions from './components/JudgeScoringInstructions' +import { renderSlideIcon } from '../../utils/renderSlideIcon' const leftDrawerWidth = 150 const rightDrawerWidth = 700 @@ -48,6 +53,7 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { const [activeSlideIndex, setActiveSlideIndex] = useState<number>(0) const teams = useAppSelector((state) => state.presentation.competition.teams) const slides = useAppSelector((state) => state.presentation.competition.slides) + const currentQuestion = slides[activeSlideIndex]?.questions[0] const handleSelectSlide = (index: number) => { setActiveSlideIndex(index) dispatch(setCurrentSlide(slides[index])) @@ -86,8 +92,8 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { button key={slide.id} > - <Typography variant="h6">Slide ID: {slide.id} </Typography> - <ListItemText primary={slide.title} /> + {renderSlideIcon(slide)} + <ListItemText primary={`Sida ${slide.order + 1}`} /> </SlideListItem> ))} </List> @@ -101,7 +107,13 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { anchor="right" > <div className={classes.toolbar} /> - <List> + {currentQuestion && ( + <ScoreHeaderPaper $rightDrawerWidth={rightDrawerWidth} elevation={4}> + <Typography variant="h4">{`${currentQuestion.name} (${currentQuestion.total_score}p)`}</Typography> + </ScoreHeaderPaper> + )} + <ScoreHeaderPadding /> + <List style={{ overflowY: 'scroll', overflowX: 'hidden' }}> {teams && teams.map((answer, index) => ( <div key={answer.name}> @@ -110,8 +122,10 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { </div> ))} </List> + <ScoreFooterPadding /> + <JudgeScoringInstructions question={currentQuestion} /> </RightDrawer> - <div style={{ height: 64 }} /> + <div className={classes.toolbar} /> <Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}> <InnerContent> <SlideDisplay /> diff --git a/client/src/pages/views/components/JudgeScoreDisplay.tsx b/client/src/pages/views/components/JudgeScoreDisplay.tsx index 6308e39b..2745e129 100644 --- a/client/src/pages/views/components/JudgeScoreDisplay.tsx +++ b/client/src/pages/views/components/JudgeScoreDisplay.tsx @@ -1,15 +1,35 @@ import { Box, Typography } from '@material-ui/core' +import axios from 'axios' import React from 'react' -import { useAppSelector } from '../../../hooks' +import { getPresentationCompetition } from '../../../actions/presentation' +import { useAppDispatch, useAppSelector } from '../../../hooks' import { AnswerContainer, ScoreDisplayContainer, ScoreDisplayHeader, ScoreInput } from './styled' type ScoreDisplayProps = { teamIndex: number } -const questionMaxScore = 5 const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => { + const dispatch = useAppDispatch() const currentTeam = useAppSelector((state) => state.presentation.competition.teams[teamIndex]) + const currentCompetititonId = useAppSelector((state) => state.presentation.competition.id) + const activeQuestion = useAppSelector( + (state) => + state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide?.id)?.questions[0] + ) + const scores = currentTeam.question_answers.map((questionAnswer) => questionAnswer.score) + const questionMaxScore = activeQuestion?.total_score + const activeAnswer = currentTeam.question_answers.find( + (questionAnswer) => questionAnswer.question_id === activeQuestion?.id + ) + const handleEditScore = async (newScore: number, answerId: number) => { + await axios + .put(`/api/competitions/${currentCompetititonId}/teams/${currentTeam.id}/answers/${answerId}`, { + score: newScore, + }) + .then(() => dispatch(getPresentationCompetition(currentCompetititonId.toString()))) + } + return ( <ScoreDisplayContainer> <ScoreDisplayHeader> @@ -17,21 +37,25 @@ const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => { <Box fontWeight="fontWeightBold">{currentTeam.name}</Box> </Typography> - <ScoreInput - label="Poäng" - defaultValue={0} - inputProps={{ style: { fontSize: 20 } }} - InputProps={{ disableUnderline: true, inputProps: { min: 0, max: questionMaxScore } }} - type="number" - ></ScoreInput> + {activeAnswer && ( + <ScoreInput + label="Poäng" + defaultValue={activeAnswer?.score} + inputProps={{ style: { fontSize: 20 } }} + InputProps={{ disableUnderline: true, inputProps: { min: 0, max: questionMaxScore } }} + type="number" + onChange={(event) => handleEditScore(+event.target.value, activeAnswer.id)} + /> + )} </ScoreDisplayHeader> - <Typography variant="h6">Alla poäng: 2 0 0 0 0 0 0 0 0</Typography> - <Typography variant="h6">Total poäng: 9</Typography> - <AnswerContainer> - <Typography variant="body1"> - Svar: blablablablablablablablablabla blablablablabla blablablablabla blablablablablablablablablabla{' '} - </Typography> - </AnswerContainer> + <Typography variant="h6">Alla poäng: [ {scores.map((score) => `${score} `)}]</Typography> + <Typography variant="h6">Total poäng: {scores.reduce((a, b) => a + b, 0)}</Typography> + {activeAnswer && ( + <AnswerContainer> + <Typography variant="body1">{activeAnswer.answer}</Typography> + </AnswerContainer> + )} + {!activeAnswer && <Typography variant="body1">Inget svar</Typography>} </ScoreDisplayContainer> ) } diff --git a/client/src/pages/views/components/JudgeScoringInstructions.tsx b/client/src/pages/views/components/JudgeScoringInstructions.tsx new file mode 100644 index 00000000..3cc80319 --- /dev/null +++ b/client/src/pages/views/components/JudgeScoringInstructions.tsx @@ -0,0 +1,28 @@ +import { Box, Card, Typography } from '@material-ui/core' +import axios from 'axios' +import React from 'react' +import { getPresentationCompetition } from '../../../actions/presentation' +import { useAppDispatch, useAppSelector } from '../../../hooks' +import { RichQuestion } from '../../../interfaces/ApiRichModels' +import { + AnswerContainer, + JudgeScoringInstructionsContainer, + ScoreDisplayContainer, + ScoreDisplayHeader, + ScoreInput, +} from './styled' + +type JudgeScoringInstructionsProps = { + question: RichQuestion +} + +const JudgeScoringInstructions = ({ question }: JudgeScoringInstructionsProps) => { + return ( + <JudgeScoringInstructionsContainer elevation={3}> + <Typography variant="h4">Rättningsinstruktioner</Typography> + <Typography variant="body1">Såhär rättar du denhär frågan</Typography> + </JudgeScoringInstructionsContainer> + ) +} + +export default JudgeScoringInstructions diff --git a/client/src/pages/views/components/PresentationComponent.tsx b/client/src/pages/views/components/PresentationComponent.tsx index cb95576f..a41f7912 100644 --- a/client/src/pages/views/components/PresentationComponent.tsx +++ b/client/src/pages/views/components/PresentationComponent.tsx @@ -37,6 +37,7 @@ const PresentationComponent = ({ component, width, height, scale }: Presentation minWidth={75 * scale} minHeight={75 * scale} disableDragging={true} + enableResizing={false} bounds="parent" //Multiply by scale to show components correctly for current screen size size={{ width: component.w * scale, height: component.h * scale }} diff --git a/client/src/pages/views/components/styled.tsx b/client/src/pages/views/components/styled.tsx index b522b20d..fef186f7 100644 --- a/client/src/pages/views/components/styled.tsx +++ b/client/src/pages/views/components/styled.tsx @@ -1,4 +1,4 @@ -import { TextField } from '@material-ui/core' +import { Card, Paper, TextField } from '@material-ui/core' import styled from 'styled-components' export const SlideContainer = styled.div` @@ -32,3 +32,13 @@ export const AnswerContainer = styled.div` display: flex; flex-wrap: wrap; ` + +export const JudgeScoringInstructionsContainer = styled(Paper)` + position: absolute; + bottom: 0; + height: 250px; + width: 100%; + display: flex; + align-items: center; + flex-direction: column; +` diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx index 17d09581..8f63892a 100644 --- a/client/src/pages/views/styled.tsx +++ b/client/src/pages/views/styled.tsx @@ -1,4 +1,4 @@ -import { AppBar, Button, Drawer, Toolbar, Typography } from '@material-ui/core' +import { AppBar, Button, Card, Drawer, Paper, Toolbar, Typography } from '@material-ui/core' import styled from 'styled-components' export const JudgeAppBar = styled(AppBar)` @@ -146,3 +146,26 @@ export const PresenterInnerContent = styled.div` export const ParticipantContainer = styled.div` max-width: calc((100vh / 9) * 16); ` + +interface ScoreHeaderPaperProps { + $rightDrawerWidth: number +} + +export const ScoreHeaderPaper = styled(Card)<ScoreHeaderPaperProps>` + position: absolute; + top: 66px; + width: ${(props) => (props ? props.$rightDrawerWidth : 0)}px; + height: 71px; + display: flex; + justify-content: center; + align-items: center; + z-index: 10; +` + +export const ScoreHeaderPadding = styled.div` + min-height: 71px; +` + +export const ScoreFooterPadding = styled.div` + min-height: 250px; +` diff --git a/client/src/reducers/presentationReducer.test.ts b/client/src/reducers/presentationReducer.test.ts index ee08e068..202d9406 100644 --- a/client/src/reducers/presentationReducer.test.ts +++ b/client/src/reducers/presentationReducer.test.ts @@ -6,7 +6,8 @@ import presentationReducer from './presentationReducer' const initialState = { competition: { name: '', - id: 0, + id: 1, + background_image: undefined, city_id: 0, slides: [], year: 0, @@ -14,7 +15,8 @@ const initialState = { }, slide: { competition_id: 0, - id: 0, + background_image: undefined, + id: -1, order: 0, timer: 0, title: '', @@ -48,7 +50,7 @@ it('should handle SET_PRESENTATION_COMPETITION', () => { }) ).toEqual({ competition: testCompetition, - slide: testCompetition.slides[0], + slide: initialState.slide, code: initialState.code, timer: initialState.timer, }) diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index b657f5a4..b0c4791e 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -14,7 +14,7 @@ interface PresentationState { const initialState: PresentationState = { competition: { name: '', - id: 0, + id: 1, city_id: 0, slides: [], year: 0, @@ -23,7 +23,7 @@ const initialState: PresentationState = { }, slide: { competition_id: 0, - id: 0, + id: -1, order: 0, timer: 0, title: '', @@ -41,7 +41,6 @@ export default function (state = initialState, action: AnyAction) { case Types.SET_PRESENTATION_COMPETITION: return { ...state, - slide: action.payload.slides[0] as Slide, competition: action.payload as RichCompetition, } case Types.SET_PRESENTATION_CODE: diff --git a/client/src/utils/renderSlideIcon.tsx b/client/src/utils/renderSlideIcon.tsx new file mode 100644 index 00000000..ba1eafcb --- /dev/null +++ b/client/src/utils/renderSlideIcon.tsx @@ -0,0 +1,21 @@ +import { RichSlide } from '../interfaces/ApiRichModels' +import React from 'react' +import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined' +import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined' +import DnsOutlinedIcon from '@material-ui/icons/DnsOutlined' +import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined' + +export const renderSlideIcon = (slide: RichSlide) => { + if (slide.questions && slide.questions[0] && slide.questions[0].type_id) { + switch (slide.questions[0].type_id) { + case 1: + return <CreateOutlinedIcon /> // text question + case 2: + return <BuildOutlinedIcon /> // practical qustion + case 3: + return <DnsOutlinedIcon /> // multiple choice question + } + } else { + return <InfoOutlinedIcon /> // information slide + } +} diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py index e3e225b9..79a9c7a9 100644 --- a/server/app/apis/answers.py +++ b/server/app/apis/answers.py @@ -42,7 +42,6 @@ class QuestionAnswers(Resource): item = dbc.get.question_answer(competition_id, team_id, answer_id) return item_response(schema.dump(item)) - @check_jwt(editor=True) def put(self, competition_id, team_id, answer_id): args = question_answer_edit_parser.parse_args(strict=True) item = dbc.get.question_answer(competition_id, team_id, answer_id) -- GitLab