Fügt die Funktionalität zur Aktualisierung der Vereinseinstellungen hinzu. Implementiert die Methode updateClubSettings im clubsController, um Begrüßungstexte und Mitgliedsnummern zu aktualisieren. Aktualisiert das Club-Modell, um neue Felder für greetingText und associationMemberNumber zu unterstützen. Ergänzt die Routen in clubRoutes, um die neuen Einstellungen zu verarbeiten. Fügt eine neue Ansicht für die Vereins-Einstellungen im Frontend hinzu und aktualisiert die Navigation entsprechend.

This commit is contained in:
Torsten Schulz (local)
2025-10-03 19:49:19 +02:00
parent 4b1a046149
commit ac727c6c5b
12 changed files with 1144 additions and 48 deletions

View File

@@ -75,6 +75,10 @@
<nav class="sidebar-footer">
<div class="nav-section">
<h4 class="nav-title">Einstellungen</h4>
<router-link to="/club-settings" class="nav-link">
<span class="nav-icon">🏟</span>
Vereins-Einstellungen
</router-link>
<a href="/mytischtennis-account" class="nav-link">
<span class="nav-icon">🔗</span>
myTischtennis-Account

View File

@@ -71,14 +71,12 @@ export default {
...mapActions(['closeDialog', 'minimizeDialog', 'restoreDialog', 'bringDialogToFront']),
getDialogComponent(componentName) {
console.log('🔍 Suche Komponente:', componentName);
const components = {
'MatchReportDialog': MatchReportDialog,
'MatchReportApiDialog': MatchReportApiDialog,
'MatchReportHeaderActions': MatchReportHeaderActions
};
const component = components[componentName] || null;
console.log('🔍 Gefundene Komponente:', component ? component.name : 'null');
return component;
},

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ import PendingApprovalsView from './views/PendingApprovalsView.vue';
import ScheduleView from './views/ScheduleView.vue';
import TournamentsView from './views/TournamentsView.vue';
import TrainingStatsView from './views/TrainingStatsView.vue';
import ClubSettings from './views/ClubSettings.vue';
import PredefinedActivities from './views/PredefinedActivities.vue';
import OfficialTournaments from './views/OfficialTournaments.vue';
import MyTischtennisAccount from './views/MyTischtennisAccount.vue';
@@ -31,6 +32,7 @@ const routes = [
{ path: '/schedule', component: ScheduleView},
{ path: '/tournaments', component: TournamentsView },
{ path: '/training-stats', component: TrainingStatsView },
{ path: '/club-settings', component: ClubSettings },
{ path: '/predefined-activities', component: PredefinedActivities },
{ path: '/official-tournaments', component: OfficialTournaments },
{ path: '/mytischtennis-account', component: MyTischtennisAccount },

View File

@@ -0,0 +1,107 @@
<template>
<div class="club-settings">
<h1>Vereins-Einstellungen</h1>
<section class="card">
<h2>Begrüßungstext</h2>
<div class="greeting-grid">
<textarea v-model="greeting" class="greeting-input" rows="10" placeholder="Begrüßungstext für Heimspiele..."></textarea>
<div class="legend">
<h3>Platzhalter</h3>
<ul>
<li><code>{home}</code> Name Heimmannschaft</li>
<li><code>{guest}</code> Name Gastmannschaft</li>
<li><code>{homeplayers}</code> Spieler und Doppel Heimmannschaft</li>
<li><code>{guestplayers}</code> Spieler und Doppel Gastmannschaft</li>
</ul>
</div>
</div>
<p class="hint">Dieser Text erscheint im Reiter "Begrüßung" des Spielberichtsbogens.</p>
</section>
<section class="card">
<h2>Verbands-Mitgliedsnummer</h2>
<input v-model="associationMemberNumber" class="text-input" placeholder="z. B. 12-3456" />
</section>
<section class="card actions-card">
<div class="actions">
<button class="btn btn-primary" @click="save">Speichern</button>
<span v-if="saved" class="saved-hint">Gespeichert</span>
</div>
</section>
</div>
</template>
<script>
import apiClient from '../apiClient';
export default {
name: 'ClubSettings',
data() {
return {
greeting: '',
associationMemberNumber: '',
saved: false,
};
},
mounted() {
const stored = localStorage.getItem('clubGreeting');
if (stored) this.greeting = stored;
const storedNr = localStorage.getItem('associationMemberNumber');
if (storedNr) this.associationMemberNumber = storedNr;
},
methods: {
async save() {
localStorage.setItem('clubGreeting', this.greeting || '');
localStorage.setItem('associationMemberNumber', this.associationMemberNumber || '');
try {
// Beispiel: aktuellen Club aus localStorage
const clubId = localStorage.getItem('currentClub');
if (clubId) {
await apiClient.put(`/clubs/${clubId}/settings`, {
greetingText: this.greeting,
associationMemberNumber: this.associationMemberNumber,
});
}
this.saved = true;
setTimeout(() => (this.saved = false), 1500);
} catch (e) {
this.saved = false;
alert('Speichern fehlgeschlagen');
}
},
},
};
</script>
<style scoped>
.club-settings { padding: 20px; }
.card {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
max-width: 800px;
}
.greeting-grid { display: grid; grid-template-columns: 1fr 280px; gap: 12px; align-items: start; }
.legend { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px; }
.legend h3 { margin: 0 0 8px 0; font-size: 0.95rem; color: #333; }
.legend ul { margin: 0; padding-left: 18px; }
.legend li { margin-bottom: 6px; color: #444; }
.legend code { background: #eef2f7; padding: 1px 4px; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
.greeting-input {
width: 100%;
border: 1px solid #ddd;
border-radius: 6px;
padding: 10px;
font-size: 14px;
}
.text-input { width: 100%; border: 1px solid #ddd; border-radius: 6px; padding: 8px; font-size: 14px; }
.actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.btn.btn-primary { background: var(--primary-color); color: #fff; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; }
.btn.btn-primary:hover { background: var(--primary-hover); }
.saved-hint { color: #28a745; font-weight: 600; }
.hint { color: #666; font-size: 12px; margin-top: 8px; }
</style>