Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • tddd96-grupp11/teknikattan-scoring-system
1 result
Show changes
Showing
with 811 additions and 53 deletions
import {
Button,
createStyles,
FormControl,
InputLabel,
makeStyles,
MenuItem,
Popover,
TextField,
Theme,
} from '@material-ui/core'
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
import { Alert, AlertTitle } from '@material-ui/lab'
import axios from 'axios'
import { Formik, FormikHelpers } from 'formik'
import React, { useEffect } from 'react'
import * as Yup from 'yup'
import { getSearchUsers } from '../../../actions/searchUser'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { City, Role, User } from '../../../interfaces/ApiModels'
import { EditUserModel, FormModel } from '../../../interfaces/FormModels'
import { AddContent, AddForm } from '../styledComp'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
textField: {
marginBottom: '10px',
},
editButton: {
marginTop: '20px',
paddingTop: '10px',
paddingBottom: '10px',
},
deleteButton: {
marginTop: '40px',
},
})
)
type formType = FormModel<EditUserModel>
const noRoleSelected = 'Admin'
const noCitySelected = 'Linköping'
const userSchema: Yup.SchemaOf<formType> = Yup.object({
model: Yup.object()
.shape({
name: Yup.string(),
email: Yup.string().email().required('Email krävs'),
password: Yup.string()
.min(6, 'Lösenord måste vara minst 6 tecken.')
.matches(/[a-zA-Z]/, 'Lösenord får enbart innehålla a-z, A-Z.'),
role: Yup.string().required('Roll krävs').notOneOf([noCitySelected], 'Välj en roll'),
city: Yup.string().required('Stad krävs').notOneOf([noRoleSelected], 'Välj en stad'),
})
.required(),
error: Yup.string().optional(),
})
type UserIdProps = {
user: User
}
const EditUser = ({ user }: UserIdProps) => {
const dispatch = useAppDispatch()
const classes = useStyles()
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
const [selectedRole, setSelectedRole] = React.useState<Role | undefined>()
const roles = useAppSelector((state) => state.roles.roles)
const [selectedCity, setSelectedCity] = React.useState<City | undefined>()
const cities = useAppSelector((state) => state.cities.cities)
const startRole = roles.find((x) => x.id == user.role_id)
const startCity = cities.find((x) => x.id == user.city_id)
const open = Boolean(anchorEl)
const id = open ? 'simple-popover' : undefined
useEffect(() => {
setSelectedCity(startCity)
setSelectedRole(startRole)
}, [])
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const handleDeleteUsers = async () => {
if (confirm('Are u sure?')) {
await axios
.delete(`/auth/delete/${user.id}`)
.then(() => {
setAnchorEl(null)
dispatch(getSearchUsers())
})
.catch(({ response }) => {
console.warn(response.data)
})
}
}
const handleSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
const params = {
email: values.model.email,
name: values.model.name,
city_id: selectedCity?.id as number,
role_id: selectedRole?.id as number,
}
const req: any = {}
if (params.email !== user.email) {
req['email'] = params.email
}
if (params.name !== user.name) {
req['name'] = params.name
}
if (params.city_id !== user.city_id) {
req['city_id'] = params.city_id
}
if (params.role_id !== user.role_id) {
req['role_id'] = params.role_id
}
await axios
.put('/users/' + user.id, req)
.then((res) => {
setAnchorEl(null)
dispatch(getSearchUsers())
})
.catch(({ response }) => {
console.warn(response.data)
if (response.data && response.data.message)
actions.setFieldError('error', response.data && response.data.message)
else actions.setFieldError('error', 'Something went wrong, please try again')
})
.finally(() => {
actions.setSubmitting(false)
})
}
const userInitialValues: formType = {
model: {
email: user.email as string,
name: user.name as string,
city: startCity?.name as string,
role: startRole?.name as string,
},
}
return (
<div>
<Button onClick={handleClick}>
<MoreHorizIcon />
</Button>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<AddContent>
<Formik initialValues={userInitialValues} validationSchema={userSchema} onSubmit={handleSubmit}>
{(formik) => (
<AddForm onSubmit={formik.handleSubmit}>
<TextField
className={classes.textField}
label="Email"
name="model.email"
helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
error={Boolean(formik.touched.model?.email && formik.errors.model?.email)}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
defaultValue={user.email}
margin="normal"
/>
<TextField
className={classes.textField}
label="Namn"
name="model.name"
helperText={formik.touched.model?.name ? formik.errors.model?.name : ''}
error={Boolean(formik.touched.model?.name && formik.errors.model?.name)}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
defaultValue={user.name}
margin="normal"
/>
<FormControl>
<InputLabel shrink id="demo-customized-select-native">
Region
</InputLabel>
<TextField
className={classes.textField}
select
name="model.city"
id="standard-select-currency"
value={selectedCity ? selectedCity.name : noCitySelected}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={Boolean(formik.errors.model?.city && formik.touched.model?.city)}
helperText={formik.touched.model?.city && formik.errors.model?.city}
margin="normal"
>
{cities &&
cities.map((city) => (
<MenuItem key={city.name} value={city.name} onClick={() => setSelectedCity(city)}>
{city.name}
</MenuItem>
))}
</TextField>
</FormControl>
<FormControl>
<InputLabel shrink id="demo-customized-select-native">
Roll
</InputLabel>
<TextField
className={classes.textField}
select
name="model.role"
id="standard-select-currency"
value={selectedRole ? selectedRole.name : noRoleSelected}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={Boolean(formik.errors.model?.role && formik.touched.model?.role)}
helperText={formik.touched.model?.role && formik.errors.model?.role}
margin="normal"
>
{roles &&
roles.map((role) => (
<MenuItem key={role.name} value={role.name} onClick={() => setSelectedRole(role)}>
{role.name}
</MenuItem>
))}
</TextField>
</FormControl>
<FormControl>
<InputLabel shrink id="demo-customized-select-native">
Password
</InputLabel>
<TextField
className={classes.textField}
label="Lösenord"
name="model.password"
helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
error={Boolean(formik.touched.model?.password && formik.errors.model?.password)}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
defaultValue=""
margin="normal"
type="password"
/>
</FormControl>
<Button
className={classes.editButton}
type="submit"
fullWidth
variant="contained"
color="primary"
disabled={!formik.isValid || !formik.values.model?.role || !formik.values.model?.city}
>
Ändra
</Button>
<Button
onClick={handleDeleteUsers}
className={classes.deleteButton}
fullWidth
variant="contained"
color="secondary"
>
Ta bort
</Button>
{formik.errors.error && (
<Alert severity="error">
<AlertTitle>Error</AlertTitle>
{formik.errors.error}
</Alert>
)}
</AddForm>
)}
</Formik>
</AddContent>
</Popover>
</div>
)
}
export default EditUser
import { Button, Menu, TablePagination, TextField, Typography } from '@material-ui/core'
import { TablePagination, TextField, Typography } from '@material-ui/core'
import FormControl from '@material-ui/core/FormControl'
import InputLabel from '@material-ui/core/InputLabel'
import MenuItem from '@material-ui/core/MenuItem'
......@@ -11,16 +11,14 @@ import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
import axios from 'axios'
import React, { useEffect } from 'react'
import { getCities } from '../../../actions/cities'
import { getRoles } from '../../../actions/roles'
import { getSearchUsers, setFilterParams } from '../../../actions/searchUser'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { UserFilterParams } from '../../../interfaces/UserData'
import { User } from '../../../interfaces/ApiModels'
import { UserFilterParams } from '../../../interfaces/FilterParams'
import { FilterContainer, TopBar } from '../styledComp'
import AddUser from './AddUser'
import { FilterContainer, RemoveCompetition, TopBar } from './styled'
import EditUser from './EditUser'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
......@@ -34,8 +32,10 @@ const useStyles = makeStyles((theme: Theme) =>
)
const UserManager: React.FC = (props: any) => {
const [editAnchorEl, setEditAnchorEl] = React.useState<null | HTMLElement>(null)
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const [activeId, setActiveId] = React.useState<number | undefined>(undefined)
const [selectedUser, setSelectedUser] = React.useState<User | undefined>(undefined)
const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined)
const users = useAppSelector((state) => state.searchUsers.users)
const filterParams = useAppSelector((state) => state.searchUsers.filterParams)
......@@ -45,22 +45,36 @@ const UserManager: React.FC = (props: any) => {
const classes = useStyles()
const noFilterText = 'Alla'
const dispatch = useAppDispatch()
const handleClick = (event: React.MouseEvent<HTMLButtonElement>, id: number) => {
const open = Boolean(anchorEl)
const id = open ? 'simple-popover' : undefined
const handleClick = (event: React.MouseEvent<HTMLButtonElement>, user: User) => {
setAnchorEl(event.currentTarget)
setActiveId(id)
setSelectedUser(user)
}
const handleClose = () => {
setAnchorEl(null)
setActiveId(undefined)
setSelectedUser(undefined)
console.log('close')
}
const handleEditClose = () => {
setEditAnchorEl(null)
console.log('edit close')
}
useEffect(() => {
dispatch(getCities())
dispatch(getRoles())
dispatch(getSearchUsers())
}, [])
useEffect(() => {
console.log('asd')
setEditAnchorEl(null)
setAnchorEl(null)
}, [users])
const onSearchChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
if (timerHandle) {
clearTimeout(timerHandle)
......@@ -71,25 +85,16 @@ const UserManager: React.FC = (props: any) => {
dispatch(setFilterParams({ ...filterParams, email: event.target.value }))
}
const handleDeleteUsers = async () => {
if (activeId) {
await axios
.delete(`/auth/delete/${activeId}`)
.then(() => {
setAnchorEl(null)
dispatch(getSearchUsers())
})
.catch(({ response }) => {
console.warn(response.data)
})
}
}
const handleFilterChange = (newParams: UserFilterParams) => {
dispatch(setFilterParams(newParams))
dispatch(getSearchUsers())
}
const handleStateClick = () => {
setEditAnchorEl(anchorEl)
setAnchorEl(null)
}
return (
<div>
<TopBar>
......@@ -171,9 +176,7 @@ const UserManager: React.FC = (props: any) => {
<TableCell>{cities.find((city) => city.id === row.city_id)?.name || ''}</TableCell>
<TableCell>{roles.find((role) => role.id === row.role_id)?.name || ''}</TableCell>
<TableCell align="right">
<Button onClick={(event) => handleClick(event, row.id)}>
<MoreHorizIcon />
</Button>
<EditUser user={row}></EditUser>
</TableCell>
</TableRow>
))}
......@@ -189,11 +192,6 @@ const UserManager: React.FC = (props: any) => {
page={filterParams.page}
onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
/>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem>Redigera</MenuItem>
<MenuItem>Byt lösenord</MenuItem>
<RemoveCompetition onClick={handleDeleteUsers}>Ta bort</RemoveCompetition>
</Menu>
</div>
)
}
......
......@@ -6,7 +6,7 @@ import { useHistory } from 'react-router-dom'
import * as Yup from 'yup'
import { loginUser } from '../../../actions/user'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { AccountLoginModel } from '../../../interfaces/models'
import { AccountLoginModel } from '../../../interfaces/FormModels'
import { CenteredCircularProgress, LoginForm } from './styled'
interface AccountLoginFormModel {
......
......@@ -4,7 +4,7 @@ import axios from 'axios'
import { Formik, FormikHelpers } from 'formik'
import React from 'react'
import * as Yup from 'yup'
import { CompetitionLoginModel } from '../../../interfaces/models'
import { CompetitionLoginModel } from '../../../interfaces/FormModels'
import { LoginForm } from './styled'
interface CompetitionLoginFormModel {
......
......@@ -7,8 +7,10 @@ import ListItemText from '@material-ui/core/ListItemText'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import React from 'react'
import { useParams } from 'react-router-dom'
import { Content } from '../views/styled'
import SettingsPanel from './components/SettingsPanel'
import { SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
import SlideEditor from './components/SlideEditor'
import { PresentationEditorContainer, SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
function createSlide(name: string) {
return { name }
......@@ -28,9 +30,6 @@ const rightDrawerWidth = 390
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
display: 'flex',
},
appBar: {
width: `calc(100% - ${rightDrawerWidth}px)`,
marginLeft: leftDrawerWidth,
......@@ -71,7 +70,7 @@ const PresentationEditorPage: React.FC = () => {
const classes = useStyles()
const params: CompetitionParams = useParams()
return (
<div className={classes.root}>
<PresentationEditorContainer>
<CssBaseline />
<AppBar position="fixed" className={classes.appBar}>
<ToolBarContainer>
......@@ -120,7 +119,11 @@ const PresentationEditorPage: React.FC = () => {
>
<SettingsPanel></SettingsPanel>
</Drawer>
</div>
<Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
<SlideEditor />
</Content>
</PresentationEditorContainer>
)
}
......
import { Checkbox } from '@material-ui/core'
import React, { useState } from 'react'
import { Rnd } from 'react-rnd'
import { Component } from '../../../interfaces/ApiModels'
import { Position } from '../../../interfaces/Components'
type CheckboxComponentProps = {
component: Component
}
const CheckboxComponent = ({ component }: CheckboxComponentProps) => {
const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y })
return (
<Rnd
bounds="parent"
onDragStop={(e, d) => {
setCurrentPos({ x: d.x, y: d.y })
}}
position={{ x: currentPos.x, y: currentPos.y }}
>
<Checkbox
disableRipple
style={{
transform: 'scale(3)',
}}
/>
</Rnd>
)
}
export default CheckboxComponent
......@@ -48,9 +48,9 @@ const CompetitionSettings: React.FC = () => {
return (
<div className={classes.textInputContainer}>
<form noValidate autoComplete="off">
<TextField className={classes.textInput} id="outlined-basic" label="Tävlingsnamn" variant="outlined" />
<TextField className={classes.textInput} label="Tävlingsnamn" variant="outlined" />
<Divider />
<TextField className={classes.textInput} id="outlined-basic" label="Stad" variant="outlined" />
<TextField className={classes.textInput} label="Stad" variant="outlined" />
</form>
<List>
......
import React, { useState } from 'react'
import { Rnd } from 'react-rnd'
import { ImageComponent } from '../../../interfaces/ApiModels'
import { Position, Size } from '../../../interfaces/Components'
type ImageComponentProps = {
component: ImageComponent
}
const ImageComponentDisplay = ({ component }: ImageComponentProps) => {
const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y })
const [currentSize, setCurrentSize] = useState<Size>({ w: component.w, h: component.h })
return (
<Rnd
minWidth={50}
minHeight={50}
bounds="parent"
onDragStop={(e, d) => {
setCurrentPos({ x: d.x, y: d.y })
}}
size={{ width: currentSize.w, height: currentSize.h }}
position={{ x: currentPos.x, y: currentPos.y }}
onResize={(e, direction, ref, delta, position) => {
setCurrentSize({
w: ref.offsetWidth,
h: ref.offsetHeight,
})
setCurrentPos(position)
}}
onResizeStop={() => {
console.log('Skicka data till server')
}}
>
<img
src="https://365psd.com/images/previews/c61/cartoon-cow-52394.png"
height={currentSize.h}
width={currentSize.w}
draggable={false}
/>
</Rnd>
)
}
export default ImageComponentDisplay
import React from 'react'
import { ComponentTypes } from '../../../enum/ComponentTypes'
import CheckboxComponent from './CheckboxComponent'
import ImageComponentDisplay from './ImageComponentDisplay'
import { SlideEditorContainer } from './styled'
import TextComponentDisplay from './TextComponentDisplay'
const SlideEditor: React.FC = () => {
// const components = useAppSelector(state => state.editor.slide.components) // get the current RichSlide
const components: any[] = [
{ id: 0, x: 15, y: 150, w: 200, h: 300, type: ComponentTypes.Checkbox },
{ id: 1, x: 15, y: 250, w: 200, h: 300, type: ComponentTypes.Checkbox },
{ id: 2, x: 15, y: 350, w: 200, h: 300, type: ComponentTypes.Checkbox },
{ id: 3, x: 300, y: 500, w: 100, h: 300, type: ComponentTypes.Text, text: 'text component', font: 'arial' },
{ id: 4, x: 250, y: 100, w: 200, h: 300, type: ComponentTypes.Image },
{ id: 5, x: 350, y: 100, w: 200, h: 300, type: ComponentTypes.Image },
]
return (
<SlideEditorContainer>
{components.map((component) => {
switch (component.type) {
case ComponentTypes.Checkbox:
return <CheckboxComponent key={component.id} component={component} />
break
case ComponentTypes.Text:
return <TextComponentDisplay key={component.id} component={component} />
break
case ComponentTypes.Image:
return <ImageComponentDisplay key={component.id} component={component} />
break
default:
break
}
})}
</SlideEditorContainer>
)
}
export default SlideEditor
......@@ -137,7 +137,7 @@ const SlideSettings: React.FC = () => {
{answers.map((answer) => (
<div key={answer.id}>
<ListItem divider>
<TextField className={classes.textInput} id="outlined-basic" label={answer.name} variant="outlined" />
<TextField className={classes.textInput} label={answer.name} variant="outlined" />
<Checkbox color="default" />
<CloseIcon className={classes.clickableIcon} onClick={() => handleCloseAnswerClick(answer.id)} />
</ListItem>
......@@ -155,7 +155,7 @@ const SlideSettings: React.FC = () => {
{texts.map((text) => (
<div key={text.id}>
<ListItem divider>
<TextField className={classes.textInput} id="outlined-basic" label={text.name} variant="outlined" />
<TextField className={classes.textInput} label={text.name} variant="outlined" />
<MoreHorizOutlinedIcon className={classes.clickableIcon} />
<CloseIcon className={classes.clickableIcon} onClick={() => handleCloseTextClick(text.id)} />
</ListItem>
......
import { Editor } from '@tinymce/tinymce-react'
import React, { useState } from 'react'
import { Rnd } from 'react-rnd'
import { TextComponent } from '../../../interfaces/ApiModels'
import { Position, Size } from '../../../interfaces/Components'
type ImageComponentProps = {
component: TextComponent
}
const TextComponentDisplay = ({ component }: ImageComponentProps) => {
const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y })
const [currentSize, setCurrentSize] = useState<Size>({ w: component.w, h: component.h })
const handleEditorChange = (e: any) => {
console.log('Content was updated:', e.target.getContent())
//TODO: axios.post
}
return (
<Rnd
minWidth={50}
minHeight={50}
bounds="parent"
onDragStop={(e, d) => {
setCurrentPos({ x: d.x, y: d.y })
}}
size={{ width: currentSize.w, height: currentSize.h }}
position={{ x: currentPos.x, y: currentPos.y }}
onResize={(e, direction, ref, delta, position) => {
setCurrentSize({
w: ref.offsetWidth,
h: ref.offsetHeight,
})
setCurrentPos(position)
}}
onResizeStop={() => {
console.log('skickar till server')
}}
>
<div style={{ height: '100%', width: '100%' }}>
<Editor
initialValue={component.text}
init={{
body_class: 'mceBlackBody',
height: '100%',
menubar: false,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste code help wordcount',
],
toolbar:
'undo redo | formatselect | fontselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | removeformat | help',
}}
onChange={handleEditorChange}
/>
</div>
</Rnd>
)
}
export default TextComponentDisplay
......@@ -5,3 +5,11 @@ export const SettingsTab = styled(Tab)`
height: 64px;
min-width: 195px;
`
export const SlideEditorContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
`
......@@ -19,3 +19,7 @@ export const SlideListItem = styled(ListItem)`
text-align: center;
height: 60px;
`
export const PresentationEditorContainer = styled.div`
height: 100%;
`
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>
)
})
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>
)
})
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
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>
)
})
import React from 'react'
import SlideDisplay from './components/SlideDisplay'
const ParticipantViewPage: React.FC = () => {
return <div>Deltagare</div>
return <SlideDisplay />
}
export default ParticipantViewPage
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>
)
})
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