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 (11)
Showing
with 223 additions and 89 deletions
{
//editor
"editor.formatOnSave": true,
"editor.formatOnPaste": false,
"editor.tabCompletion": "on",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
},
//python
"python.venvPath": "${workspaceFolder}\\server",
"python.analysis.extraPaths": [
"server"
],
"python.terminal.activateEnvironment": true,
"python.formatting.provider": "black",
"python.formatting.blackPath": "server\\env\\Scripts\\black.exe",
"python.formatting.blackArgs": [
"--line-length",
"119"
],
//eslint
"eslint.workingDirectories": [
"./client"
],
"eslint.options": {
"configFile": "./.eslintrc"
},
"prettier.configPath": "./client/.prettierrc",
//git
"git.ignoreLimitWarning": true,
//language specific
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.exclude": {
"**/__pycache__": true,
"**/.pytest_cache": true,
"**/env/Include": true,
"**/env/Lib": true
},
"files.watcherExclude": {
"**/env/Lib/**": true
},
"search.exclude": {
"**/env": true
},
}
\ No newline at end of file
//editor
"editor.formatOnSave": true,
"editor.formatOnPaste": false,
"editor.tabCompletion": "on",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": false
},
//python
"python.venvPath": "${workspaceFolder}\\server",
"python.analysis.extraPaths": ["server"],
"python.terminal.activateEnvironment": true,
"python.formatting.provider": "black",
"python.formatting.blackPath": "server\\env\\Scripts\\black.exe",
"python.formatting.blackArgs": ["--line-length", "119"],
//eslint
"eslint.workingDirectories": ["./client"],
"eslint.options": {
"configFile": "./.eslintrc"
},
"prettier.configPath": "./client/.prettierrc",
//git
"git.ignoreLimitWarning": true,
//language specific
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.exclude": {
"**/__pycache__": true,
"**/.pytest_cache": true,
"**/env/Include": true,
"**/env/Lib": true
},
"files.watcherExclude": {
"**/env/Lib/**": true
},
"search.exclude": {
"**/env": true
}
}
......@@ -96,5 +96,5 @@
"html"
]
},
"proxy": "http://localhost:5000/api/"
"proxy": "http://localhost:5000/"
}
client/public/t8-circled.png

42.4 KiB

......@@ -4,7 +4,7 @@ import Types from './types'
export const getCities = () => async (dispatch: AppDispatch) => {
await axios
.get('/misc/cities')
.get('/api/misc/cities')
.then((res) => {
dispatch({
type: Types.SET_CITIES,
......
......@@ -15,7 +15,7 @@ export const getCompetitions = () => async (dispatch: AppDispatch, getState: ()
year: currentParams.year,
}
await axios
.get('/competitions/search', { params })
.get('/api/competitions/search', { params })
.then((res) => {
dispatch({
type: Types.SET_COMPETITIONS,
......
import axios from 'axios'
import { AppDispatch } from './../store'
import { AppDispatch, RootState } from './../store'
import Types from './types'
export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch) => {
export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch, getState: () => RootState) => {
await axios
.get(`/competitions/${id}`)
.get(`/api/competitions/${id}`)
.then((res) => {
dispatch({
type: Types.SET_EDITOR_COMPETITION,
payload: res.data,
})
if (getState().editor.activeSlideId === -1 && res.data.slides[0]) {
setEditorSlideId(res.data.slides[0].id)(dispatch)
}
})
.catch((err) => {
console.log(err)
......
......@@ -6,7 +6,7 @@ import Types from './types'
export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch) => {
await axios
.get(`/competitions/${id}`)
.get(`/api/competitions/${id}`)
.then((res) => {
dispatch({
type: Types.SET_PRESENTATION_COMPETITION,
......@@ -20,7 +20,7 @@ export const getPresentationCompetition = (id: string) => async (dispatch: AppDi
export const getPresentationTeams = (id: string) => async (dispatch: AppDispatch) => {
await axios
.get(`/competitions/${id}/teams`)
.get(`/api/competitions/${id}/teams`)
.then((res) => {
dispatch({
type: Types.SET_PRESENTATION_TEAMS,
......
......@@ -4,7 +4,7 @@ import Types from './types'
export const getRoles = () => async (dispatch: AppDispatch) => {
await axios
.get('/misc/roles')
.get('/api/misc/roles')
.then((res) => {
dispatch({
type: Types.SET_ROLES,
......
......@@ -15,7 +15,7 @@ export const getSearchUsers = () => async (dispatch: AppDispatch, getState: () =
email: currentParams.email,
}
await axios
.get('/users/search', { params })
.get('/api/users/search', { params })
.then((res) => {
dispatch({
type: Types.SET_SEARCH_USERS,
......
......@@ -4,7 +4,7 @@ import Types from './types'
export const getTypes = () => async (dispatch: AppDispatch) => {
await axios
.get('/misc/types')
.get('/api/misc/types')
.then((res) => {
dispatch({
type: Types.SET_TYPES,
......
......@@ -7,7 +7,7 @@ import Types from './types'
export const loginUser = (userData: AccountLoginModel, history: History) => async (dispatch: AppDispatch) => {
dispatch({ type: Types.LOADING_UI })
await axios
.post('/auth/login', userData)
.post('/api/auth/login', userData)
.then((res) => {
const token = `Bearer ${res.data.access_token}`
localStorage.setItem('token', token) //setting token to local storage
......@@ -28,7 +28,7 @@ export const loginUser = (userData: AccountLoginModel, history: History) => asyn
export const getUserData = () => async (dispatch: AppDispatch) => {
dispatch({ type: Types.LOADING_USER })
await axios
.get('/users')
.get('/api/users')
.then((res) => {
dispatch({
type: Types.SET_USER,
......@@ -42,7 +42,7 @@ export const getUserData = () => async (dispatch: AppDispatch) => {
export const logoutUser = () => async (dispatch: AppDispatch) => {
localStorage.removeItem('token')
await axios.post('/auth/logout').then(() => {
await axios.post('/api/auth/logout').then(() => {
delete axios.defaults.headers.common['Authorization']
dispatch({
type: Types.SET_UNAUTHENTICATED,
......
......@@ -61,7 +61,7 @@ export interface Question extends NameID {
export interface QuestionAlternative {
id: number
text: string
value: boolean
value: number
question_id: number
}
export interface QuestionAnswer {
......
......@@ -34,5 +34,5 @@ export interface RichQuestion {
total_score: number
question_type: QuestionType
type_id: number
question_alternatives: QuestionAlternative[]
alternatives: QuestionAlternative[]
}
......@@ -10,10 +10,17 @@ import { City } from '../../../interfaces/ApiModels'
import { AddCompetitionModel, FormModel } from '../../../interfaces/FormModels'
import { AddButton, AddContent, AddForm } from '../styledComp'
/**
* Component description:
* This component handles the functionality when adding a competition to the system
* This component is a child component to CompetitionManager.tsx
*/
type formType = FormModel<AddCompetitionModel>
const noCitySelected = 'Välj stad'
//Description of the form and what is required
const competitionSchema: Yup.SchemaOf<formType> = Yup.object({
model: Yup.object()
.shape({
......@@ -45,20 +52,25 @@ const AddCompetition: React.FC = (props: any) => {
const dispatch = useAppDispatch()
const id = open ? 'simple-popover' : undefined
const currentYear = new Date().getFullYear()
// Handles the actual submition to the database
const handleCompetitionSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
// The parameters sent
const params = {
name: values.model.name,
year: values.model.year,
city_id: selectedCity?.id as number,
}
await axios
.post('/competitions', params)
.post('/api/competitions', params) // send to database
.then(() => {
actions.resetForm()
actions.resetForm() // reset the form
setAnchorEl(null)
dispatch(getCompetitions())
dispatch(getCompetitions()) // refresh competitions
setSelectedCity(undefined)
})
// if the post request fails
.catch(({ response }) => {
console.warn(response.data)
if (response.data && response.data.message)
......@@ -83,6 +95,12 @@ const AddCompetition: React.FC = (props: any) => {
>
Ny Tävling
</AddButton>
{/**
* The "pop up" menu for adding a competition
* contains 3 fields; Name, Region and Year
*
*/}
<Popover
id={id}
open={open}
......
......@@ -21,6 +21,13 @@ import { CompetitionFilterParams } from '../../../interfaces/FilterParams'
import { FilterContainer, RemoveMenuItem, TopBar, YearFilterTextField } from '../styledComp'
import AddCompetition from './AddCompetition'
/**
* Component description:
* This component shows a list of all the competitions which a user can search through
* We can also start, duplicate or delete a competition
*/
// Use defined styling
const useStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
......@@ -41,6 +48,7 @@ const CompetitionManager: React.FC = (props: any) => {
const filterParams = useAppSelector((state) => state.competitions.filterParams)
const competitionTotal = useAppSelector((state) => state.competitions.total)
const cities = useAppSelector((state) => state.cities.cities)
const classes = useStyles()
const noFilterText = 'Alla'
const dispatch = useAppDispatch()
......@@ -59,6 +67,7 @@ const CompetitionManager: React.FC = (props: any) => {
dispatch(getCompetitions())
}, [])
// Searchfuntion to search for a specific string
const onSearchChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
if (timerHandle) {
clearTimeout(timerHandle)
......@@ -69,10 +78,30 @@ const CompetitionManager: React.FC = (props: any) => {
dispatch(setFilterParams({ ...filterParams, name: event.target.value }))
}
// Function to remove a competition from the systems database
const handleDeleteCompetition = async () => {
if (activeId) {
await axios
.delete(`/competitions/${activeId}`)
.delete(`/api/competitions/${activeId}`)
.then(() => {
setAnchorEl(null)
dispatch(getCompetitions()) // refresh the competition list
})
.catch(({ response }) => {
console.warn(response.data)
})
}
}
const handleStartCompetition = () => {
history.push(`/presenter/id=${activeId}&code=123123`)
console.log('GLHF!')
}
const handleDuplicateCompetition = async () => {
if (activeId) {
await axios
.post(`/api/competitions/${activeId}/copy`)
.then(() => {
setAnchorEl(null)
dispatch(getCompetitions())
......@@ -163,6 +192,7 @@ const CompetitionManager: React.FC = (props: any) => {
))}
</TableBody>
</Table>
{/** We can't find any competitions at all or with a specific filter */}
{(!competitions || competitions.length === 0) && (
<Typography>Inga tävlingar hittades med nuvarande filter</Typography>
)}
......@@ -176,8 +206,8 @@ const CompetitionManager: React.FC = (props: any) => {
onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
/>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={() => history.push(`/presenter/id=${activeId}&code=123123`)}>Starta</MenuItem>
<MenuItem onClick={handleClose}>Duplicera</MenuItem>
<MenuItem onClick={handleStartCompetition}>Starta</MenuItem>
<MenuItem onClick={handleDuplicateCompetition}>Duplicera</MenuItem>
<RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem>
</Menu>
</div>
......
......@@ -51,7 +51,7 @@ const AddRegion: React.FC = (props: any) => {
name: values.model.name,
}
await axios
.post('/misc/cities', params)
.post('/api/misc/cities', params)
.then(() => {
actions.resetForm()
dispatch(getCities())
......
......@@ -45,7 +45,7 @@ const RegionManager: React.FC = (props: any) => {
const handleDeleteCity = async () => {
if (activeId) {
await axios
.delete(`/misc/cities/${activeId}`)
.delete(`/api/misc/cities/${activeId}`)
.then(() => {
setAnchorEl(null)
dispatch(getCities())
......@@ -63,7 +63,7 @@ const RegionManager: React.FC = (props: any) => {
const handleAddCity = async () => {
await axios
.post(`/misc/cities`, { name: newCity })
.post(`/api/misc/cities`, { name: newCity })
.then(() => {
setAnchorEl(null)
dispatch(getCities())
......
......@@ -59,7 +59,7 @@ const AddUser: React.FC = (props: any) => {
role_id: selectedRole?.id as number,
}
await axios
.post('/auth/signup', params)
.post('/api/auth/signup', params)
.then(() => {
actions.resetForm()
setAnchorEl(null)
......
import {
Button,
createStyles,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
FormControl,
InputLabel,
makeStyles,
......@@ -8,6 +13,8 @@ import {
Popover,
TextField,
Theme,
useMediaQuery,
useTheme,
} from '@material-ui/core'
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
import { Alert, AlertTitle } from '@material-ui/lab'
......@@ -62,6 +69,11 @@ type UserIdProps = {
}
const EditUser = ({ user }: UserIdProps) => {
// for dialog alert
const [openAlert, setOpen] = React.useState(false)
const theme = useTheme()
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
const dispatch = useAppDispatch()
const classes = useStyles()
......@@ -87,21 +99,25 @@ const EditUser = ({ user }: UserIdProps) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setOpen(false)
setAnchorEl(null)
}
const handleVerifyDelete = () => {
setOpen(true)
}
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)
})
}
setOpen(false)
await axios
.delete(`/api/auth/delete/${user.id}`)
.then(() => {
setAnchorEl(null)
dispatch(getSearchUsers())
})
.catch(({ response }) => {
console.warn(response.data)
})
}
const handleSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
......@@ -125,7 +141,7 @@ const EditUser = ({ user }: UserIdProps) => {
req['role_id'] = params.role_id
}
await axios
.put('/users/' + user.id, req)
.put('/api/users/' + user.id, req)
.then((res) => {
setAnchorEl(null)
dispatch(getSearchUsers())
......@@ -273,7 +289,7 @@ const EditUser = ({ user }: UserIdProps) => {
Ändra
</Button>
<Button
onClick={handleDeleteUsers}
onClick={handleVerifyDelete}
className={classes.deleteButton}
fullWidth
variant="contained"
......@@ -281,6 +297,27 @@ const EditUser = ({ user }: UserIdProps) => {
>
Ta bort
</Button>
<Dialog
fullScreen={fullScreen}
open={openAlert}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">{'Ta bort användare?'}</DialogTitle>
<DialogContent>
<DialogContentText>
Är du säker på att du vill ta bort användaren och all dess information från systemet?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
Avbryt
</Button>
<Button onClick={handleDeleteUsers} color="primary" autoFocus>
Ta bort
</Button>
</DialogActions>
</Dialog>
{formik.errors.error && (
<Alert severity="error">
......
import React from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { useTheme } from '@material-ui/core/styles';
export default function ResponsiveDialog() {
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open responsive dialog
</Button>
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">{"Use Google's location service?"}</DialogTitle>
<DialogContent>
<DialogContentText>
Let Google help apps determine location. This means sending anonymous location data to
Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
Disagree
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
</div>
);
}
\ No newline at end of file