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 342 additions and 133 deletions
export interface Competition {
name: string
id: number
city_id: number
year: number
}
export interface CompetitionFilterParams {
name?: string
year?: number
cityId?: number
styleId?: number
page: number
pageSize: number
}
export interface Position {
x: number
y: number
}
export interface Size {
w: number
h: number
}
export interface UserData {
id: number
export interface CompetitionFilterParams {
name?: string
email: string
role_id: number
city_id: number
year?: number
cityId?: number
styleId?: number
page: number
pageSize: number
}
export interface SearchUserFilterParams {
name?: string
year?: number
cityId?: number
styleId?: number
page: number
pageSize: number
}
export interface UserFilterParams {
......
export interface ServerResponse {
code: number
message: string
}
export interface FormModel<T> {
model: T
error?: string
}
//#region LOGIN
export interface AccountLoginModel {
email: string
password: string
}
export interface CompetitionLoginModel {
code: string
}
//#endregion
////ADD////
export interface AddCompetitionModel {
name: string
city: string
year: number
}
export interface CompetitionLoginModel {
code: string
}
export interface AddUserModel {
email: string
password: string
......@@ -21,9 +34,15 @@ export interface AddUserModel {
name?: string
}
export interface AddCityModel {
name: string
}
////EDIT////
export interface EditUserModel {
email: string
role: string
city: string
name?: string
password?: string
}
export interface Role {
id: number
name: string
}
export interface SearchUSerFilterParams {
name?: string
year?: number
cityId?: number
styleId?: number
page: number
pageSize: number
}
export interface Slide {
competition_id: number
id: number
order: number
timer: number
title: string
}
export interface City {
export interface Team {
id: number
name: string
}
export interface ViewParams {
id: string
code: string
}
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'
......@@ -6,6 +7,42 @@ import store from '../../store'
import AdminPage from './AdminPage'
it('renders admin view', () => {
const cityRes: any = {
data: {
items: [
{
id: 1,
name: 'Link\u00f6ping',
},
{
id: 2,
name: 'Stockholm',
},
],
count: 2,
total_count: 3,
},
}
const rolesRes: any = {
data: {
items: [
{
id: 1,
name: 'role1',
},
{
id: 2,
name: 'role2',
},
],
count: 2,
total_count: 3,
},
}
;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
if (path === '/misc/cities') return Promise.resolve(cityRes)
else return Promise.resolve(rolesRes)
})
render(
<Provider store={store}>
<BrowserRouter>
......
......@@ -12,18 +12,21 @@ import {
} from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import DashboardIcon from '@material-ui/icons/Dashboard'
import MailIcon from '@material-ui/icons/Mail'
import React from 'react'
import ExitToAppIcon from '@material-ui/icons/ExitToApp'
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 { getCities } from '../../actions/cities'
import { getRoles } from '../../actions/roles'
import { logoutUser } from '../../actions/user'
import { useAppDispatch } from '../../hooks'
import CompetitionManager from './components/CompetitionManager'
import Regions from './components/Regions'
import UserManager from './components/UserManager'
import { useAppDispatch, useAppSelector } from '../../hooks'
import CompetitionManager from './competitions/CompetitionManager'
import RegionManager from './regions/Regions'
import { LeftDrawer } from './styled'
import UserManager from './users/UserManager'
const drawerWidth = 250
const menuItems = ['Startsida', 'Regioner', 'Användare', 'Tävlingshanterare']
const useStyles = makeStyles((theme: Theme) =>
createStyles({
......@@ -42,7 +45,7 @@ const useStyles = makeStyles((theme: Theme) =>
content: {
flexGrow: 1,
backgroundColor: theme.palette.background.default,
paddingLeft: theme.spacing(30),
paddingLeft: theme.spacing(31),
},
})
)
......@@ -55,13 +58,50 @@ const AdminView: React.FC = () => {
dispatch(logoutUser())
}
const dispatch = useAppDispatch()
const currentUser = useAppSelector((state) => state.user.userInfo)
const isAdmin = () => currentUser && currentUser.role.name === 'Admin'
const menuAdminItems = [
{ text: 'Startsida', icon: DashboardIcon },
{ text: 'Regioner', icon: LocationCityIcon },
{ text: 'Användare', icon: PeopleIcon },
{ text: 'Tävlingshanterare', icon: SettingsOverscanIcon },
]
const menuEditorItems = [
{ text: 'Startsida', icon: DashboardIcon },
{ text: 'Tävlingshanterare', icon: SettingsOverscanIcon },
]
const renderItems = () => {
const menuItems = isAdmin() ? menuAdminItems : menuEditorItems
return menuItems.map((value, index) => (
<ListItem
button
component={Link}
key={value.text}
to={`${url}/${value.text.toLowerCase()}`}
selected={index === openIndex}
onClick={() => setOpenIndex(index)}
>
<ListItemIcon>{React.createElement(value.icon)}</ListItemIcon>
<ListItemText primary={value.text} />
</ListItem>
))
}
useEffect(() => {
dispatch(getCities())
dispatch(getRoles())
}, [])
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<Typography variant="h5" noWrap>
{menuItems[openIndex]}
{isAdmin() ? menuAdminItems[openIndex].text : menuEditorItems[openIndex].text}
</Typography>
</Toolbar>
</AppBar>
......@@ -76,25 +116,18 @@ const AdminView: React.FC = () => {
<div>
<div className={classes.toolbar} />
<Divider />
<List>
{menuItems.map((text, index) => (
<ListItem
button
component={Link}
key={text}
to={`${url}/${text.toLowerCase()}`}
selected={index === openIndex}
onClick={() => setOpenIndex(index)}
>
<ListItemIcon>{index === 0 ? <DashboardIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<List>{renderItems()}</List>
<Divider />
<List>
<ListItem>
<Button onClick={handleLogout} type="submit" fullWidth variant="contained" color="primary">
<Button
onClick={handleLogout}
type="submit"
fullWidth
variant="contained"
color="primary"
endIcon={<ExitToAppIcon></ExitToAppIcon>}
>
Logga ut
</Button>
</ListItem>
......@@ -110,7 +143,7 @@ const AdminView: React.FC = () => {
</Typography>
</Route>
<Route path={`${path}/regioner`}>
<Regions />
<RegionManager />
</Route>
<Route path={`${path}/användare`}>
<UserManager />
......
......@@ -6,22 +6,15 @@ import React from 'react'
import * as Yup from 'yup'
import { getCompetitions } from '../../../actions/competitions'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { City } from '../../../interfaces/City'
import { AddCompetitionModel } from '../../../interfaces/models'
import { AddCompetitionButton, AddCompetitionContent, AddCompetitionForm } from './styled'
import { City } from '../../../interfaces/ApiModels'
import { AddCompetitionModel, FormModel } from '../../../interfaces/FormModels'
import { AddButton, AddContent, AddForm } from '../styledComp'
interface ServerResponse {
code: number
message: string
}
type formType = FormModel<AddCompetitionModel>
interface AddCompetitionFormModel {
model: AddCompetitionModel
error?: string
}
const noCitySelected = 'Välj stad'
const competitionSchema: Yup.SchemaOf<AddCompetitionFormModel> = Yup.object({
const competitionSchema: Yup.SchemaOf<formType> = Yup.object({
model: Yup.object()
.shape({
name: Yup.string().required('Namn krävs'),
......@@ -51,17 +44,14 @@ const AddCompetition: React.FC = (props: any) => {
const dispatch = useAppDispatch()
const id = open ? 'simple-popover' : undefined
const currentYear = new Date().getFullYear()
const handleCompetitionSubmit = async (
values: AddCompetitionFormModel,
actions: FormikHelpers<AddCompetitionFormModel>
) => {
const handleCompetitionSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
const params = {
name: values.model.name,
year: values.model.year,
city_id: selectedCity?.id as number,
}
await axios
.post<ServerResponse>('/competitions', params)
.post('/competitions', params)
.then(() => {
actions.resetForm()
setAnchorEl(null)
......@@ -79,14 +69,19 @@ const AddCompetition: React.FC = (props: any) => {
})
}
const competitionInitialValues: AddCompetitionFormModel = {
const competitionInitialValues: formType = {
model: { name: '', city: userCity?.name ? userCity.name : noCitySelected, year: currentYear },
}
return (
<div>
<AddCompetitionButton color="secondary" variant="contained" onClick={handleClick}>
<AddButton
style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
color="default"
variant="contained"
onClick={handleClick}
>
Ny Tävling
</AddCompetitionButton>
</AddButton>
<Popover
id={id}
open={open}
......@@ -101,14 +96,14 @@ const AddCompetition: React.FC = (props: any) => {
horizontal: 'center',
}}
>
<AddCompetitionContent>
<AddContent>
<Formik
initialValues={competitionInitialValues}
validationSchema={competitionSchema}
onSubmit={handleCompetitionSubmit}
>
{(formik) => (
<AddCompetitionForm onSubmit={formik.handleSubmit}>
<AddForm onSubmit={formik.handleSubmit}>
<TextField
label="Namn"
name="model.name"
......@@ -170,10 +165,10 @@ const AddCompetition: React.FC = (props: any) => {
{formik.errors.error}
</Alert>
)}
</AddCompetitionForm>
</AddForm>
)}
</Formik>
</AddCompetitionContent>
</AddContent>
</Popover>
</div>
)
......
......@@ -14,13 +14,12 @@ import TableRow from '@material-ui/core/TableRow'
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
import axios from 'axios'
import React, { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { getCities } from '../../../actions/cities'
import { Link, useHistory } from 'react-router-dom'
import { getCompetitions, setFilterParams } from '../../../actions/competitions'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { CompetitionFilterParams } from '../../../interfaces/CompetitionFilterParams'
import { CompetitionFilterParams } from '../../../interfaces/FilterParams'
import { FilterContainer, RemoveMenuItem, TopBar, YearFilterTextField } from '../styledComp'
import AddCompetition from './AddCompetition'
import { FilterContainer, RemoveCompetition, TopBar, YearFilterTextField } from './styled'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
......@@ -44,6 +43,7 @@ const CompetitionManager: React.FC = (props: any) => {
const classes = useStyles()
const noFilterText = 'Alla'
const dispatch = useAppDispatch()
const history = useHistory()
const handleClick = (event: React.MouseEvent<HTMLButtonElement>, id: number) => {
setAnchorEl(event.currentTarget)
setActiveId(id)
......@@ -55,7 +55,6 @@ const CompetitionManager: React.FC = (props: any) => {
}
useEffect(() => {
dispatch(getCities())
dispatch(getCompetitions())
}, [])
......@@ -176,9 +175,9 @@ 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={handleClose}>Starta</MenuItem>
<MenuItem onClick={() => history.push(`/presenter/id=${activeId}&code=123123`)}>Starta</MenuItem>
<MenuItem onClick={handleClose}>Duplicera</MenuItem>
<RemoveCompetition onClick={handleDeleteCompetition}>Ta bort</RemoveCompetition>
<RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem>
</Menu>
</div>
)
......
import { Grid, TextField } from '@material-ui/core'
import FormControl from '@material-ui/core/FormControl'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import AddIcon from '@material-ui/icons/Add'
import { Alert, AlertTitle } from '@material-ui/lab'
import axios from 'axios'
import { Formik, FormikHelpers } from 'formik'
import React from 'react'
import * as Yup from 'yup'
import { getCities } from '../../../actions/cities'
import { useAppDispatch } from '../../../hooks'
import { AddCityModel, FormModel } from '../../../interfaces/FormModels'
import { AddButton, AddForm } from '../styledComp'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
width: '100%',
},
margin: {
margin: theme.spacing(1),
},
button: {
width: '40px',
height: '40px',
marginTop: '20px',
},
})
)
type formType = FormModel<AddCityModel>
const schema: Yup.SchemaOf<formType> = Yup.object({
model: Yup.object()
.shape({
name: Yup.string()
.required('Minst två bokstäver krävs')
.min(2)
.matches(/[a-zA-Z]/, 'Namnet får enbart innehålla a-z, A-Z.'),
})
.required(),
error: Yup.string().optional(),
})
const AddRegion: React.FC = (props: any) => {
const classes = useStyles()
const dispatch = useAppDispatch()
const handleSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
const params = {
name: values.model.name,
}
await axios
.post('/misc/cities', params)
.then(() => {
actions.resetForm()
dispatch(getCities())
})
.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 initValues: formType = {
model: { name: '' },
}
return (
<Formik initialValues={initValues} validationSchema={schema} onSubmit={handleSubmit}>
{(formik) => (
<AddForm onSubmit={formik.handleSubmit}>
<FormControl className={classes.margin}>
<Grid container={true}>
<TextField
className={classes.margin}
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}
name="model.name"
label="Region"
></TextField>
<AddButton
style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
className={classes.button}
color="default"
variant="contained"
type="submit"
>
<AddIcon></AddIcon>
</AddButton>
</Grid>
</FormControl>
{formik.errors.error && (
<Alert severity="error">
<AlertTitle>Error</AlertTitle>
{formik.errors.error}
</Alert>
)}
</AddForm>
)}
</Formik>
)
}
export default AddRegion
import { Button, Menu, TextField, Typography } from '@material-ui/core'
import FormControl from '@material-ui/core/FormControl'
import { Button, Menu, Typography } from '@material-ui/core'
import Paper from '@material-ui/core/Paper'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import Table from '@material-ui/core/Table'
......@@ -13,8 +12,8 @@ import axios from 'axios'
import React, { useEffect } from 'react'
import { getCities } from '../../../actions/cities'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { RemoveCompetition, TopBar } from './styled'
import { RemoveMenuItem, TopBar } from '../styledComp'
import AddRegion from './AddRegion'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
......@@ -26,12 +25,12 @@ const useStyles = makeStyles((theme: Theme) =>
})
)
const UserManager: React.FC = (props: any) => {
const RegionManager: React.FC = (props: any) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const [activeId, setActiveId] = React.useState<number | undefined>(undefined)
const citiesTotal = useAppSelector((state) => state.cities.total)
const cities = useAppSelector((state) => state.cities.cities)
const [newCity, setNewCity] = React.useState()
const [newCity, setNewCity] = React.useState<string>()
const classes = useStyles()
const dispatch = useAppDispatch()
const handleClose = () => {
......@@ -81,12 +80,7 @@ const UserManager: React.FC = (props: any) => {
return (
<div>
<TopBar>
<FormControl className={classes.margin}>
<TextField className={classes.margin} value={newCity} onChange={handleChange} label="Region"></TextField>
<Button color="primary" variant="contained" onClick={handleAddCity}>
Lägg till
</Button>
</FormControl>
<AddRegion></AddRegion>
</TopBar>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
......@@ -113,10 +107,10 @@ const UserManager: React.FC = (props: any) => {
{(!cities || cities.length === 0) && <Typography>Inga regioner hittades</Typography>}
</TableContainer>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<RemoveCompetition onClick={handleDeleteCity}>Ta bort</RemoveCompetition>
<RemoveMenuItem onClick={handleDeleteCity}>Ta bort</RemoveMenuItem>
</Menu>
</div>
)
}
export default UserManager
export default RegionManager
......@@ -7,20 +7,22 @@ export const TopBar = styled.div`
align-items: flex-end;
`
export const AddCompetitionButton = styled(Button)`
export const AddButton = styled(Button)`
margin-bottom: 8px;
`
export const AddCompetitionForm = styled.form`
export const AddForm = styled.form`
display: flex;
flex-direction: column;
`
export const AddCompetitionContent = styled.div`
padding: 15px;
export const AddContent = styled.div`
padding: 25px;
padding-bottom: 40px;
width: 300px;
`
export const RemoveCompetition = styled(MenuItem)`
export const RemoveMenuItem = styled(MenuItem)`
color: red;
`
......
import { Button, FormControl, InputLabel, MenuItem, Popover, TextField } from '@material-ui/core'
import PersonAddIcon from '@material-ui/icons/PersonAdd'
import { Alert, AlertTitle } from '@material-ui/lab'
import axios from 'axios'
import { Formik, FormikHelpers } from 'formik'
......@@ -6,28 +7,19 @@ import React from 'react'
import * as Yup from 'yup'
import { getSearchUsers } from '../../../actions/searchUser'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { City } from '../../../interfaces/City'
import { AddUserModel } from '../../../interfaces/models'
import { Role } from '../../../interfaces/Role'
import { AddCompetitionButton, AddCompetitionContent, AddCompetitionForm } from './styled'
import { City, Role } from '../../../interfaces/ApiModels'
import { AddUserModel, FormModel } from '../../../interfaces/FormModels'
import { AddButton, AddContent, AddForm } from '../styledComp'
interface ServerResponse {
code: number
message: string
}
interface AddUserFormModel {
model: AddUserModel
error?: string
}
type formType = FormModel<AddUserModel>
const noRoleSelected = 'Välj roll'
const noCitySelected = 'Välj stad'
const userSchema: Yup.SchemaOf<AddUserFormModel> = Yup.object({
const userSchema: Yup.SchemaOf<formType> = Yup.object({
model: Yup.object()
.shape({
name: Yup.string(), //.required('Namn krävs'),
name: Yup.string(),
email: Yup.string().email().required('Email krävs'),
password: Yup.string()
.required('Lösenord krävs.')
......@@ -58,7 +50,7 @@ const AddUser: React.FC = (props: any) => {
const open = Boolean(anchorEl)
const dispatch = useAppDispatch()
const id = open ? 'simple-popover' : undefined
const handleCompetitionSubmit = async (values: AddUserFormModel, actions: FormikHelpers<AddUserFormModel>) => {
const handleSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
const params = {
email: values.model.email,
password: values.model.password,
......@@ -67,7 +59,7 @@ const AddUser: React.FC = (props: any) => {
role_id: selectedRole?.id as number,
}
await axios
.post<ServerResponse>('/auth/signup', params)
.post('/auth/signup', params)
.then(() => {
actions.resetForm()
setAnchorEl(null)
......@@ -86,14 +78,20 @@ const AddUser: React.FC = (props: any) => {
})
}
const userInitialValues: AddUserFormModel = {
const userInitialValues: formType = {
model: { email: '', password: '', name: '', city: noCitySelected, role: noRoleSelected },
}
return (
<div>
<AddCompetitionButton color="secondary" variant="contained" onClick={handleClick}>
<AddButton
style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
color="default"
variant="contained"
onClick={handleClick}
endIcon={<PersonAddIcon></PersonAddIcon>}
>
Ny Användare
</AddCompetitionButton>
</AddButton>
<Popover
id={id}
open={open}
......@@ -108,10 +106,10 @@ const AddUser: React.FC = (props: any) => {
horizontal: 'center',
}}
>
<AddCompetitionContent>
<Formik initialValues={userInitialValues} validationSchema={userSchema} onSubmit={handleCompetitionSubmit}>
<AddContent>
<Formik initialValues={userInitialValues} validationSchema={userSchema} onSubmit={handleSubmit}>
{(formik) => (
<AddCompetitionForm onSubmit={formik.handleSubmit}>
<AddForm onSubmit={formik.handleSubmit}>
<TextField
label="Email"
name="model.email"
......@@ -208,10 +206,10 @@ const AddUser: React.FC = (props: any) => {
{formik.errors.error}
</Alert>
)}
</AddCompetitionForm>
</AddForm>
)}
</Formik>
</AddCompetitionContent>
</AddContent>
</Popover>
</div>
)
......