some enhancements for tournaments
This commit is contained in:
@@ -1,107 +1,127 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="tournaments-view">
|
||||
<h2>Turnier</h2>
|
||||
<div>
|
||||
<div>
|
||||
<h3>Datum</h3>
|
||||
<div>
|
||||
<select v-model="selectedDate">
|
||||
<option value="new">Neues Turnier</option>
|
||||
<option v-for="date in dates" :key="date.id" :value="date.id">
|
||||
{{ new Date(date.date).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="selectedDate === 'new'">
|
||||
<div>
|
||||
<input type="date" v-model="newDate" />
|
||||
<button @click="createTournament">Erstellen</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<h3>Turnier</h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isGroupTournament">
|
||||
Spielen in Gruppen
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Teilnehmer</h4>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member.firstName }} {{ participant.member.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }} {{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button type="button" @click="addParticipant">Hinzufügen</button>
|
||||
</div>
|
||||
<div v-if="isGroupTournament && participants.length > 1">
|
||||
<label>
|
||||
Anzahl Gruppen:
|
||||
<input type="number" v-model="numberOfGroups">
|
||||
</label>
|
||||
<button @click="createGroups">Gruppen erstellen</button>
|
||||
<button @click="randomizeGroups">Zufällig verteilen</button>
|
||||
</div>
|
||||
<div v-if="groups && groups.length > 0">
|
||||
<h4>Gruppen</h4>
|
||||
<ul class="groupoverview">
|
||||
<li v-for="group in groups" :key="group.groupId">
|
||||
<h4>Gruppe {{ group.groupId }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Spielername</th>
|
||||
<th>Bilanz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="participant in group.participants" :key="participant.id">
|
||||
<td>{{ participant.name }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Begegnung</th>
|
||||
<th>Sätze</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :key="match.id">
|
||||
<td>{{ match.groupId ? "Gr " + match.groupId : match.round }}</td>
|
||||
<td>{{ getPlayerName(match.player1) }} - {{ getPlayerName(match.player2) }}</td>
|
||||
<td v-for="result in match.tournamentResults">{{ result.pointsPlayer1 }}:{{ result.pointsPlayer2 }}</td>
|
||||
<td><input size="5" type="text" v-model="match.result" @keyup.enter="saveMatchResult(match, match.tournamentResults.length + 1, match.result)" /></td>
|
||||
<td v-if="match.isFinished">{{ match.result ?? '0:0' }}</td>
|
||||
<td v-else><button @click="finishMatch(match)">Abschließen</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Datumsauswahl / Neues Turnier -->
|
||||
<div class="tournament-config">
|
||||
<h3>Datum</h3>
|
||||
<select v-model="selectedDate">
|
||||
<option value="new">Neues Turnier</option>
|
||||
<option v-for="date in dates" :key="date.id" :value="date.id">
|
||||
{{ new Date(date.date).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="selectedDate === 'new'" class="new-tournament">
|
||||
<input type="date" v-model="newDate" />
|
||||
<button @click="createTournament">Erstellen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Konfiguration & Gruppenphase -->
|
||||
<div v-if="selectedDate !== 'new'" class="tournament-setup">
|
||||
<label>
|
||||
<input type="checkbox" v-model="isGroupTournament" />
|
||||
Spielen in Gruppen
|
||||
</label>
|
||||
|
||||
<section class="participants">
|
||||
<h4>Teilnehmer</h4>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member.firstName }}
|
||||
{{ participant.member.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }}
|
||||
{{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="addParticipant">Hinzufügen</button>
|
||||
</section>
|
||||
|
||||
<section v-if="isGroupTournament && participants.length > 1" class="group-controls">
|
||||
<label>
|
||||
Anzahl Gruppen:
|
||||
<input type="number" v-model.number="numberOfGroups" min="1" />
|
||||
</label>
|
||||
<button @click="createGroups">Gruppen erstellen</button>
|
||||
<button @click="randomizeGroups">Zufällig verteilen</button>
|
||||
</section>
|
||||
|
||||
<section v-if="groups.length" class="groups-overview">
|
||||
<h3>Gruppenübersicht</h3>
|
||||
<div v-for="group in groups" :key="group.groupId" class="group-table">
|
||||
<h4>Gruppe {{ group.groupId }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Spieler</th>
|
||||
<th>Punkte</th>
|
||||
<th>Satz</th>
|
||||
<th>Diff</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="pl in groupRankings[group.groupId]" :key="pl.id">
|
||||
<td>{{ pl.position }}.</td>
|
||||
<td>{{ pl.name }}</td>
|
||||
<td>{{ pl.points }}</td>
|
||||
<td>{{ pl.setsWon }}:{{ pl.setsLost }}</td>
|
||||
<td>
|
||||
{{ pl.setDiff >= 0 ? '+' + pl.setDiff : pl.setDiff }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- K.o.-Runde starten -->
|
||||
<div v-if="participants.length > 1 && !showKnockout" class="ko-start">
|
||||
<button @click="startKnockout">
|
||||
K.o.-Runde starten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- K.o.-Runde anzeigen -->
|
||||
<section v-if="showKnockout" class="ko-round">
|
||||
<h4>K.-o.-Runde</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Runde</th>
|
||||
<th>Begegnung</th>
|
||||
<th>Ergebnis</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in knockoutMatches" :key="m.id">
|
||||
<td>{{ m.round }}</td>
|
||||
<td>
|
||||
{{ getPlayerName(m.player1) }} –
|
||||
{{ getPlayerName(m.player2) }}
|
||||
</td>
|
||||
<td>{{ m.result || '-' }}</td>
|
||||
<td v-if="!m.isFinished">
|
||||
<input v-model="m.resultInput" placeholder="z.B. 11:4, 4:11, 4, -4"
|
||||
@keyup.enter="saveMatchResult(m, m.resultInput)" />
|
||||
<button @click="finishMatch(m)">
|
||||
Fertig
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -111,60 +131,77 @@ import apiClient from '../apiClient';
|
||||
|
||||
export default {
|
||||
name: 'TournamentsView',
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedDate: 'new',
|
||||
newDate: '',
|
||||
dates: [],
|
||||
|
||||
participants: [],
|
||||
selectedMember: null,
|
||||
clubMembers: [],
|
||||
|
||||
numberOfGroups: 1,
|
||||
isGroupTournament: false,
|
||||
groups: [],
|
||||
|
||||
matches: [],
|
||||
showKnockout: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub']),
|
||||
knockoutMatches() {
|
||||
return this.matches.filter(m => m.round !== 'group');
|
||||
},
|
||||
groupRankings() {
|
||||
const byGroup = {};
|
||||
this.groups.forEach(g => {
|
||||
byGroup[g.groupId] = g.participants.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
points: 0,
|
||||
setsWon: 0,
|
||||
setsLost: 0,
|
||||
setDiff: 0,
|
||||
}));
|
||||
});
|
||||
this.matches.forEach(m => {
|
||||
if (!m.isFinished || m.round !== 'group') return;
|
||||
const [s1, s2] = m.result.split(':').map(n => +n);
|
||||
const arr = byGroup[m.groupId];
|
||||
if (!arr) return;
|
||||
const e1 = arr.find(x => x.id === m.player1.id);
|
||||
const e2 = arr.find(x => x.id === m.player2.id);
|
||||
if (!e1 || !e2) return;
|
||||
if (s1 > s2) e1.points += 2;
|
||||
else if (s2 > s1) e2.points += 2;
|
||||
e1.setsWon += s1; e1.setsLost += s2;
|
||||
e2.setsWon += s2; e2.setsLost += s1;
|
||||
});
|
||||
const rankings = {};
|
||||
Object.entries(byGroup).forEach(([gid, arr]) => {
|
||||
arr.forEach(p => p.setDiff = p.setsWon - p.setsLost);
|
||||
arr.sort((a, b) => {
|
||||
if (b.points !== a.points) return b.points - a.points;
|
||||
if (b.setDiff !== a.setDiff) return b.setDiff - a.setDiff;
|
||||
if (b.setsWon !== a.setsWon) return b.setsWon - a.setsWon;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
rankings[gid] = arr.map((p, i) => ({
|
||||
...p, position: i + 1
|
||||
}));
|
||||
});
|
||||
return rankings;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedDate: async function (newVal) {
|
||||
if (newVal !== 'new') {
|
||||
try {
|
||||
const groupResponse = await apiClient.get(`/tournament/${this.currentClub}/${newVal}`);
|
||||
this.isGroupTournament = groupResponse.data.type === 'groups';
|
||||
const participantsResponse = await apiClient.post('/tournament/participants', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: newVal,
|
||||
});
|
||||
this.participants = participantsResponse.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
await this.fetchGroups();
|
||||
selectedDate: {
|
||||
immediate: true,
|
||||
handler: async function (val) {
|
||||
if (val === 'new') return;
|
||||
await this.loadTournamentData();
|
||||
}
|
||||
},
|
||||
isGroupTournament: async function (newVal) {
|
||||
if (newVal) {
|
||||
this.numberOfGroups = 2;
|
||||
} else {
|
||||
this.numberOfGroups = 1;
|
||||
}
|
||||
await apiClient.post('/tournament/modus', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
type: newVal ? 'groups' : 'bestOf',
|
||||
numberOfGroups: this.numberOfGroups,
|
||||
});
|
||||
},
|
||||
numberOfGroups: async function (newVal) {
|
||||
await apiClient.post('/tournament/modus', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
type: this.isGroupTournament ? 'groups' : 'bestOf',
|
||||
numberOfGroups: newVal,
|
||||
});
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@@ -172,132 +209,163 @@ export default {
|
||||
this.$router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const responseDates = await apiClient.get(`/tournament/${this.currentClub}`);
|
||||
this.dates = responseDates.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching tournaments:', error);
|
||||
}
|
||||
|
||||
try {
|
||||
const responseMembers = await apiClient.get(`/clubmembers/get/${this.currentClub}/false`);
|
||||
this.clubMembers = responseMembers.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching club members:', error);
|
||||
}
|
||||
// Turniere und Mitglieder laden
|
||||
const d = await apiClient.get(`/tournament/${this.currentClub}`);
|
||||
this.dates = d.data;
|
||||
const m = await apiClient.get(
|
||||
`/clubmembers/get/${this.currentClub}/false`
|
||||
);
|
||||
this.clubMembers = m.data;
|
||||
},
|
||||
methods: {
|
||||
async loadTournamentData() {
|
||||
// 1) Turnier‐Metadaten holen (Typ + Anzahl Gruppen)
|
||||
const tRes = await apiClient.get(
|
||||
`/tournament/${this.currentClub}/${this.selectedDate}`
|
||||
);
|
||||
const tournament = tRes.data;
|
||||
this.isGroupTournament = tournament.type === 'groups';
|
||||
this.numberOfGroups = tournament.numberOfGroups;
|
||||
|
||||
// 2) Teilnehmer
|
||||
const pRes = await apiClient.post('/tournament/participants', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
this.participants = pRes.data;
|
||||
|
||||
// 3) Gruppen (mit Teilnehmern)
|
||||
const gRes = await apiClient.get('/tournament/groups', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
this.groups = gRes.data;
|
||||
|
||||
// 4) Alle Matches
|
||||
const mRes = await apiClient.get(
|
||||
`/tournament/matches/${this.currentClub}/${this.selectedDate}`
|
||||
);
|
||||
this.matches = mRes.data;
|
||||
|
||||
// 5) Steuere K.o.-Anzeige
|
||||
this.showKnockout = this.matches.some(m => m.round !== 'group');
|
||||
},
|
||||
|
||||
getPlayerName(p) {
|
||||
return p.member.firstName + ' ' + p.member.lastName;
|
||||
},
|
||||
|
||||
async createTournament() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament', {
|
||||
clubId: this.currentClub,
|
||||
name: this.newDate,
|
||||
date: this.newDate,
|
||||
});
|
||||
this.dates = response.data;
|
||||
this.newDate = '';
|
||||
} catch (error) {
|
||||
console.error('Error creating tournament:', error);
|
||||
}
|
||||
const r = await apiClient.post('/tournament', {
|
||||
clubId: this.currentClub,
|
||||
tournamentName: this.newDate,
|
||||
date: this.newDate
|
||||
});
|
||||
this.dates = r.data;
|
||||
this.selectedDate = this.dates[this.dates.length - 1].id;
|
||||
this.newDate = '';
|
||||
},
|
||||
|
||||
async addParticipant() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament/participant', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
participant: this.selectedMember,
|
||||
});
|
||||
this.participants = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error adding participant:', error);
|
||||
}
|
||||
const r = await apiClient.post('/tournament/participant', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
participant: this.selectedMember
|
||||
});
|
||||
this.participants = r.data;
|
||||
},
|
||||
|
||||
async createGroups() {
|
||||
await apiClient.put('/tournament/groups', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
await this.fetchGroups();
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
|
||||
async randomizeGroups() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament/groups', {
|
||||
const r = await apiClient.post('/tournament/groups', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error randomizing groups:', error);
|
||||
this.participants = r.data;
|
||||
} catch (err) {
|
||||
alert('Fehler beim Zufällig‑Verteilen:\n' +
|
||||
(err.response?.data?.error || err.message));
|
||||
}
|
||||
await this.fetchGroups();
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
async fetchGroups() {
|
||||
try {
|
||||
const response = await apiClient.get('/tournament/groups', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
this.groups = response.data;
|
||||
const matchesResponse = await apiClient.get(`/tournament/matches/${this.currentClub}/${this.selectedDate}`);
|
||||
this.matches = matchesResponse.data;
|
||||
console.log(this.matches);
|
||||
} catch (error) {
|
||||
console.error('Error fetching groups:', error);
|
||||
}
|
||||
},
|
||||
getPlayerName(player) {
|
||||
return player.member.firstName + ' ' + player.member.lastName;
|
||||
},
|
||||
async saveMatchResult(match, set, result) {
|
||||
try {
|
||||
await apiClient.post('/tournament/match/result', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id,
|
||||
set,
|
||||
result: result,
|
||||
});
|
||||
this.fetchGroups();
|
||||
} catch (error) {
|
||||
console.error('Error saving match result:', error);
|
||||
|
||||
async saveMatchResult(match, result) {
|
||||
// wenn kein ':' dabei, ergänzen
|
||||
if (result.indexOf(':') === -1) {
|
||||
result = result.indexOf('-') > -1
|
||||
? '11:' + result
|
||||
: (result * -1) + ':11';
|
||||
}
|
||||
await apiClient.post('/tournament/match/result', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id,
|
||||
set: (match.tournamentResults?.length || 0) + 1,
|
||||
result
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
|
||||
async finishMatch(match) {
|
||||
try {
|
||||
await apiClient.post('/tournament/match/finish', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id,
|
||||
});
|
||||
this.fetchGroups();
|
||||
} catch (error) {
|
||||
console.error('Error finishing match:', error);
|
||||
}
|
||||
await apiClient.post('/tournament/match/finish', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.id
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
},
|
||||
|
||||
async startKnockout() {
|
||||
await apiClient.post('/tournament/knockout', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tournaments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
.tournaments-view {
|
||||
padding: 1rem;
|
||||
}
|
||||
.groupoverview {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: left;
|
||||
padding: 0;
|
||||
|
||||
.participants,
|
||||
.group-controls,
|
||||
.groups-overview,
|
||||
.ko-round,
|
||||
.ko-start {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.groupoverview li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.group-table {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user