feat(TournamentConfigTab, TournamentResultsTab): enhance stage configuration and knockout actions

- Added ensureStageConfigurationPersisted method to validate and save stage advancements, ensuring proper configuration before advancing stages.
- Updated advanceStage method to call ensureStageConfigurationPersisted, improving the flow of tournament advancement.
- Introduced primary action buttons in TournamentResultsTab for starting and resetting knockout rounds, enhancing user interaction during tournament management.
- Styled results-primary-actions for better visibility and usability in the results tab.
This commit is contained in:
Torsten Schulz (local)
2026-03-28 11:15:38 +01:00
parent 7fdbe85d3c
commit 68f14eb5d6
2 changed files with 78 additions and 4 deletions

View File

@@ -1962,6 +1962,33 @@ export default {
this.stageConfig.error = e?.message || String(e);
}
},
async ensureStageConfigurationPersisted(existingAdvancements = null) {
const advancements = Array.isArray(existingAdvancements) ? existingAdvancements : null;
if (advancements && advancements.length > 0) return;
const { stages, advancements: builtAdvancements } = this.buildPayload();
if (!Array.isArray(builtAdvancements) || builtAdvancements.length === 0) {
throw new Error(this.$t('tournaments.stageConfigLoadError'));
}
for (const adv of builtAdvancements) {
const hasPools = Array.isArray(adv?.config?.pools) && adv.config.pools.length > 0;
if (!hasPools) {
const label = `${adv.fromStageIndex}${adv.toStageIndex}`;
throw new Error(this.$t('tournaments.atLeastOnePoolRule', { label }));
}
}
const saveRes = await apiClient.put('/tournament/stages', {
clubId: Number(this.clubId),
tournamentId: Number(this.tournamentId),
stages,
advancements: builtAdvancements,
});
if (saveRes.status >= 400) {
throw new Error(saveRes.data?.error || this.$t('messages.saveFailed'));
}
},
async advanceStage(fromStageIndex, toStageIndex) {
this.stageConfig.error = null;
this.stageConfig.success = null;
@@ -1973,14 +2000,28 @@ export default {
tournamentId: Number(this.tournamentId)
}
});
const stages = Array.isArray(getRes?.data?.stages) ? getRes.data.stages : [];
const normalized = stages.map(s => ({
const advancements = Array.isArray(getRes?.data?.advancements) ? getRes.data.advancements : [];
// Alte Turniere haben teilweise Stages, aber noch keine gespeicherten Advancements.
// Dann ziehen wir die Konfiguration vor dem eigentlichen Start automatisch nach.
await this.ensureStageConfigurationPersisted(advancements);
const refreshedRes = advancements.length > 0
? getRes
: await apiClient.get('/tournament/stages', {
params: {
clubId: Number(this.clubId),
tournamentId: Number(this.tournamentId)
}
});
const refreshedStages = Array.isArray(refreshedRes?.data?.stages) ? refreshedRes.data.stages : [];
const refreshedNormalized = refreshedStages.map(s => ({
stageIndex: Number(s.stageIndex ?? s.index ?? s.id),
stageId: Number(s.id ?? s.stageId ?? s.stageIndex),
type: s.type || s.targetType || s.target
}));
const from = normalized.find(s => s.stageIndex === Number(fromStageIndex));
const to = normalized.find(s => s.stageIndex === Number(toStageIndex));
const from = refreshedNormalized.find(s => s.stageIndex === Number(fromStageIndex));
const to = refreshedNormalized.find(s => s.stageIndex === Number(toStageIndex));
const payload = {
clubId: Number(this.clubId),

View File

@@ -14,6 +14,27 @@
<span v-if="liveMatchCount > 0" class="results-chip results-chip-live">{{ liveMatchCount }} {{ $t('tournaments.statusLive') }}</span>
<span v-if="finishedMatchCount > 0" class="results-chip">{{ finishedMatchCount }} {{ $t('tournaments.statusFinished') }}</span>
</div>
<div
v-if="(canStartKnockout && !showKnockout && numberOfGroupsForSelectedClass > 1) || (showKnockout && canResetKnockout && numberOfGroupsForSelectedClass > 1)"
class="results-primary-actions"
>
<button
v-if="canStartKnockout && !showKnockout && numberOfGroupsForSelectedClass > 1"
@click="$emit('start-knockout')"
class="btn-primary"
:disabled="knockoutOperationInProgress"
>
{{ $t('tournaments.startKORound') }}
</button>
<button
v-if="showKnockout && canResetKnockout && numberOfGroupsForSelectedClass > 1"
@click="$emit('reset-knockout')"
class="trash-btn"
:disabled="knockoutOperationInProgress"
>
🗑 {{ $t('tournaments.deleteKORound') }}
</button>
</div>
<div v-if="numberOfTables && (filteredGroupMatches.length || filteredKnockoutMatches.length)" class="distribute-tables-bar">
<button @click="$emit('distribute-tables')" class="btn-primary">
{{ $t('tournaments.distributeTables') }}
@@ -646,6 +667,18 @@ export default {
margin-bottom: 1rem;
}
.results-primary-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
margin-bottom: 1rem;
padding: 0.9rem 1rem;
border: 1px solid var(--border-color);
border-radius: 12px;
background: var(--surface-color, #ffffff);
}
.results-chip {
display: inline-flex;
align-items: center;