refactor(TournamentStats): remove InternalTournamentStats dialog and streamline state management
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 36s

- Removed the InternalTournamentStats component from App.vue and its associated state management in Vuex.
- Updated the DialogManager to include InternalTournamentStats, allowing for better dialog handling.
- Refactored the TournamentsView to utilize a new method for opening the InternalTournamentStats dialog.
- Enhanced the InternalTournamentStats component by simplifying its template and removing unnecessary props and methods.
- Improved the logic for displaying tournament statistics based on club selection, ensuring a cleaner user experience.
This commit is contained in:
Torsten Schulz (local)
2026-04-08 15:02:09 +02:00
parent 757507f212
commit 4f8e2fee89
6 changed files with 60 additions and 82 deletions

View File

@@ -181,11 +181,6 @@
<!-- Dialog Manager -->
<DialogManager />
<InternalTournamentStats
v-if="showInternalTournamentStatsDialog"
v-model="internalTournamentStatsDialogOpen"
/>
<footer class="app-footer">
<div class="footer-content">
<router-link to="/impressum" class="footer-link">Impressum</router-link>
@@ -228,13 +223,10 @@ import BaseDialog from './components/BaseDialog.vue';
import { buildInfoConfig, buildConfirmConfig } from './utils/dialogUtils.js';
const DialogManager = defineAsyncComponent(() => import('./components/DialogManager.vue'));
import InternalTournamentStats from './components/tournament/InternalTournamentStats.vue';
export default {
name: 'App',
components: {
DialogManager,
InternalTournamentStats,
BaseDialog,
InfoDialog,
ConfirmDialog,
@@ -295,21 +287,6 @@ export default {
viewReloadKey() {
return `${this.$route.fullPath}|${this.currentClub || 'no-club'}`;
},
internalTournamentStatsDialogOpen: {
get() {
return this.$store.state.internalTournamentStatsOpen;
},
set(v) {
this.$store.commit('setInternalTournamentStatsOpen', v);
},
},
showInternalTournamentStatsDialog() {
return (
this.isAuthenticated &&
!!this.currentClub &&
this.hasPermission('tournaments', 'read')
);
},
},
watch: {
currentClub(newVal) {

View File

@@ -65,6 +65,9 @@ const MemberTransferSettingsView = defineAsyncComponent(() => import('../views/M
const LogsView = defineAsyncComponent(() => import('../views/LogsView.vue'));
const ClickTtView = defineAsyncComponent(() => import('../views/ClickTtView.vue'));
const PersonalSettings = defineAsyncComponent(() => import('../views/PersonalSettings.vue'));
const InternalTournamentStats = defineAsyncComponent(() =>
import('./tournament/InternalTournamentStats.vue'),
);
export default {
components: {
@@ -77,7 +80,8 @@ export default {
MemberTransferSettingsView,
LogsView,
ClickTtView,
PersonalSettings
PersonalSettings,
InternalTournamentStats,
},
name: 'DialogManager',
computed: {
@@ -97,7 +101,8 @@ export default {
'MemberTransferSettingsView': MemberTransferSettingsView,
'LogsView': LogsView,
'ClickTtView': ClickTtView,
'PersonalSettings': PersonalSettings
'PersonalSettings': PersonalSettings,
InternalTournamentStats,
};
const component = components[componentName] || null;
return component;

View File

@@ -1,17 +1,9 @@
<template>
<BaseDialog
:model-value="modelValue"
:title="$t('tournaments.internalStatsTitle')"
size="large"
:is-modal="false"
:position="dialogPosition"
:closable="true"
:close-on-overlay="false"
@update:model-value="$emit('update:modelValue', $event)"
@update:position="dialogPosition = $event"
@close="$emit('update:modelValue', false)"
>
<div class="internal-tournament-stats" v-if="clubId">
<!-- Inhalt für DialogManager: Rahmen, Minimieren und Schließen liefert der Manager -->
<div v-if="!clubId" class="internal-tournament-stats internal-tournament-stats--embed stats-no-club">
<p class="stats-empty">{{ $t('club.selectPlaceholder') }}</p>
</div>
<div v-else class="internal-tournament-stats internal-tournament-stats--embed">
<div class="stats-toolbar">
<button
type="button"
@@ -122,7 +114,6 @@
</div>
</div>
</div>
</BaseDialog>
</template>
<script>
@@ -130,24 +121,14 @@ import { mapState } from 'vuex';
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
import apiClient from '../../apiClient.js';
import BaseDialog from '../BaseDialog.vue';
export default {
name: 'InternalTournamentStats',
components: { BaseDialog },
props: {
modelValue: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
data() {
return {
months: 12,
loading: false,
error: null,
dialogPosition: { x: 80, y: 80 },
stats: {
tournamentCount: 0,
ageClassOptions: [],
@@ -213,24 +194,13 @@ export default {
this.selectedBandKeys = [];
this.genderScope = 'all';
this.pendingResetAgeSelection = false;
if (this.modelValue) this.load();
},
modelValue(open) {
if (open) {
this.placeDialog();
if (this.clubId) this.load();
}
if (this.clubId) this.load();
},
},
mounted() {
if (this.clubId) this.load();
},
methods: {
placeDialog() {
if (typeof window === 'undefined') return;
const panelW = Math.min(900, window.innerWidth - 32);
this.dialogPosition = {
x: Math.max(16, Math.floor((window.innerWidth - panelW) / 2)),
y: Math.max(24, Math.floor(window.innerHeight * 0.08)),
};
},
periodLabel() {
const key =
this.months === 12
@@ -500,6 +470,13 @@ export default {
min-width: 0;
}
.internal-tournament-stats--embed {
padding: 1rem 1.25rem 1.25rem;
box-sizing: border-box;
height: 100%;
overflow: auto;
}
.stats-toolbar {
display: flex;
flex-wrap: wrap;

View File

@@ -43,8 +43,6 @@ const store = createStore({
// Browser-Sprache wird in i18n/index.js erkannt
return null;
})(),
/** Turnierstatistik-Einzel: Dialog bleibt beim Seitenwechsel offen (App.vue) */
internalTournamentStatsOpen: false,
},
mutations: {
setToken(state, token) {
@@ -74,7 +72,6 @@ const store = createStore({
safeSessionStorage.setItem('currentClub', club);
} else {
safeSessionStorage.removeItem('currentClub');
state.internalTournamentStatsOpen = false;
}
},
setClubsMutation(state, clubs) {
@@ -104,7 +101,6 @@ const store = createStore({
state.token = null;
safeSessionStorage.removeItem('token');
safeSessionStorage.removeItem('currentClub');
state.internalTournamentStatsOpen = false;
},
clearUsername(state) {
state.username = '';
@@ -125,6 +121,9 @@ const store = createStore({
closeDialog(state, dialogId) {
state.dialogs = state.dialogs.filter(dialog => dialog.id !== dialogId);
},
clearAllDialogs(state) {
state.dialogs = [];
},
minimizeDialog(state, dialogId) {
const dialog = state.dialogs.find(d => d.id === dialogId);
if (dialog) {
@@ -147,9 +146,6 @@ const store = createStore({
dialog.zIndex = maxZIndex + 1;
}
},
setInternalTournamentStatsOpen(state, open) {
state.internalTournamentStatsOpen = !!open;
},
},
actions: {
async login({ commit }, { token, username }) {
@@ -160,7 +156,7 @@ const store = createStore({
commit('setClubsMutation', response.data);
},
logout({ commit }) {
commit('setInternalTournamentStatsOpen', false);
commit('clearAllDialogs');
commit('clearToken');
commit('clearUsername');
commit('clearPermissions');

View File

@@ -241,7 +241,7 @@
<td class="player-name">{{ stat.firstName }} {{ stat.lastName }}</td>
<td class="stat-value">
<span v-if="memberById[stat.memberId]">
{{ memberById[stat.memberId].qttr ?? memberById[stat.memberId].ttr ?? '' }}
{{ getMemberLineupRatingLabel(memberById[stat.memberId]) }}
</span>
<span v-else></span>
</td>
@@ -889,17 +889,26 @@ export default {
return t('teamManagement.eligibilityRegular');
};
/**
* Nur für Meldungsreihenfolge / ±30-Punkte-Regel: zählt **QTTR > 0**.
* Kein QTTR, 0 oder ungültig → wie „unbewertet“ (frei verschiebbar bzgl. der Regel).
*/
const getMemberLineupRatingValue = (member) => {
const qttr = Number(member?.qttr);
if (Number.isFinite(qttr)) return qttr;
const ttr = Number(member?.ttr);
if (Number.isFinite(ttr)) return ttr;
return Number.NEGATIVE_INFINITY;
if (member == null) return Number.NEGATIVE_INFINITY;
const raw = member.qttr;
if (raw == null || raw === '') return Number.NEGATIVE_INFINITY;
const qttr = Number(raw);
if (!Number.isFinite(qttr) || qttr === 0) return Number.NEGATIVE_INFINITY;
return qttr;
};
/** Anzeige Meldung/Statistik: nur QTTR (>0); TTR ist für die Einordnung nicht relevant */
const getMemberLineupRatingLabel = (member) => {
const rating = getMemberLineupRatingValue(member);
return Number.isFinite(rating) ? String(rating) : '';
const rawQ = member?.qttr;
if (rawQ == null || rawQ === '') return '';
const q = Number(rawQ);
if (!Number.isFinite(q) || q === 0) return '';
return String(q);
};
const lineupProposalGroups = computed(() => {
@@ -1770,7 +1779,7 @@ export default {
const map = {};
clubMembers.value = membersResp.data || [];
for (const m of clubMembers.value) {
map[m.id] = { ttr: m.ttr ?? null, qttr: m.qttr ?? null };
map[m.id] = { qttr: m.qttr ?? null };
}
memberById.value = map;
} catch (e) {
@@ -2371,6 +2380,7 @@ export default {
removeMemberFromLineup,
moveLineupMember,
memberById,
getMemberLineupRatingLabel,
schedulerJobs,
formatJobDate,
loadSchedulerJobsInfo,

View File

@@ -34,7 +34,7 @@
v-if="activeMode === 'internal'"
type="button"
class="stats-open-button"
@click="$store.commit('setInternalTournamentStatsOpen', true)"
@click="openInternalTournamentStatsDialog"
>
📊 {{ $t('tournaments.internalStatsOpenButton') }}
</button>
@@ -51,6 +51,7 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import TournamentTab from './TournamentTab.vue';
export default {
@@ -64,6 +65,7 @@ export default {
};
},
computed: {
...mapGetters(['dialogs']),
currentModeDescription() {
if (this.activeMode === 'mini') return this.$t('tournaments.miniChampionships');
if (this.activeMode === 'external') return this.$t('tournaments.openTournaments');
@@ -71,9 +73,20 @@ export default {
},
},
methods: {
...mapActions(['openDialog', 'closeDialog']),
switchMode(mode) {
this.activeMode = mode;
},
openInternalTournamentStatsDialog() {
this.dialogs
.filter((d) => d.component === 'InternalTournamentStats')
.forEach((d) => this.closeDialog(d.id));
this.openDialog({
title: this.$t('tournaments.internalStatsTitle'),
component: 'InternalTournamentStats',
props: {},
});
},
},
};
</script>