create Pools, mail page, kata Select- pointsystem

This commit is contained in:
Mario Peters
2022-05-12 18:03:35 +02:00
parent d4034a906b
commit fd3b5d3ece
13 changed files with 9667 additions and 17458 deletions

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"trailingComma": "all",
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"printWidth": 200
}

23
config-overrides.js Normal file
View File

@@ -0,0 +1,23 @@
const webpack = require('webpack');
module.exports = function override(config, env) {
config.resolve.fallback = {
url: require.resolve('url'),
fs: require.resolve('fs'),
assert: require.resolve('assert'),
crypto: require.resolve('crypto-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
os: require.resolve('os-browserify/browser'),
buffer: require.resolve('buffer'),
stream: require.resolve('stream-browserify'),
zlib: require.resolve('browserify-zlib')
};
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer'],
}),
);
return config;
}

25163
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"@datapunt/matomo-tracker-react": "^0.5.1",
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
"@material-table/core": "^0.2.10",
"@material-table/core": "^0.2.33",
"@mui/icons-material": "^5.2.1",
"@mui/lab": "^5.0.0-alpha.59",
"@mui/material": "^5.2.3",
@@ -16,16 +16,32 @@
"material-ui-flags": "^1.2.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^11.14.3",
"react-router-dom": "^5.3.0",
"react-scripts": "4.0.3"
"react-i18next": "^11.16.7",
"react-router-dom": "^5.3.1",
"react-scripts": "5.0.1"
},
"devDependencies": {
"@testing-library/react": "^12.1.4",
"assert": "^2.0.0",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"eslint-plugin-react-hooks": "^4.3.0",
"https-browserify": "^1.0.0",
"os-browserify": "^0.3.0",
"process": "^0.11.10",
"react-app-rewired": "^2.2.1",
"react-error-overlay": "6.0.11",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"url": "^0.11.0"
},
"scripts": {
"start": "react-scripts start",
"start": "react-app-rewired start",
"start:backend": "cd ../kt-backend && npm start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
"eslintConfig": {
"extends": "react-app",
@@ -44,10 +60,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/react": "^12.1.2",
"eslint-plugin-react-hooks": "^4.3.0",
"react-error-overlay": "6.0.10"
}
}

View File

@@ -1,31 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta http-equiv="content-Type" content="text/html; utf-8" />
<meta http-equiv="Pragma" content="cache" />
<meta name="robots" content="INDEX,FOLLOW" />
<meta http-equiv="content-Language" content="de" />
<meta name="description" content="Tournament Administration Software" />
<meta name="keywords" content="karate, djkb, ochi, jka, shotokan, kata, kumite, dkv, deutschland, black belt, dan, ippon" />
<meta name="author" content="Mario Peters" />
<meta name="publisher" content="Mario Peters" />
<meta name="copyright" content="Mario Peters" />
<meta name="audience" content="Alle" />
<meta name="page-type" content="Software Download" />
<meta name="page-topic" content="Sport" />
<meta name="revisit-after" content="7 days" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<!--
<html lang="de">
<!-- TODO: Lang automatisch -->
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta http-equiv="content-Type" content="text/html; utf-8" />
<meta http-equiv="Pragma" content="cache" />
<meta name="robots" content="INDEX,FOLLOW" />
<meta http-equiv="content-Language" content="de" />
<meta name="description" content="Tournament Administration Software" />
<meta name="keywords" content="karate, djkb, ochi, jka, shotokan, kata, kumite, dkv, deutschland, black belt, dan, ippon" />
<meta name="author" content="Mario Peters" />
<meta name="publisher" content="Mario Peters" />
<meta name="copyright" content="Mario Peters" />
<meta name="audience" content="Alle" />
<meta name="page-type" content="Software Download" />
<meta name="page-topic" content="Sport" />
<meta name="revisit-after" content="7 days" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
@@ -34,12 +35,12 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Karateturniere.de</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
<title>Karateturniere.de</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
@@ -48,6 +49,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
--></body>
</html>

View File

@@ -1,226 +1,240 @@
import {useState, useEffect} from 'react';
import AppBar from './components/AppBar/AppBar';
import Tournaments from './components/Tournaments/Tournaments'
import Participants from './components/Participants/Participants'
import Results from './pages/Results'
import './App.css';
import LoadParticipants from './components/UseFetch/LoadParticipants';
import {BrowserRouter as Router, Switch, Route} from "react-router-dom";
import {MatomoProvider, createInstance, useMatomo} from '@datapunt/matomo-tracker-react'
import {useTranslation} from "react-i18next";
import Streams from './pages/Streams';
import Footer from './components/Footer/Footer';
import Impress from './pages/Impress';
import Dataprotection from './pages/Dataprotection';
import useFetch from './components/UseFetch/UseFetch';
import DialogController from './components/Dialog/DialogController';
import { DialogContext } from './components/Dialog/Context';
import Registration from './pages/Registration';
import { Button } from '@mui/material';
import ExitToApp from '@mui/icons-material/ExitToApp';
import { createInstance, MatomoProvider, useMatomo } from '@datapunt/matomo-tracker-react'
import ExitToApp from '@mui/icons-material/ExitToApp'
import { Button } from '@mui/material'
import { createTheme, StyledEngineProvider, ThemeProvider } from '@mui/material/styles'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import './App.css'
import AppBar from './components/AppBar/AppBar'
import { DialogContext } from './components/Dialog/Context'
import DialogController from './components/Dialog/DialogController'
import Footer from './components/Footer/Footer'
import LoginDialog from './components/LoginDialog/LoginDialog'
import { ThemeProvider, StyledEngineProvider, createTheme } from '@mui/material/styles';
import Participants from './components/Participants/Participants'
import Tournaments from './components/Tournaments/Tournaments'
import LoadParticipants from './components/UseFetch/LoadParticipants'
import useFetch from './components/UseFetch/UseFetch'
import Dataprotection from './pages/Dataprotection'
import Impress from './pages/Impress'
import Mail from './pages/Mail'
import Registration from './pages/Registration'
import Results from './pages/Results'
import Streams from './pages/Streams'
const theme = createTheme({
palette: {
primary: {
light: '#757ce8',
main: '#3f50b5',
dark: '#002884',
contrastText: '#fff',
palette: {
primary: {
light: '#757ce8',
main: '#3f50b5',
dark: '#002884',
contrastText: '#fff',
},
secondary: {
light: '#ff7961',
main: '#f50057',
dark: '#c51162',
contrastText: '#000',
},
},
secondary: {
light: '#ff7961',
main: '#f50057',
dark: '#c51162',
contrastText: '#000',
},
},
});
})
export default function App(props) {
const apiServer = 'http://localhost:8000';
// const apiServer = 'https://api.karateturniere.de';
const [token, setToken] = useState(null);
const [participants, setParticipants] = useState(null);
const [tournaments, setTournaments] = useState(null);
const { data: fetchedTournaments, loading: loadingFetchedTournaments } = useFetch(apiServer + '/tournaments');
const [dialog, setDialog] = useState({open: false});
const [dialogContent, setDialogContent] = useState('no data');
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogMaxWidth, setDialogMaxWidth] = useState('sm');
const [user, setUser] = useState(null);
const {t, i18n} = useTranslation('common');
const { data: groups, loading: loadingGroups } = useFetch(apiServer + '/groups');
// TODO: immer den hook nutzen oder T mit prop drilling ?
// const apiServer = "http://localhost:8000";
// const apiServer = "http://api.kt.wsl";
const apiServer = 'https://api.karateturniere.de'
const [token, setToken] = useState(null)
const [participants, setParticipants] = useState(null)
const [tournaments, setTournaments] = useState(null)
const { data: fetchedTournaments, loading: loadingFetchedTournaments } = useFetch(apiServer + '/tournaments')
const [dialog, setDialog] = useState({ open: false })
const [dialogContent, setDialogContent] = useState('no data')
const [dialogOpen, setDialogOpen] = useState(false)
const [dialogMaxWidth, setDialogMaxWidth] = useState('sm')
const [user, setUser] = useState(null)
const { t, i18n } = useTranslation('common')
const { data: groups, loading: loadingGroups } = useFetch(apiServer + '/groups')
// TODO: immer den hook nutzen oder T mit prop drilling ?
const instance = createInstance({
urlBase: 'https://matomo.wattsche.de/',
siteId: 4,
userId: user?.account, // optional, default value: `undefined`.
configurations: { // optional, default value: {}
// any valid matomo configuration, all below are optional
disableCookies: true,
setRequestMethod: 'POST'
}
})
const { trackPageView, enableLinkTracking } = useMatomo();
enableLinkTracking();
// Track page view
useEffect(() => {
trackPageView()
}, [trackPageView])
useEffect(() => {
loadingFetchedTournaments && setTournaments(fetchedTournaments);
}, [loadingFetchedTournaments, fetchedTournaments]);
const checkToken = (token) => {
return new Promise((resolve, reject) => {
try {
const response = fetch(apiServer + '/checkToken' + token)
response.then((response) => {
if (response.ok) {
const account = response.headers.get('x-kt-account');
const admin = response.headers.get('x-kt-admin');
setUser({account, admin});
resolve(true);
} else {
console.log('error: ', response);
localStorage.removeItem('token is invalid');
reject(false);
}
}).catch(error => {
console.log('error: ',error);
reject(false);
});
} catch (error) {
console.log('error: ',error);
reject(false);
}
});
}
useEffect (() => {
const ls = localStorage.getItem('token');
if (ls) {
checkToken(ls).then((valid) => {
if (valid) {
setToken(ls);
}
})
}
}, []);
const handleToken = (account, admin, token) => {
setUser({account, admin})
setToken('?token=' + token);
localStorage.setItem('token', '?token=' + token);
};
const handleDialogOpen = () => {
setDialogOpen(true)
// const newDialog = {...dialog, open:true}
// setDialog(newDialog);
}
const handleDialogClose = () => {
setDialogOpen(false)
}
useEffect (() => {
setDialog({
open: dialogOpen,
setOpen: handleDialogOpen,
onClose: handleDialogClose,
content: dialogContent,
setContent: setDialogContent,
maxWidth: dialogMaxWidth,
setMaxWidth: setDialogMaxWidth,
const instance = createInstance({
urlBase: 'https://matomo.wattsche.de/',
siteId: 4,
userId: user?.account, // optional, default value: `undefined`.
configurations: {
// optional, default value: {}
// any valid matomo configuration, all below are optional
disableCookies: true,
setRequestMethod: 'POST',
},
})
}, [dialogOpen, dialogContent, dialogMaxWidth])
const openLoginDialog = () => {
dialog.setContent(<LoginDialog handleToken={handleToken} apiServer={apiServer}/>)
dialog.setOpen();
}
// console.log('dialog',dialog.open, dialog.content)
// console.log('dialogOpen',dialogOpen)
const { trackPageView, enableLinkTracking } = useMatomo()
enableLinkTracking()
// Track page view
useEffect(() => {
trackPageView()
}, [trackPageView])
if (loadingGroups && loadingFetchedTournaments ) {
return <h2>loading...</h2>
}
useEffect(() => {
loadingFetchedTournaments && setTournaments(fetchedTournaments)
}, [loadingFetchedTournaments, fetchedTournaments])
return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<MatomoProvider value={instance}>
<DialogContext.Provider value={dialog}>
<Router>
<div className="App">
{/* // TODO: mail Formular für alle, Turnier gemeldeten, abfrage, ob alle mails, oder nur für gemedelte turiere. */}
{/* // TODO: Alle Rechnungen des Turniers generieren. */}
<AppBar openLoginDialog={openLoginDialog} handleToken={handleToken} apiServer={apiServer} token={token} i18n={i18n} user={user} />
const checkToken = (token) => {
return new Promise((resolve, reject) => {
try {
const response = fetch(apiServer + '/checkToken' + token)
response
.then((response) => {
if (response.ok) {
const account = response.headers.get('x-kt-account')
const admin = response.headers.get('x-kt-admin')
setUser({ account, admin })
resolve(true)
} else {
console.log('error: ', response)
localStorage.removeItem('token is invalid')
reject(false)
}
})
.catch((error) => {
console.log('error: ', error)
reject(false)
})
} catch (error) {
console.log('error: ', error)
reject(false)
}
})
}
<Switch>
<Route path="/registration/:tid">
<main className="content">
{participants && tournaments && <Registration participants={participants} groups={groups} tournaments={tournaments} apiServer={apiServer} token={token} t={t} user={user} />}
</main>
</Route>
useEffect(() => {
const ls = localStorage.getItem('token')
if (ls) {
checkToken(ls).then((valid) => {
if (valid) {
setToken(ls)
}
})
}
}, [])
<Route path="/results/:tid">
<main className="content">
<Results apiServer={apiServer} token={token} t={t} user={user} />
</main>
</Route>
const handleToken = (account, admin, token) => {
setUser({ account, admin })
setToken('?token=' + token)
localStorage.setItem('token', '?token=' + token)
}
<Route path="/stream/:tid">
<main className="content">
<Streams apiServer={apiServer} t={t} />
</main>
</Route>
const handleDialogOpen = () => {
setDialogOpen(true)
// const newDialog = {...dialog, open:true}
// setDialog(newDialog);
}
<Route path="/impress">
<main className="content">
<Impress/>
</main>
</Route>
const handleDialogClose = () => {
setDialogOpen(false)
}
<Route path="/dataprotection">
<main className="content">
<Dataprotection/>
</main>
</Route>
useEffect(() => {
setDialog({
open: dialogOpen,
setOpen: handleDialogOpen,
onClose: handleDialogClose,
content: dialogContent,
setContent: setDialogContent,
maxWidth: dialogMaxWidth,
setMaxWidth: setDialogMaxWidth,
})
}, [dialogOpen, dialogContent, dialogMaxWidth])
<Route exact={true} path="/">
<main className="content">
<Tournaments user={user} apiServer={apiServer} token={token} groups={groups} tournaments={tournaments} setTournaments={setTournaments} t={t} />
{token && <LoadParticipants setParticipants={setParticipants} apiServer={apiServer} token={token} />}
<br/>
<br/>
{!participants && <><h3>{t('headline.participants')}</h3>
<p>{t('no-login')}</p>
<Button onClick={openLoginDialog} color="primary" variant="outlined" startIcon={<ExitToApp />}>
{t('login')}
</Button>
</>}
{participants && <Participants participants={participants} apiServer={apiServer} token={token} t={t} />}
</main>
</Route>
</Switch>
const openLoginDialog = () => {
dialog.setContent(<LoginDialog handleToken={handleToken} apiServer={apiServer} />)
dialog.setOpen()
}
<Footer />
<DialogController />
</div>
</Router>
</DialogContext.Provider>
</MatomoProvider>
</ThemeProvider>
</StyledEngineProvider>
);
}
// console.log('dialog',dialog.open, dialog.content)
// console.log('dialogOpen',dialogOpen)
if (loadingGroups && loadingFetchedTournaments) {
return <h2>loading...</h2>
}
return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<MatomoProvider value={instance}>
<DialogContext.Provider value={dialog}>
<Router>
<div className="App">
{/* // TODO: mail Formular für alle, Turnier gemeldeten, abfrage, ob alle mails, oder nur für gemedelte turiere. */}
{/* // TODO: Alle Rechnungen des Turniers generieren. */}
<AppBar openLoginDialog={openLoginDialog} handleToken={handleToken} apiServer={apiServer} token={token} i18n={i18n} user={user} />
<Switch>
<Route path="/registration/:tid">
<main className="content">
{participants && tournaments && (
<Registration participants={participants} groups={groups} tournaments={tournaments} apiServer={apiServer} token={token} t={t} user={user} />
)}
</main>
</Route>
<Route path="/results/:tid">
<main className="content">
<Results apiServer={apiServer} token={token} t={t} user={user} />
</main>
</Route>
<Route path="/stream/:tid">
<main className="content">
<Streams apiServer={apiServer} t={t} />
</main>
</Route>
<Route path="/mail/:tid">
<main className="content">
<Mail apiServer={apiServer} t={t} tournaments={tournaments} token={token} />
</main>
</Route>
<Route path="/impress">
<main className="content">
<Impress />
</main>
</Route>
<Route path="/dataprotection">
<main className="content">
<Dataprotection />
</main>
</Route>
<Route exact={true} path="/">
<main className="content">
<Tournaments user={user} apiServer={apiServer} token={token} groups={groups} tournaments={tournaments} setTournaments={setTournaments} t={t} />
{token && <LoadParticipants setParticipants={setParticipants} apiServer={apiServer} token={token} />}
<br />
<br />
{!participants && (
<>
<h3>{t('headline.participants')}</h3>
<p>{t('no-login')}</p>
<Button onClick={openLoginDialog} color="primary" variant="outlined" startIcon={<ExitToApp />}>
{t('login')}
</Button>
</>
)}
{participants && <Participants participants={participants} apiServer={apiServer} token={token} t={t} />}
</main>
</Route>
</Switch>
<Footer />
<DialogController />
</div>
</Router>
</DialogContext.Provider>
</MatomoProvider>
</ThemeProvider>
</StyledEngineProvider>
)
}

View File

@@ -0,0 +1,22 @@
import FormControl from '@mui/material/FormControl'
import TextField from '@mui/material/TextField'
import * as React from 'react'
export default function Points(props) {
const { num, average } = props
const PointsField = () => {
let result = []
for (let i = 0; i < num; i++) {
result.push(<TextField key={i} label="Size" defaultValue={average} size="small" />)
}
console.log(result)
return result
}
return (
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<PointsField />
</FormControl>
)
}

View File

@@ -0,0 +1,121 @@
import { styled } from '@mui/material/styles'
import { useEffect, useState } from 'react'
import KataSelect from '../Kata/KataSelect'
import useFetch from '../UseFetch/UseFetch'
import Points from './Points'
const PREFIX = 'PointsView'
const classes = {
container: `${PREFIX}-container`,
result: `${PREFIX}-result`,
}
const Root = styled('div')({
[`& .${classes.container}`]: {
listStyleType: 'none',
},
[`& .${classes.container} span`]: {
marginRight: '1rem',
},
[`& .${classes.result}`]: {
width: '3rem',
},
})
export default function PointsView(props) {
const { apiServer, token, groupData, allParticipants, allTeams } = props
const { data: groupEncounters, loading } = useFetch(apiServer + '/group/encounters/' + groupData.id + token)
const [encounter, setEncounter] = useState(null)
useEffect(() => {
loading && setEncounter(groupEncounters)
}, [groupEncounters, loading])
const encounterData = []
groupEncounters &&
encounter &&
encounter.forEach((element) => {
let aka, shiro
if (groupData.discipline.includes('Team')) {
aka = allTeams.find((x) => x.id === element.aka)
shiro = allTeams.find((x) => x.id === element.shiro)
} else {
aka = allParticipants.find((x) => x.id === element.aka)
shiro = allParticipants.find((x) => x.id === element.shiro)
}
const data = {
encounters: {
id: element.id,
encounter: element.begegnung,
},
aka: {
id: element.aka,
prename: aka?.vorname,
name: aka?.name,
club: aka?.verein ?? aka?.teamName,
},
shiro: {
id: element.shiro,
prename: shiro?.vorname,
name: shiro?.name,
club: shiro?.verein ?? shiro?.teamName,
},
}
encounterData.push(data)
})
console.log('encounterData', encounterData)
const Participant = (el) => {
// console.log(el)
if (el.participants.id === 0) return null
return (
<li className={classes.container}>
<span>{el.participants.id}</span>
{!groupData.discipline.includes('Team') && <span>{el.participants.prename + ' ' + el.participants.name}</span>}
<strong>{el.participants.club}</strong>
<span>{el.participants.kata}</span>
<KataSelect />
<Points num={3} average={5.5} />
<span>
{el.participants?.results?.map((result, i) => {
if (!result) return null
return <input className={classes.result} key={i} type="text" name={'r' + { i }} defaultValue={result} />
// return <TextField className={classes.result} key={i} inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }} defaultValue={result} />
})}
</span>
</li>
)
}
const participants = {
id: 1044,
prename: 'Mario',
name: 'Peters',
club: 'WAT',
kata: 'Heian Nidan',
results: [5.2, 5.5, 5.5, 5.4, 5.7],
}
return (
<Root>
<ul>
{encounterData.map((el, i) => {
console.log('el', el)
return (
<>
<Participant participants={el.aka}></Participant>
<Participant participants={el.shiro}></Participant>
</>
)
})}
<Participant participants={participants}></Participant>
</ul>
</Root>
)
}

View File

@@ -1,64 +1,61 @@
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import CloseIcon from '@mui/icons-material/Close';
import KoView from '../Groups/KoView';
import GroupInfo from '../Groups/GroupInfo';
import CloseIcon from '@mui/icons-material/Close'
import AppBar from '@mui/material/AppBar'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import IconButton from '@mui/material/IconButton'
import { styled } from '@mui/material/styles'
import Toolbar from '@mui/material/Toolbar'
import Typography from '@mui/material/Typography'
import { useState } from 'react'
import PointsView from '../Competition/PointsView'
import GroupInfo from '../Groups/GroupInfo'
import KoView from '../Groups/KoView'
const PREFIX = 'EncounterDialog';
const PREFIX = 'EncounterDialog'
const classes = {
appBar: `${PREFIX}-appBar`,
title: `${PREFIX}-title`
};
appBar: `${PREFIX}-appBar`,
title: `${PREFIX}-title`,
}
const StyledDialog = styled(Dialog)((
{
theme
}
) => ({
[`& .${classes.appBar}`]: {
position: 'relative',
},
[`& .${classes.title}`]: {
marginLeft: theme.spacing(2),
flex: 1,
}
}));
const StyledDialog = styled(Dialog)(({ theme }) => ({
[`& .${classes.appBar}`]: {
position: 'relative',
},
[`& .${classes.title}`]: {
marginLeft: theme.spacing(2),
flex: 1,
},
}))
export default function EncounterDialog(props) {
const { apiServer, token, groupData, tournamentData, open, handleClose, allParticipants, allTeams } = props;
const { apiServer, token, groupData, tournamentData, open, handleClose, allParticipants, allTeams } = props
const [koView, setKoView] = useState(true)
const toggleKoView = () => {
setKoView(!koView)
}
return (
<StyledDialog fullScreen open={open} onClose={handleClose}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
size="large">
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
{tournamentData.name} in {tournamentData.venue} ({tournamentData.date})
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
{/* Body */}
<GroupInfo groupData={groupData} />
{groupData?.pool !== 'FINAL' && <KoView groupData={groupData} token={token} apiServer={apiServer} allParticipants={allParticipants} allTeams={allTeams} />}
</StyledDialog>
);
}
return (
<StyledDialog fullScreen open={open} onClose={handleClose}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton edge="start" color="inherit" onClick={handleClose} aria-label="close" size="large">
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
{tournamentData.name} in {tournamentData.venue} ({tournamentData.date})
</Typography>
<Button autoFocus color="inherit" onClick={toggleKoView}>
toggle
</Button>
</Toolbar>
</AppBar>
{/* Body */}
<GroupInfo groupData={groupData} />
{koView && <KoView groupData={groupData} token={token} apiServer={apiServer} allParticipants={allParticipants} allTeams={allTeams} />}
{!koView && <PointsView groupData={groupData} token={token} apiServer={apiServer} allParticipants={allParticipants} allTeams={allTeams} />}
</StyledDialog>
)
}

View File

@@ -0,0 +1,50 @@
import FormControl from '@mui/material/FormControl'
import InputLabel from '@mui/material/InputLabel'
import MenuItem from '@mui/material/MenuItem'
import Select from '@mui/material/Select'
import * as React from 'react'
export default function KataSelect() {
const [kata, setKata] = React.useState('')
const handleChange = (event) => {
setKata(event.target.value)
}
return (
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<InputLabel id="kata-select">Kata</InputLabel>
<Select labelId="kata-select" id="kata-select" value={kata} label="Kata" onChange={handleChange}>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={'Heian Shodan'}>Heian Shodan</MenuItem>
<MenuItem value={'Heian Nidan'}>Heian Nidan</MenuItem>
<MenuItem value={'Heian Sandan'}>Heian Sandan</MenuItem>
<MenuItem value={'Heian Yondan'}>Heian Yondan</MenuItem>
<MenuItem value={'Heian Godan'}>Heian Godan</MenuItem>
<MenuItem value={'Tekki Shodan'}>Tekki Shodan</MenuItem>
<MenuItem value={'Tekki Nidan'}>Tekki Nidan</MenuItem>
<MenuItem value={'Tekki Sandan'}>Tekki Sandan</MenuItem>
<MenuItem value={'Bassai Dai'}>Bassai Dai</MenuItem>
<MenuItem value={'Bassai Sho'}>Bassai Sho</MenuItem>
<MenuItem value={'Empi'}>Empi</MenuItem>
<MenuItem value={'Jion'}>Jion</MenuItem>
<MenuItem value={'Hangetsu'}>Hangetsu</MenuItem>
<MenuItem value={'Kanku Dai'}>Kanku Dai</MenuItem>
<MenuItem value={'Kanku Sho'}>Kanku Sho</MenuItem>
<MenuItem value={'Jitte'}>Jitte</MenuItem>
<MenuItem value={'Jiin'}>Jiin</MenuItem>
<MenuItem value={'Nijūshiho'}>Nijūshiho</MenuItem>
<MenuItem value={'Gankaku'}>Gankaku</MenuItem>
<MenuItem value={'Chinte'}>Chinte</MenuItem>
<MenuItem value={'Sōchin'}>Sōchin</MenuItem>
<MenuItem value={'Wankan'}>Wankan</MenuItem>
<MenuItem value={'Meikyō'}>Meikyō</MenuItem>
<MenuItem value={'Gojūshiho Dai'}>Gojūshiho Dai</MenuItem>
<MenuItem value={'Gojūshiho Sho'}>Gojūshiho Sho</MenuItem>
<MenuItem value={'Unsu'}>Unsu</MenuItem>
</Select>
</FormControl>
)
}

View File

@@ -1,136 +1,129 @@
import { useContext, useState } from 'react'
import Button from '@mui/material/Button'
import CssBaseline from '@mui/material/CssBaseline'
import TextField from '@mui/material/TextField'
import Link from '@mui/material/Link'
import Grid from '@mui/material/Grid'
import Container from '@mui/material/Container'
import CssBaseline from '@mui/material/CssBaseline'
import Grid from '@mui/material/Grid'
import Link from '@mui/material/Link'
import TextField from '@mui/material/TextField'
import { useContext, useState } from 'react'
import { DialogContext } from '../Dialog/Context'
const LoginContext = (props) => {
const { handleToken, apiServer } = props
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const dialog = useContext(DialogContext)
const LoginContext = props => {
const { handleToken, apiServer } = props;
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const dialog = useContext(DialogContext);
const validateForm = () => {
return email.length > 0 && password.length > 0
}
const handleChange = event => {
if (event.target.id === 'email') {
setEmail(event.target.value.toLowerCase())
} else if (event.target.id === 'password') {
setPassword(event.target.value)
const validateForm = () => {
return email.length > 0 && password.length > 0
}
}
const handleSubmit = event => {
event.preventDefault()
const url = apiServer + '/authentications';
const data = { 'login': { 'email': email, 'password': password } };
async function login(url, data) {
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data), // data can be `string` or {object}!
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json',
})
});
if (response.status === 200) {
const account = response.headers.get('x-kt-account');
const admin = response.headers.get('x-kt-admin');
const token = response.headers.get('x-kt-token');
handleToken(account, admin, token);
if (token) {
// ReactDOM.render(<App token={'?token=' + response.data.token}/>, document.getElementById('root'))
dialog.onClose();
// return false
}
} else {
if (response.status === 204) {//No Content
console.log('no data found')
}
const handleChange = (event) => {
if (event.target.id === 'email') {
setEmail(event.target.value.toLowerCase())
} else if (event.target.id === 'password') {
setPassword(event.target.value)
}
// const res = await response;
// console.log(res);
// response.headers.forEach((val, key) => {
// console.log(key, val)
// })
} catch (error) {
console.log(error);
}
}
login(url, data);
}
const handleSubmit = (event) => {
event.preventDefault()
return <Container component="main" maxWidth="xs">
<CssBaseline />
<div>
<form noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
value={email}
onChange={handleChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={handleChange}
/>
<br />
<br />
<Button
type="button"
fullWidth
variant="contained"
color="primary"
disabled={!validateForm()}
onClick={handleSubmit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="https://karateturniere.de/anmeldung/passwort_vergessen.php" variant="body2" target="_blank">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="https://karateturniere.de/anmeldung/register.php" variant="body2" target="_blank">
{'Don\'t have an account? Sign Up'}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
const url = apiServer + '/authentications'
const data = { login: { email: email, password: password } }
async function login(url, data) {
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data), // data can be `string` or {object}!
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json',
}),
})
if (response.status === 200) {
const account = response.headers.get('x-kt-account')
const admin = response.headers.get('x-kt-admin')
const token = response.headers.get('x-kt-token')
handleToken(account, admin, token)
if (token) {
// ReactDOM.render(<App token={'?token=' + response.data.token}/>, document.getElementById('root'))
dialog.onClose()
// return false
}
} else {
if (response.status === 204) {
//No Content
console.log('no data found')
}
}
// const res = await response;
// console.log(res);
// response.headers.forEach((val, key) => {
// console.log(key, val)
// })
} catch (error) {
console.log(error)
}
}
login(url, data)
}
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div>
<form noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
value={email}
onChange={handleChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={handleChange}
/>
<br />
<br />
<Button type="button" fullWidth variant="contained" color="primary" disabled={!validateForm()} onClick={handleSubmit}>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="https://karateturniere.de/anmeldung/passwort_vergessen.php" variant="body2" target="_blank">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="https://karateturniere.de/anmeldung/register.php" variant="body2" target="_blank">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
)
}
export default LoginContext;
export default LoginContext

View File

@@ -1,354 +1,370 @@
import { styled } from '@mui/material/styles';
import clsx from 'clsx';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Collapse from '@mui/material/Collapse';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import { red, grey } from '@mui/material/colors';
import EuroSymbolIcon from '@mui/icons-material/EuroSymbol';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import VideocamIcon from '@mui/icons-material/Videocam';
import GroupIcon from '@mui/icons-material/Group';
import PersonIcon from '@mui/icons-material/Person';
import LocationOn from '@mui/icons-material/LocationOn';
import DateRange from '@mui/icons-material/DateRange';
import { convertDate } from '../../utilities/Date';
import Link from '@mui/material/Link';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import { Link as RouterLink } from "react-router-dom";
import Groups from "../Groups/Groups";
import { Button, Tooltip } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useContext, useState } from 'react';
import { DialogContext } from '../Dialog/Context';
import TriggerGroupsDialog from './TriggerGroupsDialog';
import DeleteTournamentDialog from './DeleteTournamentDialog';
import AddEditTournament from './AddEditTournament';
import AddGroups from '../Groups/AddGroups';
import EditGroups from '../Groups/EditGroups';
import DateRange from '@mui/icons-material/DateRange'
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'
import EuroSymbolIcon from '@mui/icons-material/EuroSymbol'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import GroupIcon from '@mui/icons-material/Group'
import LocationOn from '@mui/icons-material/LocationOn'
import MoreVertIcon from '@mui/icons-material/MoreVert'
import PersonIcon from '@mui/icons-material/Person'
import VideocamIcon from '@mui/icons-material/Videocam'
import { Button, Tooltip } from '@mui/material'
import Avatar from '@mui/material/Avatar'
import Card from '@mui/material/Card'
import CardActions from '@mui/material/CardActions'
import CardContent from '@mui/material/CardContent'
import CardHeader from '@mui/material/CardHeader'
import Collapse from '@mui/material/Collapse'
import { grey, red } from '@mui/material/colors'
import IconButton from '@mui/material/IconButton'
import Link from '@mui/material/Link'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import { styled } from '@mui/material/styles'
import clsx from 'clsx'
import { useContext, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link as RouterLink } from 'react-router-dom'
import { convertDate } from '../../utilities/Date'
import { DialogContext } from '../Dialog/Context'
import AddGroups from '../Groups/AddGroups'
import EditGroups from '../Groups/EditGroups'
import Groups from '../Groups/Groups'
import AddEditTournament from './AddEditTournament'
import DeleteTournamentDialog from './DeleteTournamentDialog'
import TriggerGroupsDialog from './TriggerGroupsDialog'
const PREFIX = 'TournamentsCard';
const PREFIX = 'TournamentsCard'
const classes = {
card: `${PREFIX}-card`,
cardHeader: `${PREFIX}-cardHeader`,
cardAction: `${PREFIX}-cardAction`,
cardActionBlock: `${PREFIX}-cardActionBlock`,
cardActionBlockRight: `${PREFIX}-cardActionBlockRight`,
media: `${PREFIX}-media`,
expand: `${PREFIX}-expand`,
expandOpen: `${PREFIX}-expandOpen`,
avatar: `${PREFIX}-avatar`,
title: `${PREFIX}-title`,
titleContainer: `${PREFIX}-titleContainer`,
titleRight: `${PREFIX}-titleRight`,
titleSide: `${PREFIX}-titleSide`,
subTitle: `${PREFIX}-subTitle`,
routerLink: `${PREFIX}-routerLink`
};
card: `${PREFIX}-card`,
cardHeader: `${PREFIX}-cardHeader`,
cardAction: `${PREFIX}-cardAction`,
cardActionBlock: `${PREFIX}-cardActionBlock`,
cardActionBlockRight: `${PREFIX}-cardActionBlockRight`,
media: `${PREFIX}-media`,
expand: `${PREFIX}-expand`,
expandOpen: `${PREFIX}-expandOpen`,
avatar: `${PREFIX}-avatar`,
title: `${PREFIX}-title`,
titleContainer: `${PREFIX}-titleContainer`,
titleRight: `${PREFIX}-titleRight`,
titleSide: `${PREFIX}-titleSide`,
subTitle: `${PREFIX}-subTitle`,
routerLink: `${PREFIX}-routerLink`,
}
const StyledCard = styled(Card)((
{
theme
}
) => ({
[`&.${classes.card}`]: {
backgroundColor: grey[200],
},
[`& .${classes.cardHeader}`]: {
padding: '16px 16px 0 16px',
// maxWidth: 345,
},
[`& .${classes.cardAction}`]: {
padding: '0 8px',
display: 'flex',
justifyContent: 'space-between',
},
[`& .${classes.cardActionBlock}`]: {
minWidth: '33%',
display: 'flex',
},
[`& .${classes.cardActionBlockRight}`]: {
minWidth: '33%',
display: 'flex',
justifyContent: 'end',
alignItems: 'center',
},
[`& .${classes.media}`]: {
height: 0,
paddingTop: '56.25%', // 16:9
},
[`& .${classes.expand}`]: {
transform: 'rotate(0deg)',
transition: theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest,
}),
},
[`& .${classes.expandOpen}`]: {
transform: 'rotate(180deg)',
},
[`& .${classes.avatar}`]: {
backgroundColor: red[500],
},
[`& .${classes.title}`]: {
fontSize: '1.9rem',
fontWeight: 'bold',
},
[`& .${classes.titleContainer}`]: {
fontSize: '1.3rem',
display: 'flex',
justifyContent: 'space-between',
margin: '0 2rem 0 3rem',
alignItems: 'center',
},
[`& .${classes.titleRight}`]: {
display: 'flex',
'& svg': {
marginRight: '0.2rem',
const StyledCard = styled(Card)(({ theme }) => ({
[`&.${classes.card}`]: {
backgroundColor: grey[200],
},
},
[`& .${classes.titleSide}`]: {
display: 'flex',
// justifyContent: 'space-between',
alignItems: 'center',
margin: '0 2rem 0 3rem',
'& svg': {
marginRight: '0.2rem',
[`& .${classes.cardHeader}`]: {
padding: '16px 16px 0 16px',
// maxWidth: 345,
},
},
[`& .${classes.subTitle}`]: {
fontSize: 18,
},
[`& .${classes.cardAction}`]: {
padding: '0 8px',
display: 'flex',
justifyContent: 'space-between',
},
[`& .${classes.routerLink}`]: {
textDecoration: 'inherit',
}
}));
[`& .${classes.cardActionBlock}`]: {
minWidth: '33%',
display: 'flex',
},
[`& .${classes.cardActionBlockRight}`]: {
minWidth: '33%',
display: 'flex',
justifyContent: 'end',
alignItems: 'center',
},
[`& .${classes.media}`]: {
height: 0,
paddingTop: '56.25%', // 16:9
},
[`& .${classes.expand}`]: {
transform: 'rotate(0deg)',
transition: theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest,
}),
},
[`& .${classes.expandOpen}`]: {
transform: 'rotate(180deg)',
},
[`& .${classes.avatar}`]: {
backgroundColor: red[500],
},
[`& .${classes.title}`]: {
fontSize: '1.9rem',
fontWeight: 'bold',
},
[`& .${classes.titleContainer}`]: {
fontSize: '1.3rem',
display: 'flex',
justifyContent: 'space-between',
margin: '0 2rem 0 3rem',
alignItems: 'center',
},
[`& .${classes.titleRight}`]: {
display: 'flex',
'& svg': {
marginRight: '0.2rem',
},
},
[`& .${classes.titleSide}`]: {
display: 'flex',
// justifyContent: 'space-between',
alignItems: 'center',
margin: '0 2rem 0 3rem',
'& svg': {
marginRight: '0.2rem',
},
},
[`& .${classes.subTitle}`]: {
fontSize: 18,
},
[`& .${classes.routerLink}`]: {
textDecoration: 'inherit',
},
}))
export default function TournamentsCard(props) {
const { apiServer, tournament, tournaments, setTournaments, tournamentGroups, token, user } = props;
const { apiServer, tournament, tournaments, setTournaments, tournamentGroups, token, user } = props
const [expanded, setExpanded] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const { t } = useTranslation('common');
const dialog = useContext(DialogContext);
const [expanded, setExpanded] = useState(false)
const [anchorEl, setAnchorEl] = useState(null)
const { t } = useTranslation('common')
const dialog = useContext(DialogContext)
const createFinalGroups = (tid) => {
fetch(apiServer + `/final/create` + token, {
method: 'PUT',
body: JSON.stringify({ 'tid': tid }), // data can be `string` or {object}!
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json',
})
}).then(() => {
handleClose();
console.log(`Finalgroups for tournament ${tid} created`);
}).catch(err => {
console.error(err)
});
};
const createPools = (groups) => {
fetch(apiServer + `/pools/create` + token, {
method: 'POST',
body: JSON.stringify({ groups }), // data can be `string` or {object}!
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json',
}),
})
.then(() => {
handleClose()
console.log(`Pools created`)
})
.catch((err) => {
console.error(err)
})
}
const openTriggerGroupDialog = (tid) => {
handleClose();
dialog.setContent(<TriggerGroupsDialog tid={tid} tournamentGroups={tournamentGroups} token={token} apiServer={apiServer} />)
dialog.setOpen();
};
const createFinalGroups = (tid) => {
fetch(apiServer + `/final/create` + token, {
method: 'PUT',
body: JSON.stringify({ tid: tid }), // data can be `string` or {object}!
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json',
}),
})
.then(() => {
handleClose()
console.log(`Finalgroups for tournament ${tid} created`)
})
.catch((err) => {
console.error(err)
})
}
const openAddGroupDialog = (tid) => {
handleClose();
dialog.setMaxWidth('lg')
dialog.setContent(<AddGroups tid={tid} token={token} apiServer={apiServer} />)
dialog.setOpen();
};
const openTriggerGroupDialog = (tid) => {
handleClose()
dialog.setContent(<TriggerGroupsDialog tid={tid} tournamentGroups={tournamentGroups} token={token} apiServer={apiServer} />)
dialog.setOpen()
}
const openEditGroupDialog = (tid) => {
handleClose();
dialog.setMaxWidth('lg')
dialog.setContent(<EditGroups tid={tid} token={token} apiServer={apiServer} groups={tournamentGroups} />)
dialog.setOpen();
};
const openAddGroupDialog = (tid) => {
handleClose()
dialog.setMaxWidth('lg')
dialog.setContent(<AddGroups tid={tid} token={token} apiServer={apiServer} />)
dialog.setOpen()
}
const openEditTournamentDialog = (tid) => {
handleClose();
dialog.setContent(<AddEditTournament tournaments={tournaments} setTournaments={setTournaments} token={token} apiServer={apiServer} t={t} tournament={tournament} />)
dialog.setOpen();
};
const openEditGroupDialog = (tid) => {
handleClose()
dialog.setMaxWidth('lg')
dialog.setContent(<EditGroups tid={tid} token={token} apiServer={apiServer} groups={tournamentGroups} />)
dialog.setOpen()
}
const openDeleteTournamentDialog = (tid) => {
handleClose();
dialog.setContent(<DeleteTournamentDialog tournaments={tournaments} setTournaments={setTournaments} tid={tid} token={token} apiServer={apiServer} />)
dialog.setOpen();
};
const openEditTournamentDialog = (tid) => {
handleClose()
dialog.setContent(<AddEditTournament tournaments={tournaments} setTournaments={setTournaments} token={token} apiServer={apiServer} t={t} tournament={tournament} />)
dialog.setOpen()
}
const handleExpandClick = () => {
setExpanded(!expanded);
};
const openDeleteTournamentDialog = (tid) => {
handleClose()
dialog.setContent(<DeleteTournamentDialog tournaments={tournaments} setTournaments={setTournaments} tid={tid} token={token} apiServer={apiServer} />)
dialog.setOpen()
}
const handleClick = event => {
setAnchorEl(event.currentTarget);
};
const handleExpandClick = () => {
setExpanded(!expanded)
}
const handleClose = () => {
setAnchorEl(null);
};
const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}
const tournamentData = {
date: convertDate(tournament.veranstaltungsDatum),
name: tournament.name,
venue: tournament.ort,
streams: tournament.streams,
};
const handleClose = () => {
setAnchorEl(null)
}
const closingdate = t("closing-date") + ' ' + new Date(tournament.meldeschluss).toLocaleDateString();
return (
<StyledCard className={classes.card}>
<CardHeader className={classes.cardHeader}
avatar={
<Avatar aria-label={tournament.name} className={classes.avatar}>
{tournament.id}
</Avatar>
}
action={
<>
{user?.account && <RouterLink to={'/registration/' + tournament.id} className={classes.routerLink}>
<Button color="primary" variant="contained">{t("tournament.registration")}</Button>
</RouterLink>
}
{user?.admin && <>
<IconButton
aria-label="more"
aria-controls="simple-menu"
aria-haspopup="true"
onClick={handleClick}
size="large">
<MoreVertIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{/* // Todo: gruppen einmalig aufsplitten */}
<MenuItem>TODO: Gruppen splitten</MenuItem>
<MenuItem onClick={openTriggerGroupDialog.bind(this, tournament.id)}>Gruppen auslosen</MenuItem>
<MenuItem onClick={createFinalGroups.bind(this, tournament.id)}>Finalgruppen anlegen</MenuItem>
<MenuItem onClick={openAddGroupDialog.bind(this, tournament.id)}>Gruppen hinzufügen</MenuItem>
<MenuItem onClick={openEditGroupDialog.bind(this, tournament.id)}>Gruppen bearbeiten</MenuItem>
<MenuItem onClick={openEditTournamentDialog.bind(this, tournament.id)}>Turnier bearbeiten</MenuItem>
<MenuItem onClick={openDeleteTournamentDialog.bind(this, tournament.id)}>Turnier löschen</MenuItem>
</Menu>
</>
}
</>
}
title={<div className={classes.titleContainer}>
<div className={classes.title}>{tournament.name}</div>
<div className={classes.titleRight}></div>
</div>}
subheader={<div className={classes.subTitle}>
<div className={classes.titleSide}>
<div>
<Tooltip title={`${tournament.strasse}, ${tournament.plz} ${tournament.ort}`}>
<IconButton size="large">
<LocationOn />
</IconButton>
</Tooltip>
{tournament.ort}
</div>
<div>
<Tooltip title={closingdate}>
<IconButton size="large">
<DateRange />
</IconButton>
</Tooltip>
{new Date(tournament.veranstaltungsDatum).toLocaleDateString()}
</div>
<div>
<IconButton size="large">
<PersonIcon />
</IconButton>
{tournament.startgebuehr}
</div>
<div>
<IconButton size="large">
<GroupIcon />
</IconButton>
{tournament.startgebuehrTeam}
</div>
</div>
</div>}
/>
const tournamentData = {
date: convertDate(tournament.veranstaltungsDatum),
name: tournament.name,
venue: tournament.ort,
streams: tournament.streams,
}
<CardActions className={classes.cardAction} >
<div className={classes.cardActionBlock}>
<IconButton
className={clsx(classes.expand, {
[classes.expandOpen]: expanded,
})}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
size="large">
<ExpandMoreIcon />
</IconButton>
</div>
<h4>{t('headline.groups')}</h4>
<div className={classes.cardActionBlockRight}>
<Tooltip title="Rechnung">
<Link href={"https://karateturniere.de/turnier/rechnung_PDF.php?turnier=" + tournament.id} variant="body2" target="_blank">
<IconButton aria-label="invoice" size="large">
<EuroSymbolIcon />
</IconButton>
</Link>
</Tooltip>
<Tooltip title="result">
<RouterLink to={'/results/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<EmojiEventsIcon />
</IconButton>
</RouterLink>
</Tooltip>
{/* eslint-disable-next-line eqeqeq */}
{tournamentData?.streams != undefined && <Tooltip title="Stream"><RouterLink to={'/stream/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<VideocamIcon />
</IconButton>
</RouterLink>
</Tooltip>}
const closingdate = t('closing-date') + ' ' + new Date(tournament.meldeschluss).toLocaleDateString()
return (
<StyledCard className={classes.card}>
<CardHeader
className={classes.cardHeader}
avatar={
<Avatar aria-label={tournament.name} className={classes.avatar}>
{tournament.id}
</Avatar>
}
action={
<>
{user?.account && (
<RouterLink to={'/registration/' + tournament.id} className={classes.routerLink}>
<Button color="primary" variant="contained">
{t('tournament.registration')}
</Button>
</RouterLink>
)}
{user?.admin && (
<>
<IconButton aria-label="more" aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick} size="large">
<MoreVertIcon />
</IconButton>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={createPools.bind(this, tournamentGroups)}>Pools erstellen</MenuItem>
<MenuItem onClick={openTriggerGroupDialog.bind(this, tournament.id)}>Gruppen auslosen</MenuItem>
<MenuItem onClick={createFinalGroups.bind(this, tournament.id)}>Finalgruppen anlegen</MenuItem>
<MenuItem onClick={openAddGroupDialog.bind(this, tournament.id)}>Gruppen hinzufügen</MenuItem>
<MenuItem onClick={openEditGroupDialog.bind(this, tournament.id)}>Gruppen bearbeiten</MenuItem>
<MenuItem onClick={openEditTournamentDialog.bind(this, tournament.id)}>Turnier bearbeiten</MenuItem>
<MenuItem onClick={openDeleteTournamentDialog.bind(this, tournament.id)}>Turnier löschen</MenuItem>
<RouterLink to={'/mail/' + tournament.id} style={{ textDecoration: 'inherit', color: 'inherit' }}>
<MenuItem>Email senden</MenuItem>
</RouterLink>
</Menu>
</>
)}
</>
}
title={
<div className={classes.titleContainer}>
<div className={classes.title}>{tournament.name}</div>
<div className={classes.titleRight}></div>
</div>
}
subheader={
<div className={classes.subTitle}>
<div className={classes.titleSide}>
<div>
<Tooltip title={`${tournament.strasse}, ${tournament.plz} ${tournament.ort}`}>
<IconButton size="large">
<LocationOn />
</IconButton>
</Tooltip>
{tournament.ort}
</div>
<div>
<Tooltip title={closingdate}>
<IconButton size="large">
<DateRange />
</IconButton>
</Tooltip>
{new Date(tournament.veranstaltungsDatum).toLocaleDateString()}
</div>
<div>
<IconButton size="large">
<PersonIcon />
</IconButton>
{tournament.startgebuehr}
</div>
<div>
<IconButton size="large">
<GroupIcon />
</IconButton>
{tournament.startgebuehrTeam}
</div>
</div>
</div>
}
/>
</div>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
{/* // TODO: Gruppen hier laden */}
<Groups user={user} tournamentGroups={tournamentGroups} token={token} apiServer={apiServer} tournamentData={tournamentData} />
</CardContent>
</Collapse>
</StyledCard>
);
<CardActions className={classes.cardAction}>
<div className={classes.cardActionBlock}>
<IconButton
className={clsx(classes.expand, {
[classes.expandOpen]: expanded,
})}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
size="large"
>
<ExpandMoreIcon />
</IconButton>
</div>
<h4>{t('headline.groups')}</h4>
<div className={classes.cardActionBlockRight}>
<Tooltip title="Rechnung">
<Link href={'https://karateturniere.de/turnier/rechnung_PDF.php?turnier=' + tournament.id} variant="body2" target="_blank">
<IconButton aria-label="invoice" size="large">
<EuroSymbolIcon />
</IconButton>
</Link>
</Tooltip>
<Tooltip title="result">
<RouterLink to={'/results/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<EmojiEventsIcon />
</IconButton>
</RouterLink>
</Tooltip>
{/* eslint-disable-next-line eqeqeq */}
{tournamentData?.streams != undefined && (
<Tooltip title="Stream">
<RouterLink to={'/stream/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<VideocamIcon />
</IconButton>
</RouterLink>
</Tooltip>
)}
</div>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
{/* // TODO: Gruppen hier laden */}
<Groups user={user} tournamentGroups={tournamentGroups} token={token} apiServer={apiServer} tournamentData={tournamentData} />
</CardContent>
</Collapse>
</StyledCard>
)
}

190
src/pages/Mail.jsx Normal file
View File

@@ -0,0 +1,190 @@
import { InputLabel, MenuItem, Select } from "@mui/material";
import FormControl from "@mui/material/FormControl";
import { styled } from "@mui/material/styles";
import TextField from "@mui/material/TextField";
import { useState } from "react";
import { useParams } from "react-router-dom";
// import { FormControl } from '@mui/material';
const PREFIX = "Mail";
const classes = {
stream: `${PREFIX}-stream`,
routerLink: `${PREFIX}-routerLink`,
};
// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed.
const Root = styled("div")(({ theme }) => ({
[`& .${classes.stream}`]: {
backgroundColor: "rgba(0,0,0,0.2)",
},
}));
export default function Mail(props) {
const { t, apiServer, token, tournaments } = props;
const { tid } = useParams();
const [mail, setMail] = useState({
from: "m.peters@karatenw.de",
to: "mario@wattsche.de",
subject: "DM 22",
body: "test",
tid,
});
const sendMail = () => {
fetch(apiServer + `/mail` + token, {
method: "POST",
body: JSON.stringify({ ...mail, body: nl2br(mail.body, true) }), // data can be `string` or {object}!
mode: "cors",
headers: new Headers({
"Content-Type": "application/json",
}),
})
.then(() => {
console.log(`Email gesendet`);
})
.catch((err) => {
console.error(err);
});
};
const test = (data) => {
console.log("mail: ", mail, data);
};
const nl2br = (str, replaceMode) => {
var breakTag = "<br />";
var replaceStr = replaceMode ? "$1" + breakTag : "$1" + breakTag + "$2";
return (str + "").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, replaceStr);
};
const br2nl = (str, replaceMode) => {
var replaceStr = replaceMode ? "\n" : "";
// Includes <br>, <BR>, <br />, </br>
return str.replace(/<\s*\/?br\s*[\/]?>/gi, replaceStr);
};
const changeValue = ({ target: { name, value } }) => {
setMail(() => ({ ...mail, [name]: value }));
// setMail(() => ({ ...mail, [name]: value.trim() }));
};
const changeTemplate = ({ target: { value } }) => {
setMail(() => ({ ...mail, body: br2nl(value, true) }));
};
return (
<Root>
<h1>Email senden:</h1>
<FormControl>
<TextField
id="from"
name="from"
label="from"
variant="filled"
defaultValue="m.peters@karatenw.de"
onChange={changeValue}
/>
</FormControl>
<TextField
id="to"
name="to"
label="to"
variant="filled"
defaultValue="mario@wattsche.de"
onChange={changeValue}
/>
<FormControl>
<InputLabel id="tid">Tournament</InputLabel>
<Select
labelId="tid"
id="tid"
name="tid"
value={mail.tid}
label="Tournament"
onChange={changeValue}
>
<MenuItem key={0} value="Einzelmail">
Einzelmail
</MenuItem>
{/* Alle Turniere seit 1 Jahr */}
{tournaments.map((t) => {
const dateOffset = 24 * 60 * 60 * 1000 * 365; // 1 Jahr
return (
new Date(t.veranstaltungsDatum) > Date.now() - dateOffset && (
<MenuItem key={t.id} value={t.id}>
{t.name}
</MenuItem>
)
);
})}
</Select>
</FormControl>
<FormControl>
<InputLabel id="templates">Templates</InputLabel>
<Select
labelId="templates"
id="templates"
name="templates"
value=""
label="templates"
onChange={changeTemplate}
>
<MenuItem key={0} value={""}>
reset
</MenuItem>
<MenuItem
key={1}
value={`Hallo Zusammen,
Die Listen und den Ablaufplan findet Ihr unter https://karateturniere.de/dm22/
Ebenfalls könnt ihr Euch nun wieder eine Rechnung erstellen lassen und hr seht direkt, auf welchen Pool Eure Teilnehmer starten.
Auch ein Livestream wird es wieder geben.
Am einfachsten ist es, wenn Ihr meinen YouTube Kanal abonniert. (https://www.youtube.com/channel/UCsg32mrh2kesSVFQT9adh1A)
Dann werdet ihr direkt benachrichtigt, wenn die Streams starten und Ihr findet die Streams direkt auf Eurer Startseite.
Die Links werde ich im Anschluss auch auf https://new.karateturniere.de veröffentlichen.
Hier der direkte Link zu den Streams für die DM: https://new.karateturniere.de/stream/59
Grüße,
Mario Peters`}
>
Listen Online
</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth>
<TextField
id="subject"
name="subject"
label="subject"
variant="filled"
defaultValue="DM 2022"
onChange={changeValue}
/>
</FormControl>
<FormControl fullWidth>
<TextField
id="body"
label="body"
name="body"
multiline
minRows={4}
value={mail.body}
variant="filled"
// inputProps={{ wrap: "physical" }}
// style={{ wordWrap: "break-word" }}
onChange={changeValue}
/>
</FormControl>
<br />
<button onClick={test}>test</button>
<button onClick={sendMail}>senden</button>
</Root>
);
}