This commit is contained in:
Mario Peters
2025-06-08 16:53:04 +02:00
parent c8b7454222
commit ebd6725758
65 changed files with 1550 additions and 2197 deletions

View File

@@ -1,6 +1,6 @@
import js from '@eslint/js'
import globals from 'globals'
import pluginReact from 'eslint-plugin-react'
import reactPlugin from 'eslint-plugin-react'
import pluginJsxA11y from 'eslint-plugin-jsx-a11y'
import pluginImport from 'eslint-plugin-import'
import { defineConfig } from 'eslint/config'
@@ -12,12 +12,8 @@ export default defineConfig([
{
files: ['**/*.{js,mjs,cjs,jsx}'],
languageOptions: {
// parser: babelParser,
parserOptions: {
requireConfigFile: false,
// babelOptions: {
// presets: ['@babel/preset-react'],
// },
ecmaFeatures: { jsx: true },
ecmaVersion: 2022,
sourceType: 'module',
@@ -26,7 +22,7 @@ export default defineConfig([
},
plugins: {
js,
react: pluginReact,
react: reactPlugin,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'jsx-a11y': pluginJsxA11y,
@@ -34,14 +30,15 @@ export default defineConfig([
},
extends: [
js.configs.recommended,
// pluginReact.configs.recommended,
// pluginReact.configs['jsx-runtime'],
// pluginReactHooks.configs.recommended,
// reactPlugin.configs.recommended,
// reactPlugin.configs['jsx-runtime'],
// reactHooks.configs.recommended,
// pluginJsxA11y.configs.recommended,
// pluginImport.configs.recommended,
// pluginImport.configs.typescript,
],
rules: {
...reactPlugin.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'react/jsx-uses-react': 'error',

View File

@@ -46,24 +46,28 @@ footer a:hover {
padding: 2em;
}
/* Belt styles */
.black-belt {
color: white !important;
background-color: black !important;
background-color: rgba(0, 0, 0, 0.8) !important;
}
.brown-belt {
background-color: saddlebrown !important;
background-color: rgba(168, 88, 27, 0.5) !important;
}
.violet-belt {
background-color: blueviolet !important;
background-color: rgba(138, 43, 226, 0.5) !important;
}
.green-belt {
background-color: green !important;
background-color: rgba(0, 128, 0, 0.5) !important;
}
.orange-belt {
background-color: orange !important;
background-color: rgba(255, 135, 18, 0.6) !important;
}
.yellow-belt {
background-color: yellow !important;
background-color: rgba(255, 255, 0, 0.6) !important;
}
.white-belt {
background-color: white !important;
}
/* On screens that are 600px or less */

View File

@@ -1,9 +1,9 @@
import ExitToApp from '@mui/icons-material/ExitToApp'
import { lazy, Suspense, useEffect, useState } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { Button, createTheme, ThemeProvider } from '@mui/material'
import { StyledEngineProvider } from '@mui/material/styles'
import { lazy, Suspense, useEffect, useState } from 'react'
import ExitToApp from '@mui/icons-material/ExitToApp'
import { useTranslation } from 'react-i18next'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import './App.css'
import { DialogContext } from './components/Dialog/Context'
import AppBar from './components/AppBar/AppBar'
@@ -14,6 +14,7 @@ import Spinner from './components/Spinner/Spinner'
import LoadParticipants from './components/UseFetch/LoadParticipants'
import useFetch from './components/UseFetch/UseFetch'
import LoginDialog from './components/LoginDialog/LoginDialog'
import { getStoredToken, storeToken, checkToken } from './api/account'
const Tournaments = lazy(() => import('./components/Tournaments/Tournaments'))
const Registration = lazy(() => import('./pages/Registration'))
@@ -45,8 +46,6 @@ const theme = createTheme({
})
export default function App() {
// const apiServer = 'http://localhost:8000'
// const apiServer = 'http://api.kt.wsl'
const apiServer = import.meta.env.VITE_API_SERVER
// console.log(import.meta.env)
const [token, setToken] = useState(null)
@@ -66,67 +65,31 @@ export default function App() {
// console.log('env',import.meta.env)
useEffect(() => {
!loadingFetchedTournaments && setTournaments(fetchedTournaments)
if (!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')
const ls = getStoredToken()
if (ls) {
checkToken(ls)
.then((valid) => {
if (valid) {
setToken(ls)
}
})
.catch((error) => {
console.error(`Token valid: ${error}`)
})
console.log('APP Stored token found:', ls)
checkToken(apiServer, ls).then((result) => {
if (result.valid) {
setUser({ account: result.account, admin: result.admin })
setToken(ls)
}
})
}
}, [])
}, [apiServer])
const handleToken = (account, admin, token) => {
const handleToken = (account, admin, tokenValue) => {
setUser({ account, admin })
setToken('?token=' + token)
localStorage.setItem('token', '?token=' + token)
const tokenStr = '?token=' + tokenValue
setToken(tokenStr)
storeToken(tokenStr)
}
const handleDialogOpen = () => {
setDialogOpen(true)
// const newDialog = {...dialog, open:true}
// setDialog(newDialog);
}
const handleDialogClose = () => {
setDialogOpen(false)
}
const handleDialogOpen = () => setDialogOpen(true)
const handleDialogClose = () => setDialogOpen(false)
useEffect(() => {
setDialog({
@@ -145,9 +108,6 @@ export default function App() {
dialog.setOpen()
}
// console.log('dialog',dialog.open, dialog.content)
// console.log('dialogOpen',dialogOpen)
if (loadingGroups && loadingFetchedTournaments) {
return <Spinner />
}
@@ -162,13 +122,15 @@ export default function App() {
{/* // TODO: Alle Rechnungen des Turniers generieren. */}
<AppBar openLoginDialog={openLoginDialog} handleToken={handleToken} apiServer={apiServer} token={token} user={user} />
<main className="content">
{token && <LoadParticipants setParticipants={setParticipants} apiServer={apiServer} token={token} />}
<Suspense fallback={<Spinner />}>
<Routes>
<Route
path="registration/:tid"
element={
participants &&
tournaments && <Registration participants={participants} groups={groups} tournaments={tournaments} apiServer={apiServer} token={token} user={user} />
tournaments &&
groups && <Registration apiServer={apiServer} token={token} user={user} participants={participants} groups={groups} tournaments={tournaments} />
}
/>
<Route path="results/:tid" element={<Results apiServer={apiServer} token={token} user={user} />} />
@@ -196,14 +158,13 @@ export default function App() {
setParticipants={setParticipants}
/>
)}
{token && <LoadParticipants setParticipants={setParticipants} apiServer={apiServer} token={token} />}
<br />
<br />
{!participants && (
<>
<h3>{t('headline.participants')}</h3>
<p>{t('no-login')}</p>
<Button onClick={openLoginDialog.bind(this, null)} color="primary" variant="outlined" startIcon={<ExitToApp />}>
<Button onClick={() => openLoginDialog(null)} color="primary" variant="outlined" startIcon={<ExitToApp />}>
{t('login')}
</Button>
</>

View File

@@ -61,16 +61,18 @@ export async function signUpEdit(apiServer, token, newData) {
}
export async function login(apiServer, email, password, fakeID) {
console.log('Logging in with email:', email, 'and fakeID:', fakeID)
console.log('Using API server:', apiServer)
const url = apiServer + '/authentications'
const data = { login: { email, password, fakeID } }
console.log('Request URL:', url)
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data),
mode: 'cors',
headers: { 'Content-Type': 'application/json' },
})
console.log('Response status:', response.status)
if (response.status === 200) {
return {
account: response.headers.get('x-kt-account'),
@@ -85,6 +87,8 @@ export async function login(apiServer, email, password, fakeID) {
}
export async function register(apiServer, token, newData) {
console.log('Registering with data:', newData)
const response = await fetch(apiServer + newData.register + token, {
method: 'PUT',
body: JSON.stringify(newData),
@@ -94,3 +98,30 @@ export async function register(apiServer, token, newData) {
if (!response.ok) throw new Error('Fehler bei der Registrierung')
return response.json()
}
export async function checkToken(apiServer, token) {
try {
const response = await fetch(apiServer + '/checkToken' + token)
console.log(response.status, response.ok, response.headers.get('x-kt-account'), response.headers.get('x-kt-admin'))
if (response.ok) {
const account = response.headers.get('x-kt-account')
const admin = response.headers.get('x-kt-admin')
console.log('Token check successful:', account, admin)
return { valid: true, account, admin }
} else {
localStorage.removeItem('token')
console.warn('Token check failed, removing token')
return { valid: false }
}
} catch (error) {
console.error('Token check error:', error)
return { valid: false }
}
}
export function getStoredToken() {
return localStorage.getItem('token')
}
export function storeToken(token) {
localStorage.setItem('token', token)
}

View File

@@ -85,6 +85,7 @@ export async function splitGroup(apiServer, token, newData) {
}
export async function moveToGroup(apiServer, token, data) {
console.log('moveToGroup', data)
const response = await fetch(apiServer + '/moveEncounterToGroup' + token, {
method: 'POST',
body: JSON.stringify(data),
@@ -92,5 +93,6 @@ export async function moveToGroup(apiServer, token, data) {
headers: { 'Content-Type': 'application/json' },
})
if (!response.ok) throw new Error('Fehler beim Verschieben in Gruppe')
console.log('moveToGroup response', response.ok)
return response.json()
}

View File

@@ -11,7 +11,7 @@ const classes = {
headerTitle: `${PREFIX}-headerTitle`,
}
const Root = styled(AppBar)(({ theme }) => ({
const Root = styled(AppBar)(() => ({
flexGrow: 1,
[`& .${classes.title}`]: {
flexGrow: 1,

View File

@@ -13,7 +13,6 @@ const classes = {
const Root = styled('div')({
[`& .${classes.row}`]: {
// display: 'flex',
display: 'table',
margin: '2rem',
alignItems: 'center',
@@ -29,25 +28,22 @@ const Root = styled('div')({
},
})
export default function PointsView(props) {
const { apiServer, token, groupData, allParticipants, allTeams } = props
export default function PointsView({ apiServer, token, groupData, allParticipants, allTeams }) {
const { data: groupEncounters, loading } = useFetch(apiServer + '/group/encounters/' + groupData.id + token)
const [encounter, setEncounter] = useState(null)
const [judgets, setJudgets] = useState(5)
const [judges, setJudges] = useState(5)
const [average, setAverage] = useState('')
const inputRef = useRef()
console.log('ref', inputRef)
useEffect(() => {
!loading && setEncounter(groupEncounters)
}, [groupEncounters, loading])
const encounterData = []
encounter &&
encounter.forEach((element) => {
console.log('element', element)
// Hilfsfunktion: Teilnehmerdaten für die Anzeige aufbereiten
const getEncounterData = () => {
if (!encounter) return []
return encounter.map((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)
@@ -55,8 +51,7 @@ export default function PointsView(props) {
aka = allParticipants.find((x) => x.id === element.aka)
shiro = allParticipants.find((x) => x.id === element.shiro)
}
const data = {
return {
encounters: {
id: element.id,
encounter: element.begegnung,
@@ -77,106 +72,83 @@ export default function PointsView(props) {
results: element?.wertungen,
},
}
encounterData.push(data)
})
console.log('encounterData', encounterData)
}
const Participant = (el) => {
// console.log(el)
if (el.participants.id === 0) return null
const encounterData = getEncounterData()
// Handler für Änderung der Judges/average
const changeValue = ({ target: { name, value } }) => {
if (name === 'judges') setJudges(value)
if (name === 'average') setAverage(value)
}
// Handler für Punkteänderung
const handlePointsChange = ({ target: { name, value } }) => {
const [id, number] = name.split('/')
let index = encounter.findIndex((i) => i.shiro === +id)
if (index === -1) index = encounter.findIndex((i) => i.aka === +id)
if (index === -1) return
// Kopie für State-Update!
const newEncounter = [...encounter]
if (!newEncounter[index].wertungen) newEncounter[index].wertungen = []
newEncounter[index].wertungen[parseInt(number)] = value
// Durchschnitt berechnen (ohne bestes/schlechtestes Ergebnis)
const sumArr = [...newEncounter[index].wertungen].map(Number).sort((a, b) => a - b)
if (sumArr.length > 2) {
sumArr.shift()
sumArr.pop()
}
let avg = sumArr.reduce((acc, el) => acc + el, 0)
newEncounter[index].wertungen.sum = avg
setEncounter(newEncounter)
}
// Teilnehmer-Komponente
const Participant = ({ participants }) => {
if (participants.id === 0) return null
return (
<div className={classes.row}>
<div>{el.participants.id}</div>
<div>{participants.id}</div>
<div className={classes.club}>
{!groupData.discipline.includes('Team') && <span>{el.participants.prename + ' ' + el.participants.name}</span>}
<strong>{el.participants.club}</strong>
{!groupData.discipline.includes('Team') && <span>{participants.prename + ' ' + participants.name}</span>}
<strong>{participants.club}</strong>
</div>
{/* <div>{el.participants.kata}</div> */}
<KataSelect val={el.participants.kata} />
<Points num={judgets} average={average} id={el.participants.id} results={el.participants?.results} myRef={inputRef} handlePointsChange={handlePointsChange} />
<KataSelect val={participants.kata} />
<Points num={judges} average={average} id={participants.id} results={participants?.results} myRef={inputRef} handlePointsChange={handlePointsChange} />
<FormControl sx={{ m: 1, maxWidth: 80 }} size="small">
<TextField label="Sum" variant="filled" value={el.participants?.results ? el.participants.results.sum : ''} disabled size="small" />
<TextField label="Sum" variant="filled" value={participants?.results ? participants.results.sum : ''} disabled size="small" />
</FormControl>
</div>
)
}
const changeValue = ({ target: { name, value } }) => {
name === 'judgets' && setJudgets(value)
name === 'average' && setAverage(value)
}
const handlePointsChange = ({ target: { name, value } }) => {
const [id, number] = name.split('/')
console.log('name:', name, value)
console.log('tmp', id, number)
let index = encounter.findIndex((i) => i.shiro === +id)
if (index === -1) {
index = encounter.findIndex((i) => i.aka === +id)
}
console.log('tmp', index)
console.log('encounter[index]', encounter[index])
if (encounter[index]?.wertungen === null) {
encounter[index].wertungen = []
}
encounter[index].wertungen[parseInt(number)] = value
//calc average value
const sum = [...encounter[index].wertungen]
console.log('sum1', sum)
sum.sort((a, b) => {
return a - b
})
sum.shift()
sum.pop()
let average = 0
sum.map((el) => (average += parseInt(el)))
console.log('average', average)
encounter[index].wertungen.sum = average
setEncounter(encounter)
}
// 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>
{/* judges */}
{/* Judges-Auswahl */}
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<InputLabel id="judgets">judgets</InputLabel>
<Select labelId="judgets" id="judgets" name="judgets" value="" label="judgets" onChange={changeValue}>
{[3, 4, 5, 6, 7].map((el) => {
return (
<MenuItem key={'judgets' + el} value={el}>
{el}
</MenuItem>
)
})}
<InputLabel id="judges">judges</InputLabel>
<Select labelId="judges" id="judges" name="judges" value={judges} label="judges" onChange={changeValue}>
{[3, 4, 5, 6, 7].map((el) => (
<MenuItem key={'judges' + el} value={el}>
{el}
</MenuItem>
))}
</Select>
</FormControl>
{/* average */}
{/* Average */}
<FormControl sx={{ m: 1, maxWidth: 100 }}>
<TextField name="average" type="number" label="average" defaultValue={5.5} size="small" onChange={changeValue} />
<TextField name="average" type="number" label="average" value={average} size="small" onChange={changeValue} />
</FormControl>
{/* data */}
{encounterData.map((el, i) => {
// console.log('el', el)
return (
<>
<Participant key={'a' + i} participants={el.aka}></Participant>
<Participant key={'s' + i} participants={el.shiro}></Participant>
</>
)
})}
{/* <Participant participants={participants}></Participant> */}
{/* {console.log('aa', inputRef.current.value)} */}
{/* Begegnungen */}
{encounterData.map((el, i) => (
<div key={i}>
<Participant participants={el.aka} />
<Participant participants={el.shiro} />
</div>
))}
</Root>
)
}

View File

@@ -1,18 +1,17 @@
import CloseIcon from '@mui/icons-material/Close'
import { lazy, useEffect, useState, useRef } from 'react'
import { AppBar, Button, Dialog, IconButton, styled, Toolbar, Typography, TextField, DialogContent, DialogTitle, DialogActions } from '@mui/material'
import PointsView from '../Competition/PointsView'
import GroupInfo from '../Groups/GroupInfo'
import KoView from '../Groups/KoView'
import useFetch from '../UseFetch/UseFetch'
import { generateRounds } from '../../utilities/Rounds'
import { AppBar, Button, Dialog, IconButton, styled, Toolbar, Typography, TextField } from '@mui/material'
const ListPDFTemplate = lazy(() => import('../PDF/ListPDFTemplate'))
import { pdf } from '@react-pdf/renderer'
import { savePdf } from '../../utilities/PDF'
import { changeEncounter, setWinner, addResults, lateRegistration } from '../../api/encounter'
const PREFIX = 'EncounterDialog'
const classes = {
appBar: `${PREFIX}-appBar`,
title: `${PREFIX}-title`,
@@ -36,7 +35,7 @@ export default function EncounterDialog(props) {
const inputRef = useRef(null)
useEffect(() => {
!loading && setEncounter(groupEncounters)
if (!loading) setEncounter(groupEncounters)
}, [groupEncounters, loading])
useEffect(() => {
@@ -45,9 +44,7 @@ export default function EncounterDialog(props) {
}
}, [groupData])
const toggleKoView = () => {
setKoView(!koView)
}
const toggleKoView = () => setKoView((prev) => !prev)
const showPDF = async () => {
const gid = groupData.gid + '-' + groupData.id
@@ -55,16 +52,13 @@ export default function EncounterDialog(props) {
try {
const blob = await pdf(<ListPDFTemplate {...props} tournament={tournamentData} rounds={rounds} />).toBlob()
url = URL.createObjectURL(blob)
const response = await fetch(url)
const blobData = await response.blob()
const blobUrl = window.URL.createObjectURL(blobData)
const link = document.createElement('a')
link.href = blobUrl
link.download = `${gid}.pdf`
link.click()
blob && savePdf(blob, 'lists', gid, apiServer, token, tournamentData)
} catch (error) {
console.error('Error in download process:', error)
@@ -78,7 +72,6 @@ export default function EncounterDialog(props) {
newArr[data.src.encounter][data.src.color] = data.dest.id
newArr[data.dest.encounter][data.dest.color] = data.src.id
setEncounter(newArr)
try {
await changeEncounter(apiServer, token, data, groupData.id)
console.log('participant changed')
@@ -91,12 +84,10 @@ export default function EncounterDialog(props) {
const targetEncounter = Math.floor(encounterNum / 2)
const rem = encounterNum % 2
const index = encounter.findIndex((i) => i.begegnung === targetEncounter)
//set winner local
// Sieger lokal setzen
const oldIndex = encounter.findIndex((i) => i.begegnung === encounterNum)
encounter[oldIndex].sieger = data.id
//write winner to next round
// Sieger in nächste Runde schreiben
const color = rem ? 'aka' : 'shiro'
if (encounterNum > 1) {
encounter[index][color] = data.id
@@ -115,11 +106,8 @@ export default function EncounterDialog(props) {
} catch (err) {
console.error(err)
}
// Ergebnisse speichern, falls Finale
if (encounterNum === 1 && groupData.pool === 'Final') {
console.log('beg', encounterNum)
console.log('group', groupData)
let result
if (encounter.length === 1) {
result = {
@@ -138,10 +126,6 @@ export default function EncounterDialog(props) {
p4: encounter[0].aka !== encounter[0].sieger ? encounter[0].aka : encounter[0].shiro,
}
}
console.log('result', result)
// write results to db
try {
await addResults(apiServer, token, result)
console.log('Ergebnisse hinzugefügt')
@@ -152,23 +136,17 @@ export default function EncounterDialog(props) {
}
const handleClick = async (e) => {
e = e.target.attributes
console.log('attributes', e)
console.log('groupData', groupData)
if (e['data-id']?.value == 0) {
const encounter = {
color: e['class'].value.includes('aka') ? 'aka' : 'shiro',
encounter: e['data-encounter'].value,
const attrs = e.target.attributes
if (attrs['data-id']?.value == 0) {
const encounterObj = {
color: attrs['class'].value.includes('aka') ? 'aka' : 'shiro',
encounter: attrs['data-encounter'].value,
gid: groupData.id,
team: groupData.discipline.includes('Team') ? true : false,
team: groupData.discipline.includes('Team'),
tid: inputRef.current.value,
}
console.log('encounter', encounter)
// write results to db
try {
await lateRegistration(apiServer, token, encounter)
await lateRegistration(apiServer, token, encounterObj)
console.log(inputRef.current.value + ' nachgemeldet')
} catch (err) {
console.error(err)
@@ -176,10 +154,8 @@ export default function EncounterDialog(props) {
}
}
const lateRegistrationHandler = (e) => {
console.log('e', e)
const lateRegistrationHandler = () => {
document.addEventListener('click', handleClick, true)
//
}
const rounds = generateRounds(encounter, groupData, allTeams, allParticipants, change, setWinnerHandler)
@@ -200,7 +176,6 @@ export default function EncounterDialog(props) {
<Button autoFocus color="inherit" onClick={toggleKoView}>
toggle
</Button>
{/* //TODO: Late registration */}
{user?.admin > 4 && (
<>
<TextField id="tid" inputProps={{ ref: inputRef }} label="tid" type="number" variant="filled" size="small" />
@@ -211,7 +186,6 @@ export default function EncounterDialog(props) {
)}
</Toolbar>
</AppBar>
{/* Body */}
<GroupInfo groupData={groupData} />
{koView && <KoView rounds={rounds} />}
{!koView && <PointsView groupData={groupData} token={token} apiServer={apiServer} allParticipants={allParticipants} allTeams={allTeams} />}

View File

@@ -1,119 +1,100 @@
import { useContext, useState } from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import Autocomplete from '@mui/material/Autocomplete';
import useFetch from '../UseFetch/UseFetch';
import { DialogContext } from './Context';
import { useContext, useState } from 'react'
import { Button, TextField, DialogActions, DialogContent, DialogTitle, Radio, RadioGroup, FormControlLabel, FormControl, FormLabel, Autocomplete } from '@mui/material'
import useFetch from '../UseFetch/UseFetch'
import { DialogContext } from './Context'
export default function ProfileDialog({ apiServer, edit, t, token }) {
const { data: clubs = [], loading } = useFetch(apiServer + '/getClubs' + token)
const dialog = useContext(DialogContext)
export default function ProfileDialog({apiServer, edit, t, token}) {
const { data: clubs, loading } = useFetch(apiServer + "/getClubs" + token);
const dialog = useContext(DialogContext);
const initialFormData = Object.freeze({
gender: 'M',
name: '',
prename: '',
club: '',
email: '',
email2: '',
password: '',
password2: '',
phone: '',
});
const [formData, setFormData] = useState(initialFormData);
// useEffect(() => {
// edit && setFormData(edit)
// }, [edit]);
const handleClose = () => {
dialog.onClose()
setFormData(initialFormData)
// setEdit(null)
}
const handleReset = () => {
edit ? setFormData(edit) : setFormData(initialFormData)
}
const handleChange = (e) => {
let targetKey = e.target.name;
let targetValue = e.target.value;
// Fix for Autocomplete
if (e.target?.classList?.contains('MuiAutocomplete-input')) {
targetKey = 'club';
const initialFormData = {
gender: 'M',
name: '',
prename: '',
club: '',
email: '',
email2: '',
password: '',
password2: '',
phone: '',
}
if (e.target?.classList?.contains('MuiAutocomplete-option')) {
targetKey = 'club';
targetValue = e.target.innerText;
const [formData, setFormData] = useState(initialFormData)
// Wenn edit übergeben wird, initialisiere das Formular damit
// useEffect(() => {
// if (edit) setFormData(edit)
// }, [edit])
const handleClose = () => {
dialog.onClose()
setFormData(initialFormData)
}
setFormData({
...formData,
[targetKey]: targetValue
});
};
const handleReset = () => {
setFormData(edit ? edit : initialFormData)
}
const handleSave = (e) => {
e.preventDefault();
// if (edit) {
// const index = participants.findIndex(i => i.id === edit.id);
// participants[index] = formData;
// editParticipant(formData)
// } else {
// addParticipant(formData);
// }
dialog.onClose();
};
const handleChange = (e) => {
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]: value,
}))
}
return !loading && <>
<DialogTitle id="dialog-title">{t('profile.myAccount')}</DialogTitle>
<DialogContent>
<FormControl component="fieldset">
<FormLabel component="legend">{t('profile.gender')}</FormLabel>
<RadioGroup aria-label="gender" name="gender" value={formData.gender} onChange={handleChange}>
<FormControlLabel value="W" control={<Radio />} label={t('profile.female')} />
<FormControlLabel value="M" control={<Radio color="primary" />} label={t('profile.male')} />
</RadioGroup>
</FormControl>
<TextField autoFocus margin="dense" name="name" variant="standard" label={t('participant.name')} type="text" fullWidth value={formData.name} onChange={handleChange} />
<TextField margin="dense" name="prename" variant="standard" label={t('participant.forename')} type="text" fullWidth value={formData.prename} onChange={handleChange} />
<Autocomplete
id="club"
freeSolo={true}
options={clubs}
value={formData.verein}
renderInput={(params) => <TextField {...params} variant="standard" label={t('participant.club')} margin="dense" onBlur={handleChange} />}
/>
// Spezieller Handler für Autocomplete (verein)
const handleClubChange = (event, newValue) => {
setFormData((prev) => ({
...prev,
club: newValue || '',
}))
}
<TextField margin="dense" name="email" variant="standard" label={t('email')} type="text" fullWidth value={formData.email} onChange={handleChange} />
<TextField margin="dense" name="password" variant="standard" label={t('password')} type="password" fullWidth value={formData.password} onChange={handleChange} />
<TextField margin="dense" name="password2" variant="standard" label={t('password2')} type="password" fullWidth value={formData.password2} onChange={handleChange} />
<TextField margin="dense" name="phone" variant="standard" label={t('phone')} type="text" fullWidth value={formData.phone} onChange={handleChange} />
const handleSave = (e) => {
e.preventDefault()
// Hier könntest du add/edit-Logik einbauen
dialog.onClose()
}
</DialogContent>
<DialogActions>
<Button onClick={handleReset} color="secondary">
reset
</Button>
<Button onClick={handleClose} color="secondary">
Cancel
</Button>
<Button onClick={handleSave} variant="outlined" color="primary">
OK
</Button>
</DialogActions>
</>;
return (
!loading && (
<>
<DialogTitle id="dialog-title">{t('profile.myAccount')}</DialogTitle>
<DialogContent>
<FormControl component="fieldset" sx={{ mb: 2 }}>
<FormLabel component="legend">{t('profile.gender')}</FormLabel>
<RadioGroup aria-label="gender" name="gender" value={formData.gender} onChange={handleChange} row>
<FormControlLabel value="W" control={<Radio />} label={t('profile.female')} />
<FormControlLabel value="M" control={<Radio color="primary" />} label={t('profile.male')} />
</RadioGroup>
</FormControl>
<TextField autoFocus margin="dense" name="name" variant="standard" label={t('participant.name')} type="text" fullWidth value={formData.name} onChange={handleChange} />
<TextField margin="dense" name="prename" variant="standard" label={t('participant.forename')} type="text" fullWidth value={formData.prename} onChange={handleChange} />
<Autocomplete
id="club"
freeSolo
options={clubs}
value={formData.club}
onChange={handleClubChange}
renderInput={(params) => <TextField {...params} variant="standard" label={t('participant.club')} margin="dense" name="club" onChange={handleChange} />}
/>
<TextField margin="dense" name="email" variant="standard" label={t('email')} type="email" fullWidth value={formData.email} onChange={handleChange} />
<TextField margin="dense" name="password" variant="standard" label={t('password')} type="password" fullWidth value={formData.password} onChange={handleChange} />
<TextField margin="dense" name="password2" variant="standard" label={t('password2')} type="password" fullWidth value={formData.password2} onChange={handleChange} />
<TextField margin="dense" name="phone" variant="standard" label={t('phone')} type="text" fullWidth value={formData.phone} onChange={handleChange} />
</DialogContent>
<DialogActions>
<Button onClick={handleReset} color="secondary">
reset
</Button>
<Button onClick={handleClose} color="secondary">
Cancel
</Button>
<Button onClick={handleSave} variant="outlined" color="primary">
OK
</Button>
</DialogActions>
</>
)
)
}

View File

@@ -24,18 +24,11 @@ export default function AddGroups({ token, apiServer, tid }) {
const handleClose = () => {
dialog.onClose()
setFormData([initialFormData]) //TODO: nötig ?
setFormData([initialFormData])
}
// console.log('formData up', formData[0].gid)
// console.log('[initialFormData]', [initialFormData][0].gid);
// console.log('[initialFormData]', formData[0].gid === '');
const handleNewGroup = () => {
const newFormData = [...formData, initialFormData]
setFormData(newFormData)
dialog.setContent(<AddGroups token={token} apiServer={apiServer} tid={tid} />)
console.log('formData-handleNewGroup', newFormData)
setFormData((prev) => [...prev, { ...initialFormData }])
}
const handleSave = async (e) => {
@@ -52,14 +45,9 @@ export default function AddGroups({ token, apiServer, tid }) {
<>
<DialogTitle id="form-dialog-title">{t('groups.create')}</DialogTitle>
<DialogContent>
{/* add new groups */}
{/* {formData.length === 0 && <OneGroup group={formData} key={'new'} />} */}
{formData.length > 0 &&
formData.map((group, index) => {
return <OneGroup key={index} myKey={index} token={token} apiServer={apiServer} formData={formData} setFormData={setFormData} />
})}
{formData.map((group, index) => (
<OneGroup key={index} myKey={index} token={token} apiServer={apiServer} formData={formData} setFormData={setFormData} />
))}
<IconButton aria-label="add group" onClick={handleNewGroup} size="large">
<Add />
</IconButton>

View File

@@ -1,11 +1,11 @@
import { useContext, useEffect, useState, Suspense, lazy } from 'react'
import { DialogContext } from '../Dialog/Context'
import { useTranslation } from 'react-i18next'
const OneGroup = lazy(() => import('./OneGroup'))
import { editGroups } from '../../api/groups'
import { Button, DialogActions, DialogContent, DialogTitle, CircularProgress } from '@mui/material'
const OneGroup = lazy(() => import('./OneGroup'))
export default function EditGroups({ token, apiServer, groups, tid }) {
const initialFormData = Object.freeze({
gid: '',
@@ -22,8 +22,9 @@ export default function EditGroups({ token, apiServer, groups, tid }) {
const dialog = useContext(DialogContext)
const { t } = useTranslation('common')
// Wenn Gruppen übergeben werden, setze sie als formData
useEffect(() => {
groups?.length > 0 && setFormData(groups)
if (groups?.length > 0) setFormData(groups)
}, [groups])
const handleClose = () => {
@@ -34,9 +35,6 @@ export default function EditGroups({ token, apiServer, groups, tid }) {
const handleReset = () => {
setFormData(groups)
}
// console.log('formData up', formData[0].gid)
// console.log('[initialFormData]', [initialFormData][0].gid);
// console.log('[initialFormData]', formData[0].gid === '');
const handleSave = async (e) => {
e.preventDefault()

View File

@@ -9,21 +9,20 @@ const StyledCard = styled(Card)({
backgroundColor: 'rgb(64 80 181 / 40%)',
})
export default function GroupInfo(props) {
const { groupData } = props
// console.log('groupData',groupData);
export default function GroupInfo({ groupData = {} }) {
const { gid = '', id = '', count = '', age = '', discipline = '', belt = '', gender = '', pool = '' } = groupData
return (
<StyledCard>
<CardContent>
<p>
Gruppe: {groupData.gid} - ({groupData.id}) #{groupData.count}
Gruppe: {gid} - ({id}){count && ` #${count}`}
</p>
<p>Alter: {groupData.age}</p>
<p>Disziplin: {groupData.discipline}</p>
<p>Gurt: {groupData.belt}</p>
<p>Geschlecht: {groupData.gender}</p>
<p>Pool: {groupData.pool}</p>
<p>Alter: {age}</p>
<p>Disziplin: {discipline}</p>
<p>Gurt: {belt}</p>
<p>Geschlecht: {gender}</p>
<p>Pool: {pool}</p>
</CardContent>
</StyledCard>
)

View File

@@ -5,7 +5,6 @@ import { styled } from '@mui/material/styles'
import { beltColor } from '../../utilities/Belt'
const PREFIX = 'GroupsParticipants'
const classes = {
id: `${PREFIX}-id`,
}
@@ -20,48 +19,51 @@ const Root = styled('li')(({ theme }) => ({
}))
export default function GroupParticipants({ apiServer, token, groupId, participants, allParticipants, user }) {
const { data: groupParticipants, loading } = useFetch(apiServer + '/group/' + groupId + '/participants' + token)
// Move Participants to another group
const drag = (ev) => {
const tid = ev.target.getAttribute('data-tid')
const gid = ev.target.getAttribute('data-gid')
const { data: groupParticipants = [], loading } = useFetch(apiServer + '/group/' + groupId + '/participants' + token)
// Teilnehmer für Drag & Drop vorbereiten
const handleDragStart = (ev) => {
const tid = ev.currentTarget.getAttribute('data-tid')
const gid = ev.currentTarget.getAttribute('data-gid')
ev.dataTransfer.setData('gid', gid)
ev.dataTransfer.setData('tid', tid)
ev.dataTransfer.setData('table', 'teilnehmer')
}
//const ownTournament = checkOwnTournament(accountId, tournamentId)
const showParticipants = () => {
const result = []
groupParticipants.forEach((element, index) => {
let item
if (user?.admin > 9) {
item = allParticipants?.find((x) => x.id === element.id)
}
// TODO: hier muss geprüft werden, ob der account auch das turnier erstellt hat
else if (user?.admin > 4) {
item = element
} else {
item = participants.find((x) => x.id === element.id)
}
item?.name &&
result.push(
<Root key={index} data-tid={item?.id} data-gid={groupId} draggable onDragStart={drag}>
<span className={classes.id}>{index + 1}.</span>
<Chip size="small" label={item?.gurt} style={{ background: beltColor(item?.gurt) }}></Chip>
<strong>
{item?.vorname} {item?.name}
</strong>{' '}
({getAge(item?.gebDatum)}) <i>[{item?.verein}]</i>({item?.id})
</Root>,
)
})
return result
// Teilnehmer anhand der Admin-Stufe auswählen
const findParticipant = (element) => {
if (user?.admin > 9) {
return allParticipants?.find((x) => x.id === element.id)
} else if (user?.admin > 4) {
return element
} else {
return participants.find((x) => x.id === element.id)
}
}
return <>{loading ? <div>...loading</div> : showParticipants()}</>
return (
<>
{loading ? (
<div>...loading</div>
) : (
groupParticipants?.length > 0 &&
groupParticipants
.map((element, index) => {
const item = findParticipant(element)
if (!item?.name) return null
return (
<Root key={item.id || index} data-tid={item.id} data-gid={groupId} draggable onDragStart={handleDragStart}>
<span className={classes.id}>{index + 1}.</span>
<Chip size="small" label={item.gurt} style={{ background: beltColor(item.gurt) }} />
<strong>
{item.vorname} {item.name}
</strong>
&nbsp;({getAge(item.gebDatum)}) <i>[{item.verein}]</i>({item.id})
</Root>
)
})
.filter(Boolean)
)}
</>
)
}

View File

@@ -1,44 +1,41 @@
import { styled } from '@mui/material/styles'
import useFetch from '../UseFetch/UseFetch'
const PREFIX = 'GroupsTeams'
const PREFIX = 'GroupTeams'
const classes = {
id: `${PREFIX}-id`,
}
const Root = styled('li')(({ theme }) => ({
const Root = styled('li')({
display: 'flex',
gap: '0.6rem',
marginBottom: '0.2rem',
[`& .${classes.id}`]: {
minWidth: '1rem',
},
}))
})
export default function GroupTeams(props) {
const { apiServer, token, groupId } = props
const { data: groupTeams, loading } = useFetch(`${apiServer}/group/${groupId}/teams${token}`)
export default function GroupTeams({ apiServer, token, groupId }) {
const { data: groupTeams = [], loading } = useFetch(`${apiServer}/group/${groupId}/teams${token}`)
// Move Teams to another group
const drag = (ev) => {
const tid = ev.target.getAttribute('data-tid')
const gid = ev.target.getAttribute('data-gid')
ev.dataTransfer.setData('gid', gid)
const handleDragStart = (ev) => {
const tid = ev.currentTarget.getAttribute('data-tid')
ev.dataTransfer.setData('gid', groupId)
ev.dataTransfer.setData('tid', tid)
ev.dataTransfer.setData('table', 'team')
}
const showTeams = () => {
if (!groupTeams || !Array.isArray(groupTeams)) return null;
return groupTeams.map((el, index) => (
<Root key={el.id} data-tid={el.id} data-gid={groupId} draggable onDragStart={drag}>
<span className={classes.id}>{index + 1}.</span>
<strong>{el.teamName}</strong>({el.id})
</Root>
));
}
if (loading) return <div>...loading</div>
if (!groupTeams?.length) return null
return <>{loading ? <div>...loading</div> : showTeams()}</>
return (
<>
{groupTeams.map((el, index) => (
<Root key={el.id} data-tid={el.id} data-gid={groupId} draggable onDragStart={handleDragStart}>
<span className={classes.id}>{index + 1}.</span>
<strong>{el.teamName}</strong>({el.id})
</Root>
))}
</>
)
}

View File

@@ -4,35 +4,24 @@ import EncounterDialog from '../Dialog/EncounterDialog'
import GroupsTable from './GroupsTable'
import { triggerGroup } from '../../api/groups'
export default function Groups(props) {
const { apiServer, token, tournamentGroups, tournamentData, user, participants } = props
export default function Groups({ apiServer, token, tournamentGroups, tournamentData, user, participants }) {
const [dataTournamentGroup, setDataTournamentGroup] = useState(tournamentGroups)
const [open, setOpen] = useState(false)
const [groupData, setGroupData] = useState()
const [groupData, setGroupData] = useState(null)
// Alle Teilnehmer und Teams laden
const { data: allParticipants } = useFetch(apiServer + '/allParticipants' + token)
const { data: allTeams } = useFetch(apiServer + '/teams')
// KO View Dialog Start
// Öffnet den KO-Dialog mit den Gruppendaten
const handleClickOpen = (id, gid, age, belt, discipline, gender, pool, count) => {
setGroupData({
id,
gid,
age,
belt,
discipline,
gender,
pool,
count,
})
setGroupData({ id, gid, age, belt, discipline, gender, pool, count })
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
// Dialog End
const handleClose = () => setOpen(false)
// Gruppe triggern (API-Aufruf)
const handleTriggerGroup = async (id, discipline) => {
try {
await triggerGroup(apiServer, token, id, discipline)

View File

@@ -27,7 +27,6 @@ import SearchBar from '../Common/SearchBar'
import { deleteGroup, triggerFinalists, splitGroup, moveToGroup } from '../../api/groups'
const PREFIX = 'GroupsTable'
const classes = {
participants: `${PREFIX}-participants`,
participantsIcon: `${PREFIX}-participantsIcon`,
@@ -51,8 +50,6 @@ const Root = styled('div')(({ theme }) => ({
[`& .${classes.expandOpen}`]: {
transform: 'rotate(180deg)',
},
// maxWidth: '600px',
// display: 'inline-block',
[`& div.${classes.participants}:nth-of-type(2n)`]: {
backgroundColor: 'rgba(0,0,0,0.1)',
},
@@ -66,11 +63,6 @@ const Root = styled('div')(({ theme }) => ({
justifyContent: 'space-between',
marginBottom: '0.3em',
position: 'static',
// padding: '0.3em',
// borderRadius: '0.3em',
// borderWidth: '3px',
// borderStyle: 'solid',
// borderImage: 'linear-gradient(to bottom, red, rgba(0, 0, 0, 0)) 1 100%',
},
[`& .${classes.groupHeaderActions}`]: {
display: 'flex',
@@ -140,14 +132,13 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
const [confirmOpen, setConfirmOpen] = useState(false)
const [toDelete, setToDelete] = useState(null)
const [isDragging, setIsDragging] = useState(false)
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const [allOpen, setAllOpen] = useState(false)
const [openAccordions, setOpenAccordions] = useState([])
const groupsPerPage = 20 // Anzahl pro Seite, anpassbar
const groupsPerPage = 20
const filteredGroups = dataTournamentGroup.filter(
(el) =>
(el.disziplin || '').toLowerCase().includes(search.toLowerCase()) ||
@@ -159,10 +150,9 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
const pageCount = Math.ceil(filteredGroups.length / groupsPerPage)
const paginatedGroups = filteredGroups.slice((page - 1) * groupsPerPage, page * groupsPerPage)
// Löschen einer Gruppe
const handleDeleteConfirm = async () => {
const groupsRemoved = dataTournamentGroup.filter((x) => x.id != toDelete.id)
const groupsRemoved = dataTournamentGroup.filter((x) => x.id !== toDelete.id)
setDataTournamentGroup(groupsRemoved)
try {
await deleteGroup(apiServer, token, toDelete.id)
@@ -173,10 +163,9 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
setConfirmOpen(false)
}
const handleDeleteCancel = () => {
setConfirmOpen(false)
}
const handleDeleteCancel = () => setConfirmOpen(false)
// Hilfsfunktion für Finalisten
const getPreroundGroups = (gid) => {
const result = []
let discipline
@@ -195,6 +184,7 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
}
}
// Finalisten triggern
const handleTriggerFinalists = async (id, gid) => {
const { groups, pools, discipline } = getPreroundGroups(gid)
try {
@@ -210,7 +200,6 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
dialog.setOpen()
}
const handleGroupDrop = (ev, gid) => {
ev.preventDefault()
setIsDragging(false) // <--- Highlighting nach Drop zurücksetzen
@@ -232,13 +221,11 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
gid: +gid,
},
}
moveToGroup(result)
moveToGroup(apiServer, token, result)
}
}
// const dataTest = [{id: 1068, gid: 2, turnier_id: 54, geschlecht: "M", gurtVon: "6. Kyu", gurtBis: "6. Kyu",}]
// Toggle alle Accordions
// Alle Accordions öffnen/schließen
const handleToggleAll = useCallback(() => {
if (allOpen) {
setOpenAccordions([])
@@ -249,7 +236,7 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
}
}, [allOpen, paginatedGroups])
// Einzelnes Accordion toggeln (optional, falls du das Verhalten anpassen willst)
// Einzelnes Accordion toggeln
const handleAccordionChange = (id) => (event, expanded) => {
setOpenAccordions((prev) => (expanded ? [...prev, id] : prev.filter((openId) => openId !== id)))
}
@@ -260,7 +247,6 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
<IconButton className={classes.expand + (allOpen ? ' ' + classes.expandOpen : '')} onClick={handleToggleAll} aria-expanded={allOpen} aria-label="show more" size="large">
<ExpandIcon />
</IconButton>
<SearchBar
value={search}
onChange={(e) => {
@@ -281,16 +267,14 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
return (
<Accordion
slotProps={{ heading: { component: 'div' } }}
key={index}
key={el.id}
expanded={openAccordions.includes(el.id)}
onChange={handleAccordionChange(el.id)}
data-tid={el.id}
data-gid={el.gid}
className={classes.participants}
onDragStart={() => setIsDragging(true)}
onDragOver={(ev) => {
ev.preventDefault()
}}
onDragOver={(ev) => ev.preventDefault()}
onDrop={(ev) => handleGroupDrop(ev, el.id)}
style={{
transition: 'background 0.2s, border 0.2s',
@@ -299,37 +283,34 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
borderRadius: '1em',
}}
>
{/* TODO: use AccordionSummary from mui aber rendert ein button */}
<AccordionSummary component="div" className={classes.accordionSummary} expandIcon={<ExpandMoreIcon />} aria-controls={`group${el.id}`} id={`panel${el.id}-header`}>
<div className={classes.participantsIcon}>
<Tooltip title={el.id}>{isSingle ? <Person color={gender} fontSize="large" /> : <Group color={gender} fontSize="large" />}</Tooltip>
{el.gid}
</div>
<div className={classes.groupData}>
<Chip className={classes.disziplin} size="medium" variant="outlined" color="primary" label={isKata ? (isMixed ? 'Kata-Mixed' : 'Kata') : 'Kumite'} />
<div className={classes.ageGrade}>
<Chip size="small" variant="outlined" color="primary" label={el.altervon + ' - ' + el.alterbis + ' ' + t('participant.years')} />
<Chip
size="small"
variant="filled"
color="primary"
label={el.gurtVon + '    ' + el.gurtBis}
style={{ backgroundImage: `linear-gradient(90deg, ${beltColor(el.gurtVon)} 50%, ${beltColor(el.gurtBis)} 50%)` }}
style={{
backgroundImage: `linear-gradient(90deg, ${beltColor(el.gurtVon)} 50%, ${beltColor(el.gurtBis)} 50%)`,
}}
/>
</div>
<Chip size="medium" variant="outlined" color="primary" icon={<GridView />} label={el.pool} />
</div>
{user?.admin > 4 && (
<div className={classes.actions}>
<Tooltip title="show list">
<IconButton
size="large"
onClick={(e) => {
e.stopPropagation() // Prevent accordion toggle
e.stopPropagation()
handleClickOpen(
el.id,
el.gid,
@@ -356,19 +337,17 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
<AccountTree />
</IconButton>
</Tooltip>
<Tooltip title="get Finalists">
<IconButton
size="large"
onClick={(e) => {
e.stopPropagation()
triggerFinalists(el.id, el.gid)
handleTriggerFinalists(el.id, el.gid)
}}
>
<Pin />
</IconButton>
</Tooltip>
<Tooltip title="split group">
<IconButton
size="large"
@@ -407,8 +386,11 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
{!user?.account ? null : (
<AccordionDetails>
<ol style={{ margin: 0, padding: 0 }}>
{isSingle && <GroupParticipants groupId={el.id} token={token} apiServer={apiServer} participants={participants} user={user} allParticipants={allParticipants} />}
{!isSingle && <GroupTeams groupId={el.id} token={token} apiServer={apiServer} />}
{isSingle ? (
<GroupParticipants groupId={el.id} token={token} apiServer={apiServer} participants={participants} user={user} allParticipants={allParticipants} />
) : (
<GroupTeams groupId={el.id} token={token} apiServer={apiServer} />
)}
</ol>
</AccordionDetails>
)}
@@ -419,6 +401,7 @@ export default function GroupsTable({ dataTournamentGroup, setDataTournamentGrou
{/* Pagination */}
<Pagination count={pageCount} page={page} onChange={(_, value) => setPage(value)} sx={{ mt: 2, display: 'flex', justifyContent: 'center' }} />
{/* Bestätigungsdialog für Löschen */}
<Dialog open={confirmOpen} onClose={handleDeleteCancel}>
<DialogTitle>Löschen bestätigen</DialogTitle>
<DialogContent>

View File

@@ -2,9 +2,7 @@ import KoEncounterItem from './KoEncounterItem'
import { styled, Card, CardContent } from '@mui/material'
const PREFIX = 'KoEncounter'
const classes = {
root: `${PREFIX}-root`,
cardContent: `${PREFIX}-cardContent`,
}
@@ -17,17 +15,14 @@ const StyledCard = styled(Card)({
},
})
export default function KoEncounter(props) {
const { encounterData, change, num, setWinner } = props
const allowDrop = (ev) => {
ev.preventDefault()
}
export default function KoEncounter({ encounterData, change, num, setWinner }) {
const allowDrop = (ev) => ev.preventDefault()
const drop = (ev) => {
ev.preventDefault()
if (!ev.target.getAttribute('data-id')) {
ev.target = ev.target.parentNode
let target = ev.target
if (!target.getAttribute('data-id') && target.parentNode) {
target = target.parentNode
}
const result = {
@@ -38,23 +33,20 @@ export default function KoEncounter(props) {
color: ev.dataTransfer.getData('color'),
},
dest: {
id: +ev.target.getAttribute('data-id'),
encounter: num - ev.target.getAttribute('data-encounter'),
color: ev.target.className.includes('aka') ? 'aka' : 'shiro',
id: +target.getAttribute('data-id'),
encounter: num - target.getAttribute('data-encounter'),
color: target.className.includes('aka') ? 'aka' : 'shiro',
},
}
// console.log('ev.target', ev.target); // target
console.log('result', result) // target
change(result)
}
return (
<StyledCard>
<CardContent className={classes.cardContent} onDragOver={allowDrop} onDrop={drop}>
<KoEncounterItem color={'aka'} data={encounterData.aka} encounter={encounterData.encounters.encounter} setWinner={setWinner} />
<KoEncounterItem color={'shiro'} data={encounterData.shiro} encounter={encounterData.encounters.encounter} setWinner={setWinner} />
<KoEncounterItem color="aka" data={encounterData.aka} encounter={encounterData.encounters.encounter} setWinner={setWinner} />
<KoEncounterItem color="shiro" data={encounterData.shiro} encounter={encounterData.encounters.encounter} setWinner={setWinner} />
</CardContent>
</StyledCard>
)

View File

@@ -1,82 +1,71 @@
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'
import { styled, IconButton } from '@mui/material'
export default function KoEncounterItem(props) {
const { color, data, encounter, setWinner } = props
const PREFIX = 'KoEncounterItem'
const classes = {
aka: `${PREFIX}-aka`,
shiro: `${PREFIX}-shiro`,
rotate: `${PREFIX}-rotate`,
encounter: `${PREFIX}-encounter`,
}
const PREFIX = 'KoEncounterItem'
const Root = styled('div')({
[`&.${classes.aka}`]: {
color: 'rgba(255,255,255,0.8)',
backgroundColor: 'darkred',
display: 'flex',
minHeight: 50,
justifyContent: 'space-between',
},
[`&.${classes.shiro}`]: {
color: 'rgba(0,0,0,0.8)',
backgroundColor: 'lightgrey',
display: 'flex',
minHeight: 50,
justifyContent: 'space-between',
},
[`& .${classes.rotate}`]: {
transform: 'rotate(-90deg)',
paddingTop: 10,
},
[`& .${classes.encounter}`]: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
alignSelf: 'center',
},
})
const classes = {
aka: `${PREFIX}-aka`,
shiro: `${PREFIX}-shiro`,
rotate: `${PREFIX}-rotate`,
encounter: `${PREFIX}-encounter`,
}
const WinButton = styled(IconButton)({
color: '#ab9419cf',
})
const Root = styled('div')({
[`&.${classes.aka}`]: {
color: 'rgba(255,255,255,0.8)',
backgroundColor: 'darkred',
display: 'flex',
minHeight: 50,
justifyContent: 'space-between',
},
[`&.${classes.shiro}`]: {
color: 'rgba(0,0,0,0.8)',
backgroundColor: 'lightgrey',
display: 'flex',
minHeight: 50,
justifyContent: 'space-between',
},
[`& .${classes.rotate}`]: {
transform: 'rotate(-90deg)',
paddingTop: 10,
},
[`& .${classes.encounter}`]: {
display: 'flex',
alignItems: 'center',
},
[`& .${classes.win}`]: {
color: '#FFF',
backgroundColor: 'green',
},
})
const WinButton = styled(IconButton)(({ theme }) => ({
color: '#ab9419cf',
}))
export default function KoEncounterItem({ color, data, encounter, setWinner }) {
const drag = (ev) => {
const id = ev.target.getAttribute('data-id')
const encounter = ev.target.getAttribute('data-encounter')
const color = ev.target.getAttribute('class').includes('aka') ? 'aka' : 'shiro'
const id = ev.currentTarget.getAttribute('data-id')
const encounterVal = ev.currentTarget.getAttribute('data-encounter')
const colorVal = ev.currentTarget.className.includes('aka') ? 'aka' : 'shiro'
ev.dataTransfer.setData('id', id)
ev.dataTransfer.setData('encounter', encounter)
ev.dataTransfer.setData('color', color)
}
const EncounterData = () => {
if (data.name) {
return (
<div>
{data.prename} {data.name}
<br />
<strong>{data.club}</strong>
</div>
)
} else {
return (
<div className={classes.encounter}>
<strong>{data.club}</strong>
</div>
)
}
ev.dataTransfer.setData('encounter', encounterVal)
ev.dataTransfer.setData('color', colorVal)
}
return (
<Root data-id={data.id} data-encounter={encounter} draggable onDragStart={drag} className={classes[color]}>
<div className={classes.rotate}>{data.id === 0 ? '0000' : data.id}</div>
<EncounterData />
<WinButton onClick={setWinner.bind(this, data, encounter)} className={classes.win}>
<EmojiEventsIcon className={classes.win} />
<div className={classes.encounter}>
{data.name ? (
<>
{data.prename} {data.name}
<strong>{data.club}</strong>
</>
) : (
<strong>{data.club}</strong>
)}
</div>
<WinButton onClick={() => setWinner(data, encounter)}>
<EmojiEventsIcon />
</WinButton>
</Root>
)
}
}

View File

@@ -1,7 +1,6 @@
import { styled } from '@mui/material'
const PREFIX = 'KoView'
const classes = {
roundsContainer: `${PREFIX}-roundsContainer`,
round1: `${PREFIX}-round1`,
@@ -10,7 +9,6 @@ const classes = {
round4: `${PREFIX}-round4`,
}
// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed.
const Root = styled('div')({
[`& .${classes.roundsContainer}`]: {
display: 'flex',
@@ -18,24 +16,16 @@ const Root = styled('div')({
height: 1100,
},
[`& .${classes.round1}`]: {
'& .MuiCard-root': {
margin: '20px 10px',
},
'& .MuiCard-root': { margin: '20px 10px' },
},
[`& .${classes.round2}`]: {
'& .MuiCard-root': {
margin: '140px 10px',
},
'& .MuiCard-root': { margin: '140px 10px' },
},
[`& .${classes.round3}`]: {
'& .MuiCard-root': {
margin: '360px 10px',
},
'& .MuiCard-root': { margin: '360px 10px' },
},
[`& .${classes.round4}`]: {
'& .MuiCard-root': {
margin: '0 10px',
},
'& .MuiCard-root': { margin: '0 10px' },
},
})
@@ -43,10 +33,11 @@ export default function KoView({ rounds }) {
return (
<Root>
<div className={classes.roundsContainer}>
<div className={classes.round1}>{rounds.r1}</div>
<div className={classes.round2}>{rounds.r2}</div>
<div className={classes.round3}>{rounds.r3}</div>
<div className={classes.round4}>{rounds.r4}</div>
{['round1', 'round2', 'round3', 'round4'].map((round, idx) => (
<div key={round} className={classes[`round${idx + 1}`]}>
{rounds[`r${idx + 1}`]}
</div>
))}
</div>
</Root>
)

View File

@@ -3,20 +3,18 @@ import { DialogActions, DialogContent, DialogTitle, FormControl, IconButton, Inp
import { useTranslation } from 'react-i18next'
import { Delete } from '@mui/icons-material'
import { DialogContext } from '../Dialog/Context'
import AddGroups from '../Groups/AddGroups'
import AddGroups from './AddGroups'
import { deleteGroup } from '../../api/groups'
const PREFIX = 'OneGroup'
const classes = {
formGroup: `${PREFIX}-formGroup`,
flex: `${PREFIX}-flex`,
s: `${PREFIX}-s`,
m: `${PREFIX}-m`,
l: `${PREFIX}-l`,
}
const Root = styled('div')(({ theme }) => ({
const Root = styled('div')(() => ({
margin: '0.3rem 0',
backgroundColor: '#ebebeb',
padding: '0.5rem',
@@ -24,50 +22,29 @@ const Root = styled('div')(({ theme }) => ({
[`& .${classes.flex}`]: {
display: 'flex',
},
[`& .${classes.s}`]: {
width: '4rem',
},
[`& .${classes.m}`]: {
width: '6rem',
},
[`& .${classes.l}`]: {
width: '8rem',
},
[`& .${classes.s}`]: { width: '4rem' },
[`& .${classes.m}`]: { width: '6rem' },
[`& .${classes.l}`]: { width: '8rem' },
}))
const BELTS = ['9. Kyu', '8. Kyu', '7. Kyu', '6. Kyu', '5. Kyu', '4. Kyu', '3. Kyu', '2. Kyu', '1. Kyu', 'DAN']
export default function OneGroup({ token, apiServer, formData, setFormData, myKey, isEditMode }) {
const { t } = useTranslation('common')
const dialog = useContext(DialogContext)
// console.log('formData', formData)
// console.log('myKey', myKey)
const handleChange = (e) => {
const targetKey = e.target.name
const targetValue = e.target.value
// Neues Array und neues Objekt erzeugen!
const { name, value } = e.target
const newFormData = [...formData]
newFormData[myKey] = { ...formData[myKey], [targetKey]: targetValue }
newFormData[myKey] = { ...formData[myKey], [name]: value }
setFormData(newFormData)
}
// console.log('formData-one', formData);
const generateAgeMenu = () => {
const result = []
for (let i = 6; i < 100; i++) {
result.push(i)
}
return result
}
const ageMenu = useMemo(() => generateAgeMenu(), [])
const ageMenu = useMemo(() => Array.from({ length: 94 }, (_, i) => i + 6), [])
const deleteGroupHandler = async (id) => {
try {
await deleteGroup(apiServer, token, id)
console.log('removed')
// TODO: Snackbar
dialog.onClose()
} catch (err) {
console.error(err)
@@ -79,11 +56,11 @@ export default function OneGroup({ token, apiServer, formData, setFormData, myKe
const id = formData[myKey]?.id
dialog.setContent(
<Root>
<DialogTitle id="form-dialog-title">Delete Group</DialogTitle>
<DialogContent>Wollen Sie die Gruppe {id} wirklich löschen ?</DialogContent>
<DialogTitle id="form-dialog-title">{t('groups.delete')}</DialogTitle>
<DialogContent>{t('groups.deleteConfirm', { id })}</DialogContent>
<DialogActions>
<Button onClick={dialog.onClose} color="secondary">
Cancel
{t('cancel')}
</Button>
<Button onClick={() => deleteGroupHandler(id)} variant="outlined" color="primary">
OK
@@ -92,34 +69,32 @@ export default function OneGroup({ token, apiServer, formData, setFormData, myKe
</Root>,
)
} else {
console.log('myKey', myKey)
console.log('formData', formData)
formData.splice(myKey, 1)
console.log('formData', formData)
setFormData(formData)
const newFormData = [...formData]
newFormData.splice(myKey, 1)
setFormData(newFormData)
dialog.setContent(<AddGroups token={token} apiServer={apiServer} tid={formData[0].tid} />)
}
}
const group = formData[myKey]
return (
<Root>
<IconButton aria-label="add group" onClick={handleDeleteGroup.bind(this, myKey)} size="large">
<IconButton aria-label="delete group" onClick={() => handleDeleteGroup(myKey)} size="large">
<Delete />
</IconButton>
<TextField variant="standard" required autoFocus className={classes.s} name="gid" label={t('gid')} type="number" value={formData[myKey].gid} onChange={handleChange} />
<TextField variant="standard" required autoFocus className={classes.s} name="gid" label={t('gid')} type="number" value={group.gid} onChange={handleChange} />
<FormControl variant="standard" required className={classes.m}>
<InputLabel id="gender-label">Gender</InputLabel>
<Select labelId="gender-label" id="gender" name="geschlecht" value={formData[myKey].geschlecht} onChange={handleChange} className={classes.select}>
<MenuItem value={'M'}>Male</MenuItem>
<MenuItem value={'W'}>Female</MenuItem>
<MenuItem value={'D'}>Mixed</MenuItem>
<InputLabel id="gender-label">{t('gender')}</InputLabel>
<Select labelId="gender-label" id="gender" name="geschlecht" value={group.geschlecht} onChange={handleChange}>
<MenuItem value={'M'}>{t('male')}</MenuItem>
<MenuItem value={'W'}>{t('female')}</MenuItem>
<MenuItem value={'D'}>{t('mixed')}</MenuItem>
</Select>
</FormControl>
<FormControl variant="standard" required className={classes.l}>
<InputLabel id="dicipline-label">disziplin</InputLabel>
<Select labelId="dicipline-label" id="dicipline" name="disziplin" value={formData[myKey].disziplin} onChange={handleChange} className={classes.select}>
<InputLabel id="discipline-label">{t('discipline')}</InputLabel>
<Select labelId="discipline-label" id="discipline" name="disziplin" value={group.disziplin} onChange={handleChange}>
<MenuItem value={'Kata-Einzel'}>Kata Einzel</MenuItem>
<MenuItem value={'Kumite-Einzel'}>Kumite Einzel</MenuItem>
<MenuItem value={'Kata-Team'}>Kata Team</MenuItem>
@@ -127,112 +102,59 @@ export default function OneGroup({ token, apiServer, formData, setFormData, myKe
<MenuItem value={'Kumite-Team'}>Kumite Team</MenuItem>
</Select>
</FormControl>
<FormControl variant="standard" required className={classes.m}>
<InputLabel id="age-from-label">Alter von</InputLabel>
<Select labelId="age-from-label" id="age-from" name="altervon" value={formData[myKey].altervon} onChange={handleChange} className={classes.select}>
{ageMenu.map((i) => {
return (
<MenuItem key={i} value={i}>
{i}
</MenuItem>
)
})}
<InputLabel id="age-from-label">{t('ageFrom')}</InputLabel>
<Select labelId="age-from-label" id="age-from" name="altervon" value={group.altervon} onChange={handleChange}>
{ageMenu.map((i) => (
<MenuItem key={i} value={i}>
{i}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl variant="standard" required className={classes.m}>
<InputLabel id="age-till-label">Alter bis</InputLabel>
<Select labelId="age-till-label" id="age-till" name="alterbis" value={formData[myKey].alterbis} onChange={handleChange} className={classes.select}>
{ageMenu.map((i) => {
return (
<MenuItem key={i} value={i}>
{i}
</MenuItem>
)
})}
<InputLabel id="age-till-label">{t('ageTo')}</InputLabel>
<Select labelId="age-till-label" id="age-till" name="alterbis" value={group.alterbis} onChange={handleChange}>
{ageMenu.map((i) => (
<MenuItem key={i} value={i}>
{i}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl variant="standard" required className={classes.l}>
<InputLabel id="belt-from-label">{t('participant.belt')} von</InputLabel>
<Select labelId="belt-from-label" id="belt-from" name="gurtVon" value={formData[myKey].gurtVon} onChange={handleChange} className={classes.select}>
<MenuItem value={'9. Kyu'}>9. Kyu</MenuItem>
<MenuItem className="yellow-belt" value={'8. Kyu'}>
8. Kyu
</MenuItem>
<MenuItem className="orange-belt" value={'7. Kyu'}>
7. Kyu
</MenuItem>
<MenuItem className="green-belt" value={'6. Kyu'}>
6. Kyu
</MenuItem>
<MenuItem className="violet-belt" value={'5. Kyu'}>
5. Kyu
</MenuItem>
<MenuItem className="violet-belt" value={'4. Kyu'}>
4. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'3. Kyu'}>
3. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'2. Kyu'}>
2. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'1. Kyu'}>
1. Kyu
</MenuItem>
<MenuItem className="black-belt" value={'DAN'}>
DAN
</MenuItem>
<InputLabel id="belt-from-label">
{t('participant.belt')} {t('from')}
</InputLabel>
<Select labelId="belt-from-label" id="belt-from" name="gurtVon" value={group.gurtVon} onChange={handleChange}>
{BELTS.map((belt) => (
<MenuItem key={belt} value={belt}>
{belt}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl variant="standard" required className={classes.l}>
<InputLabel id="belt-till-label">{t('participant.belt')} bis</InputLabel>
<Select labelId="belt-till-label" id="belt-till" name="gurtBis" value={formData[myKey].gurtBis} onChange={handleChange} className={classes.select}>
<MenuItem value={'9. Kyu'}>9. Kyu</MenuItem>
<MenuItem className="yellow-belt" value={'8. Kyu'}>
8. Kyu
</MenuItem>
<MenuItem className="orange-belt" value={'7. Kyu'}>
7. Kyu
</MenuItem>
<MenuItem className="green-belt" value={'6. Kyu'}>
6. Kyu
</MenuItem>
<MenuItem className="violet-belt" value={'5. Kyu'}>
5. Kyu
</MenuItem>
<MenuItem className="violet-belt" value={'4. Kyu'}>
4. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'3. Kyu'}>
3. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'2. Kyu'}>
2. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'1. Kyu'}>
1. Kyu
</MenuItem>
<MenuItem className="black-belt" value={'DAN'}>
DAN
</MenuItem>
<InputLabel id="belt-till-label">
{t('participant.belt')} {t('to')}
</InputLabel>
<Select labelId="belt-till-label" id="belt-till" name="gurtBis" value={group.gurtBis} onChange={handleChange}>
{BELTS.map((belt) => (
<MenuItem key={belt} value={belt}>
{belt}
</MenuItem>
))}
</Select>
</FormControl>
{isEditMode && (
<FormControl variant="standard" className={classes.m}>
<InputLabel id="pool-label">pool</InputLabel>
<Select labelId="pool-label" id="pool" name="pool" value={formData[myKey].pool} onChange={handleChange}>
<MenuItem value={'A'}>A</MenuItem>
<MenuItem value={'B'}>B</MenuItem>
<MenuItem value={'C'}>C</MenuItem>
<MenuItem value={'D'}>D</MenuItem>
<MenuItem value={'E'}>E</MenuItem>
<MenuItem value={'F'}>F</MenuItem>
<MenuItem value={'Final'}>FINAL</MenuItem>
<InputLabel id="pool-label">Pool</InputLabel>
<Select labelId="pool-label" id="pool" name="pool" value={group.pool} onChange={handleChange}>
{['A', 'B', 'C', 'D', 'E', 'F', 'Final'].map((pool) => (
<MenuItem key={pool} value={pool}>
{pool}
</MenuItem>
))}
</Select>
</FormControl>
)}

View File

@@ -1,51 +1,59 @@
import { useEffect, useState } from 'react'
import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'
export default function KataSelect({ val }) {
const [kata, setKata] = useState(``)
const KATAS = [
'Heian Shodan',
'Heian Nidan',
'Heian Sandan',
'Heian Yondan',
'Heian Godan',
'Tekki Shodan',
'Tekki Nidan',
'Tekki Sandan',
'Bassai Dai',
'Bassai Sho',
'Empi',
'Jion',
'Hangetsu',
'Kanku Dai',
'Kanku Sho',
'Jitte',
'Jiin',
'Nijūshiho',
'Gankaku',
'Chinte',
'Sōchin',
'Wankan',
'Meikyō',
'Gojūshiho Dai',
'Gojūshiho Sho',
'Unsu',
]
export default function KataSelect({ val = '', onChange }) {
const [kata, setKata] = useState(val)
useEffect(() => {
val && setKata(val)
setKata(val || '')
}, [val])
const handleChange = (event) => {
setKata(event.target.value)
onChange && onChange(event.target.value)
}
return (
<FormControl sx={{ m: 1, minWidth: 160 }} size="small">
<InputLabel id="kata-select">Kata</InputLabel>
<Select labelId="kata-select" id="kata-select" value={kata} label="Kata" onChange={handleChange}>
<InputLabel id="kata-select-label">Kata</InputLabel>
<Select labelId="kata-select-label" 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>
{KATAS.map((k) => (
<MenuItem key={k} value={k}>
{k}
</MenuItem>
))}
</Select>
</FormControl>
)

View File

@@ -2,32 +2,24 @@ import { useContext, useState } from 'react'
import { DialogContext } from '../Dialog/Context'
import { DialogContent, Button, Container, CssBaseline, Grid, Link, TextField } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { login as loginApi } from '../../api/account'
import { login as loginApi } from '../../api/account.js'
const LoginContext = (props) => {
const { handleToken, apiServer, user, setState } = props
export default function LoginContent({ handleToken, apiServer, user, setState }) {
const [email, setEmail] = useState('')
const [fakeID, setFakeID] = useState('')
const [password, setPassword] = useState('')
const dialog = useContext(DialogContext)
const { t } = useTranslation('common')
const switchDialog = (dialog) => {
setState(dialog)
}
const switchDialog = (dialogName) => setState(dialogName)
const validateForm = () => {
return email.length > 0 && password.length > 0
}
const validateForm = () => 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)
} else if (event.target.id === 'fakeID') {
setFakeID(event.target.value)
}
const { id, value } = event.target
if (id === 'email') setEmail(value.toLowerCase())
else if (id === 'password') setPassword(value)
else if (id === 'fakeID') setFakeID(value)
}
const handleSubmit = async (event) => {
@@ -38,6 +30,7 @@ const LoginContext = (props) => {
handleToken(result.account, result.admin, result.token)
dialog.onClose()
} else {
// Optional: Snackbar oder Fehleranzeige
console.log('no data found')
}
} catch (error) {
@@ -49,65 +42,41 @@ const LoginContext = (props) => {
<DialogContent>
<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}
/>
{user?.admin > 9 && (
<TextField variant="outlined" margin="normal" fullWidth name="fakeID" label="fakeID" id="fakeID" autoComplete="fakeID" value={fakeID} 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://old.karateturniere.de/anmeldung/passwort_vergessen.php" variant="body2" target="_blank">
Forgot password?
</Link> */}
<Link onClick={switchDialog.bind(this, t('signUp.pwReset'))} variant="body2" href="#">
Forgot password?
</Link>
</Grid>
<Grid item>
{/* <Link href="https://old.karateturniere.de/anmeldung/register.php" variant="body2" target="_blank">
{"Don't have an account? Sign Up"}
</Link> */}
<Link onClick={switchDialog.bind(this, t('signUp.signUp'))} variant="body2" href="#">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
<form noValidate onSubmit={handleSubmit}>
<TextField variant="outlined" margin="normal" required fullWidth id="email" label={t('email')} name="email" autoComplete="email" autoFocus value={email} onChange={handleChange} />
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label={t('password')}
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={handleChange}
/>
{user?.admin > 9 && (
<TextField variant="outlined" margin="normal" fullWidth name="fakeID" label="fakeID" id="fakeID" autoComplete="fakeID" value={fakeID} onChange={handleChange} />
)}
<Button type="submit" fullWidth variant="contained" color="primary" disabled={!validateForm()} sx={{ mt: 2, mb: 2 }}>
{t('signIn')}
</Button>
<Grid container>
<Grid item xs>
<Link onClick={() => switchDialog(t('signUp.pwReset'))} variant="body2" href="#" underline="hover" sx={{ cursor: 'pointer' }}>
{t('forgotPassword')}
</Link>
</Grid>
</form>
</div>
<Grid item>
<Link onClick={() => switchDialog(t('signUp.signUp'))} variant="body2" href="#" underline="hover" sx={{ cursor: 'pointer' }}>
{t('signUp.noAccount')}
</Link>
</Grid>
</Grid>
</form>
</Container>
</DialogContent>
)
}
export default LoginContext

View File

@@ -1,25 +1,22 @@
import { Close } from '@mui/icons-material'
import LoginContent from './LoginContent'
import { useContext, useState } from 'react'
import { DialogTitle, IconButton } from '@mui/material'
import { Close } from '@mui/icons-material'
import { useTranslation } from 'react-i18next'
import { DialogContext } from '../Dialog/Context'
import LoginContent from './LoginContent'
import { PwResetContent } from './PwResetContent'
import { SignUpEditContent } from './SignUpEditContent'
import { useTranslation } from 'react-i18next'
import { DialogTitle, IconButton } from '@mui/material'
export default function LoginDialog(props) {
const { handleToken, apiServer, user, view, token } = props
export default function LoginDialog({ handleToken, apiServer, user, view, token }) {
const dialog = useContext(DialogContext)
// console.log('view', view)
const [state, setState] = useState(view ? view : 'Login')
const { t } = useTranslation('common')
const [state, setState] = useState(view || 'Login')
return (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<DialogTitle id="form-dialog-title">{state}</DialogTitle>
<IconButton aria-owns={dialog.open ? 'menu-appbar' : null} aria-haspopup="true" onClick={dialog.onClose} color="inherit" size="large">
<IconButton aria-label="close" onClick={dialog.onClose} color="inherit" size="large">
<Close />
</IconButton>
</div>
@@ -27,7 +24,6 @@ export default function LoginDialog(props) {
{state === 'Login' && <LoginContent handleToken={handleToken} apiServer={apiServer} user={user} setState={setState} />}
{(state === t('signUp.signUp') || state === 'EditAccount') && <SignUpEditContent token={token} apiServer={apiServer} user={user} state={state} setState={setState} />}
{state === t('signUp.pwReset') && <PwResetContent handleToken={handleToken} apiServer={apiServer} user={user} setState={setState} />}
<br />
</>
)

View File

@@ -2,30 +2,19 @@ import { Button, DialogActions, DialogContent, TextField } from '@mui/material'
import { useContext, useState } from 'react'
import { DialogContext } from '../Dialog/Context'
import { useTranslation } from 'react-i18next'
import { pwReset } from '../../api/account'
import { pwReset } from '../../api/account.js'
export const PwResetContent = (props) => {
const { apiServer } = props
const [formData, setFormData] = useState({ login: '' })
export const PwResetContent = ({ apiServer }) => {
const [login, setLogin] = useState('')
const dialog = useContext(DialogContext)
const { t } = useTranslation('common')
const handleChange = (e) => {
// console.log(e)
let targetKey = e.target.name
let targetValue = e.target.value
setFormData({
...formData,
[targetKey]: targetValue,
})
// console.log(formData)
}
const handleChange = (e) => setLogin(e.target.value)
const handleSave = async (e) => {
e.preventDefault()
try {
await pwReset(apiServer, formData)
await pwReset(apiServer, { login })
handleClose()
} catch (err) {
console.error(err)
@@ -34,15 +23,14 @@ export const PwResetContent = (props) => {
const handleClose = () => {
dialog.onClose()
setFormData('')
setLogin('')
}
return (
<>
<DialogContent>
<TextField required margin="dense" name="login" variant="standard" label={t('email')} type="text" fullWidth value={formData.login} onChange={handleChange} />
<TextField required margin="dense" name="login" variant="standard" label={t('email')} type="text" fullWidth value={login} onChange={handleChange} />
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="secondary">
Cancel

View File

@@ -1,22 +1,18 @@
import { useContext, useEffect, useState } from 'react'
import { Button, TextField, DialogActions, DialogContent, Radio, RadioGroup, FormControlLabel, FormControl, FormLabel, Autocomplete } from '@mui/material'
import { useTranslation } from 'react-i18next'
import useFetch from '../UseFetch/UseFetch'
import { DialogContext } from '../Dialog/Context'
import { useTranslation } from 'react-i18next'
import { getAccount, checkAccountExist, signUp, signUpEdit } from '../../api/account'
import { Button, TextField, DialogActions, DialogContent, Radio, RadioGroup, FormControlLabel, FormControl, FormLabel, Autocomplete } from '@mui/material'
import { getAccount, checkAccountExist, signUp, signUpEdit } from '../../api/account.js'
export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
const token2 = token === undefined ? '' : token
const { data: clubs, loading } = useFetch(apiServer + '/getClubs')
const dialog = useContext(DialogContext)
const { t } = useTranslation('common')
const [pwError, setPwError] = useState(false)
const [mailError, setMailError] = useState(false)
const [account, setAccount] = useState(null)
const dialog = useContext(DialogContext)
const isEdit = state === 'EditAccount'
const token2 = token || ''
const { data: clubs = [], loading } = useFetch(apiServer + '/getClubs')
const initialFormData = Object.freeze({
const initialFormData = {
anrede: 'Herr',
name: '',
vorname: '',
@@ -26,24 +22,25 @@ export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
passwort: '',
passwort2: '',
telefon: '',
})
const [formData, setFormData] = useState(initialFormData)
const getAccountHandler = async () => {
try {
const responseData = await getAccount(apiServer, token2)
setAccount(responseData[0])
setFormData(responseData[0])
setState('EditAccount')
return responseData
} catch (err) {
console.error(err)
}
}
const [formData, setFormData] = useState(initialFormData)
const [account, setAccount] = useState(null)
const [pwError, setPwError] = useState(false)
const [mailError, setMailError] = useState(false)
// Account laden, falls Bearbeitung
useEffect(() => {
token2 && getAccountHandler()
}, [])
if (isEdit && token2) {
getAccount(apiServer, token2)
.then((responseData) => {
setAccount(responseData[0])
setFormData(responseData[0])
setState('EditAccount')
})
.catch(console.error)
}
// eslint-disable-next-line
}, [isEdit, token2])
const handleClose = () => {
dialog.onClose()
@@ -52,93 +49,59 @@ export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
}
const handleReset = () => {
account ? setFormData(account) : setFormData(initialFormData)
setFormData(account || initialFormData)
}
const handleChange = (e, value) => {
// Für Autocomplete
if (typeof value === 'string') {
setFormData((prev) => ({ ...prev, verein: value }))
return
}
const { name, value: val } = e.target
setFormData((prev) => ({
...prev,
[name]: val,
}))
}
const checkEmail = async (e) => {
e.preventDefault()
const mail = e.target.value
try {
const data = await checkAccountExist(apiServer, mail)
setMailError(data === 'true' ? false : true)
setMailError(data !== 'true')
} catch (err) {
console.error(err)
}
}
const checkPw = (e) => {
// console.log(e)
e.preventDefault()
let targetValue = e.target.value
const pw = document.getElementById('pw').value
// console.log('pw', pw)
if (targetValue !== pw) {
console.log('pw unterschiedlich')
setPwError(true)
} else {
console.log('pw ok')
setPwError(false)
}
const pw = formData.passwort
const pw2 = e.target.value
setPwError(pw !== pw2)
}
const handleChange = (e) => {
// console.log(e)
const handleSave = async (e) => {
e.preventDefault()
let targetKey = e.target.name
let targetValue = e.target.value
// Fix for Autocomplete
if (e.target?.classList?.contains('MuiAutocomplete-input')) {
targetKey = 'verein'
}
if (e.target?.classList?.contains('MuiAutocomplete-option')) {
targetKey = 'verein'
targetValue = e.target.innerText
}
setFormData({
...formData,
[targetKey]: targetValue,
})
// console.log(formData)
}
const signUpAdd = async (newData) => {
try {
await signUp(apiServer, newData)
console.log('signup saved')
if (account) {
await signUpEdit(apiServer, token, formData)
} else {
await signUp(apiServer, formData)
}
dialog.onClose()
} catch (err) {
console.error(err)
}
}
const signUpEditHandler = async (newData) => {
try {
await signUpEdit(apiServer, token, newData)
console.log('signup edited')
} catch (err) {
console.error(err)
}
}
const handleSave = (e) => {
e.preventDefault()
if (account) {
signUpEditHandler(formData)
} else {
signUpAdd(formData)
}
dialog.onClose()
}
return (
!loading && (
<>
<DialogContent>
<FormControl component="fieldset">
<FormLabel component="legend">{t('salutation')}</FormLabel>
<RadioGroup row aria-label="gender" name="anrede" value={formData.anrede} onChange={handleChange}>
<RadioGroup row name="anrede" value={formData.anrede} onChange={handleChange}>
<FormControlLabel value="Frau" control={<Radio color="secondary" />} label={t('woman')} />
<FormControlLabel value="Herr" control={<Radio />} label={t('man')} />
</RadioGroup>
@@ -147,10 +110,11 @@ export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
<TextField required margin="dense" name="vorname" variant="standard" label={t('forename')} type="text" fullWidth value={formData.vorname} onChange={handleChange} />
<Autocomplete
id="club"
freeSolo={true}
freeSolo
options={clubs}
value={formData.verein}
renderInput={(params) => <TextField required {...params} variant="standard" label={t('club')} margin="dense" onBlur={handleChange} />}
onInputChange={(e, value) => setFormData((prev) => ({ ...prev, verein: value }))}
renderInput={(params) => <TextField {...params} required variant="standard" label={t('club')} margin="dense" />}
/>
<TextField required margin="dense" name="ort" variant="standard" label={t('city')} type="text" fullWidth value={formData.ort} onChange={handleChange} />
{!isEdit && (
@@ -166,10 +130,9 @@ export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
value={formData.login}
onChange={handleChange}
onBlur={checkEmail}
helperText={mailError ? 'mail is registered' : ''}
helperText={mailError ? t('signUp.mailRegistered') : ''}
/>
)}
{/* // TODO: $('#mail').html('Bitte benutzen Sie die <a href="passwort_vergessen.php">Passwort vergessen</a> Funktion.') */}
<TextField
required
id="pw"
@@ -182,7 +145,7 @@ export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
fullWidth
value={formData.passwort || ''}
onChange={handleChange}
helperText={pwError ? "passwords doesn't match" : '8 - 30 ' + t('signUp.sign')}
helperText={pwError ? t('signUp.pwNoMatch') : '8 - 30 ' + t('signUp.sign')}
inputProps={{ minLength: 8, maxLength: 30 }}
/>
<TextField
@@ -197,19 +160,18 @@ export const SignUpEditContent = ({ token, apiServer, state, setState }) => {
fullWidth
value={formData.passwort2 || ''}
onChange={handleChange}
helperText={pwError ? "passwords doesn't match" : '8 - 30 ' + t('signUp.sign')}
onBlur={checkPw}
helperText={pwError ? t('signUp.pwNoMatch') : '8 - 30 ' + t('signUp.sign')}
inputProps={{ minLength: 8, maxLength: 30 }}
/>
<TextField margin="dense" name="telefon" variant="standard" label={t('phone')} type="text" fullWidth value={formData.telefon} onChange={handleChange} />
</DialogContent>
<DialogActions>
<Button onClick={handleReset} color="secondary">
reset
{t('reset')}
</Button>
<Button onClick={handleClose} color="secondary">
Cancel
{t('cancel')}
</Button>
<Button disabled={mailError || pwError} onClick={handleSave} variant="outlined" color="primary">
OK

View File

@@ -1,37 +1,49 @@
import { PDFDownloadLink } from '@react-pdf/renderer'
// import { PDFDownloadLink } from '@react-pdf/renderer'
import InvoicePDF from './InvoicePDF'
import { useTranslation } from 'react-i18next'
import { savePdf } from '../../api/pdf'
import { savePdf } from '../../utilities/PDF'
import { pdf } from '@react-pdf/renderer'
import Button from '@mui/material/Button'
import { Download } from '@mui/icons-material'
export default function PDF(props) {
const { tournament, user, apiServer, token } = props
const { t } = useTranslation('common')
const filename = 'invoice-' + user?.account + '.pdf'
const filename = `invoice-${user?.account}-${tournament?.id}.pdf`
const savePdfHandler = async (blob, categorie) => {
const reader = new FileReader()
reader.onloadend = async () => {
const base64 = reader.result
try {
// Richtige Reihenfolge: apiServer, token, base64, categorie, gid, tid
await savePdf(apiServer, token, base64, categorie, undefined, tournament.id)
console.log('PDF gespeichert')
} catch (err) {
console.error(err)
}
const showPDF = async () => {
let url = ''
try {
const blob = await pdf(<InvoicePDF {...props} t={t} />).toBlob()
url = URL.createObjectURL(blob)
const response = await fetch(url)
const blobData = await response.blob()
const blobUrl = window.URL.createObjectURL(blobData)
const link = document.createElement('a')
link.href = blobUrl
link.download = filename
link.click()
blob && savePdf(blob, 'lists', 123, apiServer, token, { tid: tournament?.id, categorie: 'invoice', gid: user?.account })
} catch (error) {
console.error('Error in download process:', error)
} finally {
if (url) URL.revokeObjectURL(url)
}
reader.readAsBinaryString(blob)
}
return (
<>
<PDFDownloadLink document={<InvoicePDF {...props} t={t} />} fileName={filename}>
{/* {showPDF()} */}
<Button variant="outlined" onClick={showPDF} className="btn btn-primary" startIcon={<Download />}>
Download Rechnung
</Button>
{/* <PDFDownloadLink document={<InvoicePDF {...props} t={t} />} fileName={filename}>
{({ blob, url, loading, error }) => {
if (blob) savePdfHandler(blob, 'invoice')
return loading ? 'Loading document...' : t('registration.download-invoice')
}}
</PDFDownloadLink>
</PDFDownloadLink> */}
{/* <BlobProvider document={<InvoicePDF {...props} />}>
{({ blob, url, loading, error }) => {
// Do whatever you need with blob here
@@ -39,7 +51,6 @@ export default function PDF(props) {
return <div>There's something going on on the fly</div>
}}
</BlobProvider> */}
{/* render to document
{ReactPDF.render(<MyDocument />, `invoice/{filename}`)} */}
{/* render as iframe

View File

@@ -1,69 +1,69 @@
import { useContext, useEffect, useState } from 'react'
import useFetch from '../UseFetch/UseFetch'
import { DialogContext } from '../Dialog/Context'
import { Button, TextField, DialogActions, DialogContent, DialogTitle, Radio, RadioGroup, FormControlLabel, FormControl, FormLabel, InputLabel, MenuItem, Select, Autocomplete } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { addParticipant, editParticipant } from '../../api/participants'
import { BeltClass } from '../../utilities/Belt'
const BELTS = ['9. Kyu', '8. Kyu', '7. Kyu', '6. Kyu', '5. Kyu', '4. Kyu', '3. Kyu', '2. Kyu', '1. Kyu', 'DAN']
export default function AddEditParticipant({ token, apiServer, edit, setEdit, participants }) {
const { t } = useTranslation('common')
const { data: clubs, loading } = useFetch(apiServer + '/getClubs' + token)
const { data: clubs = [], loading } = useFetch(apiServer + '/getClubs' + token)
const dialog = useContext(DialogContext)
const initialFormData = Object.freeze({
const initialFormData = {
name: '',
vorname: '',
verein: '',
gurt: '',
gebDatum: '',
geschlecht: 'M',
})
}
const [formData, setFormData] = useState(initialFormData)
useEffect(() => {
edit && setFormData(edit)
if (edit) setFormData(edit)
}, [edit])
const handleClose = () => {
dialog.onClose()
setFormData(initialFormData)
setEdit(null)
setEdit && setEdit(null)
}
const handleReset = () => {
edit ? setFormData(edit) : setFormData(initialFormData)
setFormData(edit ? edit : initialFormData)
}
const handleChange = (e) => {
let targetKey = e.target.name
let targetValue = e.target.value
// Fix for Autocomplete
if (e.target?.classList?.contains('MuiAutocomplete-input')) {
targetKey = 'verein'
}
if (e.target?.classList?.contains('MuiAutocomplete-option')) {
targetKey = 'verein'
targetValue = e.target.innerText
}
setFormData({
...formData,
[targetKey]: targetValue,
})
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]: value,
}))
}
const handleSave = (e) => {
const handleClubChange = (event, newValue) => {
setFormData((prev) => ({
...prev,
verein: newValue || '',
}))
}
const handleSave = async (e) => {
e.preventDefault()
if (edit) {
const index = participants.findIndex((i) => i.id === edit.id)
participants[index] = formData
editParticipant(apiServer, token, formData)
} else {
addParticipant(apiServer, token, formData)
try {
if (edit) {
await editParticipant(apiServer, token, formData)
} else {
await addParticipant(apiServer, token, formData)
}
dialog.onClose()
} catch (err) {
console.error(err)
}
dialog.onClose()
}
return (
@@ -75,45 +75,22 @@ export default function AddEditParticipant({ token, apiServer, edit, setEdit, pa
<TextField margin="dense" name="vorname" variant="standard" label={t('participant.forename')} type="text" fullWidth value={formData.vorname} onChange={handleChange} />
<Autocomplete
id="club"
freeSolo={true}
freeSolo
options={clubs}
value={formData.verein}
renderInput={(params) => <TextField {...params} variant="standard" label={t('participant.club')} margin="dense" onBlur={handleChange} />}
onChange={handleClubChange}
renderInput={(params) => <TextField {...params} variant="standard" label={t('participant.club')} margin="dense" />}
/>
<FormControl variant="standard" style={{ minWidth: '100%' }}>
<FormControl variant="standard" fullWidth margin="dense">
<InputLabel id="gurt-label">{t('participant.belt')}</InputLabel>
<Select labelId="gurt-label" id="gurt" name="gurt" value={formData.gurt} onChange={handleChange}>
<MenuItem value={'9. Kyu'}>9. Kyu</MenuItem>
<MenuItem className="yellow-belt" value={'8. Kyu'}>
8. Kyu
</MenuItem>
<MenuItem className="orange-belt" value={'7. Kyu'}>
7. Kyu
</MenuItem>
<MenuItem className="green-belt" value={'6. Kyu'}>
6. Kyu
</MenuItem>
<MenuItem className="violet-belt" value={'5. Kyu'}>
5. Kyu
</MenuItem>
<MenuItem className="violet-belt" value={'4. Kyu'}>
4. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'3. Kyu'}>
3. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'2. Kyu'}>
2. Kyu
</MenuItem>
<MenuItem className="brown-belt" value={'1. Kyu'}>
1. Kyu
</MenuItem>
<MenuItem className="black-belt" value={'DAN'}>
DAN
</MenuItem>
{BELTS.map((belt) => (
<MenuItem key={belt} value={belt} className={BeltClass(belt)}>
{belt}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
margin="dense"
name="gebDatum"
@@ -125,9 +102,9 @@ export default function AddEditParticipant({ token, apiServer, edit, setEdit, pa
value={formData.gebDatum}
onChange={handleChange}
/>
<FormControl component="fieldset">
<FormControl component="fieldset" margin="dense">
<FormLabel component="legend">{t('participant.gender')}</FormLabel>
<RadioGroup aria-label="gender" name="geschlecht" value={formData.geschlecht} onChange={handleChange}>
<RadioGroup aria-label="gender" name="geschlecht" value={formData.geschlecht} onChange={handleChange} row>
<FormControlLabel value="W" control={<Radio />} label={t('participant.female')} />
<FormControlLabel value="M" control={<Radio color="primary" />} label={t('participant.male')} />
</RadioGroup>
@@ -135,10 +112,10 @@ export default function AddEditParticipant({ token, apiServer, edit, setEdit, pa
</DialogContent>
<DialogActions>
<Button onClick={handleReset} color="secondary">
reset
{t('reset')}
</Button>
<Button onClick={handleClose} color="secondary">
Cancel
{t('cancel')}
</Button>
<Button onClick={handleSave} variant="outlined" color="primary">
OK

View File

@@ -1,32 +1,26 @@
import { useContext } from 'react'
import { DialogContext } from '../Dialog/Context'
import { Button, DialogActions, DialogTitle } from '@mui/material'
export default function DeleteDialog(props) {
const { deleteParticipant, data, setData, participant } = props
export default function DeleteDialog({ deleteParticipant, data, setData, participant }) {
const dialog = useContext(DialogContext)
const deleteRow = () => {
const participantRemoved = data.filter((x) => {
return x.id != participant.id
})
setData(participantRemoved)
const handleDelete = () => {
setData(data.filter((x) => x.id !== participant.id))
deleteParticipant(participant.id)
dialog.onClose()
}
console.log('participant', participant)
return (
<>
<DialogTitle>
Do you want to delete {participant.vorname} {participant.name}
Do you want to delete {participant.vorname} {participant.name}?
</DialogTitle>
<DialogActions>
<Button onClick={dialog.onClose} color="primary">
Cancel
</Button>
<Button onClick={deleteRow} color="primary" autoFocus>
<Button onClick={handleDelete} color="primary" autoFocus>
Delete
</Button>
</DialogActions>

View File

@@ -1,14 +1,12 @@
import AddEditParticipant from './AddEditParticipant'
import ParticipantsMobile from './ParticipantsMobile'
import { useContext, useState } from 'react'
import { DialogContext } from '../Dialog/Context'
import { addParticipant, editParticipant, deleteParticipant } from '../../api/participants'
import { styled, Button } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { DialogContext } from '../Dialog/Context'
import AddEditParticipant from './AddEditParticipant'
import ParticipantsMobile from './ParticipantsMobile'
import { addParticipant, editParticipant, deleteParticipant } from '../../api/participants'
const PREFIX = 'Participants'
const classes = {
participantsContainer: `${PREFIX}-participantsContainer`,
}
@@ -22,19 +20,16 @@ const Root = styled('div')(({ theme }) => ({
},
}))
export default function Participants(props) {
const { apiServer, token, participants } = props
export default function Participants({ apiServer, token, participants }) {
const { t } = useTranslation('common')
// TODO: statt data direkt participants nutzen
const [data, setData] = useState(participants)
const [edit, setEdit] = useState(null)
const dialog = useContext(DialogContext)
const handleAdd = async (newData) => {
try {
await addParticipant(apiServer, token, newData)
setData([...data, newData])
const added = await addParticipant(apiServer, token, newData)
setData((prev) => [...prev, added])
} catch (err) {
console.error(err)
}
@@ -42,8 +37,8 @@ export default function Participants(props) {
const handleEdit = async (newData) => {
try {
await editParticipant(apiServer, token, newData)
// ggf. data aktualisieren
const updated = await editParticipant(apiServer, token, newData)
setData((prev) => prev.map((item) => (item.id === updated.id ? updated : item)))
} catch (err) {
console.error(err)
}
@@ -52,7 +47,7 @@ export default function Participants(props) {
const handleDelete = async (id) => {
try {
await deleteParticipant(apiServer, token, id)
// ggf. data aktualisieren
setData((prev) => prev.filter((item) => item.id !== id))
} catch (err) {
console.error(err)
}
@@ -72,27 +67,24 @@ export default function Participants(props) {
/>,
)
dialog.setOpen()
// console.log('participants ', dialog.open, dialog.content)
}
return (
<Root>
<h3>{t('headline.participants')}</h3>
{participants && (
<div className={classes.participantsContainer}>
<ParticipantsMobile
data={data}
setData={setData}
deleteParticipant={handleDelete}
addParticipant={handleAdd}
editParticipant={handleEdit}
token={token}
apiServer={apiServer}
edit={edit}
setEdit={setEdit}
/>
</div>
)}
<div className={classes.participantsContainer}>
<ParticipantsMobile
data={data}
setData={setData}
deleteParticipant={handleDelete}
addParticipant={handleAdd}
editParticipant={handleEdit}
token={token}
apiServer={apiServer}
edit={edit}
setEdit={setEdit}
/>
</div>
<br />
<Button variant="contained" color="primary" onClick={handleDialog}>
{t('btn.add-participants')}

View File

@@ -1,18 +1,16 @@
import { useContext, useState } from 'react'
import { useParams } from 'react-router-dom'
import { Avatar, Chip, FormControlLabel, IconButton, styled, Switch, Pagination } from '@mui/material'
import { DeleteOutline, Edit } from '@mui/icons-material'
import PersonIcon from '@mui/icons-material/Person'
import { useContext } from 'react'
import { useParams } from 'react-router-dom'
import { beltColor } from '../../utilities/Belt'
import { getAge } from '../../utilities/Date'
import { DialogContext } from '../Dialog/Context'
import AddEditParticipant from './AddEditParticipant'
import DeleteDialog from './DeleteDialog'
import { useState } from 'react'
import SearchBar from '../Common/SearchBar'
import { Avatar, Chip, FormControlLabel, IconButton, styled, Switch, Pagination } from '@mui/material'
const PREFIX = 'ParticipantsMobile'
const classes = {
participants: `${PREFIX}-participants`,
participantsIcon: `${PREFIX}-participantsIcon`,
@@ -107,25 +105,21 @@ const Root = styled('div')(({ theme }) => ({
},
}))
export default function ParticipantsMobile(props) {
const { action, checkSingleRegistered, countSingle, deleteParticipant, data, setData, setEdit, addParticipant, editParticipant, token, apiServer, switchRef } = props
export default function ParticipantsMobile({ action, checkSingleRegistered, countSingle, deleteParticipant, data, setData, setEdit, addParticipant, editParticipant, token, apiServer, switchRef }) {
const dialog = useContext(DialogContext)
const { tid } = useParams()
const [page, setPage] = useState(1)
const rowsPerPage = 10 // Anzahl der Einträge pro Seite
const [search, setSearch] = useState('')
const rowsPerPage = 10
const handleDeleteDialogOpen = (el) => {
dialog.setContent(<DeleteDialog deleteParticipant={deleteParticipant} data={data} setData={setData} participant={el} />)
const handleDeleteDialogOpen = (participant) => {
dialog.setContent(<DeleteDialog deleteParticipant={deleteParticipant} data={data} setData={setData} participant={participant} />)
dialog.setOpen()
}
// console.log('dialog', dialog);
const handleEditClick = (el) => {
const date = new Date(el.gebDatum).toLocaleDateString('en-CA')
el.gebDatum = date
setEdit(el)
const handleEditClick = (participant) => {
const date = new Date(participant.gebDatum).toLocaleDateString('en-CA')
setEdit({ ...participant, gebDatum: date })
dialog.setContent(
<AddEditParticipant
participants={data}
@@ -134,11 +128,10 @@ export default function ParticipantsMobile(props) {
editParticipant={editParticipant}
token={token}
apiServer={apiServer}
edit={el}
edit={{ ...participant, gebDatum: date }}
setEdit={setEdit}
/>,
)
// console.log('el', el);
dialog.setOpen()
}
@@ -153,7 +146,7 @@ export default function ParticipantsMobile(props) {
const paginatedData = filteredData.slice((page - 1) * rowsPerPage, page * rowsPerPage)
return (
<Root className={classes.root}>
<Root>
<div style={{ display: 'flex', justifyContent: 'end', margin: '0.5em 0' }}>
<SearchBar
value={search}
@@ -166,17 +159,15 @@ export default function ParticipantsMobile(props) {
</div>
{paginatedData.map((el, index) => {
const gender = el.geschlecht === 'M' ? 'primary' : 'secondary'
return (
<div key={el.id || index} data-pid={el.id} className={classes.participants}>
<div className={classes.participantsIcon}>
<PersonIcon color={gender} />
{el.id}
</div>
<div className={classes.participantsInfo}>
<div className={classes.chips}>
<Chip size="small" label={el.gurt} className={classes.chip} style={{ backgroundColor: `${beltColor(el.gurt)}` }}></Chip>
<Chip size="small" label={el.gurt} className={classes.chip} style={{ backgroundColor: beltColor(el.gurt) }} />
<Chip size="small" className={classes.chip} label={new Date(el.gebDatum).toLocaleDateString()} avatar={<Avatar>{getAge(el.gebDatum).toString()}</Avatar>} />
</div>
<div className={classes.name}>
@@ -187,19 +178,17 @@ export default function ParticipantsMobile(props) {
<i>{el.verein}</i>
</div>
</div>
<div>
{!tid && (
<div className={classes.actions}>
<IconButton color="inherit" onClick={handleEditClick.bind(this, el)} size="large">
<IconButton color="inherit" onClick={() => handleEditClick(el)} size="large">
<Edit />
</IconButton>
<IconButton color="inherit" onClick={handleDeleteDialogOpen.bind(this, el)} size="large">
<IconButton color="inherit" onClick={() => handleDeleteDialogOpen(el)} size="large">
<DeleteOutline />
</IconButton>
</div>
)}
{countSingle && (
<>
<FormControlLabel

View File

@@ -53,6 +53,9 @@ const Root = styled('table')(({ theme }) => ({
export default function Invoice({ countSingle, countTeams, singleCosts, teamCosts }) {
const { t } = useTranslation('common')
const sumSingle = countSingle * singleCosts
const sumTeams = countTeams * teamCosts
const total = sumSingle + sumTeams
return (
<TableWrapper>
@@ -68,13 +71,13 @@ export default function Invoice({ countSingle, countTeams, singleCosts, teamCost
<td>{t('registration.single-registrations')}</td>
<td>{countSingle}</td>
<td>{singleCosts} </td>
<td>{countSingle * singleCosts} </td>
<td>{sumSingle} </td>
</tr>
<tr>
<td>{t('registration.team-registrations')}</td>
<td>{countTeams}</td>
<td>{teamCosts} </td>
<td>{countTeams * teamCosts} </td>
<td>{sumTeams} </td>
</tr>
<tr>
<td>
@@ -83,7 +86,7 @@ export default function Invoice({ countSingle, countTeams, singleCosts, teamCost
<td></td>
<td></td>
<td>
<strong>{countSingle * singleCosts + countTeams * teamCosts} </strong>
<strong>{total} </strong>
</td>
</tr>
</tbody>

View File

@@ -1,16 +1,15 @@
import { Add, Remove } from '@mui/icons-material'
import PersonIcon from '@mui/icons-material/Person'
import { useRef } from 'react'
import { Avatar, Chip, IconButton, styled } from '@mui/material'
const PREFIX = 'TeamRegistrationMobile'
const classes = {
teams: `${PREFIX}-teams`,
disziplin: `${PREFIX}-disziplin`,
teamsIcon: `${PREFIX}-teamsIcon`,
teamsInfo: `${PREFIX}-teamsInfo`,
counter: `${PREFIX}-counter`,
actions: `${PREFIX}-actions`,
}
const Root = styled('div')(({ theme }) => ({
@@ -29,67 +28,59 @@ const Root = styled('div')(({ theme }) => ({
marginBottom: '0.3em',
padding: '0.3em',
borderRadius: '0.3em',
gap: '1em',
},
[`& .${classes.teamsIcon}`]: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
minWidth: 50,
},
[`& .${classes.teamsInfo}`]: {
display: 'flex',
alignItems: 'center',
gap: '1em',
minWidth: 180,
[theme.breakpoints.down('sm')]: {
display: 'block',
minWidth: 0,
},
},
[`& .${classes.disziplin}`]: {
fontWeight: 'bold',
minWidth: '8em',
display: 'inline-block',
},
[`& .${classes.teamsIcon}`]: {
[`& .${classes.counter}`]: {
minWidth: 40,
textAlign: 'center',
fontWeight: 600,
fontSize: '1.1em',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
[`& .${classes.teamsInfo}`]: {
[`& .${classes.actions}`]: {
display: 'flex',
alignItems: 'center',
[theme.breakpoints.down('sm')]: {
display: 'block',
},
gap: '0.3em',
},
}))
export default function TeamRegistrationMobile(props) {
const { checkTeamsRegistered, teamGroups, register, setSnackMessage, account, snackOpen, tooLate } = props
const counterRef = useRef([])
const deRegisterTeam = (id) => {
export default function TeamRegistrationMobile({ checkTeamsRegistered, teamGroups, register, setSnackMessage, account, snackOpen, tooLate }) {
const handleRegister = (id, type) => {
if (tooLate) {
setSnackMessage('too late')
snackOpen()
return
}
const countField = counterRef.current[id]
let counter = parseInt(countField.innerHTML)
if (counter <= 0) {
counter = 0
} else {
register({
register: '/deregisterTeam',
teamName: account[0].verein,
turniergruppeId: id,
})
setSnackMessage(`Team ${account[0].verein} deregistered for group ${id}`)
counter--
}
countField.innerHTML = counter
}
const registerTeam = (id) => {
if (tooLate) {
setSnackMessage('too late')
snackOpen()
return
}
const countField = counterRef.current[id]
let counter = parseInt(countField.innerHTML) + 1
const action = type === 'register' ? '/registerTeam' : '/deregisterTeam'
register({
register: '/registerTeam',
register: action,
teamName: account[0].verein,
turniergruppeId: id,
})
setSnackMessage(`Team ${account[0].verein} registered for group ${id}`)
countField.innerHTML = counter
setSnackMessage(`Team ${account[0].verein} ${type === 'register' ? 'registered' : 'deregistered'} for group ${id}`)
}
return (
@@ -98,26 +89,19 @@ export default function TeamRegistrationMobile(props) {
<div key={el.id} className={classes.teams}>
<div className={classes.teamsIcon}>
<PersonIcon color={el.geschlecht === 'M' ? 'primary' : 'secondary'} />
{el.id}
<span>{el.id}</span>
</div>
<div className={classes.teamsInfo}>
<span className={classes.disziplin}>{el.disziplin}</span>
<div>
<Chip size="small" label={el.gurtVon + ' - ' + el.gurtBis} style={{ margin: '0 1em' }} />
<Chip size="small" label={el.altervon + ' - ' + el.alterbis} avatar={<Avatar>age</Avatar>} />
</div>
<Chip size="small" label={`${el.gurtVon} - ${el.gurtBis}`} style={{ margin: '0 1em' }} />
<Chip size="small" label={`${el.altervon} - ${el.alterbis}`} avatar={<Avatar>age</Avatar>} />
</div>
<div className="counter" ref={(elem) => (counterRef.current[el.id] = elem)}>
{checkTeamsRegistered(el.id)}
</div>
<div>
<IconButton aria-label="deregister Team" onClick={deRegisterTeam.bind(this, el.id)} size="large">
<div className={classes.counter}>{checkTeamsRegistered(el.id)}</div>
<div className={classes.actions}>
<IconButton aria-label="deregister Team" onClick={() => handleRegister(el.id, 'deregister')} size="large">
<Remove />
</IconButton>
<IconButton aria-label="register Team" onClick={registerTeam.bind(this, el.id)} size="large">
<IconButton aria-label="register Team" onClick={() => handleRegister(el.id, 'register')} size="large">
<Add />
</IconButton>
</div>

View File

@@ -1,37 +1,31 @@
import { TextField, Button } from '@mui/material'
import { useState } from 'react'
import { TextField, Button } from '@mui/material'
import { addResults } from '../../api/results'
export default function PostResult({ apiServer, token, groups, setGroups }) {
const initialFormData = Object.freeze({
gid: '',
p1: '',
p2: '',
p3: '',
p4: '',
})
const initialFormData = { gid: '', p1: '', p2: '', p3: '', p4: '' }
const [formData, setFormData] = useState(initialFormData)
const handleChange = (e) => {
// console.log(e.target);
let targetKey = e.target.name
let targetValue = e.target.value
setFormData({
...formData,
[targetKey]: targetValue,
})
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]: value,
}))
}
const handleSubmit = async () => {
try {
await addResults(apiServer, token, formData)
console.log('Ergebnisse hinzugefügt')
const groupIndex = groups.findIndex((x) => x.id == formData.gid)
const escapedFormdata = JSON.stringify(formData)
groups[groupIndex]['results'] = escapedFormdata
setGroups([...groups]) // Array-Kopie für sauberes State-Update
const groupIndex = groups.findIndex((x) => String(x.id) === String(formData.gid))
if (groupIndex !== -1) {
const updatedGroups = [...groups]
updatedGroups[groupIndex] = {
...updatedGroups[groupIndex],
results: JSON.stringify(formData),
}
setGroups(updatedGroups)
}
} catch (err) {
console.error(err)
}

View File

@@ -1,20 +1,18 @@
import { styled } from '@mui/material'
const PREFIX = 'ResultsEntry'
const PREFIX = 'ResultsEntry'
const classes = {
place: `${PREFIX}-place`,
participant: `${PREFIX}-participant`,
name: `${PREFIX}-name`,
}
const Root = styled('div')(({ theme }) => ({
[`&`]: {
backgroundColor: 'rgba(0,0,0,0.2)',
margin: '0.2rem 0',
padding: '0 0.4rem',
display: 'flex',
alignItems: 'center',
},
const Root = styled('div')({
backgroundColor: 'rgba(0,0,0,0.2)',
margin: '0.2rem 0',
padding: '0 0.4rem',
display: 'flex',
alignItems: 'center',
[`& .${classes.place}`]: {
backgroundColor: 'rgba(0,0,0,0.2)',
padding: '0.2rem 0.6rem',
@@ -24,38 +22,21 @@ const Root = styled('div')(({ theme }) => ({
display: 'flex',
},
[`& .${classes.name}`]: {
fontWeight: '600',
fontWeight: 600,
marginRight: '0.4rem',
},
}))
})
export default function ResultsEntry(props) {
const { p, place, team } = props
export default function ResultsEntry({ p, place, team }) {
if (!p?.id || p.id <= 0) return null
return (
<>
{p?.id > 0 && (
<Root>
<div className={classes.place}>{place}</div>
{/* Participants */}
{!team && (
<div className={classes.participant}>
<div className={classes.name}>
{p?.vorname} {p?.name}
</div>
<div>[{p?.verein}]</div>
</div>
)}
{/* Teams */}
{team && (
<div className={classes.participant}>
<div className={classes.name}>{p?.teamName}</div>
</div>
)}
</Root>
)}
</>
<Root>
<div className={classes.place}>{place}</div>
<div className={classes.participant}>
<div className={classes.name}>{team ? p?.teamName : `${p?.vorname} ${p?.name}`}</div>
{!team && <div>[{p?.verein}]</div>}
</div>
</Root>
)
}

View File

@@ -1,7 +1,7 @@
import ResultsEntry from './ResultsEntry'
import { Chip, styled, Avatar } from '@mui/material'
const PREFIX = 'ResultsEntryContainer'
const PREFIX = 'ResultsEntryContainer'
const classes = {
groupResult: `${PREFIX}-groupResult`,
groupHeader: `${PREFIX}-groupHeader`,
@@ -33,25 +33,27 @@ const Root = styled('div')(({ theme }) => ({
},
}))
export default function ResultsEntryContainer(props) {
const { el, team, place, user } = props
export default function ResultsEntryContainer({ el, team, place, user }) {
const gender = el.geschlecht === 'M' ? 'primary' : 'secondary'
const places = [
{ p: place.p1, place: 1 },
{ p: place.p2, place: 2 },
{ p: place.p3, place: 3 },
{ p: place.p4, place: 3 },
]
return (
<Root className={classes.groupResult}>
<div className={classes.groupHeader}>
<Chip className={classes.chip} color={gender} label={el.disziplin} avatar={<Avatar>{el.gid}</Avatar>}></Chip>
<Chip className={classes.chip} label={el.altervon + ' - ' + el.alterbis} avatar={<Avatar>age</Avatar>}></Chip>
<Chip className={classes.chip} label={el.gurtVon + ' - ' + el.gurtBis}></Chip>
<Chip className={classes.chip} color={gender} label={el.disziplin} avatar={<Avatar>{el.gid}</Avatar>} />
<Chip className={classes.chip} label={`${el.altervon} - ${el.alterbis}`} avatar={<Avatar>age</Avatar>} />
<Chip className={classes.chip} label={`${el.gurtVon} - ${el.gurtBis}`} />
</div>
<div className={classes.groupBody}>
{user?.admin > 2 && <span>{el.id}</span>}
<ResultsEntry p={place.p1} place={1} team={team} />
<ResultsEntry p={place.p2} place={2} team={team} />
<ResultsEntry p={place.p3} place={3} team={team} />
<ResultsEntry p={place.p4} place={3} team={team} />
{places.map(({ p, place }, idx) => (
<ResultsEntry key={idx} p={p} place={place} team={team} />
))}
</div>
</Root>
)

View File

@@ -1,37 +1,22 @@
import ResultsEntryContainer from '../Results/ResultsEntryContainer'
export default function TournamentResults({ groups, user, participants, teams }) {
const result = []
const getParticipant = (id) => {
return participants.find((x) => x.id == id)
}
const getTeams = (id) => {
return teams.find((x) => x.id == id)
}
const getParticipant = (id) => participants.find((x) => x.id == id)
const getTeam = (id) => teams.find((x) => x.id == id)
groups.forEach((el) => {
if (el.platz1 || el.platz1team > 0 || el.results) {
let place, team
const parseResults = el.results && JSON.parse(el.results)
if (el.disziplin.includes('Team')) {
team = true
place = {
p1: el.results ? getTeams(parseResults.p1) : getTeams(el.platz1team),
p2: el.results ? getTeams(parseResults.p2) : getTeams(el.platz2team),
p3: el.results ? getTeams(parseResults.p3) : getTeams(el.platz3team),
p4: el.results ? getTeams(parseResults.p4) : getTeams(el.platz4team),
}
} else {
team = false
place = {
p1: el.results ? getParticipant(parseResults.p1) : getParticipant(el.platz1),
p2: el.results ? getParticipant(parseResults.p2) : getParticipant(el.platz2),
p3: el.results ? getParticipant(parseResults.p3) : getParticipant(el.platz3),
p4: el.results ? getParticipant(parseResults.p4) : getParticipant(el.platz4),
}
return groups
.filter((el) => el.platz1 || el.platz1team > 0 || el.results)
.map((el) => {
const isTeam = el.disziplin?.includes('Team')
const results = el.results ? JSON.parse(el.results) : null
const place = {
p1: results ? (isTeam ? getTeam(results.p1) : getParticipant(results.p1)) : isTeam ? getTeam(el.platz1team) : getParticipant(el.platz1),
p2: results ? (isTeam ? getTeam(results.p2) : getParticipant(results.p2)) : isTeam ? getTeam(el.platz2team) : getParticipant(el.platz2),
p3: results ? (isTeam ? getTeam(results.p3) : getParticipant(results.p3)) : isTeam ? getTeam(el.platz3team) : getParticipant(el.platz3),
p4: results ? (isTeam ? getTeam(results.p4) : getParticipant(results.p4)) : isTeam ? getTeam(el.platz4team) : getParticipant(el.platz4),
}
result.push(<ResultsEntryContainer key={el.id} el={el} team={team} place={place} user={user} />)
}
})
return result
return <ResultsEntryContainer key={el.id} el={el} team={isTeam} place={place} user={user} />
})
}

View File

@@ -2,7 +2,15 @@ import { Box, CircularProgress } from '@mui/material'
export default function Spinner() {
return (
<Box sx={{ display: 'flex', minWidth: '100%', minHeight: '100vh', alignItems: 'center', justifyContent: 'center' }}>
<Box
sx={{
display: 'flex',
minWidth: '100%',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
}}
>
<CircularProgress size={60} />
</Box>
)

View File

@@ -5,13 +5,11 @@ import { useTranslation } from 'react-i18next'
import { addTournament, editTournament } from '../../api/tournaments'
const PREFIX = 'AddEditTournament'
const classes = {
formGroup: `${PREFIX}-formGroup`,
halfSize: `${PREFIX}-halfSize`,
}
// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed.
const Root = styled('div')(({ theme }) => ({
[`& .${classes.formGroup}`]: {
margin: '1rem 0',
@@ -43,15 +41,25 @@ const initialFormData = Object.freeze({
streams: '',
})
export default function AddEditTournament(props) {
const { token, apiServer, tournament, tournaments, setTournaments } = props
const { t } = useTranslation('common')
function toDateInput(val) {
if (!val) return ''
// Falls schon im richtigen Format, zurückgeben
if (/^\d{4}-\d{2}-\d{2}$/.test(val)) return val
try {
return new Date(val).toISOString().slice(0, 10)
} catch {
return ''
}
}
export default function AddEditTournament({ token, apiServer, tournament, tournaments, setTournaments }) {
const { t } = useTranslation('common')
const [formData, setFormData] = useState(initialFormData)
const dialog = useContext(DialogContext)
useEffect(() => {
tournament && setFormData(tournament)
if (tournament) setFormData(tournament)
else setFormData(initialFormData)
}, [tournament])
const handleClose = () => {
@@ -60,28 +68,23 @@ export default function AddEditTournament(props) {
}
const handleReset = () => {
tournament ? setFormData(tournament) : setFormData(initialFormData)
setFormData(tournament ? tournament : initialFormData)
}
const handleChange = (e) => {
// console.log(e.target);
const targetKey = e.target.name
const targetValue = e.target.value
setFormData({
...formData,
[targetKey]: targetValue,
})
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]: value,
}))
}
const handleSave = async (e) => {
e.preventDefault()
try {
if (tournament) {
const index = tournaments.findIndex((i) => i.id === tournament.id)
tournaments[index] = formData
setTournaments([...tournaments])
const updatedTournaments = tournaments.map((i) => (i.id === tournament.id ? formData : i))
setTournaments(updatedTournaments)
await editTournament(apiServer, token, formData)
} else {
setTournaments([...tournaments, formData])
@@ -109,7 +112,7 @@ export default function AddEditTournament(props) {
InputLabelProps={{ shrink: true }}
label={t('date')}
type="date"
value={new Date(formData.veranstaltungsDatum).toLocaleDateString('en-CA')}
value={toDateInput(formData.veranstaltungsDatum)}
onChange={handleChange}
/>
<TextField
@@ -120,7 +123,7 @@ export default function AddEditTournament(props) {
InputLabelProps={{ shrink: true }}
label={t('closing-date')}
type="date"
value={new Date(formData.meldeschluss).toLocaleDateString('en-CA')}
value={toDateInput(formData.meldeschluss)}
onChange={handleChange}
/>
<TextField
@@ -172,10 +175,10 @@ export default function AddEditTournament(props) {
</DialogContent>
<DialogActions>
<Button onClick={handleReset} color="secondary">
reset
{t('reset')}
</Button>
<Button onClick={handleClose} color="secondary">
Cancel
{t('cancel')}
</Button>
<Button onClick={handleSave} variant="outlined" color="primary">
OK

View File

@@ -1,16 +1,13 @@
import { useContext } from 'react'
import { DialogContext } from '../Dialog/Context'
import { deleteTournament } from '../../api/tournaments'
import { Button, DialogActions, DialogTitle } from '@mui/material'
export default function DeleteTournamentDialog(props) {
const { apiServer, token, tid, tournaments, setTournaments } = props
export default function DeleteTournamentDialog({ apiServer, token, tid, tournaments, setTournaments }) {
const dialog = useContext(DialogContext)
const handleDeleteTournament = async () => {
const tournamentsRemoved = tournaments.filter((x) => x.id != tid)
setTournaments(tournamentsRemoved)
setTournaments(tournaments.filter((x) => x.id !== tid))
try {
await deleteTournament(apiServer, token, tid)
console.log(`Tournament ${tid} deleted`)
@@ -22,7 +19,7 @@ export default function DeleteTournamentDialog(props) {
return (
<>
<DialogTitle>Do you want to delete Tournament {tid}</DialogTitle>
<DialogTitle>Do you want to delete Tournament {tid}?</DialogTitle>
<DialogActions>
<Button onClick={dialog.onClose} color="primary">
Cancel

View File

@@ -2,11 +2,11 @@ import { useContext } from 'react'
import { Menu, MenuItem, IconButton } from '@mui/material'
import MoreVertIcon from '@mui/icons-material/MoreVert'
import { Link as RouterLink } from 'react-router-dom'
import DeleteTournamentDialog from './DeleteTournamentDialog'
import { DialogContext } from '../Dialog/Context'
import AddGroups from '../Groups/AddGroups'
import EditGroups from '../Groups/EditGroups'
import AddEditTournament from './AddEditTournament'
import DeleteTournamentDialog from './DeleteTournamentDialog'
import TriggerGroupsDialog from './TriggerGroupsDialog'
import { createPools, createFinalGroups } from '../../api/tournaments'
@@ -32,35 +32,14 @@ export default function TournamentMenu({ anchorEl, open, onOpen, onClose, user,
console.error(err)
}
}
const openTriggerGroupDialog = (tid) => {
const openDialog = (Component, props = {}, maxWidth) => {
handleClose()
dialog.setContent(<TriggerGroupsDialog tid={tid} tournamentGroups={tournamentGroups} token={token} apiServer={apiServer} />)
if (maxWidth) dialog.setMaxWidth(maxWidth)
dialog.setContent(<Component {...props} />)
dialog.setOpen()
}
const openAddGroupDialog = (tid) => {
handleClose()
dialog.setMaxWidth('lg')
dialog.setContent(<AddGroups tid={tid} 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 openEditTournamentDialog = () => {
handleClose()
dialog.setContent(<AddEditTournament tournaments={tournaments} setTournaments={setTournaments} token={token} apiServer={apiServer} tournament={tournament} />)
dialog.setOpen()
}
const openDeleteTournamentDialog = (tid) => {
handleClose()
dialog.setContent(<DeleteTournamentDialog tournaments={tournaments} setTournaments={setTournaments} tid={tid} token={token} apiServer={apiServer} />)
dialog.setOpen()
}
return (
<>
<IconButton aria-label="more" aria-controls="simple-menu" aria-haspopup="true" onClick={onOpen} size="large">
@@ -68,17 +47,17 @@ export default function TournamentMenu({ anchorEl, open, onOpen, onClose, user,
</IconButton>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={open} onClose={onClose}>
<MenuItem onClick={handleCreatePools}>Pools erstellen</MenuItem>
<MenuItem onClick={openTriggerGroupDialog.bind(this, tournament.id)}>Gruppen auslosen</MenuItem>
<MenuItem onClick={() => openDialog(TriggerGroupsDialog, { tid: tournament.id, tournamentGroups, token, apiServer })}>Gruppen auslosen</MenuItem>
<MenuItem onClick={handleCreateFinalGroups}>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={'/pdf/lists/' + tournament.id} style={{ textDecoration: 'inherit', color: 'inherit' }}>
<MenuItem onClick={() => openDialog(AddGroups, { tid: tournament.id, token, apiServer }, 'lg')}>Gruppen hinzufügen</MenuItem>
<MenuItem onClick={() => openDialog(EditGroups, { tid: tournament.id, token, apiServer, groups: tournamentGroups }, 'lg')}>Gruppen bearbeiten</MenuItem>
<MenuItem onClick={() => openDialog(AddEditTournament, { tournaments, setTournaments, token, apiServer, tournament })}>Turnier bearbeiten</MenuItem>
<MenuItem onClick={() => openDialog(DeleteTournamentDialog, { tournaments, setTournaments, tid: tournament.id, token, apiServer })}>Turnier löschen</MenuItem>
<RouterLink to={`/pdf/lists/${tournament.id}`} style={{ textDecoration: 'inherit', color: 'inherit' }}>
<MenuItem>PDF erstellen</MenuItem>
</RouterLink>
{user?.admin > 9 && (
<RouterLink to={'/mail/' + tournament.id} style={{ textDecoration: 'inherit', color: 'inherit' }}>
<RouterLink to={`/mail/${tournament.id}`} style={{ textDecoration: 'inherit', color: 'inherit' }}>
<MenuItem>Email senden</MenuItem>
</RouterLink>
)}

View File

@@ -8,7 +8,6 @@ import { SpeedDial, SpeedDialAction, SpeedDialIcon, styled } from '@mui/material
import { DialogContext } from '../Dialog/Context'
const PREFIX = 'Tournaments'
const classes = {
olderEntry: `${PREFIX}-olderEntry`,
speedDial: `${PREFIX}-speedDial`,
@@ -33,18 +32,12 @@ const Root = styled('div')(({ theme }) => ({
},
}))
export default function Tournaments(props) {
const { apiServer, groups, tournaments, setTournaments, token, user, participants, setParticipants } = props
export default function Tournaments({ apiServer, groups, tournaments, setTournaments, token, user, participants, setParticipants }) {
const [speedDialOpen, setSpeedDialOpen] = useState(false)
const dialog = useContext(DialogContext)
const handleClose = () => {
setSpeedDialOpen(false)
}
const handleOpen = () => {
setSpeedDialOpen(true)
}
const handleClose = () => setSpeedDialOpen(false)
const handleOpen = () => setSpeedDialOpen(true)
const handleAddPerson = () => {
dialog.setContent(<AddEditParticipant participants={participants} setParticipants={setParticipants} token={token} apiServer={apiServer} />)
@@ -63,52 +56,41 @@ export default function Tournaments(props) {
{ icon: <PersonAdd />, name: 'Add Participant', action: handleAddPerson },
]
const showTournaments = (tournaments) => {
let result = []
const today = new Date().getFullYear()
for (let i = 0; i < tournaments.length; i++) {
const date = new Date(tournaments[i].veranstaltungsDatum).getFullYear()
if (date >= today - 1) {
// Turniere ab vorletzen Jahr
result.push(
<div key={tournaments[i]['id']}>
<TournamentsCard
tournaments={tournaments}
setTournaments={setTournaments}
user={user}
participants={participants}
tournament={tournaments[i]}
tournamentGroups={filterObject('turnier_id', tournaments[i]['id'], groups)}
apiServer={apiServer}
token={token}
/>
</div>,
)
} else {
result.push(
<div className={classes.olderEntry} key={tournaments[i]['id']}>
<TournamentsCard
tournaments={tournaments}
setTournaments={setTournaments}
user={user}
tournament={tournaments[i]}
tournamentGroups={filterObject('turnier_id', tournaments[i]['id'], groups)}
apiServer={apiServer}
token={token}
/>
</div>,
)
}
}
return result
}
const today = new Date().getFullYear()
const currentTournaments = tournaments.filter((t) => new Date(t.veranstaltungsDatum).getFullYear() >= today - 1)
const olderTournaments = tournaments.filter((t) => new Date(t.veranstaltungsDatum).getFullYear() < today - 1)
return (
<Root>
{showTournaments(tournaments)}
{/* // TODO: activate SpeedDial auch für turnierersteller. */}
{currentTournaments.map((t) => (
<div key={t.id}>
<TournamentsCard
tournaments={tournaments}
setTournaments={setTournaments}
user={user}
participants={participants}
tournament={t}
tournamentGroups={filterObject('turnier_id', t.id, groups)}
apiServer={apiServer}
token={token}
/>
</div>
))}
{olderTournaments.map((t) => (
<div className={classes.olderEntry} key={t.id}>
<TournamentsCard
tournaments={tournaments}
setTournaments={setTournaments}
user={user}
tournament={t}
tournamentGroups={filterObject('turnier_id', t.id, groups)}
apiServer={apiServer}
token={token}
/>
</div>
))}
{user?.admin > 4 && (
<SpeedDial ariaLabel="SpeedDial example" className={classes.speedDial} icon={<SpeedDialIcon />} onClose={handleClose} onOpen={handleOpen} open={speedDialOpen} direction={'up'}>
<SpeedDial ariaLabel="SpeedDial example" className={classes.speedDial} icon={<SpeedDialIcon />} onClose={handleClose} onOpen={handleOpen} open={speedDialOpen} direction="up">
{actions.map((action) => (
<SpeedDialAction key={action.name} icon={action.icon} tooltipTitle={action.name} onClick={action.action} />
))}

View File

@@ -1,5 +1,5 @@
import { useState } from 'react'
import { Avatar, Button, Card, CardActions, CardContent, CardHeader, Collapse, IconButton, Tooltip, styled } from '@mui/material'
import { Avatar, Button, Card, CardActions, CardContent, CardHeader, Collapse, IconButton, Tooltip, styled, Box } from '@mui/material'
import { grey, red } from '@mui/material/colors'
import {
DateRange as DateRangeIcon,
@@ -13,14 +13,10 @@ import {
} from '@mui/icons-material'
import { useTranslation } from 'react-i18next'
import { Link as RouterLink } from 'react-router-dom'
import Groups from '../Groups/Groups'
import Box from '@mui/material/Box'
import TournamentMenu from './TournamentMenu'
const PREFIX = 'TournamentsCard'
const classes = {
card: `${PREFIX}-card`,
cardHeader: `${PREFIX}-cardHeader`,
@@ -35,7 +31,6 @@ const classes = {
titleContainer: `${PREFIX}-titleContainer`,
titleRight: `${PREFIX}-titleRight`,
titleSide: `${PREFIX}-titleSide`,
subTitle: `${PREFIX}-subTitle`,
routerLink: `${PREFIX}-routerLink`,
titleAction: `${PREFIX}-titleAction`,
}
@@ -150,7 +145,6 @@ const StyledCard = styled(Card)(({ theme }) => ({
[`& .${classes.routerLink}`]: {
textDecoration: 'inherit',
},
// Zusätzliche Anpassungen für Buttons und Icons
[`& .MuiButton-root`]: {
[theme.breakpoints.down('sm')]: {
fontSize: '0.9rem',
@@ -176,7 +170,7 @@ const StyledCard = styled(Card)(({ theme }) => ({
},
[`& .${classes.media}`]: {
height: 0,
paddingTop: '56.25%', // 16:9
paddingTop: '56.25%',
},
[`& .${classes.expand}`]: {
transform: 'rotate(0deg)',
@@ -189,23 +183,14 @@ const StyledCard = styled(Card)(({ theme }) => ({
},
}))
export default function TournamentsCard(props) {
const { apiServer, tournament, tournaments, setTournaments, tournamentGroups, token, user, participants } = props
export default function TournamentsCard({ apiServer, tournament, tournaments, setTournaments, tournamentGroups, token, user, participants }) {
const [expanded, setExpanded] = useState(false)
const [anchorEl, setAnchorEl] = useState(null)
const { t } = useTranslation('common')
const handleExpandClick = () => {
setExpanded(!expanded)
}
const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const handleExpandClick = () => setExpanded((prev) => !prev)
const handleMenuOpen = (event) => setAnchorEl(event.currentTarget)
const handleMenuClose = () => setAnchorEl(null)
const tournamentData = {
id: tournament.id,
@@ -216,7 +201,8 @@ export default function TournamentsCard(props) {
account_id: tournament.account_id,
}
const closingdate = t('closing-date') + ' ' + new Date(tournament.meldeschluss).toLocaleDateString()
const closingDate = `${t('closing-date')} ${new Date(tournament.meldeschluss).toLocaleDateString()}`
return (
<StyledCard className={classes.card}>
<CardHeader
@@ -229,13 +215,8 @@ export default function TournamentsCard(props) {
action={
<div className={classes.titleAction}>
{user?.account && (
<Box
sx={{
display: { xs: 'none', sm: 'flex' },
alignItems: 'center',
}}
>
<RouterLink to={'registration/' + tournament.id} className={classes.routerLink}>
<Box sx={{ display: { xs: 'none', sm: 'flex' }, alignItems: 'center' }}>
<RouterLink to={`registration/${tournament.id}`} className={classes.routerLink}>
<Button color="primary" variant="contained">
{t('tournament.registration')}
</Button>
@@ -246,8 +227,8 @@ export default function TournamentsCard(props) {
<TournamentMenu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onOpen={handleClick}
onClose={handleClose}
onOpen={handleMenuOpen}
onClose={handleMenuClose}
user={user}
tournament={tournament}
tournamentGroups={tournamentGroups}
@@ -257,9 +238,7 @@ export default function TournamentsCard(props) {
token={token}
tournamentData={tournamentData}
participants={participants}
handleClose={handleClose}
handleClose={handleMenuClose}
/>
)}
</div>
@@ -282,7 +261,7 @@ export default function TournamentsCard(props) {
{tournament.ort}
</div>
<div>
<Tooltip title={closingdate}>
<Tooltip title={closingDate}>
<IconButton size="large">
<DateRangeIcon />
</IconButton>
@@ -303,17 +282,9 @@ export default function TournamentsCard(props) {
</div>
</div>
{/* Registrierungs-Button: mobil unterhalb des Headers */}
{user?.account && (
<Box
sx={{
display: { xs: 'block', sm: 'none' },
px: 2,
mt: 1,
mb: 1,
}}
>
<RouterLink to={'registration/' + tournament.id} className={classes.routerLink}>
<Box sx={{ display: { xs: 'block', sm: 'none' }, px: 2, mt: 1, mb: 1 }}>
<RouterLink to={`registration/${tournament.id}`} className={classes.routerLink}>
<Button color="primary" variant="contained" fullWidth>
{t('tournament.registration')}
</Button>
@@ -323,31 +294,30 @@ export default function TournamentsCard(props) {
<CardActions className={classes.cardAction}>
<div className={classes.cardActionBlock}>
<IconButton className={classes.expand + (expanded ? ' ' + classes.expandOpen : '')} onClick={handleExpandClick} aria-expanded={expanded} aria-label="show more" size="large">
<IconButton className={`${classes.expand}${expanded ? ' ' + classes.expandOpen : ''}`} onClick={handleExpandClick} aria-expanded={expanded} aria-label="show more" size="large">
<ExpandMoreIcon />
</IconButton>
</div>
<h4>{t('headline.groups')}</h4>
<div className={classes.cardActionBlockRight}>
{/* //TODO: hier Rechnung generieren */}
<Tooltip title="Infos">
<RouterLink to={'info/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<RouterLink to={`info/${tournament.id}`}>
<IconButton aria-label="info" size="large">
<InfoIcon />
</IconButton>
</RouterLink>
</Tooltip>
<Tooltip title="result">
<RouterLink to={'results/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<RouterLink to={`results/${tournament.id}`}>
<IconButton aria-label="results" size="large">
<EmojiEventsIcon />
</IconButton>
</RouterLink>
</Tooltip>
{tournamentData?.streams != undefined && (
{tournamentData?.streams && (
<Tooltip title="Stream">
<RouterLink to={'stream/' + tournament.id}>
<IconButton aria-label="emoji_events" size="large">
<RouterLink to={`stream/${tournament.id}`}>
<IconButton aria-label="stream" size="large">
<VideocamIcon />
</IconButton>
</RouterLink>
@@ -357,7 +327,6 @@ export default function TournamentsCard(props) {
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
{/* // TODO: Gruppen hier laden */}
<Groups user={user} tournamentGroups={tournamentGroups} token={token} apiServer={apiServer} tournamentData={tournamentData} participants={participants} />
</CardContent>
</Collapse>

View File

@@ -1,6 +1,6 @@
import { render, renderHook } from '@testing-library/react'
import { render } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import { vi, describe, it, expect } from 'vitest'
import { vi, describe, it } from 'vitest'
import { DialogContext } from '../Dialog/Context'
import TournamentsCard from './TournamentsCard'
import AddGroups from '../Groups/AddGroups'
@@ -9,11 +9,9 @@ import Groups from '../Groups/Groups'
import AddEditTournament from './AddEditTournament'
import DeleteTournamentDialog from './DeleteTournamentDialog'
import TriggerGroupsDialog from './TriggerGroupsDialog'
import { dataTournament } from './__mocks__/dataTournament'
import { dataGroup } from '../Groups/__mocks__/dataGroup'
import useFetch from '../../components/UseFetch/UseFetch'
// Mocks für i18n und DialogContext
// i18n und DialogContext Mock
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key) => key,
@@ -29,7 +27,6 @@ const mockDialogContextValue = {
onClose: () => {},
open: false,
dialog: { onClose: () => {}, tid: 1 },
// ...weitere Werte nach Bedarf...
}
describe('Komponenten Smoke-Tests', () => {

View File

@@ -3,21 +3,18 @@ import { useContext } from 'react'
import { DialogContext } from '../Dialog/Context'
import { triggerGroup } from '../../api/groups'
export default function TriggerGroupsDialog(props) {
const { apiServer, token, tournamentGroups } = props
export default function TriggerGroupsDialog({ apiServer, token, tournamentGroups }) {
const dialog = useContext(DialogContext)
const handleDialogClose = () => {
dialog.onClose()
}
const handleClose = () => dialog.onClose()
const triggerTournament = async () => {
const handleTrigger = async () => {
try {
for (const tournament of tournamentGroups) {
await triggerGroup(apiServer, token, tournament.id, tournament.disziplin)
console.log(`groups of tournament ${tournament.id} triggered`)
for (const group of tournamentGroups) {
await triggerGroup(apiServer, token, group.id, group.disziplin)
console.log(`groups of tournament ${group.id} triggered`)
}
handleDialogClose()
handleClose()
} catch (err) {
console.error(err)
}
@@ -27,13 +24,13 @@ export default function TriggerGroupsDialog(props) {
<>
<DialogTitle id="dialog-title">Alle Gruppen neu auslosen?</DialogTitle>
<DialogContent>
<DialogContentText id="dialog-description">Alle Gruppe werden neu Ausgelost!</DialogContentText>
<DialogContentText id="dialog-description">Alle Gruppen werden neu ausgelost!</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose} color="primary" autoFocus>
<Button onClick={handleClose} color="primary" autoFocus>
abbrechen
</Button>
<Button onClick={triggerTournament} color="secondary">
<Button onClick={handleTrigger} color="secondary">
Gruppen auslosen
</Button>
</DialogActions>

View File

@@ -1,13 +1,13 @@
import { useEffect } from 'react'
import useFetch from './UseFetch'
export default function LoadParticipants(props) {
const { token, apiServer, setParticipants } = props
const { data: participants, loading } = useFetch(apiServer + '/participants' + token)
export default function LoadParticipants({ token, apiServer, setParticipants }) {
const url = `${apiServer}/participants${token ? token : ''}`
const { data: participants, loading } = useFetch(url)
useEffect(() => {
!loading && setParticipants(participants)
if (!loading && participants) setParticipants(participants)
}, [participants, loading, setParticipants])
return <br />
return null
}

View File

@@ -1,22 +1,38 @@
import {useState, useEffect} from 'react';
import { useState, useEffect } from 'react'
export default function useFetch(url) {
const [data,setData] = useState(null);
const [loading,setLoading] = useState(true);
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
const abortCtrl = new AbortController();
async function fetchData(url) {
const response = await fetch(url);
const data = await response.json();
// console.log('data: ', data);
setData(data);
setLoading(false);
}
fetchData(url);
// return () => fetchData.abort();
return () => abortCtrl.abort();
}, [url]);
useEffect(() => {
if (!url) {
setData(null)
setLoading(false)
setError(null)
return
}
return {data, loading};
}
const abortCtrl = new AbortController()
setLoading(true)
setError(null)
async function fetchData() {
try {
const response = await fetch(url, { signal: abortCtrl.signal })
if (!response.ok) throw new Error('Network response was not ok')
const json = await response.json()
setData(json)
} catch (err) {
if (err.name !== 'AbortError') setError(err)
} finally {
setLoading(false)
}
}
fetchData()
return () => abortCtrl.abort()
}, [url])
return { data, loading, error }
}

View File

@@ -1,7 +1,7 @@
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
// TODO: die übersetzungen nicht bundlen
// TODO: Übersetzungen nicht bundlen, sondern dynamisch laden
import common_de from './translations/de/common.json'
import common_en from './translations/en/common.json'
@@ -11,31 +11,21 @@ import common_it from './translations/it/common.json'
import common_cs from './translations/cs/common.json'
import common_nn from './translations/nn/common.json'
const resources = {
de: { common: common_de },
en: { common: common_en },
fr: { common: common_fr },
es: { common: common_es },
it: { common: common_it },
cs: { common: common_cs },
nn: { common: common_nn },
}
i18n.use(initReactI18next).init({
interpolation: { escapeValue: false }, // React already does escaping
lng: 'de', // language to use
fallbackLng: 'en', // fallback: ;
resources: {
en: {
common: common_en, // 'common' is our custom namespace
},
de: {
common: common_de,
},
fr: {
common: common_fr,
},
es: {
common: common_es,
},
it: {
common: common_it,
},
cs: {
common: common_cs,
},
nn: {
common: common_nn,
},
},
interpolation: { escapeValue: false }, // React macht Escaping bereits
lng: 'de', // Standardsprache
fallbackLng: 'en',
resources,
})
export default i18n

View File

@@ -9,23 +9,21 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
/* Tabellen-Design für Material-UI */
tr.MuiTableRow-root:nth-of-type(even) {
background-color: rgb(238, 238, 238);
background-color: #eee;
}
tr.MuiTableRow-root:nth-of-type(odd) {
background-color: rgb(250, 250, 250);
background-color: #fafafa;
}
tr.MuiTableRow-root:hover {
background-color: rgba(0, 0, 0, 0.17);
}
th.MuiTableCell-root.MuiTableCell-head {
font-weight: 800;
}
/* material-icons-regular - latin */
/* Material Icons Font */
@font-face {
font-family: 'Material Icons';
font-style: normal;
@@ -33,12 +31,11 @@ th.MuiTableCell-root.MuiTableCell-head {
src: url('../src/assets/fonts/material-icons-v143-latin-regular.woff2') format('woff2');
font-display: swap;
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* oder gewünschte Größe */
font-size: 24px;
display: inline-block;
line-height: 1;
letter-spacing: normal;
@@ -50,7 +47,7 @@ th.MuiTableCell-root.MuiTableCell-head {
-webkit-font-smoothing: antialiased;
}
/* roboto-regular - latin */
/* Roboto Font */
@font-face {
font-family: 'Roboto';
font-style: normal;

View File

@@ -1,8 +1,8 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import App from './App'
import './index.css'
import './i18n.js'
import './i18n'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>

View File

@@ -1,17 +1,13 @@
/* eslint-disable react/no-unescaped-entities */
import { Button, styled } from '@mui/material'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { Button, styled } from '@mui/material'
const PREFIX = 'Dataprotection'
const classes = {
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.routerLink}`]: {
textDecoration: 'inherit',
@@ -585,6 +581,8 @@ export default function Dataprotection() {
{t('back-to-page')}
</Button>
</Link>
<br />
<div style={{ marginTop: 32, color: '#888', fontSize: '0.9em' }}>Version 1.0 Stand: 08.06.2025</div>
</Root>
)
}

View File

@@ -1,17 +1,14 @@
/* eslint-disable react/no-unescaped-entities */
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { Button, styled } from '@mui/material'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
const PREFIX = 'Impress'
const classes = {
routerLink: `${PREFIX}-routerLink`,
}
const Root = styled('div')(({ theme }) => ({
const Root = styled('div')(() => ({
[`& .${classes.routerLink}`]: {
textDecoration: 'inherit',
},

View File

@@ -1,20 +1,17 @@
import { useParams } from 'react-router-dom'
import { useParams, Link } from 'react-router-dom'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import useFetch from '../components/UseFetch/UseFetch'
import { Link } from 'react-router-dom'
import { styled, Button } from '@mui/material'
import { useTranslation } from 'react-i18next'
const PREFIX = 'Streams'
const PREFIX = 'Info'
const classes = {
info: `${PREFIX}-stream`,
info: `${PREFIX}-info`,
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}`]: {
[`& .${classes.info}`]: {
backgroundColor: 'rgba(0,0,0,0.2)',
margin: '1rem',
padding: '1rem',
@@ -23,42 +20,53 @@ const Root = styled('div')(({ theme }) => ({
alignItems: 'center',
justifyContent: 'space-around',
},
[`& .${classes.routerLink}`]: {
textDecoration: 'inherit',
},
}))
export default function Info(props) {
const { apiServer } = props
export default function Info({ apiServer }) {
const { t } = useTranslation('common')
const { tid } = useParams()
const { data: tournament } = useFetch(apiServer + '/tournament/' + tid)
const { data: tournament } = useFetch(`${apiServer}/tournament/${tid}`)
const infos = tournament?.info ? JSON.parse(tournament?.info) : { 'Keine Infos vorhanden': null }
let infos = {}
try {
infos = tournament?.info ? JSON.parse(tournament.info) : {}
} catch {
infos = {}
}
return (
<Root>
<h1>{tournament?.name}</h1>
<h2>
{new Date(tournament?.veranstaltungsDatum).toLocaleDateString()} @ {tournament?.ort}
{tournament?.veranstaltungsDatum && new Date(tournament.veranstaltungsDatum).toLocaleDateString()} @ {tournament?.ort}
</h2>
<br />
<h2>Infos:</h2>
<br />
{infos !== null &&
Object.entries(infos).map((row) => {
return (
<div className={classes.info} key={row}>
<h3>
<a href={row[1]} target="_blank" rel="noreferrer">
{row[0]}
{Object.keys(infos).length > 0 ? (
Object.entries(infos).map(([label, url]) => (
<div className={classes.info} key={label}>
<h3>
{url ? (
<a href={url} target="_blank" rel="noreferrer">
{label}
</a>
</h3>
</div>
)
})}
) : (
label
)}
</h3>
</div>
))
) : (
<div className={classes.info}>
<h3>Keine Infos vorhanden</h3>
</div>
)}
<br />
<br />
<Link to={'/'}>
<Link to={'/'} className={classes.routerLink}>
<Button variant="outlined" startIcon={<ArrowBackIcon />}>
{t('back-to-page')}
</Button>

View File

@@ -1,19 +1,18 @@
import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
import { useParams } from 'react-router-dom'
import { styled, Paper } from '@mui/material'
import { useTranslation } from 'react-i18next'
const PREFIX = 'Live'
const classes = {
container: `${PREFIX}-container`,
participant: `${PREFIX}-participant`,
}
const Container = styled('div')(({ theme }) => ({
const Container = styled('div')({
display: 'flex',
flexWrap: 'wrap',
}))
})
const Tatami = styled(Paper)(({ theme }) => ({
width: '20rem',
@@ -31,94 +30,61 @@ const Tatami = styled(Paper)(({ theme }) => ({
},
}))
export default function Live(props) {
const { apiServer, token, user } = props
function Encounters({ encounters }) {
return encounters.map((el, index) => (
<Tatami elevation={3} key={index}>
<div className={classes.container}>
<div className={classes.participant}>{el.aka}</div>
<div>{el.pool}</div>
<div className={classes.participant}>{el.shiro}</div>
</div>
</Tatami>
))
}
export default function Live({ apiServer, token, user }) {
const { t } = useTranslation('common')
const { tid } = useParams()
const initData = {
tid: 65,
encounter: [
{
pool: 'A',
aka: 333,
shiro: 444,
},
{
pool: 'B',
aka: 111,
shiro: 123,
},
{
pool: 'C',
aka: 111,
shiro: 123,
},
{
pool: 'D',
aka: 111,
shiro: 123,
},
{
pool: 'F',
aka: 111,
shiro: 123,
},
],
}
const [data, setData] = useState({
tid: tid || 65,
encounter: [],
})
const wsRef = useRef(null)
const wsUrl = `ws://api.karateturniere.de/ws${token}`
const [data, setData] = useState(initData)
useEffect(() => {
const ws = new WebSocket(
// 'ws://localhost:8000/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcGVAd2F0dHNjaGUuZGUiLCJpZCI6MTE0MCwiYWRtaW4iOiIiLCJpYXQiOjE2NTAxMTE1NTEsImV4cCI6MTY1MDE5Nzk1MX0.gko7Im54rBLM0V4Yiwgx1KBUgvOrDImQQ7mSAGLoH30',
wsUrl,
)
const wsUrl = `ws://api.karateturniere.de/ws${token ? '?token=' + token : ''}`
const ws = new WebSocket(wsUrl)
wsRef.current = ws
ws.onopen = (event) => {
ws.onopen = () => {
ws.send('PING')
console.log('onopen')
console.log('WebSocket geöffnet')
}
ws.onmessage = function (event) {
const json = JSON.parse(event.data)
console.log('onMessage 1', json)
ws.onmessage = (event) => {
try {
if (event.type == 'message') {
const json = JSON.parse(event.data)
if (event.type === 'message' && json.data) {
setData(json.data)
console.log('data', data)
console.log('json.data', json.data)
// console.log('onMessage', json.data);
}
} catch (err) {
console.log(err)
console.error('WebSocket Fehler:', err)
}
}
//clean up function
return () => ws.close()
}, [])
const Encounters = () => {
console.log('enc', data)
const result = data?.encounter.map((el, index) => {
// console.log(el)
return (
<Tatami elevation={3} key={index}>
<div className={classes.container}>
<div className={classes.participant}>{el.aka}</div>
<div>{el.pool}</div>
<div className={classes.participant}>{el.shiro}</div>
</div>
</Tatami>
)
})
return result
}
ws.onerror = (err) => {
console.error('WebSocket Fehler:', err)
}
return () => {
ws.close()
}
}, [token])
return (
<>
<h1>Turnier: {data?.tid}</h1>
<Container>{data?.encounter && <Encounters></Encounters>}</Container>
<Container>{data?.encounter && data.encounter.length > 0 ? <Encounters encounters={data.encounter} /> : <div>Keine Begegnungen vorhanden</div>}</Container>
</>
)
}

View File

@@ -1,10 +1,10 @@
import { InputLabel, MenuItem, Select, FormControl, styled, TextField } from '@mui/material'
import { InputLabel, MenuItem, Select, FormControl, styled, TextField, Button } from '@mui/material'
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
import { useState } from 'react'
import { useParams } from 'react-router-dom'
import { sendMail } from '../api/mail'
const PREFIX = 'Mail'
const classes = {
stream: `${PREFIX}-stream`,
routerLink: `${PREFIX}-routerLink`,
@@ -14,92 +14,22 @@ const Root = styled('div')(({ theme }) => ({
[`& .${classes.stream}`]: {
backgroundColor: 'rgba(0,0,0,0.2)',
},
margin: '2rem',
maxWidth: 600,
}))
export default function Mail(props) {
const { 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 sendMailHandler = async () => {
try {
await sendMail(apiServer, token, { ...mail, body: nl2br(mail.body, true) })
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,
const templates = [
{
label: 'reset',
value: '',
},
{
label: 'Listen Online',
value: `Hallo Zusammen,
Die Listen und den Ablaufplan findet Ihr unter https://old.karateturniere.de/dm22/
Ebenfalls könnt ihr Euch nun wieder eine Rechnung erstellen lassen und hr seht direkt, auf welchen Pool Eure Teilnehmer starten.
Ebenfalls könnt ihr Euch nun wieder eine Rechnung erstellen lassen und ihr 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)
@@ -109,34 +39,91 @@ Die Links werde ich im Anschluss auch auf https://karateturniere.de veröffentli
Hier der direkte Link zu den Streams für die DM: https://karateturniere.de/stream/59
Grüße,
Mario Peters`}
>
Listen Online
Mario Peters`,
},
]
function nl2br(str) {
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br />')
}
function br2nl(str) {
return str.replace(/<\s*\/?br\s*[\/]?>/gi, '\n')
}
export default function Mail({ apiServer, token, tournaments }) {
const { tid } = useParams()
const [mail, setMail] = useState({
from: 'm.peters@karatenw.de',
to: 'mario@wattsche.de',
subject: 'DM 22',
body: 'test',
tid,
})
const handleChange = ({ target: { name, value } }) => {
setMail((prev) => ({ ...prev, [name]: value }))
}
const handleTemplateChange = ({ target: { value } }) => {
setMail((prev) => ({ ...prev, body: br2nl(value) }))
}
const sendMailHandler = async () => {
try {
await sendMail(apiServer, token, { ...mail, body: nl2br(mail.body) })
console.log('Email gesendet')
} catch (err) {
console.error(err)
}
}
// Nur Turniere aus dem letzten Jahr
const dateOffset = 365 * 24 * 60 * 60 * 1000
const recentTournaments = tournaments.filter((t) => new Date(t.veranstaltungsDatum) > Date.now() - dateOffset)
return (
<Root>
<h1>Email senden:</h1>
<FormControl fullWidth margin="normal">
<TextField id="from" name="from" label="Absender" variant="filled" value={mail.from} onChange={handleChange} />
</FormControl>
<FormControl fullWidth margin="normal">
<TextField id="to" name="to" label="Empfänger" variant="filled" value={mail.to} onChange={handleChange} />
</FormControl>
<FormControl fullWidth margin="normal">
<InputLabel id="tid">Turnier</InputLabel>
<Select labelId="tid" id="tid" name="tid" value={mail.tid} label="Turnier" onChange={handleChange}>
<MenuItem key={0} value="Einzelmail">
Einzelmail
</MenuItem>
{recentTournaments.map((t) => (
<MenuItem key={t.id} value={t.id}>
{t.name}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl fullWidth>
<TextField id="subject" name="subject" label="subject" variant="filled" defaultValue="DM 2022" onChange={changeValue} />
<FormControl fullWidth margin="normal">
<InputLabel id="templates">Vorlagen</InputLabel>
<Select labelId="templates" id="templates" name="templates" value="" label="Vorlagen" onChange={handleTemplateChange}>
{templates.map((tpl, idx) => (
<MenuItem key={idx} value={tpl.value}>
{tpl.label}
</MenuItem>
))}
</Select>
</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 fullWidth margin="normal">
<TextField id="subject" name="subject" label="Betreff" variant="filled" value={mail.subject} onChange={handleChange} />
</FormControl>
<br />
<button onClick={test}>test</button>
<button onClick={sendMailHandler}>senden</button>
<FormControl fullWidth margin="normal">
<TextField id="body" label="Nachricht" name="body" multiline minRows={6} value={mail.body} variant="filled" onChange={handleChange} />
</FormControl>
<Button variant="contained" color="primary" endIcon={<ArrowForwardIcon />} onClick={sendMailHandler} sx={{ mt: 2 }}>
Senden
</Button>
</Root>
)
}

View File

@@ -1,5 +1,4 @@
import { PDFDownloadLink } from '@react-pdf/renderer'
// import { PDFDownloadLink, PDFViewer } from '@react-pdf/renderer'
import ListPDFTemplate from '../components/PDF/ListPDFTemplate'
import { savePdf } from '../utilities/PDF'
import Spinner from '../components/Spinner/Spinner'
@@ -8,56 +7,50 @@ import useFetch from '../components/UseFetch/UseFetch'
import { generateRounds } from '../utilities/Rounds'
import { countGroups, generateGroupData } from '../utilities/Groups'
export default function OnePDFList(props) {
const { apiServer, token } = props
export default function OnePDFList({ apiServer, token }) {
const { tid, gid } = useParams()
// console.log('tid: ', tid)
// console.log('gid: ', gid)
const { data: tournament, loading: tournamentLoading } = useFetch(apiServer + '/tournament/' + tid + token)
const { data: tournamentGroups, loading: tournamentGroupsLoading } = useFetch(apiServer + '/group/' + tid + token)
const { data: allParticipants, loading: allParticipantsLoading } = useFetch(apiServer + '/tournament/' + tid + '/participants' + token)
const { data: allTeams, loading: allTeamsLoading } = useFetch(apiServer + '/teams')
const tokenParam = token ? '?token=' + token : ''
// console.log('tournamentGroups: ', tournamentGroups)
const groupIndex = tournamentGroups?.findIndex((x) => x.id === +gid)
// console.log('groupIndex: ', groupIndex)
const { data: tournament, loading: tournamentLoading } = useFetch(`${apiServer}/tournament/${tid}${tokenParam}`)
const { data: tournamentGroups, loading: tournamentGroupsLoading } = useFetch(`${apiServer}/group/${tid}${tokenParam}`)
const { data: allParticipants, loading: allParticipantsLoading } = useFetch(`${apiServer}/tournament/${tid}/participants${tokenParam}`)
const { data: allTeams, loading: allTeamsLoading } = useFetch(`${apiServer}/teams`)
const { data: encounter, loading: encounterLoading } = useFetch(`${apiServer}/group/encounters/${gid}${tokenParam}`)
const groupData =
!tournamentGroupsLoading &&
generateGroupData(
tournamentGroups[groupIndex].id,
tournamentGroups[groupIndex].gid,
tournamentGroups[groupIndex].altervon + '-' + tournamentGroups[groupIndex].alterbis,
tournamentGroups[groupIndex].gurtVon + '-' + tournamentGroups[groupIndex].gurtBis,
tournamentGroups[groupIndex].disziplin,
tournamentGroups[groupIndex].geschlecht,
tournamentGroups[groupIndex].pool,
countGroups(tournamentGroups[groupIndex].gid, tournamentGroups),
)
// console.log('groupData: ', groupData)
const { data: encounter, loading: encounterLoading } = useFetch(apiServer + '/group/encounters/' + gid + token)
if (tournamentGroupsLoading || tournamentLoading || allParticipantsLoading | allTeamsLoading || encounterLoading) {
if (tournamentGroupsLoading || tournamentLoading || allParticipantsLoading || allTeamsLoading || encounterLoading) {
return <Spinner />
}
const rounds = !tournamentGroupsLoading && generateRounds(encounter, groupData, allTeams, allParticipants, null, null)
const groupIndex = tournamentGroups?.findIndex((x) => x.id === +gid)
const group = tournamentGroups?.[groupIndex]
const groupData =
group &&
generateGroupData(
group.id,
group.gid,
`${group.altervon}-${group.alterbis}`,
`${group.gurtVon}-${group.gurtBis}`,
group.disziplin,
group.geschlecht,
group.pool,
countGroups(group.gid, tournamentGroups),
)
const rounds = groupData && generateRounds(encounter, groupData, allTeams, allParticipants, null, null)
return (
<>
{!tournamentGroupsLoading && groupData && (
<PDFDownloadLink document={<ListPDFTemplate {...props} groupData={groupData} tournament={tournament} rounds={rounds} allParticipants={allParticipants} allTeams={allTeams} />}>
{({ blob, url, loading, error }) => {
console.log(blob, url, loading, error)
// url && window.open(url, '_blank')
const gid = groupData.gid + ' - ' + groupData.id
// const gid = '1-1588'
console.log('groupData: ', groupData)
console.log('loading: ', loading)
blob && savePdf(blob, 'lists', gid, apiServer, token, tid)
const result = loading ? 'loading' : 'done'
return result
{groupData && (
<PDFDownloadLink
document={
<ListPDFTemplate apiServer={apiServer} token={token} groupData={groupData} tournament={tournament} rounds={rounds} allParticipants={allParticipants} allTeams={allTeams} />
}
>
{({ blob, url, loading }) => {
const filename = `${groupData.gid} - ${groupData.id}`
if (blob) savePdf(blob, 'lists', filename, apiServer, token, tid)
return loading ? 'loading' : 'done'
}}
</PDFDownloadLink>
)}

View File

@@ -1,18 +1,18 @@
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import CloseIcon from '@mui/icons-material/Close'
import { lazy, useEffect, useRef, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import ParticipantsMobile from '../components/Participants/ParticipantsMobile'
import Invoice from '../components/Registration/Invoice'
import TeamRegistrationMobile from '../components/Registration/TeamRegistrationMobile'
import { filterObject, findGroup, splitGroups } from '../utilities/Groups'
import { Button, IconButton, Snackbar, styled } from '@mui/material'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import CloseIcon from '@mui/icons-material/Close'
import { useTranslation } from 'react-i18next'
import ParticipantsMobile from '../components/Participants/ParticipantsMobile'
import TeamRegistrationMobile from '../components/Registration/TeamRegistrationMobile'
import Invoice from '../components/Registration/Invoice'
import { filterObject, findGroup, splitGroups } from '../utilities/Groups'
import { getAccount, getRegisteredSingle, getRegisteredTeams, register as registerApi } from '../api/account'
const PDF = lazy(() => import('../components/PDF/PDF'))
const PREFIX = 'Registration'
const classes = {
routerLink: `${PREFIX}-routerLink`,
}
@@ -23,136 +23,64 @@ const Root = styled('div')(({ theme }) => ({
},
}))
export default function Registration(props) {
const { participants, groups, apiServer, token, tournaments, user } = props
export default function Registration({ participants, groups, apiServer, token, tournaments, user }) {
const { t } = useTranslation('common')
const [account, setAccount] = useState(null)
const [countSingle, setCountSingle] = useState(null)
const [countTeams, setCountTeams] = useState(null)
const { tid } = useParams()
const [account, setAccount] = useState(null)
const [countSingle, setCountSingle] = useState([])
const [countTeams, setCountTeams] = useState([])
const [open, setOpen] = useState(false)
const [invoice, setInvoice] = useState(false)
const [snackMessage, setSnackMessage] = useState(false)
const [snackMessage, setSnackMessage] = useState('')
const switchRef = useRef([])
const tGroups = filterObject('turnier_id', parseInt(tid), groups)
const { singleGroups, teamGroups } = splitGroups(tGroups)
const switchRef = useRef([])
const tournament = filterObject('id', parseInt(tid), tournaments)[0]
const getAccountHandler = () => {
getAccount(apiServer, token)
.then((responseData) => {
setAccount(responseData)
return responseData
})
.catch((err) => {
console.error(err)
})
}
const getRegisteredSingleHandler = () => {
getRegisteredSingle(apiServer, tid, token)
.then((responseData) => {
setCountSingle(responseData)
return responseData
})
.catch((err) => {
console.error(err)
})
}
const getRegisteredTeamsHandler = () => {
getRegisteredTeams(apiServer, tid, token)
.then((responseData) => {
setCountTeams(responseData)
return responseData
})
.catch((err) => {
console.error(err)
})
}
// Daten laden
useEffect(() => {
getAccountHandler()
getRegisteredSingleHandler()
getRegisteredTeamsHandler()
// eslint-disable-next-line react-hooks/exhaustive-deps
getAccount(apiServer, token).then(setAccount).catch(console.error)
getRegisteredSingle(apiServer, tid, token).then(setCountSingle).catch(console.error)
getRegisteredTeams(apiServer, tid, token).then(setCountTeams).catch(console.error)
}, [apiServer, tid, token])
// Registrierung
const register = (newData) => {
registerApi(apiServer, token, newData)
.then(() => {
if (newData.register === '/registerParticipant' || newData.register === '/deregisterParticipant') {
getRegisteredSingleHandler()
} else if (newData.register === '/registerTeam' || newData.register === '/deregisterTeam') {
getRegisteredTeamsHandler()
if (['/registerParticipant', '/deregisterParticipant'].includes(newData.register)) {
getRegisteredSingle(apiServer, tid, token).then(setCountSingle)
} else if (['/registerTeam', '/deregisterTeam'].includes(newData.register)) {
getRegisteredTeams(apiServer, tid, token).then(setCountTeams)
}
snackOpen()
})
.catch((err) => {
console.error(err)
})
.catch(console.error)
}
const checkSingleRegistered = (id, discipline) => {
let result = false
countSingle.forEach((row) => {
if (row.disziplin === discipline && row.teilnehmer_id === id) {
result = true
}
})
return result
}
// Hilfsfunktionen
const checkSingleRegistered = (id, discipline) => countSingle.some((row) => row.disziplin === discipline && row.teilnehmer_id === id)
const checkTeamsRegistered = (id) => {
let result = 0
countTeams.forEach((row) => {
if (row.id === id) {
return (result = row.count)
}
})
return result
}
const checkTeamsRegistered = (id) => countTeams.find((row) => row.id === id)?.count || 0
const countTeamRegistrations = () => {
let result = 0
countTeams &&
countTeams.forEach((element) => {
result += element.count
})
return result
}
const countTeamRegistrations = () => countTeams.reduce((sum, el) => sum + (el.count || 0), 0)
// Snackbar
const snackOpen = () => {
setOpen(true)
}
const snackOpen = () => setOpen(true)
const snackClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setOpen(false)
if (reason !== 'clickaway') setOpen(false)
}
const tooLate = new Date(tournament.meldeschluss).getTime() + 86400000 - Date.now() < 0
const showInvoice = () => {
setInvoice(true)
}
const tooLate = new Date(tournament?.meldeschluss).getTime() + 86400000 - Date.now() < 0
const generateAction = (discipline) => {
const disciplineName = discipline === 'kata' ? 'Kata-Einzel' : 'Kumite-Einzel'
return {
icon: discipline,
tooltip: discipline.charAt(0).toUpperCase() + discipline.slice(1),
registered: (rowData) => {
const checked = checkSingleRegistered(rowData.id, disciplineName)
return checked
},
registered: (rowData) => checkSingleRegistered(rowData.id, disciplineName),
onClick: (event, rowData) => {
if (tooLate) {
// TODO: make global
setSnackMessage('too late')
snackOpen()
return
@@ -161,22 +89,19 @@ export default function Registration(props) {
if (turniergruppeId === 'error') {
setSnackMessage('no group found for ' + disciplineName)
snackOpen()
} else {
if (turniergruppeId) {
const myRef = switchRef.current[rowData.id + discipline]
let registerPath
myRef?.classList.contains('Mui-checked') ? (registerPath = '/deregisterParticipant') : (registerPath = '/registerParticipant')
register({
register: registerPath,
teilnehmerId: rowData.id,
turniergruppeId: turniergruppeId,
})
if (registerPath === '/deregisterParticipant') {
setSnackMessage(`You deregistred ${rowData.vorname} ${rowData.name} for ${disciplineName}`)
} else {
setSnackMessage(`You registred ${rowData.vorname} ${rowData.name} for ${disciplineName}`)
}
}
} else if (turniergruppeId) {
const myRef = switchRef.current[rowData.id + discipline]
const registerPath = myRef?.classList.contains('Mui-checked') ? '/deregisterParticipant' : '/registerParticipant'
register({
register: registerPath,
teilnehmerId: rowData.id,
turniergruppeId,
})
setSnackMessage(
registerPath === '/deregisterParticipant'
? `You deregistred ${rowData.vorname} ${rowData.name} for ${disciplineName}`
: `You registred ${rowData.vorname} ${rowData.name} for ${disciplineName}`,
)
}
},
}
@@ -196,7 +121,6 @@ export default function Registration(props) {
<h3>
{t('registration.closing-date')} {new Date(tournament.meldeschluss).toLocaleDateString()}
</h3>
<ParticipantsMobile
switchRef={switchRef}
data={participants}
@@ -207,9 +131,8 @@ export default function Registration(props) {
countSingle={countSingle}
getRegisteredSingle={getRegisteredSingle}
/>
<br></br>
<br></br>
<br />
<br />
{teamGroups.length === 0 && <h3>Keine Teamgruppen vorhanden</h3>}
{countTeams && teamGroups.length > 0 && (
<>
@@ -226,8 +149,8 @@ export default function Registration(props) {
/>
</>
)}
<br></br>
<br></br>
<br />
<br />
{countTeams && (
<Invoice
tournaments={tournaments}
@@ -237,29 +160,26 @@ export default function Registration(props) {
teamCosts={tournament.startgebuehrTeam}
/>
)}
<br></br>
<br></br>
{invoice && (
<PDF
apiServer={apiServer}
token={token}
tournament={tournament}
countSingle={countSingle}
countTeams={countTeams}
countTeamRegistrations={countTeamRegistrations()}
singleCosts={tournament.startgebuehr}
teamCosts={tournament.startgebuehrTeam}
user={user}
/>
)}
<Button onClick={showInvoice} variant="outlined" startIcon={<ArrowBackIcon />}>
{t('registration.generate-invoice')}
</Button>
<br />
<br />
<Link to={'/'} className={classes.routerLink}>
<Button variant="outlined" startIcon={<ArrowBackIcon />}>
<Button variant="outlined" startIcon={<ArrowBackIcon />} style={{ marginRight: '2rem' }}>
{t('back-to-page')}
</Button>
</Link>
<PDF
apiServer={apiServer}
token={token}
tournament={tournament}
countSingle={countSingle}
countTeams={countTeams}
countTeamRegistrations={countTeamRegistrations()}
singleCosts={tournament.startgebuehr}
teamCosts={tournament.startgebuehrTeam}
user={user}
/>
{/* // TODO: make global */}
<Snackbar
anchorOrigin={{

View File

@@ -1,18 +1,13 @@
import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import useFetch from '../components/UseFetch/UseFetch'
import { useParams, Link } from 'react-router-dom'
import { styled, Button } from '@mui/material'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import { useTranslation } from 'react-i18next'
import useFetch from '../components/UseFetch/UseFetch'
import PostResult from '../components/Results/PostResult'
import TournamentResults from '../components/Results/TournamentResults'
import { Link } from 'react-router-dom'
import { styled, Button } from '@mui/material'
import { useTranslation } from 'react-i18next'
// TODO: farben anpassen im theme (chips, grautöne)
const PREFIX = 'Results'
const classes = {
routerLink: `${PREFIX}-routerLink`,
}
@@ -24,53 +19,37 @@ const Root = styled('div')(() => ({
},
}))
export default function Results(props) {
const { apiServer, token, user } = props
export default function Results({ apiServer, token, user }) {
const { t } = useTranslation('common')
const { tid } = useParams()
const { data: participants } = useFetch(apiServer + '/tournament/' + tid + '/participants')
const { data: teams } = useFetch(apiServer + '/teams')
const { data: tournament } = useFetch(apiServer + '/tournament/' + tid)
const { data: fetchedGroups } = useFetch(apiServer + '/group/' + tid)
const { data: participants } = useFetch(`${apiServer}/tournament/${tid}/participants`)
const { data: teams } = useFetch(`${apiServer}/teams`)
const { data: tournament } = useFetch(`${apiServer}/tournament/${tid}`)
const { data: fetchedGroups } = useFetch(`${apiServer}/group/${tid}`)
const [groups, setGroups] = useState(null)
console.log('groups', groups)
// console.log('participants', participants)
// console.log('teams', teams)
// console.log('tournament', tournament)
// console.log('fetchedGroups', fetchedGroups)
// console.log('loadingParticipants', loadingParticipants)
// console.log('loadingTeams', loadingTeams)
// console.log('loadingTournament', loadingTournament)
// console.log('loadingFetchedGroups', loadingFetchedGroups)
useEffect(() => {
setGroups(fetchedGroups)
}, [fetchedGroups])
return (
fetchedGroups &&
tournament &&
participants &&
teams &&
groups && (
<Root>
<h1>{tournament?.name}</h1>
<h2>
{new Date(tournament?.veranstaltungsDatum).toLocaleDateString()} @ {tournament?.ort}
</h2>
<br />
<h3>Ergebnisse:</h3>
{user?.admin > 2 && token && <PostResult apiServer={apiServer} token={token} groups={groups} setGroups={setGroups} />}
<TournamentResults groups={groups} participants={participants} teams={teams} user={user} />
if (!(fetchedGroups && tournament && participants && teams && groups)) return null
<Link to={'/'} className={classes.routerLink}>
<Button variant="outlined" startIcon={<ArrowBackIcon />}>
{t('back-to-page')}
</Button>
</Link>
</Root>
)
return (
<Root>
<h1>{tournament?.name}</h1>
<h2>
{new Date(tournament?.veranstaltungsDatum).toLocaleDateString()} @ {tournament?.ort}
</h2>
<br />
<h3>Ergebnisse:</h3>
{user?.admin > 2 && token && <PostResult apiServer={apiServer} token={token} groups={groups} setGroups={setGroups} />}
<TournamentResults groups={groups} participants={participants} teams={teams} user={user} />
<Link to={'/'} className={classes.routerLink}>
<Button variant="outlined" startIcon={<ArrowBackIcon />}>
{t('back-to-page')}
</Button>
</Link>
</Root>
)
}

View File

@@ -1,18 +1,15 @@
import { useParams } from 'react-router-dom'
import { useParams, Link } from 'react-router-dom'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import useFetch from '../components/UseFetch/UseFetch'
import { Link } from 'react-router-dom'
import { styled, Button } from '@mui/material'
import { useTranslation } from 'react-i18next'
const PREFIX = 'Streams'
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)',
@@ -29,17 +26,43 @@ const Root = styled('div')(({ theme }) => ({
},
}))
export default function Streams(props) {
const { apiServer } = props
function ShowStreams({ streams }) {
if (!streams || Object.keys(streams).length === 0) {
return (
<div className={classes.stream}>
<h2>Kein Stream vorhanden</h2>
</div>
)
}
return Object.entries(streams).map(([key, value]) => (
<div className={classes.stream} key={key}>
<h2>{key}:</h2>
<h3>
{value ? (
<a href={value} target="_blank" rel="noreferrer">
{value}
</a>
) : (
'Kein Link'
)}
</h3>
</div>
))
}
export default function Streams({ apiServer }) {
const { t } = useTranslation('common')
const { tid } = useParams()
const { data: tournament } = useFetch(apiServer + '/tournament/' + tid)
const { data: tournament } = useFetch(`${apiServer}/tournament/${tid}`)
const noStreams = { 'Kein Stream vorhanden': '' }
const streams = tournament?.streams !== undefined ? JSON.parse(tournament?.streams) : noStreams
let streams = {}
try {
streams = tournament?.streams ? JSON.parse(tournament.streams) : {}
} catch {
streams = {}
}
// order streams by key
// Streams nach Key sortieren
const streamsOrdered = Object.keys(streams)
.sort()
.reduce((obj, key) => {
@@ -47,32 +70,15 @@ export default function Streams(props) {
return obj
}, {})
const ShowStreams = () => {
const result = []
for (const [key, value] of Object.entries(streamsOrdered)) {
result.push(
<div className={classes.stream} key={key}>
<h2>{key}:</h2>
<h3>
<a href={value} target="_blank" rel="noreferrer">
{value}
</a>
</h3>
</div>,
)
}
return result
}
return (
<Root>
<h1>{tournament?.name}</h1>
<h2>
{new Date(tournament?.veranstaltungsDatum).toLocaleDateString()} @ {tournament?.ort}
{tournament?.veranstaltungsDatum && new Date(tournament.veranstaltungsDatum).toLocaleDateString()} @ {tournament?.ort}
</h2>
<br />
<h2>Streams:</h2>
<ShowStreams />
<ShowStreams streams={streamsOrdered} />
<Link to={'/'} className={classes.routerLink}>
<Button variant="outlined" startIcon={<ArrowBackIcon />}>
{t('back-to-page')}

View File

@@ -1,53 +1,39 @@
import { Link, useParams } from 'react-router-dom'
import useFetch from '../components/UseFetch/UseFetch'
import { useEffect } from 'react'
export default function TournamentPDFLists(props) {
const { apiServer, token } = props
export default function TournamentPDFLists({ apiServer, token }) {
const { tid } = useParams()
const { data: tournamentGroups, loading: tournamentGroupsLoading } = useFetch(apiServer + '/group/' + tid + token)
const tokenParam = token ? '?token=' + token : ''
const { data: tournamentGroups, loading } = useFetch(`${apiServer}/group/${tid}${tokenParam}`)
if (tournamentGroupsLoading) {
return <>loading</>
}
console.log('tournamentGroups: ', tournamentGroups)
const groupIds = []
tournamentGroups?.forEach((group) => {
groupIds.push(group.id)
})
console.log('groupId[0]: ', groupIds[0])
const GenLinks = () => {
const result = []
groupIds?.forEach((id) => {
result.push(
<>
<iframe src={`/pdf/lists/${tid}/one/${id}`} title={id} />
<Link to={`./one/${id}`}>
<p key={id}>{id}</p>
</Link>
</>,
)
})
//TODO: ohne den code, werden keine pdfs generiert
fetch('http://localhost:3000/pdf/lists/60/one/1538')
.then((response) => {
return response.json()
useEffect(() => {
// PDF-Generierung für alle Gruppen anstoßen
if (tournamentGroups && tournamentGroups.length > 0) {
tournamentGroups.forEach((group) => {
fetch(`/pdf/lists/${tid}/one/${group.id}`)
.then(() => {})
.catch((err) => console.error(err))
})
.then((responseData) => {
// console.log('getAccount: ', responseData);
return responseData
})
.catch((err) => {
console.error(err)
})
return result
}
}, [tournamentGroups, tid])
if (loading) return <>loading</>
if (!tournamentGroups || tournamentGroups.length === 0) {
return <div>Keine Gruppen gefunden</div>
}
return (
<>
<GenLinks />
{tournamentGroups.map((group) => (
<div key={group.id} style={{ marginBottom: 24 }}>
<iframe src={`/pdf/lists/${tid}/one/${group.id}`} title={`PDF Gruppe ${group.id}`} style={{ width: '100%', height: 400, border: '1px solid #ccc' }} />
<Link to={`./one/${group.id}`}>
<p>{group.id}</p>
</Link>
</div>
))}
</>
)
}

View File

@@ -1,38 +1,33 @@
const beltClassMap = {
DAN: 'black-belt',
'1. Kyu': 'brown-belt',
'2. Kyu': 'brown-belt',
'3. Kyu': 'brown-belt',
'4. Kyu': 'violet-belt',
'5. Kyu': 'violet-belt',
'6. Kyu': 'green-belt',
'7. Kyu': 'orange-belt',
'8. Kyu': 'yellow-belt',
'9. Kyu': 'white-belt',
}
const beltColorMap = {
DAN: 'rgba(0, 0, 0, 0.50)',
'1. Kyu': 'rgba(168, 88, 27, 0.5)',
'2. Kyu': 'rgba(168, 88, 27, 0.5)',
'3. Kyu': 'rgba(168, 88, 27, 0.5)',
'4. Kyu': 'rgba(138, 43, 226, 0.5)',
'5. Kyu': 'rgba(138, 43, 226, 0.5)',
'6. Kyu': 'rgba(0, 128, 0, 0.5)',
'7. Kyu': 'rgba(255, 135, 18, 0.6)',
'8. Kyu': 'rgba(255, 255, 0, 0.6)',
'9. Kyu': 'white',
}
export function BeltClass(belt) {
let beltClass
if (belt === 'DAN') {
beltClass = 'black-belt'
} else if (belt === '1. Kyu' || belt === '2. Kyu' || belt === '3. Kyu') {
beltClass = 'brown-belt'
} else if (belt === '4. Kyu' || belt === '5. Kyu') {
beltClass = 'violet-belt'
} else if (belt === '6. Kyu') {
beltClass = 'green-belt'
} else if (belt === '7. Kyu') {
beltClass = 'orange-belt'
} else if (belt === '8. Kyu') {
beltClass = 'yellow-belt'
} else if (belt === '9. Kyu') {
beltClass = 'white-belt'
}
return beltClass
return beltClassMap[belt] || ''
}
export function beltColor(belt) {
let color
if (belt === 'DAN') {
color = 'rgba(0, 0, 0, 0.50)'
} else if (belt === '1. Kyu' || belt === '2. Kyu' || belt === '3. Kyu') {
color = 'rgba(168, 88, 27, 0.5)'
} else if (belt === '4. Kyu' || belt === '5. Kyu') {
color = 'rgba(138, 43, 226, 0.5)'
} else if (belt === '6. Kyu') {
color = 'rgba(0, 128, 0, 0.5)'
} else if (belt === '7. Kyu') {
color = 'rgba(255, 135, 18, 0.6)'
} else if (belt === '8. Kyu') {
color = 'rgba(255, 255, 0, 0.6)'
} else if (belt === '9. Kyu') {
color = 'white'
}
return color
return beltColorMap[belt] || ''
}

View File

@@ -1,21 +1,18 @@
export function convertDate(date) {
const result = new Date(date)
return result.getDay() + '.' + result.getMonth() + '.' + result.getFullYear()
const d = new Date(date)
const day = String(d.getDate()).padStart(2, '0')
const month = String(d.getMonth() + 1).padStart(2, '0')
const year = d.getFullYear()
return `${day}.${month}.${year}`
}
export function getAge(birthDate, tournamentDate) {
const birth = new Date(birthDate)
const tournament = (tournamentDate && new Date(tournamentDate)) || new Date() // Fallback auf das aktuelle Datum, falls kein Turnierdatum angegeben ist
// Jahre direkt vergleichen
const tournament = tournamentDate ? new Date(tournamentDate) : new Date()
let age = tournament.getFullYear() - birth.getFullYear()
// Monate und Tage vergleichen, um zu prüfen, ob der Geburtstag im Zieljahr schon war
const monthDifference = tournament.getMonth() - birth.getMonth()
if (monthDifference < 0 || (monthDifference === 0 && tournament.getDate() < birth.getDate())) {
age-- // Wenn der Geburtstag noch nicht war, ein Jahr abziehen
const m = tournament.getMonth() - birth.getMonth()
if (m < 0 || (m === 0 && tournament.getDate() < birth.getDate())) {
age--
}
return age
}

View File

@@ -1,33 +1,23 @@
import { getAge } from './Date'
/**
* Aus alle Gruppen wird eine bestimmte Gruppe für das Turnier
*
*
* @param {string} key - A string param
* @param {string} val - A string param
* @param {object} groups - A string param
*
* @return {string} A good string
*
* Filtert ein Array von Objekten nach einem bestimmten Key/Value.
* @param {string} key
* @param {string|number} val
* @param {Array} arr
* @returns {Array}
*/
export const filterObject = (key, val, object) => {
let result = []
if (object) {
object.forEach((row) => {
if (row[key] == val) {
result.push(row)
}
})
return result
}
}
export const filterObject = (key, val, arr) => (Array.isArray(arr) ? arr.filter((row) => row[key] == val) : [])
// Gruppen nach Einzel / Team aufsplitten
export const splitGroups = (group) => {
let singleGroups = []
let teamGroups = []
group.forEach((row) => {
/**
* Teilt Gruppen in Einzel- und Teamgruppen auf.
* @param {Array} groups
* @returns {{singleGroups: Array, teamGroups: Array}}
*/
export const splitGroups = (groups = []) => {
const singleGroups = []
const teamGroups = []
groups.forEach((row) => {
if (row.disziplin === 'Kumite-Team' || row.disziplin === 'Kata-Team' || row.disziplin === 'Kata-Team-Mixed') {
teamGroups.push(row)
} else {
@@ -37,50 +27,40 @@ export const splitGroups = (group) => {
return { singleGroups, teamGroups }
}
// Passende Gruppe finden um einen Teilnehmer zu melden.
export const findGroup = (participant, groups, dicipline, tournamentDate) => {
let result = []
/**
* Findet passende Gruppe für einen Teilnehmer.
* @param {object} participant
* @param {Array} groups
* @param {string} discipline
* @param {string|Date} tournamentDate
* @returns {Array|String}
*/
export const findGroup = (participant, groups, discipline, tournamentDate) => {
const age = getAge(participant.gebDatum, tournamentDate)
const belt = parseInt(participant.gurt.charAt(0)) ? parseInt(participant.gurt.charAt(0)) : 0
groups.forEach((row) => {
if (
row.disziplin === dicipline &&
row.geschlecht === participant.geschlecht &&
(parseInt(row.gurtVon.charAt(0)) ? parseInt(row.gurtVon.charAt(0)) : 0) >= belt &&
belt >= (parseInt(row.gurtBis.charAt(0)) ? parseInt(row.gurtBis.charAt(0)) : 0) &&
row.altervon <= age &&
age <= row.alterbis
) {
result.push(row)
}
const belt = parseInt(participant.gurt) || 0
const result = groups.filter((row) => {
const gurtVon = parseInt(row.gurtVon) || 0
const gurtBis = parseInt(row.gurtBis) || 0
return row.disziplin === discipline && row.geschlecht === participant.geschlecht && gurtVon >= belt && belt >= gurtBis && row.altervon <= age && age <= row.alterbis
})
if (result.length > 0) {
console.log('passende Gruppen', result)
return result
} else {
return 'error'
}
return result.length > 0 ? result : 'error'
}
export const generateGroupData = (id, gid, age, belt, discipline, gender, pool, count) => {
return {
id,
gid,
age,
belt,
discipline,
gender,
pool,
count,
}
}
/**
* Erstellt ein Gruppen-Objekt für PDFs etc.
*/
export const generateGroupData = (id, gid, age, belt, discipline, gender, pool, count) => ({
id,
gid,
age,
belt,
discipline,
gender,
pool,
count,
})
export const countGroups = (gid, tournamentGroups) => {
let count = 0
for (let i in tournamentGroups) {
if (tournamentGroups[i].gid === gid && tournamentGroups[i].pool !== 'Final') {
count++
}
}
return count
}
/**
* Zählt die Anzahl der Gruppen mit gleichem gid (außer Final).
*/
export const countGroups = (gid, tournamentGroups = []) => tournamentGroups.filter((g) => g.gid === gid && g.pool !== 'Final').length

View File

@@ -2,16 +2,14 @@ import { savePdf as savePdfApi } from '../api/pdf'
export const savePdf = (blob, categorie, gid, apiServer, token, tid) => {
const reader = new FileReader()
blob.text().then(() => {
reader.onloadend = async () => {
const base64 = reader.result
try {
await savePdfApi(apiServer, token, base64, categorie, gid, tid)
console.log('PDF gespeichert')
} catch (err) {
console.error(err)
}
reader.onloadend = async () => {
const base64 = reader.result.split(',')[1] // Nur der Base64-Teil
try {
await savePdfApi(apiServer, token, base64, categorie, gid, tid)
console.log('PDF gespeichert')
} catch (err) {
console.error(err)
}
reader.readAsBinaryString(blob)
})
}
reader.readAsDataURL(blob)
}

View File

@@ -3,48 +3,49 @@ import KoEncounter from '../components/Groups/KoEncounter'
export const generateRounds = (encounter, groupData, allTeams, allParticipants, change, setWinner) => {
const rounds = { r1: [], r2: [], r3: [], r4: [] }
encounter &&
encounter.forEach((element) => {
let aka, shiro
if (!encounter) return rounds
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)
}
encounter.forEach((element) => {
let aka, shiro
const encounterData = {
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,
},
}
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)
}
if (element?.begegnung >= 8) {
rounds.r1.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={15} setWinner={setWinner} />)
} else if (3 < element?.begegnung && element?.begegnung < 8) {
rounds.r2.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={7} setWinner={setWinner} />)
} else if (1 < element?.begegnung && element?.begegnung < 4) {
rounds.r3.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={3} setWinner={setWinner} />)
} else if (element?.begegnung === 1) {
rounds.r4.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={1} setWinner={setWinner} />)
}
})
const encounterData = {
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,
},
}
// Runde bestimmen
if (element?.begegnung >= 8) {
rounds.r1.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={15} setWinner={setWinner} />)
} else if (element?.begegnung > 3 && element?.begegnung < 8) {
rounds.r2.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={7} setWinner={setWinner} />)
} else if (element?.begegnung > 1 && element?.begegnung < 4) {
rounds.r3.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={3} setWinner={setWinner} />)
} else if (element?.begegnung === 1) {
rounds.r4.push(<KoEncounter key={element?.begegnung} encounterData={encounterData} change={change} num={1} setWinner={setWinner} />)
}
})
// console.log('rounds', rounds)
return rounds
}