diff --git a/frontend/src/App.vue b/frontend/src/App.vue index a0011c43..0e0db17c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -142,6 +142,37 @@ + + +
+

{{ $t('club.mobileSelectHint') }}

+
+ + +
+
+
@@ -184,12 +215,14 @@ import logoUrl from './assets/logo.png'; import DialogManager from './components/DialogManager.vue'; import InfoDialog from './components/InfoDialog.vue'; import ConfirmDialog from './components/ConfirmDialog.vue'; +import BaseDialog from './components/BaseDialog.vue'; import { buildInfoConfig, buildConfirmConfig } from './utils/dialogUtils.js'; export default { name: 'App', components: { DialogManager , + BaseDialog, InfoDialog, ConfirmDialog}, data() { @@ -214,10 +247,15 @@ export default { sessionInterval: null, logoUrl, userDropdownOpen: false, + viewportWidth: typeof window !== 'undefined' ? window.innerWidth : 1280, + showMobileClubPicker: false, }; }, computed: { ...mapGetters(['isAuthenticated', 'currentClub', 'clubs', 'sidebarCollapsed', 'username', 'hasPermission', 'isClubOwner', 'userRole', 'language']), + isMobileViewport() { + return this.viewportWidth <= 768; + }, canManageApprovals() { // Nur anzeigen, wenn Permissions geladen sind UND Berechtigung vorhanden if (!this.currentClub) return false; @@ -246,6 +284,9 @@ export default { if (newVal === 'new') { this.$router.push('/createclub'); } + if (newVal) { + this.showMobileClubPicker = false; + } // Removed automatic redirect to training-stats to allow manual navigation }, isAuthenticated(newVal) { @@ -260,10 +301,21 @@ export default { clearInterval(this.sessionInterval); this.sessionInterval = null; } + this.showMobileClubPicker = false; } }, + clubs: { + handler() { + this.updateMobileClubPickerState(); + }, + deep: true + } }, methods: { + handleViewportResize() { + this.viewportWidth = window.innerWidth; + this.updateMobileClubPickerState(); + }, toggleUserDropdown(event) { event.stopPropagation(); this.userDropdownOpen = !this.userDropdownOpen; @@ -316,6 +368,7 @@ export default { async handleClubSelectionChange() { if (!this.selectedClub) { await this.setCurrentClub(null); + this.updateMobileClubPickerState(); return; } @@ -323,15 +376,53 @@ export default { if (this.selectedClub === 'new') { this.$router.push('/createclub'); } + this.updateMobileClubPickerState(); + }, + + async selectClubFromMobilePicker(clubId) { + this.selectedClub = clubId; + await this.handleClubSelectionChange(); + }, + + async initializeClubSelectionState() { + if (!this.isAuthenticated) { + this.showMobileClubPicker = false; + return; + } + + if (this.currentClub) { + this.selectedClub = this.currentClub; + this.showMobileClubPicker = false; + return; + } + + if (!this.isMobileViewport) { + this.showMobileClubPicker = false; + return; + } + + if (this.clubs.length === 1) { + this.selectedClub = this.clubs[0].id; + await this.handleClubSelectionChange(); + return; + } + + this.showMobileClubPicker = this.clubs.length > 1; + }, + + updateMobileClubPickerState() { + if (!this.isAuthenticated || !this.isMobileViewport || this.currentClub || this.$route.path === '/createclub') { + this.showMobileClubPicker = false; + return; + } + this.showMobileClubPicker = this.clubs.length > 1; }, async loadUserData() { try { const response = await apiClient.get('/clubs'); this.setClubs(response.data); - if (this.currentClub) { - this.selectedClub = this.currentClub; - } + await this.initializeClubSelectionState(); this.checkSession(); // Session-Check alle 30 Sekunden this.sessionInterval = setInterval(this.checkSession, 30000); @@ -373,9 +464,7 @@ export default { try { const response = await apiClient.get('/clubs'); this.setClubs(response.data); - if (this.currentClub) { - this.selectedClub = this.currentClub; - } + await this.initializeClubSelectionState(); this.checkSession(); // Session-Check alle 30 Sekunden this.sessionInterval = setInterval(this.checkSession, 30000); @@ -384,10 +473,12 @@ export default { this.selectedClub = null; } } + window.addEventListener('resize', this.handleViewportResize); }, beforeUnmount() { clearInterval(this.sessionInterval); document.removeEventListener('click', this.handleClickOutside); + window.removeEventListener('resize', this.handleViewportResize); } }; @@ -731,6 +822,34 @@ export default { width: 100%; } +.mobile-club-picker { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.mobile-club-picker-text { + margin: 0; + color: var(--text-muted); +} + +.mobile-club-picker-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.mobile-club-picker-option { + width: 100%; + justify-content: center; +} + +.mobile-club-picker-option.secondary-option { + background: white; + color: var(--primary-color); + border: 1.5px solid var(--primary-color); +} + .auth-links a { text-align: center; padding: 0.75rem; @@ -800,6 +919,10 @@ export default { } @media (max-width: 768px) { + .auth-nav { + display: none; + } + .sidebar { width: 220px; } @@ -938,6 +1061,10 @@ export default { .sidebar:not(.sidebar-collapsed) ~ .main-content { margin-left: 240px; } + + .auth-nav + .main-content { + margin-left: 0; + } } /* Button-Varianten */ diff --git a/frontend/src/components/schedule/ScheduleLayoutShell.vue b/frontend/src/components/schedule/ScheduleLayoutShell.vue index 42fcb345..84252984 100644 --- a/frontend/src/components/schedule/ScheduleLayoutShell.vue +++ b/frontend/src/components/schedule/ScheduleLayoutShell.vue @@ -1,55 +1,51 @@