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
Commits on Source (33)
Showing
with 162 additions and 126 deletions
......@@ -8285,9 +8285,9 @@
"integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ=="
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
},
"hpack.js": {
"version": "2.1.6",
......@@ -11020,9 +11020,9 @@
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.21",
......@@ -16916,9 +16916,9 @@
}
},
"url-parse": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
......
/*
/**
This file handles actions for the cities redux state
*/
......@@ -6,7 +6,7 @@ import axios from 'axios'
import { AppDispatch } from './../store'
import Types from './types'
// Action creator to get all cities from api and send appropriate actions to reducer
/** Action creator to get all cities from api and send appropriate actions to reducer */
export const getCities = () => async (dispatch: AppDispatch) => {
await axios
.get('/api/misc/cities')
......
......@@ -54,7 +54,7 @@ it('dispatches correct action when logging out from competition', async () => {
return Promise.resolve({ data: {} })
})
const store = mockStore({})
await logoutCompetition()(store.dispatch)
await logoutCompetition('Judge')(store.dispatch)
expect(store.getActions()).toEqual([{ type: Types.SET_COMPETITION_LOGIN_UNAUTHENTICATED }])
})
......@@ -62,7 +62,7 @@ it('dispatches correct action when failing to log in user', async () => {
console.log = jest.fn()
const errorMessage = 'getting teams failed'
;(mockedAxios.post as jest.Mock).mockImplementation(() => {
return Promise.reject({ response: { data: errorMessage } })
return Promise.reject({ response: { data: { message: errorMessage } } })
})
const store = mockStore({})
const history = createMemoryHistory()
......
/*
/**
This file handles actions for the competitionLogin redux state
*/
......@@ -8,7 +8,7 @@ import { AppDispatch, RootState } from '../store'
import { getPresentationCompetition } from './presentation'
import Types from './types'
// Action creator to attempt to login with competition code
/** Action creator to attempt to login with competition code */
export const loginCompetition = (code: string, history: History, redirect: boolean) => async (
dispatch: AppDispatch,
getState: () => RootState
......@@ -18,7 +18,7 @@ export const loginCompetition = (code: string, history: History, redirect: boole
.post('/api/auth/login/code', { code })
.then((res) => {
const token = `Bearer ${res.data.access_token}`
localStorage.setItem('competitionToken', token) //setting token to local storage
localStorage.setItem(`${res.data.view}Token`, token) //setting token to local storage
axios.defaults.headers.common['Authorization'] = token //setting authorize token to header in axios
dispatch({ type: Types.CLEAR_COMPETITION_LOGIN_ERRORS }) // no error
dispatch({
......@@ -35,14 +35,23 @@ export const loginCompetition = (code: string, history: History, redirect: boole
}
})
.catch((err) => {
dispatch({ type: Types.SET_COMPETITION_LOGIN_ERRORS, payload: err && err.response && err.response.data })
let errorMessage = err?.response?.data?.message
if (err?.response?.status === 401) {
errorMessage = 'Inkorrekt kod. Dubbelkolla koden och försök igen.'
}
if (err?.response?.status === 404) {
errorMessage = 'En tävling med den koden existerar inte. Dubbelkolla koden och försök igen.'
}
dispatch({ type: Types.SET_COMPETITION_LOGIN_ERRORS, payload: errorMessage })
console.log(err)
})
}
// Log out from competition and remove jwt token from local storage and axios
export const logoutCompetition = () => async (dispatch: AppDispatch) => {
localStorage.removeItem('competitionToken')
export const logoutCompetition = (role: 'Judge' | 'Operator' | 'Team' | 'Audience') => async (
dispatch: AppDispatch
) => {
localStorage.removeItem(`${role}Token`)
await axios.post('/api/auth/logout').then(() => {
delete axios.defaults.headers.common['Authorization']
dispatch({
......
/*
/**
This file handles actions for the competitions redux state
*/
......@@ -7,7 +7,7 @@ import { CompetitionFilterParams } from '../interfaces/FilterParams'
import { AppDispatch, RootState } from './../store'
import Types from './types'
// Get all competitions using filterParams from current state
/** Get all competitions using filterParams from current state */
export const getCompetitions = () => async (dispatch: AppDispatch, getState: () => RootState) => {
const currentParams: CompetitionFilterParams = getState().competitions.filterParams
// Send params in snake-case for api
......@@ -40,7 +40,12 @@ export const getCompetitions = () => async (dispatch: AppDispatch, getState: ()
})
}
// Dispatch action to set filter params
/** Dispatch action to set filter params */
export const setFilterParams = (params: CompetitionFilterParams) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_COMPETITIONS_FILTER_PARAMS, payload: params })
}
// DIspatch action to set loading
export const setEditorLoading = (loading: boolean) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_EDITOR_LOADING, payload: loading })
}
/*
/**
This file handles actions for the editor redux state
*/
......@@ -6,7 +6,7 @@ import axios from 'axios'
import { AppDispatch, RootState } from './../store'
import Types from './types'
// Save competition in editor state from input id
/** Save competition in editor state from input id */
export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch, getState: () => RootState) => {
await axios
.get(`/api/competitions/${id}`)
......@@ -28,7 +28,7 @@ export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch
})
}
// Set activeSlideId in editor state
/** Set activeSlideId in editor state */
export const setEditorSlideId = (id: number) => (dispatch: AppDispatch) => {
dispatch({
type: Types.SET_EDITOR_SLIDE_ID,
......@@ -36,7 +36,7 @@ export const setEditorSlideId = (id: number) => (dispatch: AppDispatch) => {
})
}
// Set activeViewTypeId in editor state
/** Set activeViewTypeId in editor state */
export const setEditorViewId = (id: number) => (dispatch: AppDispatch) => {
dispatch({
type: Types.SET_EDITOR_VIEW_ID,
......
......@@ -3,12 +3,7 @@ import expect from 'expect' // You can use any testing library
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { Slide } from '../interfaces/ApiModels'
import {
getPresentationCompetition,
setCurrentSlide,
setCurrentSlideNext,
setCurrentSlidePrevious,
} from './presentation'
import { getPresentationCompetition, setCurrentSlideByOrder } from './presentation'
import Types from './types'
const middlewares = [thunk]
......@@ -26,23 +21,16 @@ it('dispatches no actions when failing to get competitions', async () => {
})
it('dispatches correct actions when setting slide', () => {
const testSlide: Slide = { competition_id: 0, id: 5, order: 5, timer: 20, title: '', background_image: undefined }
const expectedActions = [{ type: Types.SET_PRESENTATION_SLIDE, payload: testSlide }]
const store = mockStore({})
setCurrentSlide(testSlide)(store.dispatch)
expect(store.getActions()).toEqual(expectedActions)
})
it('dispatches correct actions when setting previous slide', () => {
const expectedActions = [{ type: Types.SET_PRESENTATION_SLIDE_PREVIOUS }]
const store = mockStore({})
setCurrentSlidePrevious()(store.dispatch)
expect(store.getActions()).toEqual(expectedActions)
})
it('dispatches correct actions when setting next slide', () => {
const expectedActions = [{ type: Types.SET_PRESENTATION_SLIDE_NEXT }]
const store = mockStore({})
setCurrentSlideNext()(store.dispatch)
const testSlide: Slide = {
competition_id: 0,
id: 123123,
order: 43523,
timer: 20,
title: '',
background_image: undefined,
}
const expectedActions = [{ type: Types.SET_PRESENTATION_SLIDE_ID, payload: testSlide.id }]
const store = mockStore({ presentation: { competition: { id: 2, slides: [testSlide] } } })
setCurrentSlideByOrder(testSlide.order)(store.dispatch, store.getState as any)
expect(store.getActions()).toEqual(expectedActions)
})
/*
/**
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, RootState } from './../store'
import { TimerState } from '../interfaces/Timer'
import { AppDispatch, RootState } from './../store'
import Types from './types'
// Save competition in presentation state from input id
/** Save competition in presentation state from input id */
export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch, getState: () => RootState) => {
await axios
.get(`/api/competitions/${id}`)
......@@ -17,8 +16,8 @@ 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)
if (getState().presentation?.activeSlideId === -1 && res.data?.slides[0]) {
setCurrentSlideByOrder(0)(dispatch, getState)
}
})
.catch((err) => {
......@@ -26,36 +25,23 @@ export const getPresentationCompetition = (id: string) => async (dispatch: AppDi
})
}
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 })
}
export const setCurrentSlideByOrder = (order: number) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_PRESENTATION_SLIDE_BY_ORDER, payload: order })
/** Set presentation slide using input order */
export const setCurrentSlideByOrder = (order: number) => (dispatch: AppDispatch, getState: () => RootState) => {
const slideId = getState().presentation.competition.slides.find((slide) => slide.order === order)?.id
dispatch({ type: Types.SET_PRESENTATION_SLIDE_ID, payload: slideId })
}
/** Set code of presentation */
export const setPresentationCode = (code: string) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_PRESENTATION_CODE, payload: code })
}
export const setPresentationTimer = (timer: Timer) => (dispatch: AppDispatch) => {
/** Set timer to input value */
export const setPresentationTimer = (timer: TimerState) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_PRESENTATION_TIMER, payload: timer })
}
export const setPresentationTimerDecrement = () => (dispatch: AppDispatch) => {
dispatch({
type: Types.SET_PRESENTATION_TIMER,
payload: {
enabled: store.getState().presentation.timer.enabled,
value: store.getState().presentation.timer.value - 1,
},
})
/** Set show_scoreboard to input value */
export const setPresentationShowScoreboard = (show_scoreboard: boolean) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_PRESENTATION_SHOW_SCOREBOARD, payload: show_scoreboard })
}
/*
/**
This file handles actions for the roles redux state
*/
......@@ -6,7 +6,7 @@ import axios from 'axios'
import { AppDispatch } from './../store'
import Types from './types'
// Get all roles and dispatch action to save them to roles state
/** Get all roles and dispatch action to save them to roles state */
export const getRoles = () => async (dispatch: AppDispatch) => {
await axios
.get('/api/misc/roles')
......
/*
/**
This file handles actions for the searchUser redux state
*/
......@@ -7,7 +7,7 @@ import { UserFilterParams } from '../interfaces/FilterParams'
import { AppDispatch, RootState } from './../store'
import Types from './types'
// Get all users using current filterParams in searchUser state
/** Get all users using current filterParams in searchUser state */
export const getSearchUsers = () => async (dispatch: AppDispatch, getState: () => RootState) => {
const currentParams: UserFilterParams = getState().searchUsers.filterParams
// Send params in snake-case for api
......@@ -40,6 +40,7 @@ export const getSearchUsers = () => async (dispatch: AppDispatch, getState: () =
})
}
/** Set filterParams in searchUser state */
export const setFilterParams = (params: UserFilterParams) => (dispatch: AppDispatch) => {
dispatch({ type: Types.SET_SEARCH_USERS_FILTER_PARAMS, payload: params })
}
/*
/**
This file handles actions for the statistics redux state
*/
......@@ -6,7 +6,7 @@ import axios from 'axios'
import { AppDispatch } from './../store'
import Types from './types'
// Get all statistics and dispatch actions to save them to statistics state
/** Get all statistics and dispatch actions to save them to statistics state */
export const getStatistics = () => async (dispatch: AppDispatch) => {
await axios
.get('/api/misc/statistics')
......
/*
This file includes all redux action types
/**
This file includes all redux action action types
*/
/** Includes all actions types */
export default {
// User login action types
LOADING_UI: 'LOADING_UI',
LOADING_USER: 'LOADING_USER',
LOADING_COMPETITION_LOGIN: 'LOADING_COMPETITION_LOGIN',
SET_ERRORS: 'SET_ERRORS',
CLEAR_ERRORS: 'CLEAR_ERRORS',
SET_ROLES: 'SET_ROLES',
SET_USER: 'SET_USER',
SET_UNAUTHENTICATED: 'SET_UNAUTHENTICATED',
SET_AUTHENTICATED: 'SET_AUTHENTICATED',
// Search user action types
SET_SEARCH_USERS: 'SET_SEARCH_USERS',
SET_SEARCH_USERS_FILTER_PARAMS: 'SET_SEARCH_USERS_FILTER_PARAMS',
SET_SEARCH_USERS_COUNT: 'SET_SEARCH_USERS_COUNT',
SET_SEARCH_USERS_TOTAL_COUNT: 'SET_SEARCH_USERS_TOTAL_COUNT',
SET_ERRORS: 'SET_ERRORS',
CLEAR_ERRORS: 'CLEAR_ERRORS',
// Competition login action types
LOADING_COMPETITION_LOGIN: 'LOADING_COMPETITION_LOGIN',
SET_COMPETITION_LOGIN_DATA: 'SET_COMPETITION_LOGIN_DATA',
SET_COMPETITION_LOGIN_UNAUTHENTICATED: 'SET_COMPETITION_LOGIN_UNAUTHENTICATED',
SET_COMPETITION_LOGIN_ERRORS: 'SET_COMPETITION_LOGIN_ERRORS',
CLEAR_COMPETITION_LOGIN_ERRORS: 'CLEAR_COMPETITION_LOGIN_ERRORS',
SET_UNAUTHENTICATED: 'SET_UNAUTHENTICATED',
SET_AUTHENTICATED: 'SET_AUTHENTICATED',
// Competitions action types
SET_COMPETITIONS: 'SET_COMPETITIONS',
SET_COMPETITIONS_FILTER_PARAMS: 'SET_COMPETITIONS_FILTER_PARAMS',
SET_COMPETITIONS_TOTAL: 'SET_COMPETITIONS_TOTAL',
SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT',
// Editor action types
SET_EDITOR_COMPETITION: 'SET_EDITOR_COMPETITION',
SET_EDITOR_SLIDE_ID: 'SET_EDITOR_SLIDE_ID',
SET_EDITOR_VIEW_ID: 'SET_EDITOR_VIEW_ID',
SET_EDITOR_LOADING: 'SET_EDITOR_LOADING',
// Presentation action types
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_SLIDE_BY_ORDER: 'SET_PRESENTATION_SLIDE_BY_ORDER',
SET_PRESENTATION_SLIDE_ID: 'SET_PRESENTATION_SLIDE_ID',
SET_PRESENTATION_CODE: 'SET_PRESENTATION_CODE',
SET_PRESENTATION_TIMER: 'SET_PRESENTATION_TIMER',
SET_PRESENTATION_SHOW_SCOREBOARD: 'SET_PRESENTATION_SHOW_SCOREBOARD',
// Cities action types
SET_CITIES: 'SET_CITIES',
SET_CITIES_TOTAL: 'SET_CITIES_TOTAL',
SET_CITIES_COUNT: 'SET_CITIES_COUNT',
// Types action types
SET_TYPES: 'SET_TYPES',
// Media action types
SET_MEDIA_ID: 'SET_MEDIA_ID',
SET_MEDIA_FILENAME: 'SET_MEDIA_ID',
SET_MEDIA_TYPE_ID: 'SET_MEDIA_TYPE_ID',
SET_MEDIA_USER_ID: 'SET_MEDIA_USER_ID',
// Statistics action types
SET_STATISTICS: 'SET_STATISTICS',
}
/*
/**
This file handles actions for the types redux state
*/
......@@ -6,7 +6,7 @@ import axios from 'axios'
import { AppDispatch } from './../store'
import Types from './types'
// Get all types and save them to types state
/** Get all types and save them to types state */
export const getTypes = () => async (dispatch: AppDispatch) => {
await axios
.get('/api/misc/types')
......
/*
/**
This file handles actions for the user redux state
*/
......@@ -8,7 +8,7 @@ import { AppDispatch } from '../store'
import { AccountLoginModel } from './../interfaces/FormModels'
import Types from './types'
// Attempt to log in user, dispatch correct actions and save jwt token to localStorage and axios auth header
/** Attempt to log in user, dispatch correct actions and save jwt token to localStorage and axios auth header */
export const loginUser = (userData: AccountLoginModel, history: History) => async (dispatch: AppDispatch) => {
dispatch({ type: Types.LOADING_UI })
await axios
......@@ -30,7 +30,7 @@ export const loginUser = (userData: AccountLoginModel, history: History) => asyn
})
}
// Get data for user and save to user state
/** Get data for user and save to user state */
export const getUserData = () => async (dispatch: AppDispatch) => {
dispatch({ type: Types.LOADING_USER })
await axios
......@@ -46,7 +46,7 @@ export const getUserData = () => async (dispatch: AppDispatch) => {
})
}
// Log out user and remove jwt token from local storage and axios
/** Log out user and remove jwt token from local storage and axios */
export const logoutUser = () => async (dispatch: AppDispatch) => {
localStorage.removeItem('token')
await axios.post('/api/auth/logout').then(() => {
......
......@@ -137,6 +137,7 @@ describe('Admin page', () => {
const competitionRegionSelectSelector = '[data-testid="competitionRegion"]'
const acceptAddCompetition = '[data-testid="acceptCompetition"]'
const removeCompetitionButtonSelector = '[data-testid="removeCompetitionButton"]'
const acceptRemoveCompetitionSelector = '[data-testid="acceptRemoveCompetition"]'
const testCompetitionName = 'New test competition'
const testCompetitionRegion = 'Linköping'
......@@ -169,6 +170,8 @@ describe('Admin page', () => {
await page.click(testCompetitionSelector)
await page.waitForTimeout(100)
await page.click(removeCompetitionButtonSelector)
await page.waitForTimeout(100)
await page.click(acceptRemoveCompetitionSelector)
await page.waitForTimeout(1000)
competitions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(competitions).not.toContain(testCompetitionName)
......
/** This file includes typed versions of redux hooks */
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
/** Typed version of useDispatch, this should be used every single time instead of useDispatch */
export const useAppDispatch = () => useDispatch<AppDispatch>()
/** Typed version of useSelector, this should be used every single time instead of useSelector */
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
......@@ -36,7 +36,7 @@ export interface Slide {
competition_id: number
id: number
order: number
timer: number
timer: number | null
title: string
background_image?: Media
}
......@@ -54,7 +54,7 @@ export interface Team extends NameID {
export interface Question extends NameID {
slide_id: number
total_score: number
total_score: number | null
type_id: number
correcting_instructions: string
}
......@@ -66,11 +66,17 @@ export interface QuestionAlternative {
question_id: number
}
export interface QuestionAnswer {
export interface QuestionAlternativeAnswer {
id: number
question_id: number
team_id: number
question_alternative_id: number
answer: string
}
export interface QuestionScore {
id: number
team_id: number
question_id: number
score: number
}
......
import { Component, Media, QuestionAlternative, QuestionAnswer, QuestionType } from './ApiModels'
import { Component, Media, QuestionAlternative, QuestionAlternativeAnswer, QuestionScore } from './ApiModels'
export interface RichCompetition {
name: string
......@@ -13,7 +13,7 @@ export interface RichCompetition {
export interface RichSlide {
id: number
order: number
timer: number
timer: number | null
title: string
competition_id: number
background_image?: Media
......@@ -24,7 +24,8 @@ export interface RichSlide {
export interface RichTeam {
id: number
name: string
question_answers: QuestionAnswer[]
question_alternative_answers: QuestionAlternativeAnswer[]
question_scores: QuestionScore[]
competition_id: number
}
......@@ -34,7 +35,6 @@ export interface RichQuestion {
name: string
title: string
total_score: number
question_type: QuestionType
type_id: number
correcting_instructions: string
alternatives: QuestionAlternative[]
......
export interface Timer {
export interface TimerState {
value: number | null
enabled: boolean
value: number
}
......@@ -17,8 +17,10 @@ import LocationCityIcon from '@material-ui/icons/LocationCity'
import PeopleIcon from '@material-ui/icons/People'
import SettingsOverscanIcon from '@material-ui/icons/SettingsOverscan'
import React, { useEffect } from 'react'
import { Link, Route, Switch, useRouteMatch } from 'react-router-dom'
import { Link, Route, Switch, useLocation, useRouteMatch } from 'react-router-dom'
import { getCities } from '../../actions/cities'
import { setEditorLoading } from '../../actions/competitions'
import { setEditorSlideId } from '../../actions/editor'
import { getRoles } from '../../actions/roles'
import { getStatistics } from '../../actions/statistics'
import { getTypes } from '../../actions/typesAction'
......@@ -57,6 +59,7 @@ const useStyles = makeStyles((theme: Theme) =>
const AdminView: React.FC = () => {
const classes = useStyles()
const location = useLocation()
const [openIndex, setOpenIndex] = React.useState(0)
const { path, url } = useRouteMatch()
const currentUser = useAppSelector((state) => state.user.userInfo)
......@@ -73,20 +76,32 @@ const AdminView: React.FC = () => {
dispatch(getRoles())
dispatch(getTypes())
dispatch(getStatistics())
dispatch(setEditorLoading(true))
dispatch(setEditorSlideId(-1))
}, [])
useEffect(() => {
setActiveTabFromUrl()
}, [isAdmin])
const setActiveTabFromUrl = () => {
let activeIndex
if (isAdmin) activeIndex = menuAdminItems.findIndex((menuItem) => location.pathname.endsWith(menuItem.route))
else activeIndex = menuEditorItems.findIndex((menuItem) => location.pathname.endsWith(menuItem.route))
if (activeIndex !== -1) setOpenIndex(activeIndex)
}
const menuAdminItems = [
{ text: 'Startsida', icon: DashboardIcon },
{ text: 'Regioner', icon: LocationCityIcon },
{ text: 'Användare', icon: PeopleIcon },
{ text: 'Tävlingshanterare', icon: SettingsOverscanIcon },
{ text: 'Startsida', icon: DashboardIcon, route: 'dashboard' },
{ text: 'Regioner', icon: LocationCityIcon, route: 'regions' },
{ text: 'Användare', icon: PeopleIcon, route: 'users' },
{ text: 'Tävlingshanterare', icon: SettingsOverscanIcon, route: 'competition-manager' },
]
const menuEditorItems = [
{ text: 'Startsida', icon: DashboardIcon },
{ text: 'Tävlingshanterare', icon: SettingsOverscanIcon },
{ text: 'Startsida', icon: DashboardIcon, route: 'dashboard' },
{ text: 'Tävlingshanterare', icon: SettingsOverscanIcon, route: 'competition-manager' },
]
const renderItems = () => {
const menuItems = isAdmin ? menuAdminItems : menuEditorItems
return menuItems.map((value, index) => (
......@@ -95,7 +110,7 @@ const AdminView: React.FC = () => {
button
component={Link}
key={value.text}
to={`${url}/${value.text.toLowerCase()}`}
to={`${url}/${value.route}`}
selected={index === openIndex}
onClick={() => setOpenIndex(index)}
>
......@@ -147,16 +162,16 @@ const AdminView: React.FC = () => {
<main className={classes.content}>
<div className={classes.toolbar} />
<Switch>
<Route exact path={[path, `${path}/startsida`]}>
<Route exact path={[path, `${path}/dashboard`]}>
<Dashboard />
</Route>
<Route path={`${path}/regioner`}>
<Route path={`${path}/regions`}>
<RegionManager />
</Route>
<Route path={`${path}/användare`}>
<Route path={`${path}/users`}>
<UserManager />
</Route>
<Route path={`${path}/tävlingshanterare`}>
<Route path={`${path}/competition-manager`}>
<CompetitionManager />
</Route>
</Switch>
......