feat(TournamentResultsTab, TournamentTab): add knockout operation handling
- Introduced knockoutOperationInProgress state to manage button states during knockout operations in TournamentResultsTab.vue. - Updated button elements to disable during ongoing operations, enhancing user experience and preventing multiple submissions. - Integrated knockoutOperationInProgress state in TournamentTab.vue to control the flow of knockout-related actions.
This commit is contained in:
@@ -145,12 +145,12 @@
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="canStartKnockout && !showKnockout && numberOfGroupsForSelectedClass > 1" class="results-next-step">
|
||||
<button @click="$emit('start-knockout')" class="btn-primary">
|
||||
<button @click="$emit('start-knockout')" class="btn-primary" :disabled="knockoutOperationInProgress">
|
||||
{{ $t('tournaments.startKORound') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="showKnockout && canResetKnockout && numberOfGroupsForSelectedClass > 1" class="results-next-step results-next-step-muted">
|
||||
<button @click="$emit('reset-knockout')" class="trash-btn">
|
||||
<button @click="$emit('reset-knockout')" class="trash-btn" :disabled="knockoutOperationInProgress">
|
||||
🗑️ {{ $t('tournaments.deleteKORound') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -335,6 +335,10 @@ export default {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
knockoutOperationInProgress: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
getTotalNumberOfGroups: {
|
||||
type: Number,
|
||||
required: true
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
:can-start-knockout="canStartKnockout"
|
||||
:show-knockout="showKnockout"
|
||||
:can-reset-knockout="canResetKnockout"
|
||||
:knockout-operation-in-progress="knockoutOperationInProgress"
|
||||
:get-total-number-of-groups="getTotalNumberOfGroups"
|
||||
:grouped-ranking-list="groupedRankingList"
|
||||
:participants="participants"
|
||||
@@ -392,6 +393,7 @@ export default {
|
||||
groups: [],
|
||||
matches: [],
|
||||
showKnockout: false,
|
||||
knockoutOperationInProgress: false,
|
||||
showParticipants: false, // Kollaps-Status für Teilnehmerliste
|
||||
hasTrainingToday: false, // Gibt es einen Trainingstag heute?
|
||||
editingResult: {
|
||||
@@ -1936,6 +1938,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (quickAction.kind === 'start-knockout') {
|
||||
if (this.knockoutOperationInProgress) return;
|
||||
this.setActiveTab('results');
|
||||
this.resultsSubTab = 'matches';
|
||||
await this.startKnockout();
|
||||
@@ -2622,80 +2625,88 @@ export default {
|
||||
},
|
||||
|
||||
async startKnockout() {
|
||||
if (this.knockoutOperationInProgress) return;
|
||||
if (!this.currentClub || !this.selectedDate || this.selectedDate === 'new') {
|
||||
await this.showInfo(this.$t('messages.error'), this.$t('tournaments.selectTournamentFirst'), '', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.knockoutOperationInProgress = true;
|
||||
// Wenn eine Stage-Konfiguration existiert, ist /tournament/stages/advance der
|
||||
// korrekte Weg, weil nur dort die Pool-Regeln (z.B. Plätze 1,2) berücksichtigt werden.
|
||||
// Fallback ist die Legacy-Route /tournament/knockout.
|
||||
try {
|
||||
const stagesRes = await apiClient.get('/tournament/stages', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
try {
|
||||
const stagesRes = await apiClient.get('/tournament/stages', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
|
||||
const stages = stagesRes?.data?.stages;
|
||||
if (Array.isArray(stages) && stages.length > 0) {
|
||||
// Backend arbeitet mit expliziten Stage-Indizes (z.B. 1 und 3), die nicht
|
||||
// zwingend 1..N sind. Daher müssen wir die Indizes aus der Antwort ableiten.
|
||||
const normalizedStages = stages
|
||||
.map(s => ({
|
||||
...s,
|
||||
// Prefer explicit index field; fall back to id for ordering if needed
|
||||
stageIndex: Number(s.stageIndex ?? s.index ?? s.id),
|
||||
stageId: Number(s.id ?? s.stageId ?? s.stageIndex)
|
||||
}))
|
||||
.filter(s => Number.isFinite(s.stageIndex));
|
||||
const stages = stagesRes?.data?.stages;
|
||||
if (Array.isArray(stages) && stages.length > 0) {
|
||||
const normalizedStages = stages
|
||||
.map(s => ({
|
||||
...s,
|
||||
stageIndex: Number(s.stageIndex ?? s.index ?? s.id),
|
||||
stageId: Number(s.id ?? s.stageId ?? s.stageIndex)
|
||||
}))
|
||||
.filter(s => Number.isFinite(s.stageIndex));
|
||||
|
||||
// Ermittle die Reihenfolge der Stages
|
||||
const ordered = normalizedStages.sort((a, b) => a.stageIndex - b.stageIndex);
|
||||
const groupStage = ordered.find(s => (s.type || s.targetType || s.target) === 'groups');
|
||||
const knockoutStage = ordered.find(s => (s.type || s.targetType || s.target) === 'knockout');
|
||||
const ordered = normalizedStages.sort((a, b) => a.stageIndex - b.stageIndex);
|
||||
const groupStage = ordered.find(s => (s.type || s.targetType || s.target) === 'groups');
|
||||
const knockoutStage = ordered.find(s => (s.type || s.targetType || s.target) === 'knockout');
|
||||
|
||||
if (groupStage && knockoutStage) {
|
||||
// Falls es Zwischenstufen vom Typ 'groups' gibt, iteriere bis zur KO‑Stufe
|
||||
let fromIdx = groupStage.stageIndex;
|
||||
let fromId = groupStage.stageId;
|
||||
for (const stage of ordered) {
|
||||
if (stage.stageIndex <= fromIdx) continue;
|
||||
// Advance Schrittweise zur nächsten Stage; prefer IDs if backend expects them
|
||||
const payload = {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
};
|
||||
if (Number.isFinite(fromId) && Number.isFinite(stage.stageId)) {
|
||||
payload.fromStageId = fromId;
|
||||
payload.toStageId = stage.stageId;
|
||||
} else {
|
||||
payload.fromStageIndex = fromIdx;
|
||||
payload.toStageIndex = stage.stageIndex;
|
||||
}
|
||||
await apiClient.post('/tournament/stages/advance', payload);
|
||||
if (groupStage && knockoutStage) {
|
||||
let fromIdx = groupStage.stageIndex;
|
||||
let fromId = groupStage.stageId;
|
||||
for (const stage of ordered) {
|
||||
if (stage.stageIndex <= fromIdx) continue;
|
||||
const payload = {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
};
|
||||
if (Number.isFinite(fromId) && Number.isFinite(stage.stageId)) {
|
||||
payload.fromStageId = fromId;
|
||||
payload.toStageId = stage.stageId;
|
||||
} else {
|
||||
payload.fromStageIndex = fromIdx;
|
||||
payload.toStageIndex = stage.stageIndex;
|
||||
}
|
||||
await apiClient.post('/tournament/stages/advance', payload);
|
||||
fromIdx = stage.stageIndex;
|
||||
fromId = stage.stageId;
|
||||
|
||||
// Update trackers
|
||||
fromIdx = stage.stageIndex;
|
||||
fromId = stage.stageId;
|
||||
if ((stage.type || stage.targetType || stage.target) === 'knockout') {
|
||||
await this.loadTournamentData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wenn KO erreicht, beende
|
||||
if ((stage.type || stage.targetType || stage.target) === 'knockout') {
|
||||
await this.loadTournamentData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nach jedem Schritt neu laden, damit Folgeschritt korrekte Daten hat
|
||||
await this.loadTournamentData();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Stage-basierter Start der K.o.-Runde nicht möglich, verwende Legacy-Fallback.', e);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignorieren und Legacy-Fallback nutzen.
|
||||
// (z.B. wenn Endpoint nicht verfügbar oder Stages nicht konfiguriert)
|
||||
}
|
||||
|
||||
await apiClient.post('/tournament/knockout', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
await apiClient.post('/tournament/knockout', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Starten der K.o.-Runde:', error);
|
||||
await this.showInfo(
|
||||
this.$t('messages.error'),
|
||||
this.$t('tournaments.errorCreatingGroups'),
|
||||
error?.response?.data?.error || error?.response?.data?.message || error?.message || '',
|
||||
'error'
|
||||
);
|
||||
} finally {
|
||||
this.knockoutOperationInProgress = false;
|
||||
}
|
||||
},
|
||||
|
||||
formatResult(match) {
|
||||
|
||||
Reference in New Issue
Block a user