feat(clubSettings): enhance club settings UI with loading and error handling

- Added loading and error states to the club settings view, improving user feedback during data retrieval.
- Introduced new translations for error messages in the German locale, enhancing localization support.
- Updated the save method to alert users when no club is selected, ensuring clarity in user actions.
This commit is contained in:
Torsten Schulz (local)
2026-03-11 16:08:47 +01:00
parent 8750ac6d65
commit 5c9901209c
2 changed files with 55 additions and 20 deletions

View File

@@ -735,7 +735,9 @@
"associationMemberNumberPlaceholder": "z. B. 12-3456",
"save": "Speichern",
"saved": "Gespeichert",
"saveFailed": "Speichern fehlgeschlagen"
"saveFailed": "Speichern fehlgeschlagen",
"loadFailed": "Einstellungen konnten nicht geladen werden",
"noClubSelected": "Bitte wählen Sie zuerst einen Verein aus."
},
"predefinedActivities": {
"title": "Vordefinierte Aktivitäten",

View File

@@ -26,7 +26,10 @@
<!-- Settings Tab -->
<div v-if="activeTab === 'settings'">
<section class="card">
<p v-if="!currentClub" class="hint hint-warning">{{ $t('clubSettings.noClubSelected') }}</p>
<p v-else-if="loading" class="hint">{{ $t('common.loading') }}</p>
<p v-else-if="loadError" class="hint hint-error">{{ loadError }}</p>
<section v-if="currentClub && !loading" class="card">
<h2>{{ $t('clubSettings.greetingText') }}</h2>
<div class="greeting-grid">
<textarea v-model="greeting" class="greeting-input" rows="10" :placeholder="$t('clubSettings.greetingPlaceholder')"></textarea>
@@ -43,12 +46,12 @@
<p class="hint">{{ $t('clubSettings.greetingHint') }}</p>
</section>
<section class="card">
<section v-if="currentClub && !loading" class="card">
<h2>{{ $t('clubSettings.associationMemberNumber') }}</h2>
<input v-model="associationMemberNumber" class="text-input" :placeholder="$t('clubSettings.associationMemberNumberPlaceholder')" />
</section>
<section class="card actions-card">
<section v-if="currentClub && !loading" class="card actions-card">
<div class="actions">
<button class="btn btn-primary" @click="save">{{ $t('clubSettings.save') }}</button>
<span v-if="saved" class="saved-hint">{{ $t('clubSettings.saved') }}</span>
@@ -72,6 +75,7 @@
</template>
<script>
import { mapGetters } from 'vuex';
import apiClient from '../apiClient';
import TrainingGroupsTab from '../components/TrainingGroupsTab.vue';
import TrainingTimesTab from '../components/TrainingTimesTab.vue';
@@ -88,27 +92,54 @@ export default {
greeting: '',
associationMemberNumber: '',
saved: false,
loading: false,
loadError: null,
};
},
mounted() {
const stored = localStorage.getItem('clubGreeting');
if (stored) this.greeting = stored;
const storedNr = localStorage.getItem('associationMemberNumber');
if (storedNr) this.associationMemberNumber = storedNr;
computed: {
...mapGetters(['currentClub']),
},
watch: {
currentClub: {
handler(clubId) {
if (clubId) this.loadClubSettings();
},
immediate: true,
},
},
methods: {
async save() {
localStorage.setItem('clubGreeting', this.greeting || '');
localStorage.setItem('associationMemberNumber', this.associationMemberNumber || '');
async loadClubSettings() {
if (!this.currentClub) {
this.greeting = '';
this.associationMemberNumber = '';
this.loadError = null;
return;
}
this.loadError = null;
this.loading = true;
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,
});
}
const response = await apiClient.get(`/clubs/${this.currentClub}`);
const club = response.data;
this.greeting = club?.greetingText ?? '';
this.associationMemberNumber = club?.associationMemberNumber ?? '';
} catch (e) {
this.loadError = this.$t('clubSettings.loadFailed');
this.greeting = '';
this.associationMemberNumber = '';
} finally {
this.loading = false;
}
},
async save() {
if (!this.currentClub) {
alert(this.$t('clubSettings.noClubSelected'));
return;
}
try {
await apiClient.put(`/clubs/${this.currentClub}/settings`, {
greetingText: this.greeting,
associationMemberNumber: this.associationMemberNumber,
});
this.saved = true;
setTimeout(() => (this.saved = false), 1500);
} catch (e) {
@@ -148,6 +179,8 @@ export default {
.btn.btn-primary:hover { background: var(--primary-hover); }
.saved-hint { color: #28a745; font-weight: 600; }
.hint { color: #666; font-size: 12px; margin-top: 8px; }
.hint-warning { color: #856404; background: #fff3cd; padding: 12px; border-radius: 6px; }
.hint-error { color: #721c24; background: #f8d7da; padding: 12px; border-radius: 6px; }
.tab-navigation {
display: flex;