diff --git a/client/src/Main.tsx b/client/src/Main.tsx index b7c74b556e3e072f8da96d328617d7461977b4c9..b999a9775f21bbc999dd1bbd00ca773bdb1ea02c 100644 --- a/client/src/Main.tsx +++ b/client/src/Main.tsx @@ -22,7 +22,7 @@ const Main: React.FC = () => { <Switch> <SecureRoute login exact path="/" component={LoginPage} /> <SecureRoute path="/admin" component={AdminPage} /> - <SecureRoute path="/editor/competition-id=:id" component={PresentationEditorPage} /> + <SecureRoute path="/editor/competition-id=:competitionId" component={PresentationEditorPage} /> <Route exact path="/:code" component={ViewSelectPage} /> <Route exact path="/participant/id=:id&code=:code" component={ParticipantViewPage} /> <SecureRoute exact path="/presenter/id=:id&code=:code" component={PresenterViewPage} /> diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts index 223b802dc1d26f8ac30a1b7e6c2b6ef5495b0ddc..90b728c54aa18a483ed6ab000f718c407b46abfd 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 4de675fb43475f432cfa2f13a93f30c8ef46e782..06fdee0afa3b467c5d0fe21f525b9ca7ec5e6a6a 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 5ce395e260b5ff60021fa9039c59e24fc7aee05a..cd9d22c2055af4b4ddded3260db8b124b9124e6d 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' @@ -83,18 +80,18 @@ const useStyles = makeStyles((theme: Theme) => ) interface CompetitionParams { - id: string + competitionId: string } const PresentationEditorPage: React.FC = () => { const classes = useStyles() - const { id }: CompetitionParams = useParams() + const { competitionId }: CompetitionParams = useParams() const dispatch = useAppDispatch() const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) const competition = useAppSelector((state) => state.editor.competition) const competitionLoading = useAppSelector((state) => state.editor.loading) useEffect(() => { - dispatch(getEditorCompetition(id)) + dispatch(getEditorCompetition(competitionId)) dispatch(getCities()) dispatch(getTypes()) }, []) @@ -104,8 +101,8 @@ const PresentationEditorPage: React.FC = () => { } const createNewSlide = async () => { - await axios.post(`/api/competitions/${id}/slides`, { title: 'new slide' }) - dispatch(getEditorCompetition(id)) + await axios.post(`/api/competitions/${competitionId}/slides`, { title: 'new slide' }) + dispatch(getEditorCompetition(competitionId)) } const [contextState, setContextState] = React.useState<{ @@ -128,32 +125,17 @@ const PresentationEditorPage: React.FC = () => { } const handleRemoveSlide = async () => { - await axios.delete(`/api/competitions/${id}/slides/${contextState.slideId}`) - dispatch(getEditorCompetition(id)) + await axios.delete(`/api/competitions/${competitionId}/slides/${contextState.slideId}`) + dispatch(getEditorCompetition(competitionId)) setContextState(initialState) } const handleDuplicateSlide = async () => { - await axios.post(`/api/competitions/${id}/slides/${contextState.slideId}/copy`) - dispatch(getEditorCompetition(id)) + await axios.post(`/api/competitions/${competitionId}/slides/${contextState.slideId}/copy`) + dispatch(getEditorCompetition(competitionId)) 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', @@ -188,9 +170,6 @@ const PresentationEditorPage: React.FC = () => { <ViewButton variant="contained" color="secondary"> Deltagarvy </ViewButton> - <ViewButton variant="contained" color="secondary"> - Domarvy - </ViewButton> </ViewButtonGroup> </ToolBarContainer> </AppBar> diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx index 53c2ecdb4dd23cc81df0140bd2bede66dfe18b88..7ea2a84f99171fd75555e606abe2681a7f3c0c0d 100644 --- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx +++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx @@ -1,14 +1,7 @@ import { - Button, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, Divider, FormControl, InputLabel, - List, ListItem, ListItemText, MenuItem, @@ -16,84 +9,44 @@ import { TextField, Typography, } from '@material-ui/core' -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' -import CloseIcon from '@material-ui/icons/Close' +import { Center, ImportedImage, SettingsList, PanelContainer, FirstItem, AddButton } from './styled' import axios from 'axios' import React, { useState } from 'react' import { useParams } from 'react-router-dom' import { getEditorCompetition } from '../../../actions/editor' import { useAppDispatch, useAppSelector } from '../../../hooks' import { City } from '../../../interfaces/ApiModels' - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - textInputContainer: { - '& > *': { - margin: theme.spacing(1), - width: '100%', - background: 'white', - }, - }, - textInput: { - margin: theme.spacing(2), - width: '87%', - background: 'white', - }, - textCenter: { - textAlign: 'center', - }, - center: { - display: 'flex', - justifyContent: 'center', - background: 'white', - }, - importedImage: { - width: 70, - height: 50, - background: 'white', - }, - dropDown: { - margin: theme.spacing(2), - width: '87%', - background: 'white', - }, - addButtons: { - padding: 5, - }, - panelList: { - padding: 0, - }, - }) -) +import Teams from './Teams' interface CompetitionParams { - id: string + competitionId: string } const CompetitionSettings: React.FC = () => { - const classes = useStyles() - const { id }: CompetitionParams = useParams() + const { competitionId }: CompetitionParams = useParams() const dispatch = useAppDispatch() const competition = useAppSelector((state) => state.editor.competition) + const cities = useAppSelector((state) => state.cities.cities) + const updateCompetitionName = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { await axios - .put(`/api/competitions/${id}`, { name: event.target.value }) + .put(`/api/competitions/${competitionId}`, { name: event.target.value }) .then(() => { - dispatch(getEditorCompetition(id)) + dispatch(getEditorCompetition(competitionId)) }) .catch(console.log) } - const cities = useAppSelector((state) => state.cities.cities) const updateCompetitionCity = async (city: City) => { await axios - .put(`/api/competitions/${id}`, { city_id: city.id }) + .put(`/api/competitions/${competitionId}`, { city_id: city.id }) .then(() => { - dispatch(getEditorCompetition(id)) + dispatch(getEditorCompetition(competitionId)) }) .catch(console.log) } + /* Finds the right city object from a city name */ const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { cities.forEach((city) => { if (event.target.value === city.name) { @@ -102,109 +55,55 @@ const CompetitionSettings: React.FC = () => { }) } - const removeTeam = async (tid: number) => { - await axios - .delete(`/api/competitions/${id}/teams/${tid}`) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - const addTeam = async () => { - setAddTeamOpen(false) - await axios - .post(`/api/competitions/${id}/teams`, { name: selectedTeamName }) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - // For "add team" dialog - const [addTeamOpen, setAddTeamOpen] = useState(false) - const openAddTeam = () => { - setAddTeamOpen(true) - } - const closeAddTeam = () => { - setAddTeamOpen(false) - } - let selectedTeamName = '' - const updateSelectedTeamName = (event: React.ChangeEvent<{ value: string }>) => { - selectedTeamName = event.target.value - } - return ( - <div className={classes.textInputContainer}> - <form noValidate autoComplete="off"> - <TextField - className={classes.textInput} - id="outlined-basic" - label={'Tävlingsnamn'} - defaultValue={competition.name} - onChange={updateCompetitionName} - variant="outlined" - /> + <PanelContainer> + <SettingsList> + <FirstItem> + <ListItem> + <TextField + id="outlined-basic" + label={'Tävlingsnamn'} + defaultValue={competition.name} + onChange={updateCompetitionName} + variant="outlined" + fullWidth={true} + /> + </ListItem> + </FirstItem> <Divider /> - <FormControl variant="outlined" className={classes.dropDown}> - <InputLabel>Region</InputLabel> - <Select - value={cities.find((city) => city.id === competition.city_id)?.name || ''} - label="Region" - onChange={handleChange} - > - {cities.map((city) => ( - <MenuItem value={city.name} key={city.name}> - <Button>{city.name}</Button> - </MenuItem> - ))} - </Select> - </FormControl> - </form> - <List className={classes.panelList}> <ListItem> - <ListItemText className={classes.textCenter} primary="Lag" /> + <FormControl fullWidth variant="outlined"> + <InputLabel>Region</InputLabel> + <Select + value={cities.find((city) => city.id === competition.city_id)?.name || ''} + label="Region" + onChange={handleChange} + > + {cities.map((city) => ( + <MenuItem value={city.name} key={city.name}> + <Typography variant="button">{city.name}</Typography> + </MenuItem> + ))} + </Select> + </FormControl> </ListItem> - {competition.teams && - competition.teams.map((team) => ( - <div key={team.id}> - <ListItem divider button> - <ListItemText primary={team.name} /> - <CloseIcon onClick={() => removeTeam(team.id)} /> - </ListItem> - </div> - ))} + </SettingsList> - <ListItem className={classes.center} button onClick={openAddTeam}> - <Typography className={classes.addButtons} variant="button"> - Lägg till lag - </Typography> - </ListItem> - <Dialog open={addTeamOpen} onClose={closeAddTeam}> - <DialogTitle className={classes.center}>Lägg till lag</DialogTitle> - <DialogContent> - <DialogContentText>Skriv namnet på laget och klicka sedan på bekräfta.</DialogContentText> - <TextField autoFocus margin="dense" label="Lagnamn" fullWidth onChange={updateSelectedTeamName} /> - </DialogContent> - <DialogActions> - <Button onClick={closeAddTeam} color="secondary"> - Avbryt - </Button> - <Button onClick={addTeam} color="primary"> - Bekräfta - </Button> - </DialogActions> - </Dialog> - </List> + <Teams competitionId={competitionId} /> - <ListItem button> - <img - id="temp source, todo: add image source to elements of pictureList" - src="https://i1.wp.com/stickoutmedia.se/wp-content/uploads/2021/01/placeholder-3.png?ssl=1" - className={classes.importedImage} - /> - <ListItemText className={classes.textCenter}>Välj bakgrundsbild ...</ListItemText> - </ListItem> - </div> + <SettingsList> + <ListItem button> + <ImportedImage + id="temp source, todo: add image source to elements of pictureList" + src="https://i1.wp.com/stickoutmedia.se/wp-content/uploads/2021/01/placeholder-3.png?ssl=1" + /> + <Center> + <ListItemText>Välj bakgrundsbild ...</ListItemText> + </Center> + </ListItem> + </SettingsList> + </PanelContainer> ) } diff --git a/client/src/pages/presentationEditor/components/SlideDisplay.tsx b/client/src/pages/presentationEditor/components/SlideDisplay.tsx index 6ddb38ef12d2957ffc85defe1bcf725557a79e65..f134d0a8ff4e8fef092ae4b92ef8e6b07fa38a71 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/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index 2f403f41ddf4ff66ad11301c213522c24bc81334..278887c1cb8b841224704ceb1f2e1bb9fa5c7b6a 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -7,18 +7,18 @@ import { useAppSelector } from '../../../hooks' import Instructions from './slideSettingsComponents/Instructions' import MultipleChoiceAlternatives from './slideSettingsComponents/MultipleChoiceAlternatives' import SlideType from './slideSettingsComponents/SlideType' -import { Center, ImportedImage, SettingsList, SlidePanel } from './styled' +import { Center, ImportedImage, SettingsList, PanelContainer } from './styled' import Timer from './slideSettingsComponents/Timer' import Images from './slideSettingsComponents/Images' import Texts from './slideSettingsComponents/Texts' import QuestionSettings from './slideSettingsComponents/QuestionSettings' interface CompetitionParams { - id: string + competitionId: string } const SlideSettings: React.FC = () => { - const { id }: CompetitionParams = useParams() + const { competitionId }: CompetitionParams = useParams() const activeSlide = useAppSelector((state) => // Gets the slide with id=activeSlideId from the database. @@ -26,26 +26,30 @@ const SlideSettings: React.FC = () => { ) return ( - <SlidePanel> + <PanelContainer> <SettingsList> - {activeSlide && <SlideType activeSlide={activeSlide} competitionId={id} />} + {activeSlide && <SlideType activeSlide={activeSlide} competitionId={competitionId} />} <Divider /> - {activeSlide && <Timer activeSlide={activeSlide} competitionId={id} />} + {activeSlide && <Timer activeSlide={activeSlide} competitionId={competitionId} />} </SettingsList> - {activeSlide?.questions[0] && <QuestionSettings activeSlide={activeSlide} competitionId={id} />} + {activeSlide?.questions[0] && <QuestionSettings activeSlide={activeSlide} competitionId={competitionId} />} { // Choose answer alternatives depending on the slide type } - {activeSlide?.questions[0]?.type_id === 1 && <Instructions activeSlide={activeSlide} competitionId={id} />} - {activeSlide?.questions[0]?.type_id === 2 && <Instructions activeSlide={activeSlide} competitionId={id} />} + {activeSlide?.questions[0]?.type_id === 1 && ( + <Instructions activeSlide={activeSlide} competitionId={competitionId} /> + )} + {activeSlide?.questions[0]?.type_id === 2 && ( + <Instructions activeSlide={activeSlide} competitionId={competitionId} /> + )} {activeSlide?.questions[0]?.type_id === 3 && ( - <MultipleChoiceAlternatives activeSlide={activeSlide} competitionId={id} /> + <MultipleChoiceAlternatives activeSlide={activeSlide} competitionId={competitionId} /> )} - {activeSlide && <Texts activeSlide={activeSlide} competitionId={id} />} + {activeSlide && <Texts activeSlide={activeSlide} competitionId={competitionId} />} - {activeSlide && <Images activeSlide={activeSlide} competitionId={id} />} + {activeSlide && <Images activeSlide={activeSlide} competitionId={competitionId} />} <SettingsList> <ListItem button> @@ -58,7 +62,7 @@ const SlideSettings: React.FC = () => { </Center> </ListItem> </SettingsList> - </SlidePanel> + </PanelContainer> ) } diff --git a/client/src/pages/presentationEditor/components/Teams.tsx b/client/src/pages/presentationEditor/components/Teams.tsx new file mode 100644 index 0000000000000000000000000000000000000000..564dbc847638f1d16e6d09b7ae96019e02856a07 --- /dev/null +++ b/client/src/pages/presentationEditor/components/Teams.tsx @@ -0,0 +1,100 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + ListItem, + ListItemText, + TextField, +} from '@material-ui/core' +import axios from 'axios' +import React, { useState } from 'react' +import { getEditorCompetition } from '../../../actions/editor' +import { useAppDispatch, useAppSelector } from '../../../hooks' +import { Center, Clickable } from './styled' +import { AddButton, SettingsList } from './styled' +import CloseIcon from '@material-ui/icons/Close' + +type TeamsProps = { + competitionId: string +} + +const Teams = ({ competitionId }: TeamsProps) => { + const dispatch = useAppDispatch() + const competition = useAppSelector((state) => state.editor.competition) + const addTeam = async () => { + setAddTeamOpen(false) + await axios + .post(`/api/competitions/${competitionId}/teams`, { name: selectedTeamName }) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + // For "add team" dialog + const [addTeamOpen, setAddTeamOpen] = useState(false) + const openAddTeam = () => { + setAddTeamOpen(true) + } + const closeAddTeam = () => { + setAddTeamOpen(false) + } + let selectedTeamName = '' + const updateSelectedTeamName = (event: React.ChangeEvent<{ value: string }>) => { + selectedTeamName = event.target.value + } + + const removeTeam = async (tid: number) => { + await axios + .delete(`/api/competitions/${competitionId}/teams/${tid}`) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + + return ( + <SettingsList> + <ListItem divider> + <Center> + <ListItemText primary="Lag" /> + </Center> + </ListItem> + {competition.teams && + competition.teams.map((team) => ( + <div key={team.id}> + <ListItem divider> + <ListItemText primary={team.name} /> + <Clickable> + <CloseIcon onClick={() => removeTeam(team.id)} /> + </Clickable> + </ListItem> + </div> + ))} + <ListItem button onClick={openAddTeam}> + <Center> + <AddButton variant="button">Lägg till lag</AddButton> + </Center> + </ListItem> + <Dialog open={addTeamOpen} onClose={closeAddTeam}> + <DialogTitle>Lägg till lag</DialogTitle> + <DialogContent> + <DialogContentText>Skriv namnet på laget och klicka sedan på bekräfta.</DialogContentText> + <TextField autoFocus margin="dense" label="Lagnamn" fullWidth onChange={updateSelectedTeamName} /> + </DialogContent> + <DialogActions> + <Button onClick={closeAddTeam} color="secondary"> + Avbryt + </Button> + <Button onClick={addTeam} color="primary"> + Bekräfta + </Button> + </DialogActions> + </Dialog> + </SettingsList> + ) +} + +export default Teams diff --git a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx index 8ef64b2724aba0fa6a669250dbe185e0c7dffcee..b03696f37987a83347f57fb81c6b25febcb84824 100644 --- a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx +++ b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx @@ -12,12 +12,11 @@ type ImageComponentProps = { } interface CompetitionParams { - id: string + competitionId: string } const TextComponentEdit = ({ component }: ImageComponentProps) => { - const { id }: CompetitionParams = useParams() - const competitionId = useAppSelector((state) => state.editor.competition.id) + const { competitionId }: CompetitionParams = useParams() const [content, setContent] = useState('') const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined) const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) @@ -40,14 +39,14 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { await axios.put(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${component.id}`, { text: newText, }) - dispatch(getEditorCompetition(id)) + dispatch(getEditorCompetition(competitionId)) }, 250) ) } const handleDeleteText = async (componentId: number) => { - await axios.delete(`/api/competitions/${id}/slides/${activeSlideId}/components/${componentId}`) - dispatch(getEditorCompetition(id)) + await axios.delete(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${componentId}`) + dispatch(getEditorCompetition(competitionId)) } return ( diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx index d8cc164cc8de656a7f732a92d35f9eb45572d453..fa087dccbb39c77a0a2e433d5a6a6c8b4d201fd1 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx @@ -2,14 +2,13 @@ */ import { ListItem, ListItemText, Typography } from '@material-ui/core' import CloseIcon from '@material-ui/icons/Close' -import React, { useState } from 'react' -import { useDispatch } from 'react-redux' +import React from 'react' import { Center, HiddenInput, SettingsList, AddImageButton, ImportedImage, AddButton } from '../styled' import axios from 'axios' import { getEditorCompetition } from '../../../../actions/editor' import { RichSlide } from '../../../../interfaces/ApiRichModels' import { ImageComponent, Media } from '../../../../interfaces/ApiModels' -import { useAppSelector } from '../../../../hooks' +import { useAppDispatch, useAppSelector } from '../../../../hooks' type ImagesProps = { activeSlide: RichSlide @@ -17,7 +16,7 @@ type ImagesProps = { } const Images = ({ activeSlide, competitionId }: ImagesProps) => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const uploadFile = async (formData: FormData) => { // Uploads the file to the server and creates a Media object in database. @@ -107,7 +106,7 @@ const Images = ({ activeSlide, competitionId }: ImagesProps) => { </div> ))} - <ListItem button> + <ListItem button style={{ padding: 0 }}> <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} /> <AddImageButton htmlFor="contained-button-file"> <AddButton variant="button">Lägg till bild</AddButton> diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx index 97b27c9d4fee4a39eaf0f4de28296621c35f8990..858dd75e65dc554cc6e1c2083f46873a925335a1 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx @@ -1,9 +1,8 @@ import { ListItem, ListItemText, TextField, withStyles } from '@material-ui/core' import axios from 'axios' import React from 'react' -import { useDispatch } from 'react-redux' import { getEditorCompetition } from '../../../../actions/editor' -import { useAppDispatch, useAppSelector } from '../../../../hooks' +import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' import { Center, SettingsList } from '../styled' @@ -13,12 +12,10 @@ type InstructionsProps = { } const Instructions = ({ activeSlide, competitionId }: InstructionsProps) => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined) - const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) const updateInstructionsText = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { - /* TODO: Implement instructions field in question and add put API if (timerHandle) { clearTimeout(timerHandle) setTimerHandle(undefined) @@ -29,10 +26,11 @@ const Instructions = ({ activeSlide, competitionId }: InstructionsProps) => { console.log('Content was updated on server. id: ', activeSlide.questions[0].id) if (activeSlide && activeSlide.questions[0]) { await axios + // TODO: Implement instructions field in question and add put API .put( `/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`, { - name: event.target.value, + instructions: event.target.value, } ) .then(() => { @@ -42,7 +40,6 @@ const Instructions = ({ activeSlide, competitionId }: InstructionsProps) => { } }, 250) ) - */ } return ( diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx index 62afda68577c5153d7e253a7b8989dfcb3e6b28a..2bc10b3588133245d54767dbbc7d8381e4134b59 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx @@ -1,7 +1,6 @@ import { ListItem, ListItemText, TextField } from '@material-ui/core' import axios from 'axios' import React, { useEffect, useState } from 'react' -import { useDispatch } from 'react-redux' import { getEditorCompetition } from '../../../../actions/editor' import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' @@ -13,7 +12,7 @@ type QuestionSettingsProps = { } const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const updateQuestion = async ( updateTitle: boolean, @@ -57,16 +56,14 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) </Center> </ListItem> <ListItem divider> - <Center> - <TextField - id="outlined-basic" - defaultValue={''} - label="Frågans titel" - onChange={(event) => updateQuestion(true, event)} - variant="outlined" - fullWidth={true} - /> - </Center> + <TextField + id="outlined-basic" + defaultValue={''} + label="Frågans titel" + onChange={(event) => updateQuestion(true, event)} + variant="outlined" + fullWidth={true} + /> </ListItem> <ListItem> <Center> diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx index 3194f7c923db059113f9b7d2ad3c109387780029..bc251b91e092c05457db1b64bb8bbe566d76dd55 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx @@ -5,6 +5,7 @@ import { DialogContent, DialogContentText, DialogTitle, + FormControl, InputLabel, ListItem, MenuItem, @@ -16,7 +17,7 @@ import React, { useState } from 'react' import { getEditorCompetition } from '../../../../actions/editor' import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' -import { Center, FormControlDropdown, SlideTypeInputLabel } from '../styled' +import { Center, FirstItem } from '../styled' type SlideTypeProps = { activeSlide: RichSlide @@ -85,52 +86,55 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { } } return ( - <ListItem> - <FormControlDropdown variant="outlined"> - <SlideTypeInputLabel>Sidtyp</SlideTypeInputLabel> - <Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp"> - <MenuItem value={0}> - <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> - Informationssida - </Typography> - </MenuItem> - <MenuItem value={1}> - <Typography variant="button" onClick={() => openSlideTypeDialog(1)}> - Skriftlig fråga - </Typography> - </MenuItem> - <MenuItem value={2}> - <Typography variant="button" onClick={() => openSlideTypeDialog(2)}> - Praktisk fråga - </Typography> - </MenuItem> - <MenuItem value={3}> - <Typography variant="button" onClick={() => openSlideTypeDialog(3)}> - Flervalsfråga - </Typography> - </MenuItem> - </Select> - </FormControlDropdown> - <Dialog open={slideTypeDialog} onClose={closeSlideTypeDialog}> - <Center> - <DialogTitle color="secondary">Varning!</DialogTitle> - </Center> - <DialogContent> - <DialogContentText> - Om du ändrar sidtypen kommer eventuella frågeinställningar gå förlorade. Det inkluderar: frågans namn, poäng - och svarsalternativ.{' '} - </DialogContentText> - </DialogContent> - <DialogActions> - <Button onClick={closeSlideTypeDialog} color="secondary"> - Avbryt - </Button> - <Button onClick={updateSlideType} color="primary"> - Bekräfta - </Button> - </DialogActions> - </Dialog> - </ListItem> + <FirstItem> + <ListItem> + <FormControl fullWidth variant="outlined"> + <InputLabel>Sidtyp</InputLabel> + <Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp"> + <MenuItem value={0}> + <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> + Informationssida + </Typography> + </MenuItem> + <MenuItem value={1}> + <Typography variant="button" onClick={() => openSlideTypeDialog(1)}> + Skriftlig fråga + </Typography> + </MenuItem> + <MenuItem value={2}> + <Typography variant="button" onClick={() => openSlideTypeDialog(2)}> + Praktisk fråga + </Typography> + </MenuItem> + <MenuItem value={3}> + <Typography variant="button" onClick={() => openSlideTypeDialog(3)}> + Flervalsfråga + </Typography> + </MenuItem> + </Select> + </FormControl> + + <Dialog open={slideTypeDialog} onClose={closeSlideTypeDialog}> + <Center> + <DialogTitle color="secondary">Varning!</DialogTitle> + </Center> + <DialogContent> + <DialogContentText> + Om du ändrar sidtypen kommer eventuella frågeinställningar gå förlorade. Det inkluderar: frågans namn, + poäng och svarsalternativ.{' '} + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={closeSlideTypeDialog} color="secondary"> + Avbryt + </Button> + <Button onClick={updateSlideType} color="primary"> + Bekräfta + </Button> + </DialogActions> + </Dialog> + </ListItem> + </FirstItem> ) } diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx index dd0b40845e3252ccb2fed874d30a03b19498f915..39cf09af3ca31b73f30c258570e28365117a4282 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx @@ -1,13 +1,12 @@ import { Divider, ListItem, ListItemText, Typography } from '@material-ui/core' import React from 'react' -import { useAppSelector } from '../../../../hooks' +import { useAppDispatch, useAppSelector } from '../../../../hooks' import { TextComponent } from '../../../../interfaces/ApiModels' import { RichSlide } from '../../../../interfaces/ApiRichModels' import { AddButton, Center, SettingsList, TextCard } from '../styled' import TextComponentEdit from '../TextComponentEdit' import axios from 'axios' import { getEditorCompetition } from '../../../../actions/editor' -import { useDispatch } from 'react-redux' type TextsProps = { activeSlide: RichSlide @@ -22,7 +21,7 @@ const Texts = ({ activeSlide, competitionId }: TextsProps) => { ?.components.filter((component) => component.type_id === 1) as TextComponent[] ) - const dispatch = useDispatch() + const dispatch = useAppDispatch() const handleAddText = async () => { if (activeSlide) { await axios.post(`/api/competitions/${competitionId}/slides/${activeSlide?.id}/components`, { diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index af2108d63ef518819ea5aca0e925c528cba48e2b..7255c2bbc8a57475662191cd57428986a105348b 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -54,25 +54,15 @@ export const ToolbarPadding = styled.div` padding-top: 55px; ` -export const FormControlDropdown = styled(FormControl)` - width: 100%; - margin-top: 10px; -` - -export const SlideTypeInputLabel = styled(InputLabel)` +export const FirstItem = styled.div` width: 100%; + padding-top: 10px; ` export const AlternativeTextField = styled(TextField)` width: 87%; ` -export const NoPadding = styled.div` - padding: 0; - height: 100%; - width: 100%; -` - export const Center = styled.div` display: flex; justify-content: center; @@ -81,16 +71,13 @@ export const Center = styled.div` width: 100%; ` -export const SlidePanel = styled.div` +export const PanelContainer = styled.div` padding: 10px; width: 100%; ` export const AddButton = styled(Typography)` - padding-left: 8px; - padding-right: 8px; - padding-top: 7px; - padding-bottom: 7px; + padding: 7px 8px 7px 8px; ` export const ImportedImage = styled.img` @@ -103,7 +90,7 @@ export const Clickable = styled.div` ` export const AddImageButton = styled.label` - padding: 0; + padding: 8px 13px 8px 13px; cursor: 'pointer'; display: flex; justify-content: center; diff --git a/client/src/pages/views/JudgeViewPage.test.tsx b/client/src/pages/views/JudgeViewPage.test.tsx index 29de4d12e22fcaac0f6af990bc504077a977b470..2e15f0904705726d364672b87af478ddaf922fbb 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 0d16daaccec0f8b496ae10f62e45354e9b39f829..5a806d237f190d6483bc8265ec0eac61c58e9151 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,7 +1,6 @@ -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 { useParams } from 'react-router-dom' import { getPresentationCompetition, setCurrentSlide, setPresentationCode } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' @@ -19,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 @@ -49,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])) @@ -87,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> @@ -102,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}> @@ -111,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 6308e39b0576a81967068eae4365d43e6ca9befc..2745e12970f3d5a20d2057adbc76ecc0ce2e6d6f 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 0000000000000000000000000000000000000000..3cc803194a9754bea8a9b23b3f2b073d6547c05c --- /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 cb95576f9e908d170eee33d78b7f92a285e61be1..a41f7912469256a6e946522790f4f8203f8da60f 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 b522b20d3548b4f11864373d5a91088424c45a58..fef186f792dafe8ee259faa693cf6707975d06ba 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 17d09581501876a9f44e389c031e2f64f04fda82..8f63892ade7928842c12d4dc73df988a505f4cea 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 ee08e0685125f095274474430008f4ad4eaa357e..202d9406853628a32c77b9e42e1bcc502299889d 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 b657f5a463b339a4fbfc8309f79157ed78320f9e..b0c4791e9ab1f0ef05c931caa1fc4e9fe8268917 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 0000000000000000000000000000000000000000..ba1eafcb8052469dd8b627b95d2eee518bfd5fe8 --- /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 + } +}