From b24f3bba874eea9e4aaa02367132612dd1237e3a Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Fri, 9 Apr 2021 10:13:05 +0000 Subject: [PATCH] Resolve "Add routes and template for Presentation view" --- client/.eslintrc | 52 ++++----- client/src/Main.tsx | 8 +- client/src/actions/presentation.ts | 44 ++++++++ client/src/actions/types.ts | 5 + client/src/interfaces/Slide.ts | 7 ++ client/src/interfaces/Team.ts | 4 + client/src/interfaces/ViewParams.ts | 4 + client/src/pages/admin/AdminPage.test.tsx | 37 ++++++ client/src/pages/admin/AdminPage.tsx | 2 +- .../admin/components/CompetitionManager.tsx | 5 +- .../src/pages/views/AudienceViewPage.test.tsx | 8 +- client/src/pages/views/JudgeViewPage.test.tsx | 38 ++++++- client/src/pages/views/JudgeViewPage.tsx | 106 +++++++++++++++++- .../pages/views/ParticipantViewPage.test.tsx | 8 +- .../src/pages/views/ParticipantViewPage.tsx | 3 +- .../pages/views/PresenterViewPage.test.tsx | 43 +++++++ client/src/pages/views/PresenterViewPage.tsx | 74 ++++++++++++ .../views/components/JudgeScoreDisplay.tsx | 39 +++++++ .../views/components/SlideDisplay.test.tsx | 13 +++ .../pages/views/components/SlideDisplay.tsx | 15 +++ client/src/pages/views/components/styled.tsx | 27 +++++ client/src/pages/views/styled.tsx | 74 +++++++++++- client/src/reducers/allReducers.ts | 2 + client/src/reducers/presentationReducer.ts | 72 ++++++++++++ client/src/styled.tsx | 1 - 25 files changed, 651 insertions(+), 40 deletions(-) create mode 100644 client/src/actions/presentation.ts create mode 100644 client/src/interfaces/Slide.ts create mode 100644 client/src/interfaces/Team.ts create mode 100644 client/src/interfaces/ViewParams.ts create mode 100644 client/src/pages/views/PresenterViewPage.test.tsx create mode 100644 client/src/pages/views/PresenterViewPage.tsx create mode 100644 client/src/pages/views/components/JudgeScoreDisplay.tsx create mode 100644 client/src/pages/views/components/SlideDisplay.test.tsx create mode 100644 client/src/pages/views/components/SlideDisplay.tsx create mode 100644 client/src/pages/views/components/styled.tsx create mode 100644 client/src/reducers/presentationReducer.ts diff --git a/client/.eslintrc b/client/.eslintrc index 3e2dbeb0..d5bbb2ed 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -1,29 +1,29 @@ { - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module", - "project": [ - "tsconfig.json" - ] - }, - "ecmaFeatures": { - "jsx": true - }, - "settings": { - "react": { - "version": "detect" - } - }, - "extends": [ - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - "prettier/@typescript-eslint", - "plugin:prettier/recommended" - ], - "rules": { - "prettier/prettier": ["warn", { - "endOfLine":"auto" - }] + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module", + "project": ["tsconfig.json"] + }, + "ecmaFeatures": { + "jsx": true + }, + "settings": { + "react": { + "version": "detect" } + }, + "extends": [ + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended" + ], + "rules": { + "prettier/prettier": [ + "warn", + { + "endOfLine": "auto" + } + ] } - +} diff --git a/client/src/Main.tsx b/client/src/Main.tsx index c5494c88..f32aad9f 100644 --- a/client/src/Main.tsx +++ b/client/src/Main.tsx @@ -6,6 +6,7 @@ import PresentationEditorPage from './pages/presentationEditor/PresentationEdito import AudienceViewPage from './pages/views/AudienceViewPage' import JudgeViewPage from './pages/views/JudgeViewPage' import ParticipantViewPage from './pages/views/ParticipantViewPage' +import PresenterViewPage from './pages/views/PresenterViewPage' import ViewSelectPage from './pages/views/ViewSelectPage' import SecureRoute from './utils/SecureRoute' @@ -17,9 +18,10 @@ const Main: React.FC = () => { <SecureRoute path="/admin" component={AdminPage} /> <SecureRoute path="/editor/competition-id=:id" component={PresentationEditorPage} /> <Route exact path="/view" component={ViewSelectPage} /> - <Route exact path="/view/participant" component={ParticipantViewPage} /> - <Route exact path="/view/judge" component={JudgeViewPage} /> - <Route exact path="/view/audience" component={AudienceViewPage} /> + <Route exact path="/participant/id=:id&code=:code" component={ParticipantViewPage} /> + <SecureRoute exact path="/presenter/id=:id&code=:code" component={PresenterViewPage} /> + <Route exact path="/judge/id=:id&code=:code" component={JudgeViewPage} /> + <Route exact path="/audience/id=:id&code=:code" component={AudienceViewPage} /> </Switch> </BrowserRouter> ) diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts new file mode 100644 index 00000000..9e482d2b --- /dev/null +++ b/client/src/actions/presentation.ts @@ -0,0 +1,44 @@ +import axios from 'axios' +import { Slide } from '../interfaces/Slide' +import { AppDispatch } from './../store' +import Types from './types' + +export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch) => { + await axios + .get(`/competitions/${id}`) + .then((res) => { + dispatch({ + type: Types.SET_PRESENTATION_COMPETITION, + payload: res.data, + }) + }) + .catch((err) => { + console.log(err) + }) +} + +export const getPresentationTeams = (id: string) => async (dispatch: AppDispatch) => { + await axios + .get(`/competitions/${id}/teams`) + .then((res) => { + dispatch({ + type: Types.SET_PRESENTATION_TEAMS, + payload: res.data.items, + }) + }) + .catch((err) => { + console.log(err) + }) +} + +export const setCurrentSlide = (slide: Slide) => (dispatch: AppDispatch) => { + dispatch({ type: Types.SET_PRESENTATION_SLIDE, payload: slide }) +} + +export const setCurrentSlidePrevious = () => (dispatch: AppDispatch) => { + dispatch({ type: Types.SET_PRESENTATION_SLIDE_PREVIOUS }) +} + +export const setCurrentSlideNext = () => (dispatch: AppDispatch) => { + dispatch({ type: Types.SET_PRESENTATION_SLIDE_NEXT }) +} diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index b72a3586..c0b7b629 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -15,6 +15,11 @@ export default { SET_COMPETITIONS_FILTER_PARAMS: 'SET_COMPETITIONS_FILTER_PARAMS', SET_COMPETITIONS_TOTAL: 'SET_COMPETITIONS_TOTAL', SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT', + SET_PRESENTATION_COMPETITION: 'SET_PRESENTATION_COMPETITION', + SET_PRESENTATION_SLIDE: 'SET_PRESENTATION_SLIDE', + SET_PRESENTATION_SLIDE_PREVIOUS: 'SET_PRESENTATION_SLIDE_PREVIOUS', + SET_PRESENTATION_SLIDE_NEXT: 'SET_PRESENTATION_SLIDE_NEXT', + SET_PRESENTATION_TEAMS: 'SET_PRESENTATION_TEAMS', SET_CITIES: 'SET_CITIES', SET_CITIES_TOTAL: 'SET_CITIES_TOTAL', SET_CITIES_COUNT: 'SET_CITIES_COUNT', diff --git a/client/src/interfaces/Slide.ts b/client/src/interfaces/Slide.ts new file mode 100644 index 00000000..dcdbd366 --- /dev/null +++ b/client/src/interfaces/Slide.ts @@ -0,0 +1,7 @@ +export interface Slide { + competition_id: number + id: number + order: number + timer: number + title: string +} diff --git a/client/src/interfaces/Team.ts b/client/src/interfaces/Team.ts new file mode 100644 index 00000000..10b4350e --- /dev/null +++ b/client/src/interfaces/Team.ts @@ -0,0 +1,4 @@ +export interface Team { + id: number + name: string +} diff --git a/client/src/interfaces/ViewParams.ts b/client/src/interfaces/ViewParams.ts new file mode 100644 index 00000000..e9aa6a5c --- /dev/null +++ b/client/src/interfaces/ViewParams.ts @@ -0,0 +1,4 @@ +export interface ViewParams { + id: string + code: string +} diff --git a/client/src/pages/admin/AdminPage.test.tsx b/client/src/pages/admin/AdminPage.test.tsx index efb56bb2..ab694cee 100644 --- a/client/src/pages/admin/AdminPage.test.tsx +++ b/client/src/pages/admin/AdminPage.test.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react' +import mockedAxios from 'axios' import React from 'react' import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' @@ -6,6 +7,42 @@ import store from '../../store' import AdminPage from './AdminPage' it('renders admin view', () => { + const cityRes: any = { + data: { + items: [ + { + id: 1, + name: 'Link\u00f6ping', + }, + { + id: 2, + name: 'Stockholm', + }, + ], + count: 2, + total_count: 3, + }, + } + const rolesRes: any = { + data: { + items: [ + { + id: 1, + name: 'role1', + }, + { + id: 2, + name: 'role2', + }, + ], + count: 2, + total_count: 3, + }, + } + ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => { + if (path === '/misc/cities') return Promise.resolve(cityRes) + else return Promise.resolve(rolesRes) + }) render( <Provider store={store}> <BrowserRouter> diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx index 8b0fb81c..793da84f 100644 --- a/client/src/pages/admin/AdminPage.tsx +++ b/client/src/pages/admin/AdminPage.tsx @@ -51,7 +51,7 @@ const useStyles = makeStyles((theme: Theme) => content: { flexGrow: 1, backgroundColor: theme.palette.background.default, - paddingLeft: theme.spacing(30), + paddingLeft: theme.spacing(31), }, }) ) diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx index 07d7f32d..c271641f 100644 --- a/client/src/pages/admin/components/CompetitionManager.tsx +++ b/client/src/pages/admin/components/CompetitionManager.tsx @@ -14,7 +14,7 @@ import TableRow from '@material-ui/core/TableRow' import MoreHorizIcon from '@material-ui/icons/MoreHoriz' import axios from 'axios' import React, { useEffect } from 'react' -import { Link } from 'react-router-dom' +import { Link, useHistory } from 'react-router-dom' import { getCompetitions, setFilterParams } from '../../../actions/competitions' import { useAppDispatch, useAppSelector } from '../../../hooks' import { CompetitionFilterParams } from '../../../interfaces/FilterParams' @@ -43,6 +43,7 @@ const CompetitionManager: React.FC = (props: any) => { const classes = useStyles() const noFilterText = 'Alla' const dispatch = useAppDispatch() + const history = useHistory() const handleClick = (event: React.MouseEvent<HTMLButtonElement>, id: number) => { setAnchorEl(event.currentTarget) setActiveId(id) @@ -174,7 +175,7 @@ const CompetitionManager: React.FC = (props: any) => { onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })} /> <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}> - <MenuItem onClick={handleClose}>Starta</MenuItem> + <MenuItem onClick={() => history.push(`/presenter/id=${activeId}&code=123123`)}>Starta</MenuItem> <MenuItem onClick={handleClose}>Duplicera</MenuItem> <RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem> </Menu> diff --git a/client/src/pages/views/AudienceViewPage.test.tsx b/client/src/pages/views/AudienceViewPage.test.tsx index 1573f3a4..d00d4277 100644 --- a/client/src/pages/views/AudienceViewPage.test.tsx +++ b/client/src/pages/views/AudienceViewPage.test.tsx @@ -1,7 +1,13 @@ import { render } from '@testing-library/react' import React from 'react' +import { Provider } from 'react-redux' +import store from '../../store' import AudienceViewPage from './AudienceViewPage' it('renders audience view page', () => { - render(<AudienceViewPage />) + render( + <Provider store={store}> + <AudienceViewPage /> + </Provider> + ) }) diff --git a/client/src/pages/views/JudgeViewPage.test.tsx b/client/src/pages/views/JudgeViewPage.test.tsx index 5ff1cc5d..537dae4c 100644 --- a/client/src/pages/views/JudgeViewPage.test.tsx +++ b/client/src/pages/views/JudgeViewPage.test.tsx @@ -1,7 +1,43 @@ import { render } from '@testing-library/react' +import mockedAxios from 'axios' import React from 'react' +import { Provider } from 'react-redux' +import { BrowserRouter } from 'react-router-dom' +import store from '../../store' import JudgeViewPage from './JudgeViewPage' it('renders judge view page', () => { - render(<JudgeViewPage />) + const compRes: any = { + data: { + slides: [{ id: 0, title: '' }], + }, + } + const teamsRes: any = { + data: { + items: [ + { + id: 1, + name: 'team1', + }, + { + id: 2, + name: 'team2', + }, + ], + count: 2, + total_count: 3, + }, + } + + ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => { + if (path.endsWith('/teams')) return Promise.resolve(teamsRes) + else return Promise.resolve(compRes) + }) + render( + <BrowserRouter> + <Provider store={store}> + <JudgeViewPage /> + </Provider> + </BrowserRouter> + ) }) diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 457ebe08..293a1f08 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,7 +1,109 @@ -import React from 'react' +import { Divider, List, ListItemText } from '@material-ui/core' +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { getPresentationCompetition, getPresentationTeams, setCurrentSlide } from '../../actions/presentation' +import { useAppDispatch, useAppSelector } from '../../hooks' +import { ViewParams } from '../../interfaces/ViewParams' +import { SlideListItem } from '../presentationEditor/styled' +import JudgeScoreDisplay from './components/JudgeScoreDisplay' +import SlideDisplay from './components/SlideDisplay' +import { + Content, + JudgeAnswersLabel, + JudgeAppBar, + JudgeQuestionsLabel, + JudgeToolbar, + LeftDrawer, + RightDrawer, +} from './styled' + +const leftDrawerWidth = 150 +const rightDrawerWidth = 390 + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + leftDrawerPaper: { + width: leftDrawerWidth, + }, + rightDrawerPaper: { + width: rightDrawerWidth, + }, + toolbar: theme.mixins.toolbar, + }) +) const JudgeViewPage: React.FC = () => { - return <div>Judge</div> + const classes = useStyles() + const { id, code }: ViewParams = useParams() + const dispatch = useAppDispatch() + const [activeSlideIndex, setActiveSlideIndex] = useState<number>(0) + useEffect(() => { + dispatch(getPresentationCompetition(id)) + dispatch(getPresentationTeams(id)) + }, []) + const teams = useAppSelector((state) => state.presentation.teams) + const slides = useAppSelector((state) => state.presentation.competition.slides) + const handleSelectSlide = (index: number) => { + setActiveSlideIndex(index) + dispatch(setCurrentSlide(slides[index])) + } + return ( + <div> + <JudgeAppBar position="fixed"> + <JudgeToolbar> + <JudgeQuestionsLabel variant="h5">Frågor</JudgeQuestionsLabel> + <JudgeAnswersLabel variant="h5">Svar</JudgeAnswersLabel> + </JudgeToolbar> + </JudgeAppBar> + <LeftDrawer + width={leftDrawerWidth} + variant="permanent" + classes={{ + paper: classes.leftDrawerPaper, + }} + anchor="left" + > + <div className={classes.toolbar} /> + <List> + {slides.map((slide, index) => ( + <SlideListItem + selected={index === activeSlideIndex} + onClick={() => handleSelectSlide(index)} + divider + button + key={slide.id} + > + <ListItemText primary={slide.title} /> + </SlideListItem> + ))} + </List> + </LeftDrawer> + <RightDrawer + width={rightDrawerWidth} + variant="permanent" + classes={{ + paper: classes.rightDrawerPaper, + }} + anchor="right" + > + <div className={classes.toolbar} /> + <List> + {teams.map((answer, index) => ( + <div key={answer.name}> + <JudgeScoreDisplay teamIndex={index} /> + <Divider /> + </div> + ))} + </List> + </RightDrawer> + aaa + <Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}> + <div className={classes.toolbar} /> + <SlideDisplay /> + </Content> + </div> + ) } export default JudgeViewPage diff --git a/client/src/pages/views/ParticipantViewPage.test.tsx b/client/src/pages/views/ParticipantViewPage.test.tsx index f7154a9d..85360e4f 100644 --- a/client/src/pages/views/ParticipantViewPage.test.tsx +++ b/client/src/pages/views/ParticipantViewPage.test.tsx @@ -1,7 +1,13 @@ import { render } from '@testing-library/react' import React from 'react' +import { Provider } from 'react-redux' +import store from '../../store' import ParticipantViewPage from './ParticipantViewPage' it('renders participant view page', () => { - render(<ParticipantViewPage />) + render( + <Provider store={store}> + <ParticipantViewPage /> + </Provider> + ) }) diff --git a/client/src/pages/views/ParticipantViewPage.tsx b/client/src/pages/views/ParticipantViewPage.tsx index a5ff1f4f..55c28af0 100644 --- a/client/src/pages/views/ParticipantViewPage.tsx +++ b/client/src/pages/views/ParticipantViewPage.tsx @@ -1,7 +1,8 @@ import React from 'react' +import SlideDisplay from './components/SlideDisplay' const ParticipantViewPage: React.FC = () => { - return <div>Deltagare</div> + return <SlideDisplay /> } export default ParticipantViewPage diff --git a/client/src/pages/views/PresenterViewPage.test.tsx b/client/src/pages/views/PresenterViewPage.test.tsx new file mode 100644 index 00000000..fd7b0a96 --- /dev/null +++ b/client/src/pages/views/PresenterViewPage.test.tsx @@ -0,0 +1,43 @@ +import { render } from '@testing-library/react' +import mockedAxios from 'axios' +import React from 'react' +import { Provider } from 'react-redux' +import { BrowserRouter } from 'react-router-dom' +import store from '../../store' +import PresenterViewPage from './PresenterViewPage' + +it('renders presenter view page', () => { + const compRes: any = { + data: { + slides: [{ id: 0, title: '' }], + }, + } + const teamsRes: any = { + data: { + items: [ + { + id: 1, + name: 'team1', + }, + { + id: 2, + name: 'team2', + }, + ], + count: 2, + total_count: 3, + }, + } + + ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => { + if (path.endsWith('/teams')) return Promise.resolve(teamsRes) + else return Promise.resolve(compRes) + }) + render( + <BrowserRouter> + <Provider store={store}> + <PresenterViewPage /> + </Provider> + </BrowserRouter> + ) +}) diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx new file mode 100644 index 00000000..131bde22 --- /dev/null +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -0,0 +1,74 @@ +import { List, ListItem, Popover } from '@material-ui/core' +import ChevronRightIcon from '@material-ui/icons/ChevronRight' +import React, { useEffect } from 'react' +import { useHistory, useParams } from 'react-router-dom' +import { + getPresentationCompetition, + getPresentationTeams, + setCurrentSlideNext, + setCurrentSlidePrevious, +} from '../../actions/presentation' +import { useAppDispatch, useAppSelector } from '../../hooks' +import { ViewParams } from '../../interfaces/ViewParams' +import SlideDisplay from './components/SlideDisplay' +import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled' + +const PresenterViewPage: React.FC = () => { + const teams = useAppSelector((state) => state.presentation.teams) + const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null) + const { id, code }: ViewParams = useParams() + const history = useHistory() + const dispatch = useAppDispatch() + useEffect(() => { + dispatch(getPresentationCompetition(id)) + dispatch(getPresentationTeams(id)) + }, []) + const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => { + setAnchorEl(event.currentTarget) + } + const handleClose = () => { + setAnchorEl(null) + } + return ( + <PresenterContainer> + <PresenterHeader> + <PresenterButton onClick={handleOpenPopover} color="primary" variant="contained"> + Visa ställning + </PresenterButton> + <PresenterButton onClick={() => history.push('/admin')} variant="contained" color="secondary"> + Avsluta tävling + </PresenterButton> + </PresenterHeader> + <SlideDisplay /> + <PresenterFooter> + <PresenterButton onClick={() => dispatch(setCurrentSlidePrevious())} variant="contained"> + <ChevronRightIcon fontSize="large" /> + </PresenterButton> + <PresenterButton onClick={() => dispatch(setCurrentSlideNext())} variant="contained"> + <ChevronRightIcon fontSize="large" /> + </PresenterButton> + </PresenterFooter> + <Popover + open={Boolean(anchorEl)} + anchorEl={anchorEl} + onClose={handleClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + > + <List> + {teams.map((team) => ( + <ListItem key={team.id}>{team.name} score: 20</ListItem> + ))} + </List> + </Popover> + </PresenterContainer> + ) +} + +export default PresenterViewPage diff --git a/client/src/pages/views/components/JudgeScoreDisplay.tsx b/client/src/pages/views/components/JudgeScoreDisplay.tsx new file mode 100644 index 00000000..ae4d8ab3 --- /dev/null +++ b/client/src/pages/views/components/JudgeScoreDisplay.tsx @@ -0,0 +1,39 @@ +import { Box, Typography } from '@material-ui/core' +import React from 'react' +import { useAppSelector } from '../../../hooks' +import { AnswerContainer, ScoreDisplayContainer, ScoreDisplayHeader, ScoreInput } from './styled' + +type ScoreDisplayProps = { + teamIndex: number +} +const questionMaxScore = 5 + +const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => { + const currentTeam = useAppSelector((state) => state.presentation.teams[teamIndex]) + return ( + <ScoreDisplayContainer> + <ScoreDisplayHeader> + <Typography variant="h5"> + <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> + </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> + </ScoreDisplayContainer> + ) +} + +export default JudgeScoreDisplay diff --git a/client/src/pages/views/components/SlideDisplay.test.tsx b/client/src/pages/views/components/SlideDisplay.test.tsx new file mode 100644 index 00000000..1a661d33 --- /dev/null +++ b/client/src/pages/views/components/SlideDisplay.test.tsx @@ -0,0 +1,13 @@ +import { render } from '@testing-library/react' +import React from 'react' +import { Provider } from 'react-redux' +import store from '../../../store' +import SlideDisplay from './SlideDisplay' + +it('renders slide display', () => { + render( + <Provider store={store}> + <SlideDisplay /> + </Provider> + ) +}) diff --git a/client/src/pages/views/components/SlideDisplay.tsx b/client/src/pages/views/components/SlideDisplay.tsx new file mode 100644 index 00000000..1c10f9cf --- /dev/null +++ b/client/src/pages/views/components/SlideDisplay.tsx @@ -0,0 +1,15 @@ +import { Typography } from '@material-ui/core' +import React from 'react' +import { useAppSelector } from '../../../hooks' +import { SlideContainer } from './styled' + +const SlideDisplay: React.FC = () => { + const currentSlide = useAppSelector((state) => state.presentation.slide) + return ( + <SlideContainer> + <Typography variant="h3">{currentSlide.title}</Typography> + </SlideContainer> + ) +} + +export default SlideDisplay diff --git a/client/src/pages/views/components/styled.tsx b/client/src/pages/views/components/styled.tsx new file mode 100644 index 00000000..0034c39b --- /dev/null +++ b/client/src/pages/views/components/styled.tsx @@ -0,0 +1,27 @@ +import { TextField } from '@material-ui/core' +import styled from 'styled-components' + +export const SlideContainer = styled.div` + display: flex; + justify-content: center; +` + +export const ScoreDisplayContainer = styled.div` + padding-top: 5px; + padding-right: 10px; + padding-left: 10px; +` + +export const ScoreDisplayHeader = styled.div` + display: flex; + justify-content: space-between; +` + +export const ScoreInput = styled(TextField)` + width: 40px; +` + +export const AnswerContainer = styled.div` + display: flex; + flex-wrap: wrap; +` diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx index 0dfbf495..261727b9 100644 --- a/client/src/pages/views/styled.tsx +++ b/client/src/pages/views/styled.tsx @@ -1,5 +1,23 @@ +import { AppBar, Button, Drawer, Toolbar, Typography } from '@material-ui/core' import styled from 'styled-components' +export const JudgeAppBar = styled(AppBar)` + z-index: 9000; +` + +export const JudgeToolbar = styled(Toolbar)` + display: flex; + justify-content: space-between; +` + +export const JudgeQuestionsLabel = styled(Typography)` + margin-left: 15px; +` + +export const JudgeAnswersLabel = styled(Typography)` + margin-right: 160px; +` + export const ViewSelectContainer = styled.div` display: flex; justify-content: center; @@ -15,4 +33,58 @@ export const ViewSelectButtonGroup = styled.div` height: 140px; margin-left: auto; margin-right: auto; -` \ No newline at end of file +` + +export const PresenterHeader = styled.div` + display: flex; + justify-content: space-between; + position: fixed; + width: 100%; +` + +export const PresenterFooter = styled.div` + display: flex; + justify-content: space-between; +` + +export const PresenterButton = styled(Button)` + width: 100px; + height: 100px; + padding-top: 16px; + padding-bottom: 16px; +` + +export const PresenterContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; +` + +interface DrawerProps { + width: number +} + +export const LeftDrawer = styled(Drawer)<DrawerProps>` + flex-shrink: 0; + position: 'relative'; + z-index: -5; + width: ${(props) => (props ? props.width : 150)}; +` + +export const RightDrawer = styled(Drawer)<DrawerProps>` + width: ${(props) => (props ? props.width : 150)}; + flex-shrink: 0; + z-index: 1; +` + +interface ContentProps { + leftDrawerWidth: number + rightDrawerWidth: number +} + +export const Content = styled.div<ContentProps>` + margin-left: ${(props) => (props ? props.leftDrawerWidth : 0)}px; + margin-right: ${(props) => (props ? props.rightDrawerWidth : 0)}px; + width: calc(100% - ${(props) => (props ? props.leftDrawerWidth + props.rightDrawerWidth : 0)}px); +` diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts index 3c9f2b6c..94743ff1 100644 --- a/client/src/reducers/allReducers.ts +++ b/client/src/reducers/allReducers.ts @@ -3,6 +3,7 @@ import { combineReducers } from 'redux' import citiesReducer from './citiesReducer' import competitionsReducer from './competitionsReducer' +import presentationReducer from './presentationReducer' import rolesReducer from './rolesReducer' import searchUserReducer from './searchUserReducer' import uiReducer from './uiReducer' @@ -14,6 +15,7 @@ const allReducers = combineReducers({ UI: uiReducer, competitions: competitionsReducer, cities: citiesReducer, + presentation: presentationReducer, roles: rolesReducer, searchUsers: searchUserReducer, }) diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts new file mode 100644 index 00000000..d71bcafb --- /dev/null +++ b/client/src/reducers/presentationReducer.ts @@ -0,0 +1,72 @@ +import { AnyAction } from 'redux' +import Types from '../actions/types' +import { RichCompetition } from './../interfaces/ApiRichModels' +import { Slide } from './../interfaces/Slide' +import { Team } from './../interfaces/Team' + +interface PresentationState { + competition: RichCompetition + slide: Slide + teams: Team[] +} + +const initialState: PresentationState = { + competition: { + name: '', + id: 0, + city: { + id: 0, + name: '', + }, + slides: [], + year: 0, + teams: [], + }, + slide: { + competition_id: 0, + id: 0, + order: 0, + timer: 0, + title: '', + }, + teams: [], +} + +export default function (state = initialState, action: AnyAction) { + switch (action.type) { + case Types.SET_PRESENTATION_COMPETITION: + return { + ...state, + slide: action.payload.slides[0] as Slide, + competition: action.payload as RichCompetition, + } + case Types.SET_PRESENTATION_TEAMS: + return { + ...state, + teams: action.payload as Team[], + } + case Types.SET_PRESENTATION_SLIDE: + return { + ...state, + slide: action.payload as Slide, + } + case Types.SET_PRESENTATION_SLIDE_PREVIOUS: + if (state.slide.order - 1 >= 0) { + return { + ...state, + slide: state.competition.slides[state.slide.order - 1], + } + } + return state + case Types.SET_PRESENTATION_SLIDE_NEXT: + if (state.slide.order + 1 < state.competition.slides.length) { + return { + ...state, + slide: state.competition.slides[state.slide.order + 1], + } + } + return state + default: + return state + } +} diff --git a/client/src/styled.tsx b/client/src/styled.tsx index 0c17f9cc..58cf0c24 100644 --- a/client/src/styled.tsx +++ b/client/src/styled.tsx @@ -1,6 +1,5 @@ import styled from 'styled-components' export const Wrapper = styled.div` - padding: 10px; height: 100%; ` -- GitLab