feat(tournament): improve result handling and display for matches and participants
This commit is contained in:
@@ -10,18 +10,23 @@
|
||||
<section v-if="Object.keys(finalPlacementsByClass).length > 0" class="final-placements">
|
||||
<h3>{{ $t('tournaments.finalPlacements') }}</h3>
|
||||
<template v-for="(classPlacements, classId) in finalPlacementsByClass" :key="`final-${classId}`">
|
||||
<div v-if="shouldShowClass(classPlacements[0]?.classId ?? (classId==='null'?null:Number(classId)))" class="class-section">
|
||||
<h4 class="class-header">{{ getClassName(classId) }}</h4>
|
||||
<div v-if="isAllSelected || shouldShowClass(classId==='null'?null:Number(classId))" class="class-section">
|
||||
<h4 class="class-header">
|
||||
{{ getClassName(classId) }}
|
||||
<span class="class-type-badge" v-if="classId!==null" :class="{ doubles: isDoubles(classId), singles: !isDoubles(classId) }">
|
||||
{{ isDoubles(classId) ? $t('tournaments.doubles') : $t('tournaments.singles') }}
|
||||
</span>
|
||||
</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-place">{{ labelPlace }}</th>
|
||||
<tr>
|
||||
<th class="col-place">{{ labelPlace }}</th>
|
||||
<th>{{ $t('tournaments.player') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(entry, entryIdx) in classPlacements" :key="`final-${classId}-${entryIdx}`">
|
||||
<td class="col-place">{{ entry.position }}.</td>
|
||||
<tr v-for="(entry, entryIdx) in classPlacements" :key="`final-${classId}-${entryIdx}`">
|
||||
<td class="col-place">{{ entry.position }}.</td>
|
||||
<td>{{ getEntryPlayerName(entry) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -32,14 +37,19 @@
|
||||
<section v-if="groupPlacements.length > 0" class="group-placements">
|
||||
<h3>{{ $t('tournaments.groupPlacements') }}</h3>
|
||||
<template v-for="(classGroups, classId) in groupPlacementsByClass" :key="`group-${classId}`">
|
||||
<div v-if="shouldShowClass(classId==='null'?null:Number(classId))" class="class-section">
|
||||
<h4 class="class-header">{{ getClassName(classId) }}</h4>
|
||||
<div v-if="isAllSelected || shouldShowClass(classId==='null'?null:Number(classId))" class="class-section">
|
||||
<h4 class="class-header">
|
||||
{{ getClassName(classId) }}
|
||||
<span class="class-type-badge" v-if="classId!==null" :class="{ doubles: isDoubles(classId), singles: !isDoubles(classId) }">
|
||||
{{ isDoubles(classId) ? $t('tournaments.doubles') : $t('tournaments.singles') }}
|
||||
</span>
|
||||
</h4>
|
||||
<div class="group-table" v-for="(g, gi) in classGroups" :key="`group-${classId}-${gi}`">
|
||||
<h5>{{ $t('tournaments.group') }} {{ g.groupNumber }}</h5>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-place">{{ labelPlace }}</th>
|
||||
<tr>
|
||||
<th class="col-place">{{ labelPlace }}</th>
|
||||
<th>{{ $t('tournaments.player') }}</th>
|
||||
<th>{{ $t('tournaments.points') }}</th>
|
||||
<th>{{ $t('tournaments.sets') }}</th>
|
||||
@@ -47,8 +57,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(r, ri) in g.rankings" :key="`r-${g.groupId}-${ri}`">
|
||||
<td class="col-place">{{ r.position }}.</td>
|
||||
<tr v-for="(r, ri) in g.rankings" :key="`r-${g.groupId}-${ri}`">
|
||||
<td class="col-place">{{ r.position }}.</td>
|
||||
<td>{{ r.name }}</td>
|
||||
<td>{{ r.points }}</td>
|
||||
<td>{{ r.setsWon }}:{{ r.setsLost }}</td>
|
||||
@@ -85,6 +95,20 @@ export default {
|
||||
},
|
||||
emits: ['update:selectedViewClass'],
|
||||
computed: {
|
||||
// Flag für 'Alle Klassen'
|
||||
isAllSelected() {
|
||||
const sel = this.selectedViewClass;
|
||||
if (sel === null || sel === undefined || sel === '') return true;
|
||||
if (typeof sel === 'string') {
|
||||
const norm = sel.trim();
|
||||
if (norm === '__none__') return false;
|
||||
const knownAll = ['__all__', 'all', 'ALL', '__allClasses__', '__ALL_CLASSES__'];
|
||||
if (knownAll.includes(norm)) return true;
|
||||
const parsed = parseInt(norm);
|
||||
return !Number.isFinite(parsed); // nicht-numerischer String => 'Alle Klassen'
|
||||
}
|
||||
return false;
|
||||
},
|
||||
labelPlace() {
|
||||
const t = this.$t && this.$t('tournaments.place');
|
||||
if (t && typeof t === 'string' && t.trim().length > 0 && t !== 'tournaments.place') return t;
|
||||
@@ -98,41 +122,128 @@ export default {
|
||||
(matchesByClass[key] ||= []).push(m);
|
||||
});
|
||||
|
||||
const addEntry = (classKey, position, participant) => {
|
||||
const member = participant?.member;
|
||||
if (!member) return;
|
||||
(byClass[classKey] ||= []);
|
||||
const key = (member.id != null && Number.isFinite(Number(member.id)))
|
||||
? `id:${Number(member.id)}`
|
||||
: `name:${(member.firstName || '').trim()}|${(member.lastName || '').trim()}`;
|
||||
const existing = byClass[classKey].find(e => {
|
||||
const ek = (e.member?.id != null && Number.isFinite(Number(e.member.id)))
|
||||
? `id:${Number(e.member.id)}`
|
||||
: `name:${(e.member?.firstName || '').trim()}|${(e.member?.lastName || '').trim()}`;
|
||||
return ek === key;
|
||||
});
|
||||
if (!existing) {
|
||||
byClass[classKey].push({ position, member, classId: classKey === 'null' ? null : Number(classKey) });
|
||||
} else if (Number(position) < Number(existing.position)) {
|
||||
existing.position = position;
|
||||
}
|
||||
};
|
||||
const isClassDoubles = (classKey) => {
|
||||
const cid = classKey === 'null' ? null : Number(classKey);
|
||||
if (cid == null) return false;
|
||||
const c = (this.tournamentClasses || []).find(x => x.id === cid);
|
||||
return Boolean(c && c.isDoubles);
|
||||
};
|
||||
|
||||
const parseWinnerLoser = (match) => {
|
||||
if (!match || !match.isFinished) return { winner: null, loser: null };
|
||||
if (String(match.result).toUpperCase() === 'BYE') {
|
||||
const winner = match.player1 || match.player2 || null;
|
||||
const loser = winner === match.player1 ? match.player2 : match.player1;
|
||||
return { winner, loser };
|
||||
}
|
||||
if (typeof match.result === 'string' && match.result.includes(':')) {
|
||||
const [a, b] = match.result.split(':').map(n => Number(n));
|
||||
if (Number.isFinite(a) && Number.isFinite(b)) {
|
||||
return { winner: a > b ? match.player1 : match.player2, loser: a > b ? match.player2 : match.player1 };
|
||||
}
|
||||
}
|
||||
return { winner: null, loser: null };
|
||||
};
|
||||
const getPairingName = (cid, playerId) => {
|
||||
const classIdNum = cid == null ? null : Number(cid);
|
||||
// Versuche zuerst: playerId ist die Pairing-ID
|
||||
let pairing = (this.pairings || []).find(p => p.classId === classIdNum && p.id === playerId);
|
||||
if (!pairing) {
|
||||
// Fallback: playerId ist Turnier-Mitglied-ID (member1Id/2Id oder external1Id/2Id)
|
||||
pairing = (this.pairings || []).find(p =>
|
||||
p.classId === classIdNum && (
|
||||
p.member1Id === playerId || p.member2Id === playerId ||
|
||||
p.external1Id === playerId || p.external2Id === playerId
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!pairing) return null;
|
||||
const n1 = pairing.member1?.member
|
||||
? `${pairing.member1.member.firstName} ${pairing.member1.member.lastName}`
|
||||
: pairing.external1
|
||||
? `${pairing.external1.firstName} ${pairing.external1.lastName}`
|
||||
: this.$t('tournaments.unknown');
|
||||
const n2 = pairing.member2?.member
|
||||
? `${pairing.member2.member.firstName} ${pairing.member2.member.lastName}`
|
||||
: pairing.external2
|
||||
? `${pairing.external2.firstName} ${pairing.external2.lastName}`
|
||||
: this.$t('tournaments.unknown');
|
||||
return `${n1} / ${n2}`;
|
||||
};
|
||||
|
||||
const addEntry = (classKey, position, participantOrPlayer, opts = {}) => {
|
||||
const cid = classKey === 'null' ? null : Number(classKey);
|
||||
(byClass[classKey] ||= []);
|
||||
if (isClassDoubles(classKey)) {
|
||||
// Teilnehmer ist ein Player-Objekt aus Match: nutze dessen id zur Paarungssuche
|
||||
const playerId = opts.playerId != null
|
||||
? opts.playerId
|
||||
: (participantOrPlayer?.id || participantOrPlayer?.member?.id || null);
|
||||
const displayName = playerId != null ? getPairingName(cid, playerId) : null;
|
||||
if (!displayName) return;
|
||||
const existing = byClass[classKey].find(e => e.displayName === displayName);
|
||||
if (!existing) {
|
||||
byClass[classKey].push({ position, displayName, classId: cid });
|
||||
} else if (Number(position) < Number(existing.position)) {
|
||||
existing.position = position;
|
||||
}
|
||||
} else {
|
||||
// Einzel: wie zuvor mit member/direkten Namen
|
||||
const member = participantOrPlayer?.member
|
||||
? participantOrPlayer.member
|
||||
: (participantOrPlayer && (participantOrPlayer.firstName || participantOrPlayer.lastName)
|
||||
? { id: participantOrPlayer.id, firstName: participantOrPlayer.firstName, lastName: participantOrPlayer.lastName }
|
||||
: null);
|
||||
if (!member) return;
|
||||
const key = (member.id != null && Number.isFinite(Number(member.id)))
|
||||
? `id:${Number(member.id)}`
|
||||
: `name:${(member.firstName || '').trim()}|${(member.lastName || '').trim()}`;
|
||||
const existing = byClass[classKey].find(e => {
|
||||
const ek = (e.member?.id != null && Number.isFinite(Number(e.member.id)))
|
||||
? `id:${Number(e.member.id)}`
|
||||
: `name:${(e.member?.firstName || '').trim()}|${(e.member?.lastName || '').trim()}`;
|
||||
return ek === key;
|
||||
});
|
||||
if (!existing) {
|
||||
byClass[classKey].push({ position, member, classId: cid });
|
||||
} else if (Number(position) < Number(existing.position)) {
|
||||
existing.position = position;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parseWinnerLoser = (match) => {
|
||||
if (!match || !match.isFinished) return { winner: null, loser: null };
|
||||
// BYE-Fälle
|
||||
if (String(match.result).toUpperCase() === 'BYE') {
|
||||
const winner = match.player1 || match.player2 || null;
|
||||
const loser = winner === match.player1 ? match.player2 : match.player1;
|
||||
const winnerSourceId = winner === match.player1 ? match.player1Id : match.player2Id;
|
||||
const loserSourceId = loser === match.player1 ? match.player1Id : match.player2Id;
|
||||
return { winner, loser, winnerSourceId, loserSourceId };
|
||||
}
|
||||
// Bevorzugt: aus tournamentResults die Satzgewinne ableiten
|
||||
if (Array.isArray(match.tournamentResults) && match.tournamentResults.length > 0) {
|
||||
let w1 = 0, w2 = 0;
|
||||
for (const r of match.tournamentResults) {
|
||||
const p1 = Number(r.pointsPlayer1);
|
||||
const p2 = Number(r.pointsPlayer2);
|
||||
if (Number.isFinite(p1) && Number.isFinite(p2)) {
|
||||
if (p1 > p2) w1++; else if (p2 > p1) w2++;
|
||||
}
|
||||
}
|
||||
if (w1 !== w2) {
|
||||
const winP = w1 > w2 ? match.player1 : match.player2;
|
||||
const loseP = w1 > w2 ? match.player2 : match.player1;
|
||||
const winnerSourceId = w1 > w2 ? match.player1Id : match.player2Id;
|
||||
const loserSourceId = w1 > w2 ? match.player2Id : match.player1Id;
|
||||
return { winner: winP, loser: loseP, winnerSourceId, loserSourceId };
|
||||
}
|
||||
}
|
||||
// Fallback: Ergebnis-String flexibel parsen (z.B. '5:11 0:1' -> nimm letztes 'a:b')
|
||||
if (typeof match.result === 'string') {
|
||||
const tokens = match.result.match(/-?\d+\s*:\s*-?\d+/g);
|
||||
if (tokens && tokens.length > 0) {
|
||||
const last = tokens[tokens.length - 1];
|
||||
const parts = last.split(':');
|
||||
const a = Number((parts[0] || '').trim());
|
||||
const b = Number((parts[1] || '').trim());
|
||||
if (Number.isFinite(a) && Number.isFinite(b)) {
|
||||
const winP = a > b ? match.player1 : match.player2;
|
||||
const loseP = a > b ? match.player2 : match.player1;
|
||||
const winnerSourceId = a > b ? match.player1Id : match.player2Id;
|
||||
const loserSourceId = a > b ? match.player2Id : match.player1Id;
|
||||
return { winner: winP, loser: loseP, winnerSourceId, loserSourceId };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { winner: null, loser: null };
|
||||
};
|
||||
|
||||
Object.entries(matchesByClass).forEach(([classKey, classMatches]) => {
|
||||
if (!classMatches || classMatches.length === 0) return;
|
||||
@@ -143,30 +254,30 @@ export default {
|
||||
const quarterfinals = classMatches.filter(m => lower(m.round).includes('viertelfinale'));
|
||||
const round16 = classMatches.filter(m => lower(m.round).includes('achtelfinale'));
|
||||
|
||||
const f = parseWinnerLoser(finalMatch);
|
||||
if (f.winner) addEntry(classKey, 1, f.winner);
|
||||
if (f.loser) addEntry(classKey, 2, f.loser);
|
||||
const f = parseWinnerLoser(finalMatch);
|
||||
if (f.winner) addEntry(classKey, 1, f.winner, { playerId: f.winnerSourceId });
|
||||
if (f.loser) addEntry(classKey, 2, f.loser, { playerId: f.loserSourceId });
|
||||
|
||||
const t = parseWinnerLoser(thirdMatch);
|
||||
if (t.winner) addEntry(classKey, 3, t.winner);
|
||||
if (t.loser) addEntry(classKey, 4, t.loser);
|
||||
const t = parseWinnerLoser(thirdMatch);
|
||||
if (t.winner) addEntry(classKey, 3, t.winner, { playerId: t.winnerSourceId });
|
||||
if (t.loser) addEntry(classKey, 4, t.loser, { playerId: t.loserSourceId });
|
||||
|
||||
if (!thirdMatch || !thirdMatch.isFinished) {
|
||||
semifinals.forEach(m => {
|
||||
const { loser } = parseWinnerLoser(m);
|
||||
if (loser) addEntry(classKey, 3, loser);
|
||||
});
|
||||
}
|
||||
quarterfinals.forEach(m => {
|
||||
const { loser } = parseWinnerLoser(m);
|
||||
if (loser) addEntry(classKey, 5, loser);
|
||||
});
|
||||
round16.forEach(m => {
|
||||
const { loser } = parseWinnerLoser(m);
|
||||
if (loser) addEntry(classKey, 9, loser);
|
||||
});
|
||||
if (!thirdMatch || !thirdMatch.isFinished) {
|
||||
semifinals.forEach(m => {
|
||||
const r = parseWinnerLoser(m);
|
||||
if (r.loser) addEntry(classKey, 3, r.loser, { playerId: r.loserSourceId });
|
||||
});
|
||||
}
|
||||
quarterfinals.forEach(m => {
|
||||
const r = parseWinnerLoser(m);
|
||||
if (r.loser) addEntry(classKey, 5, r.loser, { playerId: r.loserSourceId });
|
||||
});
|
||||
round16.forEach(m => {
|
||||
const r = parseWinnerLoser(m);
|
||||
if (r.loser) addEntry(classKey, 9, r.loser, { playerId: r.loserSourceId });
|
||||
});
|
||||
|
||||
byClass[classKey] = (byClass[classKey] || []).sort((a, b) => Number(a.position) - Number(b.position));
|
||||
byClass[classKey] = (byClass[classKey] || []).sort((a, b) => Number(a.position) - Number(b.position));
|
||||
});
|
||||
|
||||
// Ergänze alle weiteren Teilnehmer der Klasse (auch wenn sie die KO-Runde nicht erreicht haben)
|
||||
@@ -192,12 +303,13 @@ export default {
|
||||
return `name:${fn}|${ln}`;
|
||||
};
|
||||
|
||||
Object.entries(participantsByClass).forEach(([classKey, plist]) => {
|
||||
Object.entries(participantsByClass).forEach(([classKey, plist]) => {
|
||||
const existingKeys = new Set((byClass[classKey] || []).map(e => {
|
||||
if (e.member?.id != null && Number.isFinite(Number(e.member.id))) return `id:${Number(e.member.id)}`;
|
||||
const fn = (e.member?.firstName || '').trim();
|
||||
const ln = (e.member?.lastName || '').trim();
|
||||
return `name:${fn}|${ln}`;
|
||||
if (e.displayName) return `pair:${classKey}:${e.displayName}`;
|
||||
if (e.member?.id != null && Number.isFinite(Number(e.member.id))) return `id:${Number(e.member.id)}`;
|
||||
const fn = (e.member?.firstName || '').trim();
|
||||
const ln = (e.member?.lastName || '').trim();
|
||||
return `name:${fn}|${ln}`;
|
||||
}));
|
||||
|
||||
const dedupSeen = new Set();
|
||||
@@ -212,18 +324,32 @@ export default {
|
||||
const maxPos = Math.max(0, ...(byClass[classKey] || []).map(e => Number(e.position) || 0));
|
||||
let nextPos = maxPos + 1;
|
||||
|
||||
unique.forEach(p => {
|
||||
const k = getStableKeyForParticipant(p);
|
||||
if (!k || existingKeys.has(k)) return;
|
||||
// map participant to entry.member-like
|
||||
const memberLike = p.member ? p.member : {
|
||||
id: p.id,
|
||||
firstName: p.firstName,
|
||||
lastName: p.lastName
|
||||
};
|
||||
(byClass[classKey] ||= []).push({ position: nextPos++, member: memberLike, classId: classKey === 'null' ? null : Number(classKey) });
|
||||
existingKeys.add(k);
|
||||
});
|
||||
if (isClassDoubles(classKey)) {
|
||||
// Für Doppel: ergänze fehlende Paarungen pro Klasse
|
||||
const cidNum = classKey === 'null' ? null : Number(classKey);
|
||||
const classPairings = (this.pairings || []).filter(p => p.classId === cidNum);
|
||||
classPairings.forEach(pairing => {
|
||||
// Bevorzuge direkte Pairing-ID
|
||||
const displayName = getPairingName(cidNum, pairing.id) || getPairingName(cidNum, pairing.member1Id || pairing.external1Id);
|
||||
if (!displayName) return;
|
||||
const key = `pair:${classKey}:${displayName}`;
|
||||
if (existingKeys.has(key)) return;
|
||||
(byClass[classKey] ||= []).push({ position: nextPos++, displayName, classId: cidNum });
|
||||
existingKeys.add(key);
|
||||
});
|
||||
} else {
|
||||
unique.forEach(p => {
|
||||
const k = getStableKeyForParticipant(p);
|
||||
if (!k || existingKeys.has(k)) return;
|
||||
const memberLike = p.member ? p.member : {
|
||||
id: p.id,
|
||||
firstName: p.firstName,
|
||||
lastName: p.lastName
|
||||
};
|
||||
(byClass[classKey] ||= []).push({ position: nextPos++, member: memberLike, classId: classKey === 'null' ? null : Number(classKey) });
|
||||
existingKeys.add(k);
|
||||
});
|
||||
}
|
||||
|
||||
byClass[classKey] = (byClass[classKey] || []).sort((a, b) => Number(a.position) - Number(b.position));
|
||||
});
|
||||
@@ -236,26 +362,52 @@ export default {
|
||||
},
|
||||
groupPlacements() {
|
||||
const placements = [];
|
||||
this.groups.forEach(group => {
|
||||
const rankings = this.groupRankings[group.groupId] || [];
|
||||
if (rankings.length > 0) {
|
||||
placements.push({
|
||||
groupId: group.groupId,
|
||||
groupNumber: group.groupNumber,
|
||||
classId: group.classId,
|
||||
rankings: rankings.map(r => ({
|
||||
id: r.id,
|
||||
position: r.position,
|
||||
name: r.name,
|
||||
seeded: r.seeded,
|
||||
points: r.points,
|
||||
setsWon: r.setsWon,
|
||||
setsLost: r.setsLost,
|
||||
setDiff: r.setDiff
|
||||
}))
|
||||
// Primär: aus groups + groupRankings
|
||||
if ((this.groups || []).length > 0) {
|
||||
this.groups.forEach(group => {
|
||||
const rankings = this.groupRankings[group.groupId] || [];
|
||||
if (rankings.length > 0) {
|
||||
placements.push({
|
||||
groupId: group.groupId,
|
||||
groupNumber: group.groupNumber,
|
||||
classId: group.classId,
|
||||
rankings: rankings.map(r => ({
|
||||
id: r.id,
|
||||
position: r.position,
|
||||
name: r.name,
|
||||
seeded: r.seeded,
|
||||
points: r.points,
|
||||
setsWon: r.setsWon,
|
||||
setsLost: r.setsLost,
|
||||
setDiff: r.setDiff
|
||||
}))
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this.groupedRankingList && typeof this.groupedRankingList === 'object') {
|
||||
// Fallback: aus groupedRankingList (by classId -> groups)
|
||||
Object.entries(this.groupedRankingList).forEach(([classKey, groups]) => {
|
||||
(groups || []).forEach((g, idx) => {
|
||||
const rankings = Array.isArray(g.rankings) ? g.rankings : [];
|
||||
if (rankings.length === 0) return;
|
||||
placements.push({
|
||||
groupId: g.groupId || `${classKey}-${idx}`,
|
||||
groupNumber: g.groupNumber || (idx + 1),
|
||||
classId: g.classId != null ? g.classId : (classKey === 'null' ? null : Number(classKey)),
|
||||
rankings: rankings.map(r => ({
|
||||
id: r.id,
|
||||
position: r.position,
|
||||
name: r.name,
|
||||
seeded: r.seeded,
|
||||
points: r.points,
|
||||
setsWon: r.setsWon,
|
||||
setsLost: r.setsLost,
|
||||
setDiff: r.setDiff
|
||||
}))
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return placements.sort((a, b) => {
|
||||
if (a.classId !== b.classId) {
|
||||
const aNum = a.classId || 999999;
|
||||
@@ -279,10 +431,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
shouldShowClass(classId) {
|
||||
if (this.selectedViewClass === null || this.selectedViewClass === undefined || this.selectedViewClass === '__none__') {
|
||||
return true;
|
||||
}
|
||||
const selectedId = typeof this.selectedViewClass === 'string' ? parseInt(this.selectedViewClass) : this.selectedViewClass;
|
||||
const sel = this.selectedViewClass;
|
||||
if (sel === null || sel === undefined || sel === '' || this.isAllSelected) return true;
|
||||
if (sel === '__none__') return true; // 'Ohne Klasse' zeigt alle
|
||||
const selectedId = typeof sel === 'string' ? parseInt(sel) : sel;
|
||||
return classId === selectedId;
|
||||
},
|
||||
getClassName(classId) {
|
||||
@@ -298,11 +450,20 @@ export default {
|
||||
}
|
||||
},
|
||||
getEntryPlayerName(entry) {
|
||||
// Doppel: displayName vorhanden
|
||||
if (entry && entry.displayName) return entry.displayName;
|
||||
// Einzel: Member-Name
|
||||
const m = entry.member || {};
|
||||
const fn = (m.firstName || '').trim();
|
||||
const ln = (m.lastName || '').trim();
|
||||
if (fn || ln) return `${fn} ${ln}`.trim();
|
||||
return this.$t('tournaments.unknown');
|
||||
},
|
||||
isDoubles(classId) {
|
||||
const cid = classId === 'null' ? null : (typeof classId === 'string' ? parseInt(classId) : classId);
|
||||
if (cid == null) return false;
|
||||
const c = (this.tournamentClasses || []).find(x => x.id === cid);
|
||||
return Boolean(c && c.isDoubles);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -324,6 +485,25 @@ export default {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.class-type-badge {
|
||||
display: inline-block;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.class-type-badge.singles {
|
||||
background-color: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.class-type-badge.doubles {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.group-table {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -375,5 +555,5 @@ table thead th:first-child,
|
||||
table tbody td:first-child {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(entry, entryIdx) in groupedRankingList[classKey].slice(0, 3)" :key="`${entry.member.id}-${entryIdx}`">
|
||||
<tr v-for="(entry, entryIdx) in groupedRankingList[classKey].filter(e => Number(e.position) <= 3)" :key="`${entry.member.id}-${entryIdx}`">
|
||||
<td>{{ entry.position }}.</td>
|
||||
<td>
|
||||
{{ entry.member.firstName }}
|
||||
|
||||
@@ -730,11 +730,14 @@ export default {
|
||||
const loserId = loser.member ? loser.member.id : loser.id;
|
||||
|
||||
// Nur hinzufügen, wenn nicht mehr im Turnier
|
||||
const toMember = (p) => p?.member ? p.member : (p ? { id: p.id, firstName: p.firstName, lastName: p.lastName } : null);
|
||||
if (!stillInTournament.has(`${classKey}-${winnerId}`)) {
|
||||
list.push({ position: 1, member: winner.member, classId: match.classId });
|
||||
const mLike = toMember(winner);
|
||||
if (mLike) list.push({ position: 1, member: mLike, classId: match.classId });
|
||||
}
|
||||
if (!stillInTournament.has(`${classKey}-${loserId}`)) {
|
||||
list.push({ position: 2, member: loser.member, classId: match.classId });
|
||||
const mLike = toMember(loser);
|
||||
if (mLike) list.push({ position: 2, member: mLike, classId: match.classId });
|
||||
}
|
||||
// Finale setzt 1/2, keine globale Positionsvariable nötig
|
||||
} else {
|
||||
@@ -772,7 +775,8 @@ export default {
|
||||
|
||||
// Nur hinzufügen, wenn nicht mehr im Turnier
|
||||
if (!stillInTournament.has(`${classKey}-${knockedOutId}`)) {
|
||||
list.push({ position: position, member: knockedOut.member, classId: match.classId });
|
||||
const mLike = knockedOut.member ? knockedOut.member : { id: knockedOut.id, firstName: knockedOut.firstName, lastName: knockedOut.lastName };
|
||||
if (mLike) list.push({ position: position, member: mLike, classId: match.classId });
|
||||
}
|
||||
});
|
||||
// Bei Viertelfinale/Achtelfinale erhalten Verlierer die korrekten Startpositionen (5 bzw. 9)
|
||||
@@ -794,8 +798,15 @@ export default {
|
||||
loser = s1 > s2 ? thirdMatch.player2 : thirdMatch.player1;
|
||||
}
|
||||
}
|
||||
if (winner && winner.member) list.push({ position: 3, member: winner.member, classId: thirdMatch.classId });
|
||||
if (loser && loser.member) list.push({ position: 4, member: loser.member, classId: thirdMatch.classId });
|
||||
const toMember = (p) => p?.member ? p.member : (p ? { id: p.id, firstName: p.firstName, lastName: p.lastName } : null);
|
||||
if (winner) {
|
||||
const mLike = toMember(winner);
|
||||
if (mLike) list.push({ position: 3, member: mLike, classId: thirdMatch.classId });
|
||||
}
|
||||
if (loser) {
|
||||
const mLike = toMember(loser);
|
||||
if (mLike) list.push({ position: 4, member: mLike, classId: thirdMatch.classId });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -956,6 +967,10 @@ export default {
|
||||
disconnectSocket();
|
||||
},
|
||||
methods: {
|
||||
// Fallback-Kompatibilität: einige Aufrufe erwarten loadParticipants()
|
||||
async loadParticipants() {
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
setActiveTab(tab) {
|
||||
this.activeTab = tab;
|
||||
},
|
||||
@@ -1196,17 +1211,20 @@ export default {
|
||||
}
|
||||
});
|
||||
// Setze Gruppen neu, um Vue-Reaktivität sicherzustellen
|
||||
this.groups = [...gRes.data];
|
||||
const groupsData = Array.isArray(gRes.data)
|
||||
? gRes.data
|
||||
: (Array.isArray(gRes.data?.groups) ? gRes.data.groups : []);
|
||||
this.groups = [...groupsData];
|
||||
|
||||
// Erstelle Mapping von groupId zu groupNumber
|
||||
const groupIdToNumberMap = this.groups.reduce((m, g) => {
|
||||
const groupIdToNumberMap = (this.groups || []).reduce((m, g) => {
|
||||
m[g.groupId] = g.groupNumber;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
// Stelle sicher, dass seeded-Feld vorhanden ist (für alte Einträge)
|
||||
// Initialisiere auch groupNumber basierend auf groupId
|
||||
this.participants = pRes.data.map(p => ({
|
||||
this.participants = (Array.isArray(pRes.data) ? pRes.data : []).map(p => ({
|
||||
...p,
|
||||
seeded: p.seeded || false,
|
||||
groupNumber: p.groupId ? (groupIdToNumberMap[p.groupId] || null) : null
|
||||
@@ -1248,7 +1266,7 @@ export default {
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der externen Teilnehmer ohne Klasse:', error);
|
||||
}
|
||||
this.externalParticipants = allExternalParticipants.map(p => ({
|
||||
this.externalParticipants = (Array.isArray(allExternalParticipants) ? allExternalParticipants : []).map(p => ({
|
||||
...p,
|
||||
seeded: p.seeded || false,
|
||||
isExternal: true,
|
||||
|
||||
Reference in New Issue
Block a user