feat(tournament): improve result handling and display for matches and participants

This commit is contained in:
Torsten Schulz (local)
2025-12-15 21:08:38 +01:00
parent 047b1801b3
commit 9bf37399d5
4 changed files with 318 additions and 119 deletions

View File

@@ -1452,7 +1452,8 @@ class TournamentService {
);
if (!pairing1Key || !pairing2Key) continue;
// Ergebnis kann null/undefiniert oder in anderem Format sein -> defensiv prüfen
if (!m.result || typeof m.result !== 'string' || !m.result.includes(':')) continue;
const [s1, s2] = m.result.split(':').map(n => parseInt(n, 10));
if (s1 > s2) {

View File

@@ -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;
}

View File

@@ -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 }}

View File

@@ -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,