feat(ViteConfig, App, Router, DialogManager, MembersView, ScheduleView, ClubView, Home, TournamentsView, TrainingStatsView): enhance performance and responsiveness

- Updated Vite configuration to improve chunking strategy and set a chunk size warning limit.
- Refactored App.vue and DialogManager.vue to utilize async component loading for better performance.
- Modified router.js to implement lazy loading for various views, optimizing initial load times.
- Enhanced MembersView, ScheduleView, ClubView, and TournamentsView with responsive design adjustments for improved mobile usability.
- Improved styling and layout in Home.vue and TrainingStatsView to enhance user experience across different screen sizes.
This commit is contained in:
Torsten Schulz (local)
2026-03-20 10:20:48 +01:00
parent cc6d1f6ebe
commit 36690980b7
12 changed files with 557 additions and 55 deletions

View File

@@ -209,14 +209,16 @@
</template>
<script>
import { defineAsyncComponent } from 'vue';
import { mapGetters, mapActions } from 'vuex';
import apiClient from './apiClient.js';
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';
const DialogManager = defineAsyncComponent(() => import('./components/DialogManager.vue'));
export default {
name: 'App',
components: {

View File

@@ -52,17 +52,19 @@
</template>
<script>
import { defineAsyncComponent } from 'vue';
import { mapGetters, mapActions } from 'vuex';
import MatchReportDialog from './MatchReportDialog.vue';
import MatchReportApiDialog from './MatchReportApiDialog.vue';
import MatchReportHeaderActions from './MatchReportHeaderActions.vue';
import MyTischtennisAccount from '../views/MyTischtennisAccount.vue';
import ClickTtAccount from '../views/ClickTtAccount.vue';
import PermissionsView from '../views/PermissionsView.vue';
import MemberTransferSettingsView from '../views/MemberTransferSettingsView.vue';
import LogsView from '../views/LogsView.vue';
import ClickTtView from '../views/ClickTtView.vue';
import PersonalSettings from '../views/PersonalSettings.vue';
const MyTischtennisAccount = defineAsyncComponent(() => import('../views/MyTischtennisAccount.vue'));
const ClickTtAccount = defineAsyncComponent(() => import('../views/ClickTtAccount.vue'));
const PermissionsView = defineAsyncComponent(() => import('../views/PermissionsView.vue'));
const MemberTransferSettingsView = defineAsyncComponent(() => import('../views/MemberTransferSettingsView.vue'));
const LogsView = defineAsyncComponent(() => import('../views/LogsView.vue'));
const ClickTtView = defineAsyncComponent(() => import('../views/ClickTtView.vue'));
const PersonalSettings = defineAsyncComponent(() => import('../views/PersonalSettings.vue'));
export default {
components: {

View File

@@ -431,8 +431,94 @@ export default {
}
@media (max-width: 900px) {
.members-header {
flex-direction: column;
align-items: stretch;
}
.action-buttons,
.filter-controls,
.members-results-actions {
width: 100%;
}
.action-buttons > button,
.members-bulk-actions-grid > button {
flex: 1 1 14rem;
}
.members-results-actions {
justify-content: flex-start;
}
}
@media (max-width: 640px) {
.members-overview {
gap: 0.85rem;
}
.filters-section,
.members-results-bar,
.members-bulk-actions {
padding: 0.75rem;
}
.scope-chip {
width: 100%;
justify-content: space-between;
}
.member-search-group,
.members-sort-group,
.filter-group,
.checkbox-group,
.age-range-group {
width: 100%;
min-width: 0;
flex: 1 1 100%;
}
.member-search-input,
.filter-select,
.age-range-input {
width: 100%;
min-width: 0;
}
.age-range-inputs {
flex-wrap: wrap;
}
.age-range-separator {
width: 100%;
text-align: center;
}
.members-sort-group {
align-items: stretch;
}
.members-sort-group span {
margin-bottom: 0.15rem;
}
.members-results-actions > * {
width: 100%;
}
.member-icon-button {
width: 100%;
}
.members-results-hint {
line-height: 1.35;
}
.action-buttons > button,
.members-bulk-actions-grid > button,
.btn-clear-filters {
width: 100%;
flex: 1 1 100%;
}
}
</style>

View File

@@ -410,4 +410,42 @@ export default {
overflow: visible;
}
}
@media (max-width: 640px) {
.schedule-page-header,
.schedule-workspace-header,
.schedule-sidebar-header {
flex-direction: column;
align-items: stretch;
}
.schedule-page-actions,
.schedule-workspace-actions,
.schedule-quick-links {
width: 100%;
}
.schedule-page-actions > *,
.schedule-workspace-actions > *,
.schedule-quick-link {
width: 100%;
}
.schedule-summary-item {
flex: 1 1 100%;
min-width: 0;
}
.tab-navigation {
overflow-x: auto;
flex-wrap: nowrap;
padding-bottom: 0.25rem;
scrollbar-width: thin;
}
.tab-button {
flex: 0 0 auto;
white-space: nowrap;
}
}
</style>

View File

@@ -1,34 +1,35 @@
import { createRouter, createWebHistory } from 'vue-router';
import Register from './views/Register.vue';
import Login from './views/Login.vue';
import Activate from './views/Activate.vue';
import ForgotPassword from './views/ForgotPassword.vue';
import ResetPassword from './views/ResetPassword.vue';
import Home from './views/Home.vue';
import CreateClub from './views/CreateClub.vue';
import ClubView from './views/ClubView.vue';
import MembersView from './views/MembersView.vue';
import DiaryView from './views/DiaryView.vue';
import PendingApprovalsView from './views/PendingApprovalsView.vue';
import ScheduleView from './views/ScheduleView.vue';
import TournamentsView from './views/TournamentsView.vue';
import OfficialTournaments from './views/OfficialTournaments.vue';
import TrainingStatsView from './views/TrainingStatsView.vue';
import ClubSettings from './views/ClubSettings.vue';
import PredefinedActivities from './views/PredefinedActivities.vue';
import MyTischtennisAccount from './views/MyTischtennisAccount.vue';
import ClickTtAccount from './views/ClickTtAccount.vue';
import TeamManagementView from './views/TeamManagementView.vue';
import PermissionsView from './views/PermissionsView.vue';
import LogsView from './views/LogsView.vue';
import ClickTtView from './views/ClickTtView.vue';
import MemberTransferSettingsView from './views/MemberTransferSettingsView.vue';
import PersonalSettings from './views/PersonalSettings.vue';
import Impressum from './views/Impressum.vue';
import Datenschutz from './views/Datenschutz.vue';
import { applySeoForPath } from './utils/seo.js';
import { safeSessionStorage } from './utils/storage.js';
const Register = () => import('./views/Register.vue');
const Login = () => import('./views/Login.vue');
const Activate = () => import('./views/Activate.vue');
const ForgotPassword = () => import('./views/ForgotPassword.vue');
const ResetPassword = () => import('./views/ResetPassword.vue');
const Home = () => import('./views/Home.vue');
const CreateClub = () => import('./views/CreateClub.vue');
const ClubView = () => import('./views/ClubView.vue');
const MembersView = () => import('./views/MembersView.vue');
const DiaryView = () => import('./views/DiaryView.vue');
const PendingApprovalsView = () => import('./views/PendingApprovalsView.vue');
const ScheduleView = () => import('./views/ScheduleView.vue');
const TournamentsView = () => import('./views/TournamentsView.vue');
const OfficialTournaments = () => import('./views/OfficialTournaments.vue');
const TrainingStatsView = () => import('./views/TrainingStatsView.vue');
const ClubSettings = () => import('./views/ClubSettings.vue');
const PredefinedActivities = () => import('./views/PredefinedActivities.vue');
const MyTischtennisAccount = () => import('./views/MyTischtennisAccount.vue');
const ClickTtAccount = () => import('./views/ClickTtAccount.vue');
const TeamManagementView = () => import('./views/TeamManagementView.vue');
const PermissionsView = () => import('./views/PermissionsView.vue');
const LogsView = () => import('./views/LogsView.vue');
const ClickTtView = () => import('./views/ClickTtView.vue');
const MemberTransferSettingsView = () => import('./views/MemberTransferSettingsView.vue');
const PersonalSettings = () => import('./views/PersonalSettings.vue');
const Impressum = () => import('./views/Impressum.vue');
const Datenschutz = () => import('./views/Datenschutz.vue');
const routes = [
{ path: '/register', name: 'register', component: Register, meta: { public: true } },
{ path: '/login', name: 'login', component: Login, meta: { public: true } },

View File

@@ -1,13 +1,20 @@
<template>
<div>
<h2>{{ $t('club.title') }} {{ club.name }}</h2>
<div v-if="accessAllowed">
<div v-if="openRequests.length > 0">
<h3>{{ $t('club.openAccessRequests') }}</h3>
<!-- Hier könntest du die offenen Anfragen anzeigen -->
</div>
<div class="club-view">
<header class="club-header">
<div>
<h3>{{ $t('club.members') }}</h3>
<h2>{{ $t('club.title') }} {{ club.name }}</h2>
</div>
</header>
<div v-if="accessAllowed" class="club-content">
<section v-if="openRequests.length > 0" class="club-card">
<h3>{{ $t('club.openAccessRequests') }}</h3>
</section>
<section class="club-card">
<div class="club-card-header">
<h3>{{ $t('club.members') }}</h3>
<span class="club-badge">{{ displayedMembers.length }}</span>
</div>
<ul class="members">
<li v-for="member in displayedMembers" :key="member.id" class="member-item">
<span class="gender-symbol" :class="'gender-' + (member.gender || 'unknown')" :title="labelGender(member.gender)">{{ genderSymbol(member.gender) }}</span>
@@ -16,17 +23,17 @@
</span>
</li>
</ul>
</div>
<div>
</section>
<section class="club-card">
<h3>{{ $t('club.diary') }}</h3>
</div>
</section>
</div>
<div v-else>
<div v-else class="club-card club-access-card">
<div v-if="accessRequested" class="access-requested">
{{ $t('club.accessRequestPending') }}
</div>
<div v-else>
{{ $t('club.noAccess') }}
<div v-else class="club-no-access">
<p>{{ $t('club.noAccess') }}</p>
<button @click="requestAccess" :disabled="accessRequested">{{ $t('club.requestAccess') }}</button>
</div>
</div>
@@ -225,8 +232,48 @@ export default {
</script>
<style lang="scss" scoped>
h2 {
display: block;
.club-view {
display: flex;
flex-direction: column;
gap: 1rem;
}
.club-header,
.club-card {
border: 1px solid var(--border-color, #dee2e6);
border-radius: 12px;
background: #fff;
padding: 1rem;
}
.club-header h2,
.club-card h3 {
margin: 0;
}
.club-content {
display: grid;
gap: 1rem;
}
.club-card-header {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: center;
margin-bottom: 0.75rem;
}
.club-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
padding: 0.2rem 0.55rem;
border-radius: 999px;
background: #eef2f5;
color: #4b5563;
font-weight: 700;
}
ul {
@@ -236,7 +283,13 @@ ul {
}
.members { margin-top: .25rem; }
.member-item { padding: .15rem 0; }
.member-item {
padding: 0.45rem 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.member-item:last-child {
border-bottom: none;
}
.gender-symbol, .gender-name { background: transparent; border: none; }
.gender-name.gender-male { color: #1a73e8; }
.gender-name.gender-female { color: #d81b60; }
@@ -249,8 +302,34 @@ ul {
.gender-symbol { margin-right: .35rem; opacity: .9; font-size: 1.05em; display: inline-block; width: 1.1em; text-align: center; }
.is-test { font-style: italic; }
.access-requested {
margin: 0.5rem 0;
color: #2e7d32;
font-weight: 600;
}
.club-no-access {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.85rem;
}
.club-no-access p {
margin: 0;
}
@media (max-width: 640px) {
.club-header,
.club-card {
padding: 0.85rem;
}
.club-card-header {
flex-direction: column;
align-items: flex-start;
}
.club-no-access button {
width: 100%;
}
}
</style>

View File

@@ -701,12 +701,30 @@ export default {
}
.welcome-card {
margin: 0 0.375rem;
margin: 0;
}
.card-title {
font-size: 1.5rem;
}
.marketing {
gap: 1.5rem;
}
.hero {
align-items: stretch;
text-align: left;
}
.hero-title,
.hero-subtitle {
text-align: left;
}
.hero-bullets {
grid-template-columns: 1fr;
}
.features-grid {
grid-template-columns: 1fr;
@@ -717,6 +735,10 @@ export default {
.search-topic-grid {
grid-template-columns: 1fr;
}
.seo-intro {
padding: 1.15rem;
}
.feature-card {
padding: 1.25rem 1rem;
@@ -725,7 +747,7 @@ export default {
.auth-actions, .user-actions {
flex-direction: column;
width: 100%;
max-width: 280px;
max-width: none;
}
.btn-primary, .btn-secondary {
@@ -738,6 +760,27 @@ export default {
.card-title {
font-size: 1.375rem;
}
.hero-title {
font-size: 1.55rem;
line-height: 1.25;
}
.hero-subtitle,
.seo-intro-copy p,
.search-topic p,
.seo-copy .long-text {
font-size: 0.95rem;
line-height: 1.6;
}
.seo-point,
.search-topic,
.feature-card,
.faq details {
padding-left: 0.95rem;
padding-right: 0.95rem;
}
.section-title {
font-size: 1.375rem;

View File

@@ -3655,5 +3655,89 @@ table td {
flex-direction: column;
align-items: stretch;
}
.member-preview-header,
.member-preview-actions,
.member-actions-primary,
.member-actions-secondary {
width: 100%;
}
.member-preview-actions > button,
.member-actions-primary > *,
.member-actions-secondary > * {
flex: 1 1 12rem;
}
.new-member-form > label {
padding: 0.45rem 0;
}
.new-member-form > label > span {
width: 100%;
display: block;
margin-bottom: 0.35rem;
}
.member-editor-field-hint {
margin-left: 0;
}
.contact-item {
align-items: stretch;
}
.contact-input,
.parent-name-input,
.group-select {
width: 100%;
min-width: 0;
flex: 1 1 100%;
}
.checkbox-inline {
width: 100%;
}
.members-table-wrap {
padding: 0.35rem 0.5rem 0.75rem;
}
}
@media (max-width: 640px) {
.member-preview-panel,
.newmember,
.contact-section {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.member-preview-grid {
grid-template-columns: 1fr;
}
.member-preview-actions > button,
.member-actions-primary > *,
.member-actions-secondary > *,
.btn-add-contact,
.toggle-new-member > button {
width: 100%;
flex: 1 1 100%;
}
.member-actions-cell {
min-width: 12rem;
}
.action-icons-row {
flex-wrap: wrap;
white-space: normal;
}
.member-image-thumb-small,
.member-image-thumb__placeholder {
width: 42px;
height: 42px;
}
}
</style>

View File

@@ -2244,4 +2244,49 @@ li {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.schedule-view {
gap: 12px;
}
.league-match-scope-card {
padding: 12px;
}
.league-match-scope-controls {
flex-direction: column;
align-items: stretch;
}
.team-filter-chip,
.league-match-scope-select {
width: 100%;
min-width: 0;
}
.tab-button {
padding: 10px 14px;
font-size: 0.95rem;
}
.location-info-button {
width: 38px;
height: 38px;
}
.table-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.dialog-actions {
flex-direction: column;
}
.dialog-actions > button {
width: 100%;
}
}
</style>

View File

@@ -139,6 +139,36 @@ export default {
align-items: flex-start;
flex-direction: column;
}
.mode-switcher {
width: 100%;
}
}
@media (max-width: 640px) {
.tournaments-container {
padding: 14px;
}
.workspace-title {
font-size: 1.35rem;
}
.workspace-summary {
margin-bottom: 14px;
line-height: 1.4;
}
.mode-switcher {
flex-direction: column;
align-items: stretch;
}
.mode-button {
width: 100%;
text-align: left;
justify-content: flex-start;
}
}
</style>

View File

@@ -656,6 +656,30 @@ export default {
margin-bottom: 2rem;
}
.stats-filters {
display: flex;
gap: 0.9rem;
flex-wrap: wrap;
margin-bottom: 1.25rem;
}
.stats-filter {
display: flex;
flex-direction: column;
gap: 0.35rem;
flex: 1 1 220px;
}
.stats-filter span {
font-size: 0.88rem;
font-weight: 600;
color: var(--text-secondary);
}
.stats-filter select {
width: 100%;
}
.stats-summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
@@ -1097,6 +1121,15 @@ export default {
.training-stats {
padding: 1rem;
}
.stats-filters {
flex-direction: column;
gap: 0.75rem;
}
.stats-filter {
flex-basis: auto;
}
.stats-summary {
grid-template-columns: 1fr;
@@ -1112,6 +1145,15 @@ export default {
grid-template-columns: 1fr;
}
.panel-header {
flex-direction: column;
align-items: flex-start;
}
.section-header {
padding: 1rem;
}
.trend-value {
text-align: left;
}
@@ -1124,6 +1166,11 @@ export default {
.members-table td {
padding: 0.75rem 0.5rem;
}
.training-days-container,
.members-table-container {
margin: 0 -0.25rem;
}
.modal-content {
margin: 1rem;
@@ -1140,4 +1187,25 @@ export default {
text-align: center;
}
}
@media (max-width: 480px) {
.training-stats {
padding: 0.85rem;
}
.stats-panel {
padding: 0.95rem;
}
.highlight-number {
font-size: 1.9rem;
}
.training-days-table th,
.training-days-table td,
.members-table th,
.members-table td {
padding: 0.65rem 0.45rem;
}
}
</style>

View File

@@ -4,6 +4,30 @@ import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
mode: 'development',
build: {
chunkSizeWarningLimit: 700,
rollupOptions: {
output: {
manualChunks(id) {
if (!id.includes('node_modules')) {
return;
}
if (id.includes('vue-router')) return 'router';
if (id.includes('vue-i18n')) return 'i18n';
if (id.includes('vuex')) return 'store';
if (id.includes('socket.io-client')) return 'socket';
if (id.includes('html2canvas')) return 'html2canvas';
if (id.includes('jspdf')) return 'jspdf';
if (id.includes('sortablejs')) return 'sortable';
if (id.includes('crypto-js')) return 'crypto';
if (id.includes('axios')) return 'http';
return 'vendor';
}
}
}
},
resolve: {
alias: {
'@': '/src'