diff --git a/client/src/actions/statistics.ts b/client/src/actions/statistics.ts new file mode 100644 index 0000000000000000000000000000000000000000..a32dce3553ba56ed47410b1499aeca57f7490f75 --- /dev/null +++ b/client/src/actions/statistics.ts @@ -0,0 +1,15 @@ +import axios from 'axios' +import { AppDispatch } from './../store' +import Types from './types' + +export const getStatistics = () => async (dispatch: AppDispatch) => { + await axios + .get('/api/misc/statistics') + .then((res) => { + dispatch({ + type: Types.SET_STATISTICS, + payload: res.data, + }) + }) + .catch((err) => console.log(err)) +} diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index a417f6afc797d8a69bd81d2b259283e98ed364e3..189bf16ca5f3baa63bdf8d0ad5d5ae0fa6805d10 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -29,4 +29,5 @@ export default { SET_CITIES_TOTAL: 'SET_CITIES_TOTAL', SET_CITIES_COUNT: 'SET_CITIES_COUNT', SET_TYPES: 'SET_TYPES', + SET_STATISTICS: 'SET_STATISTICS', } diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx index 89d4707ca16a96c2b7619ba036ef50780d2497b3..4823f442023e4c7366cf4ba1df3b7aecb82c6153 100644 --- a/client/src/pages/admin/AdminPage.tsx +++ b/client/src/pages/admin/AdminPage.tsx @@ -21,6 +21,7 @@ import React, { useEffect } from 'react' import { Link, Route, Switch, useRouteMatch } from 'react-router-dom' import { getCities } from '../../actions/cities' import { getRoles } from '../../actions/roles' +import { getStatistics } from '../../actions/statistics' import { getTypes } from '../../actions/typesAction' import { logoutUser } from '../../actions/user' import { useAppDispatch, useAppSelector } from '../../hooks' @@ -72,7 +73,8 @@ const AdminView: React.FC = () => { dispatch(getCities()) dispatch(getRoles()) dispatch(getTypes()) - axios.get('/api/competitions/2/codes').then(console.log).catch(console.log) + axios.get('/api/competitions/1/codes').then(console.log).catch(console.log) + dispatch(getStatistics()) }, []) const menuAdminItems = [ diff --git a/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx b/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx index 87d8272ae035ce56d509dd99380bdb1ed17426a6..eb667ebd690f6013c184ac13e2038ce1a7726de9 100644 --- a/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx +++ b/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx @@ -1,24 +1,19 @@ import { Box, Typography } from '@material-ui/core' -import React, { useEffect } from 'react' -import { getCompetitions } from '../../../../actions/competitions' -import { useAppDispatch, useAppSelector } from '../../../../hooks' +import React from 'react' +import { useAppSelector } from '../../../../hooks' const NumberOfCompetitions: React.FC = () => { - const competitions = useAppSelector((state) => state.competitions.competitions) - const dispatch = useAppDispatch() + const competitions = useAppSelector((state) => state.statistics.competitions) const handleCount = () => { - if (competitions.length >= 1000000) { - ;<div>{competitions.length / 1000000 + 'M'}</div> - } else if (competitions.length >= 1000) { - ;<div>{competitions.length / 1000 + 'K'}</div> + if (competitions >= 1000000) { + ;<div>{competitions / 1000000 + 'M'}</div> + } else if (competitions >= 1000) { + ;<div>{competitions / 1000 + 'K'}</div> } - return <div>{competitions.length}</div> + return <div>{competitions}</div> } - useEffect(() => { - dispatch(getCompetitions()) - }, []) return ( <div> <Box width="100%" height="100%"> diff --git a/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx b/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx index 360b3663b0e45e9a24018f855ede3023c95bf39a..f3195f4c18969ed2ad1e4b185b2125ac2c15f782 100644 --- a/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx +++ b/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx @@ -4,7 +4,7 @@ import { getCities } from '../../../../actions/cities' import { useAppDispatch, useAppSelector } from '../../../../hooks' const NumberOfRegions: React.FC = () => { - const regions = useAppSelector((state) => state.cities.total) + const regions = useAppSelector((state) => state.statistics.regions) const dispatch = useAppDispatch() const handleCount = () => { diff --git a/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx b/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx index af0f97677565aa264a61269c274cf890eec774d8..0e75cf1b1069814801fadc010f30ac393dfe3b25 100644 --- a/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx +++ b/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx @@ -4,7 +4,7 @@ import { getSearchUsers } from '../../../../actions/searchUser' import { useAppDispatch, useAppSelector } from '../../../../hooks' const NumberOfUsers: React.FC = () => { - const usersTotal = useAppSelector((state) => state.searchUsers.total) + const usersTotal = useAppSelector((state) => state.statistics.users) const dispatch = useAppDispatch() const handleCount = () => { diff --git a/client/src/pages/views/ParticipantViewPage.tsx b/client/src/pages/views/ParticipantViewPage.tsx index 55c28af06cff72a24862acaa03e8066d8a8f4a0c..7447f2a6e4ca74d4c14a664591d3d4e57edb0faf 100644 --- a/client/src/pages/views/ParticipantViewPage.tsx +++ b/client/src/pages/views/ParticipantViewPage.tsx @@ -1,7 +1,12 @@ -import React from 'react' +import React, { useEffect } from 'react' import SlideDisplay from './components/SlideDisplay' +import { useHistory } from 'react-router-dom' const ParticipantViewPage: React.FC = () => { + const history = useHistory() + useEffect(() => { + history.push('participant') + }, []) return <SlideDisplay /> } diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx index 40aa252d6770ad4a2359f2038c3da7e09b47c14b..23e5928a5c223f4bda26fd3c5a35e9489ac4b0b7 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -46,8 +46,11 @@ import { /** * Presentation is an active competition */ +type PresenterViewPageProps = { + competitionId: number +} -const PresenterViewPage: React.FC = () => { +const PresenterViewPage = ({ competitionId }: PresenterViewPageProps) => { // for dialog alert const [openAlert, setOpen] = React.useState(false) const theme = useTheme() @@ -55,19 +58,19 @@ 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 { code }: ViewParams = useParams() const presentation = useAppSelector((state) => state.presentation) const history = useHistory() const dispatch = useAppDispatch() useEffect(() => { - dispatch(getPresentationCompetition(id)) - dispatch(getPresentationTeams(id)) + dispatch(getPresentationCompetition(competitionId.toString())) + dispatch(getPresentationTeams(competitionId.toString())) dispatch(setPresentationCode(code)) socket_connect() socketSetSlide // Behövs denna? setTimeout(startCompetition, 500) // Ghetto, wait for everything to load - // console.log(id) + // console.log(competitionId) }, []) const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => { @@ -82,7 +85,7 @@ const PresenterViewPage: React.FC = () => { const startCompetition = () => { socketStartPresentation() console.log('started competition for') - console.log(id) + console.log(competitionId) } const handleVerifyExit = () => { diff --git a/client/src/pages/views/ViewSelectPage.tsx b/client/src/pages/views/ViewSelectPage.tsx index 96837ebe5236c54cd89e3b1998dd8d2bb43c6979..fa3a58b8fddeeb1440f3679bbdb310afa12a283b 100644 --- a/client/src/pages/views/ViewSelectPage.tsx +++ b/client/src/pages/views/ViewSelectPage.tsx @@ -9,6 +9,7 @@ import axios from 'axios' import PresenterViewPage from './PresenterViewPage' import JudgeViewPage from './JudgeViewPage' import AudienceViewPage from './AudienceViewPage' +import { useAppSelector } from '../../hooks' interface ViewSelectParams { code: string @@ -18,21 +19,26 @@ const ViewSelectPage: React.FC = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(false) const [viewType, setViewType] = useState(undefined) + const [competitionId, setCompetitionId] = useState<number | undefined>(undefined) const { code }: ViewSelectParams = useParams() const renderView = (viewTypeId: number | undefined) => { //Renders the correct view depending on view type - switch (viewTypeId) { - case 1: - return <ParticipantViewPage /> - case 2: - return <JudgeViewPage /> - case 3: - return <AudienceViewPage /> - case 4: - return <PresenterViewPage /> - default: - return <Typography>Inkorrekt vy</Typography> + // TODO: use state instead of hard-coded values + //const viewTypes = useAppSelector(state => state.types.viewTypes) + if (competitionId) { + switch (viewTypeId) { + case 1: + return <ParticipantViewPage /> + case 2: + return <JudgeViewPage /> + case 3: + return <AudienceViewPage /> + case 4: + return <PresenterViewPage competitionId={competitionId} /> + default: + return <Typography>Inkorrekt vy</Typography> + } } } @@ -42,12 +48,14 @@ const ViewSelectPage: React.FC = () => { .then((response) => { setLoading(false) setViewType(response.data[0].view_type_id) + setCompetitionId(response.data[0].pointer) }) .catch(() => { setLoading(false) setError(true) }) }, []) + return ( <ViewSelectContainer> <ViewSelectButtonGroup> diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts index 2aee46972cb6f0d33f2b128aeb6d19a9d77f106f..c23384c2a2a21dfba86c60550154985a5c879782 100644 --- a/client/src/reducers/allReducers.ts +++ b/client/src/reducers/allReducers.ts @@ -7,6 +7,7 @@ import editorReducer from './editorReducer' import presentationReducer from './presentationReducer' import rolesReducer from './rolesReducer' import searchUserReducer from './searchUserReducer' +import statisticsReducer from './statisticsReducer' import typesReducer from './typesReducer' import uiReducer from './uiReducer' import userReducer from './userReducer' @@ -22,5 +23,6 @@ const allReducers = combineReducers({ roles: rolesReducer, searchUsers: searchUserReducer, types: typesReducer, + statistics: statisticsReducer, }) export default allReducers diff --git a/client/src/reducers/statisticsReducer.ts b/client/src/reducers/statisticsReducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..78a06e1157f6428c10ea17073afbdf46cf8609e5 --- /dev/null +++ b/client/src/reducers/statisticsReducer.ts @@ -0,0 +1,24 @@ +import { AnyAction } from 'redux' +import Types from '../actions/types' + +interface StatisticsState { + users: number + competitions: number + regions: number +} + +const initialState: StatisticsState = { + users: 0, + competitions: 0, + regions: 0, +} + +export default function (state = initialState, action: AnyAction) { + switch (action.type) { + case Types.SET_STATISTICS: + state = action.payload as StatisticsState + return state + default: + return state + } +} diff --git a/server/app/__init__.py b/server/app/__init__.py index 9215563a9538bf1d33aecabc2b1abbc32779950a..2add65446ea6ca429a204a1564813d9fe19e25d0 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -34,7 +34,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"): return redirect(rp[:-1]) @app.after_request - def set_core(response): + def set_corse(response): header = response.headers header["Access-Control-Allow-Origin"] = "*" return response diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py index d56a2f8ba0163a9187aa451eac45a416890052c6..ce7b4e7d3bb8a550aa52cbc0b2492c1118999876 100644 --- a/server/app/apis/alternatives.py +++ b/server/app/apis/alternatives.py @@ -13,7 +13,7 @@ schema = QuestionAlternativeDTO.schema list_schema = QuestionAlternativeDTO.list_schema -@api.route("/") +@api.route("") @api.param("competition_id, slide_id, question_id") class QuestionAlternativeList(Resource): @check_jwt(editor=True) diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py index 308b1001de9fa325b7ff01c4e518f7db9e3c10eb..0ef3003931d52d304d66cff1cedb36b0c8cfa777 100644 --- a/server/app/apis/answers.py +++ b/server/app/apis/answers.py @@ -13,7 +13,7 @@ schema = QuestionAnswerDTO.schema list_schema = QuestionAnswerDTO.list_schema -@api.route("/") +@api.route("") @api.param("competition_id, team_id") class QuestionAnswerList(Resource): @check_jwt(editor=True) diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py index c761420fabab70254973a7d352ce6f9b7833ba25..2a2eea5c55c6e46cf516ff35dd79ed3d96f58679 100644 --- a/server/app/apis/codes.py +++ b/server/app/apis/codes.py @@ -12,7 +12,7 @@ schema = CodeDTO.schema list_schema = CodeDTO.list_schema -@api.route("/") +@api.route("") @api.param("competition_id") class CodesList(Resource): @check_jwt(editor=True) diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index 386d4051c5e32f852a9a165810e3303812e58440..4a6bf79d6bf17d132e3a5fed6a403b21762bbc13 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -15,7 +15,7 @@ rich_schema = CompetitionDTO.rich_schema list_schema = CompetitionDTO.list_schema -@api.route("/") +@api.route("") class CompetitionsList(Resource): @check_jwt(editor=True) def post(self): diff --git a/server/app/apis/components.py b/server/app/apis/components.py index c895f02322d16c8f603b97c7c60644db4ea36233..23d250256120f4bf600b10cd010b5de9aa67e0dc 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -35,7 +35,7 @@ class ComponentByID(Resource): return {}, codes.NO_CONTENT -@api.route("/") +@api.route("") @api.param("competition_id, slide_id") class ComponentList(Resource): @check_jwt(editor=True) diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py index 5364a5e44d4e309c327bf91121bc1f8d95e40afa..8b3bd5e57e5b877fdaed85f3e6a372b4984d8ef8 100644 --- a/server/app/apis/misc.py +++ b/server/app/apis/misc.py @@ -1,7 +1,8 @@ import app.database.controller as dbc from app.apis import check_jwt, item_response, list_response +from app.core import http_codes from app.core.dto import MiscDTO -from app.database.models import City, ComponentType, MediaType, QuestionType, Role, ViewType +from app.database.models import City, Competition, ComponentType, MediaType, QuestionType, Role, User, ViewType from flask_jwt_extended import jwt_required from flask_restx import Resource, reqparse @@ -72,3 +73,13 @@ class Cities(Resource): dbc.delete.default(item) items = dbc.get.all(City) return list_response(city_schema.dump(items)) + + +@api.route("/statistics") +class Statistics(Resource): + @check_jwt(editor=True) + def get(self): + user_count = User.query.count() + competition_count = Competition.query.count() + region_count = City.query.count() + return {"users": user_count, "competitions": competition_count, "regions": region_count}, http_codes.OK diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index ef8cf89c463519b8d60d78ec37bc440c494b8018..9ca4a5f229488e03931ec544dd3ccf43406e5b9e 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -12,7 +12,7 @@ schema = SlideDTO.schema list_schema = SlideDTO.list_schema -@api.route("/") +@api.route("") @api.param("competition_id") class SlidesList(Resource): @check_jwt(editor=True) diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 6596244c9085b850106e567a6974ced5428413aa..514748ae27c7ff685d2b1099d1bf710610928860 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -12,7 +12,7 @@ schema = TeamDTO.schema list_schema = TeamDTO.list_schema -@api.route("/") +@api.route("") @api.param("competition_id") class TeamsList(Resource): @check_jwt(editor=True) diff --git a/server/app/apis/users.py b/server/app/apis/users.py index 767f01cb441553e717aea58a202a03f0b50ece64..1bae000bc6821ff7c7ac3dde82474bd351c2fe4d 100644 --- a/server/app/apis/users.py +++ b/server/app/apis/users.py @@ -27,7 +27,7 @@ def edit_user(item_user, args): return dbc.edit.default(item_user, **args) -@api.route("/") +@api.route("") class UsersList(Resource): @check_jwt(editor=True) def get(self): diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index bf5d8c3b3959641d153a4ed619a3733ec70f9388..16b76d6db3cbaadab58b0b302616eead9b4e603b 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -39,13 +39,10 @@ def code_by_code(code): def code_list(competition_id): """ Gets a list of all code objects associated with a the provided competition. """ - - team_view_id = 1 - join_filters = (Code.view_type_id == team_view_id) & (Team.id == Code.pointer) - filters = ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id)) | ( - (Code.view_type_id == team_view_id) & (competition_id == Team.competition_id) - ) - return Code.query.join(Team, join_filters, isouter=True).filter(filters).all() + # team_view_id = 1 + join_competition = Competition.id == Code.pointer + filters = Competition.id == competition_id + return Code.query.join(Competition, join_competition).filter(filters).all() ### Users ###