feat(TournamentService, TournamentResultsTab): enhance knockout match handling and UI interactions

- Introduced new functions for determining knockout round order and building preferred knockout matches based on qualifiers.
- Updated TournamentResultsTab to include collapsible sections for group matches and knockout rounds, improving user experience.
- Added data properties and methods to manage the visibility of match sections and handle tournament class checks.
- Refined UI elements for better interaction, including toggle buttons and improved styling for match sections.
This commit is contained in:
Torsten Schulz (local)
2026-03-28 11:48:10 +01:00
parent 92d29dc64e
commit 0554a68eb7
8 changed files with 225 additions and 67 deletions

View File

@@ -42,10 +42,16 @@
</div>
<section v-if="filteredGroupMatches.length" class="group-matches">
<div class="results-section-header">
<h4>{{ $t('tournaments.groupMatches') }}</h4>
<span class="results-chip">{{ filteredGroupMatches.length }}</span>
<div class="results-section-title">
<h4>{{ $t('tournaments.groupMatches') }}</h4>
<span class="results-chip">{{ filteredGroupMatches.length }}</span>
</div>
<button type="button" class="section-toggle-btn" @click="groupMatchesCollapsed = !groupMatchesCollapsed">
<span class="collapse-icon" :class="{ expanded: !groupMatchesCollapsed }"></span>
{{ groupMatchesCollapsed ? collapseShowLabel : collapseHideLabel }}
</button>
</div>
<table>
<table v-if="!groupMatchesCollapsed">
<thead>
<tr>
<th>{{ $t('tournaments.round') }}</th>
@@ -177,8 +183,18 @@
</div>
<section v-if="showKnockout && numberOfGroupsForSelectedClass > 1 && filteredKnockoutMatches.length" class="ko-round">
<div class="results-section-header">
<h4>{{ $t('tournaments.koRound') }}</h4>
<span class="results-chip">{{ filteredKnockoutMatches.length }}</span>
<div class="results-section-title">
<h4>{{ $t('tournaments.koRound') }}</h4>
<span class="results-chip">{{ filteredKnockoutMatches.length }}</span>
</div>
<button
v-if="numberOfTables"
type="button"
class="btn-primary btn-inline-action"
@click="$emit('distribute-tables')"
>
{{ $t('tournaments.distributeTables') }}
</button>
</div>
<table>
<thead>
@@ -487,6 +503,25 @@ export default {
'start-knockout',
'reset-knockout'
],
data() {
const show = this.$t('common.show');
const hide = this.$t('common.hide');
return {
groupMatchesCollapsed: false,
collapseShowLabel: show && show !== 'common.show' ? show : 'Anzeigen',
collapseHideLabel: hide && hide !== 'common.hide' ? hide : 'Ausblenden'
};
},
watch: {
showKnockout: {
immediate: true,
handler(newValue) {
if (newValue && this.filteredGroupMatches.length > 0) {
this.groupMatchesCollapsed = true;
}
}
}
},
methods: {
filterMatchesByClass(matches) {
// Wenn keine Klasse ausgewählt ist (null), zeige alle
@@ -707,10 +742,45 @@ export default {
gap: 0.75rem;
}
.results-section-title {
display: flex;
align-items: center;
gap: 0.75rem;
min-width: 0;
}
.results-section-header h4 {
margin: 0 0 0.75rem 0;
}
.section-toggle-btn {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.35rem 0.65rem;
border: 1px solid var(--border-color);
border-radius: 999px;
background: var(--surface-color, #ffffff);
color: var(--text-color);
cursor: pointer;
font-size: 0.9rem;
font-weight: 600;
}
.collapse-icon {
display: inline-block;
transform: rotate(-90deg);
transition: transform 0.2s ease;
}
.collapse-icon.expanded {
transform: rotate(0deg);
}
.btn-inline-action {
white-space: nowrap;
}
/* Farbmarkierungen für Spiele */
.match-finished {
background-color: var(--background-soft) !important;

View File

@@ -259,7 +259,7 @@
"conflictSuggestionLabel": "Vorschläg:",
"workspaceProblemsTitle": "{count} offeni Pünkt",
"problemConfigTitle": "Konfiguration unvollständig",
"problemConfigDescription": "Prüef Datum, Name, Gwünnsätz und mindestens e Klass.",
"problemConfigDescription": "Prüef Datum, Name und Gwünnsätz.",
"problemUnassignedTitle": "{count} Teilnehmendi ohni Klass",
"problemUnassignedDescription": "Die Teilnehmendi bruuched no e manuelli Klassenzuewisig.",
"problemUnassignedAutoDescription": "{count} devo chönd direkt automatisch zuegordnet werde.",

View File

@@ -967,7 +967,7 @@
"conflictSuggestionLabel": "Vorschläge:",
"workspaceProblemsTitle": "{count} offene Punkte",
"problemConfigTitle": "Konfiguration unvollständig",
"problemConfigDescription": "Prüfe Datum, Name, Gewinnsätze und mindestens eine Klasse.",
"problemConfigDescription": "Prüfe Datum, Name und Gewinnsätze.",
"problemUnassignedTitle": "{count} Teilnehmer ohne Klasse",
"problemUnassignedDescription": "Diese Teilnehmer brauchen noch eine manuelle Klassenzuordnung.",
"problemUnassignedAutoDescription": "{count} davon können direkt automatisch zugeordnet werden.",

View File

@@ -259,7 +259,7 @@
"conflictSuggestionLabel": "Suggestions:",
"workspaceProblemsTitle": "{count} open issues",
"problemConfigTitle": "Configuration incomplete",
"problemConfigDescription": "Check date, name, winning sets and at least one class.",
"problemConfigDescription": "Check date, name and winning sets.",
"problemUnassignedTitle": "{count} participants without class",
"problemUnassignedDescription": "These participants still need a manual class assignment.",
"problemUnassignedAutoDescription": "{count} of them can be assigned automatically right away.",

View File

@@ -529,7 +529,7 @@
"conflictSuggestionLabel": "Suggestions:",
"workspaceProblemsTitle": "{count} open issues",
"problemConfigTitle": "Configuration incomplete",
"problemConfigDescription": "Check date, name, winning sets and at least one class.",
"problemConfigDescription": "Check date, name and winning sets.",
"problemUnassignedTitle": "{count} participants without class",
"problemUnassignedDescription": "These participants still need a manual class assignment.",
"problemUnassignedAutoDescription": "{count} of them can be assigned automatically right away.",

View File

@@ -259,7 +259,7 @@
"conflictSuggestionLabel": "Suggestions:",
"workspaceProblemsTitle": "{count} open issues",
"problemConfigTitle": "Configuration incomplete",
"problemConfigDescription": "Check date, name, winning sets and at least one class.",
"problemConfigDescription": "Check date, name and winning sets.",
"problemUnassignedTitle": "{count} participants without class",
"problemUnassignedDescription": "These participants still need a manual class assignment.",
"problemUnassignedAutoDescription": "{count} of them can be assigned automatically right away.",

View File

@@ -483,6 +483,9 @@ export default {
tournamentWideIsDoubles() {
return Boolean(this.currentTournamentWideIsDoubles);
},
hasTournamentClasses() {
return Array.isArray(this.tournamentClasses) && this.tournamentClasses.length > 0;
},
readyParticipantCount() {
return this.totalParticipantCount - this.participantConflictCount - this.unassignedParticipantCount;
},
@@ -493,7 +496,7 @@ export default {
return this.panelTitle;
},
tournamentConfigurationComplete() {
return Boolean(this.currentTournamentDate && (this.currentTournamentName || '').trim() && this.currentWinningSets >= 1 && this.tournamentClasses.length > 0);
return Boolean(this.currentTournamentDate && (this.currentTournamentName || '').trim() && this.currentWinningSets >= 1);
},
groupsFinished() {
return this.groupMatches.length > 0 && this.groupMatches.every(match => match.isFinished);
@@ -520,7 +523,7 @@ export default {
: this.$t('tournaments.statusConfigIncomplete')
});
if (this.unassignedParticipantCount > 0) {
if (this.hasTournamentClasses && this.unassignedParticipantCount > 0) {
statuses.push({
key: 'unassigned',
tone: 'warning',
@@ -660,7 +663,7 @@ export default {
});
}
if (this.unassignedParticipantCount > 0) {
if (this.hasTournamentClasses && this.unassignedParticipantCount > 0) {
problems.push({
key: 'unassigned',
priority: 20,