create Pools, mail page, kata Select- pointsystem
This commit is contained in:
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 200
|
||||
}
|
||||
23
config-overrides.js
Normal file
23
config-overrides.js
Normal 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
25163
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
37
package.json
37
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
430
src/App.js
430
src/App.js
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
22
src/components/Competition/Points.jsx
Normal file
22
src/components/Competition/Points.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
121
src/components/Competition/PointsView.jsx
Normal file
121
src/components/Competition/PointsView.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
50
src/components/Kata/KataSelect.jsx
Normal file
50
src/components/Kata/KataSelect.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
190
src/pages/Mail.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user