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