feat(clubSettings): enhance club settings with myTischtennis integration

- Added new fields for myTischtennis club ID, federation nickname, and auto-fetch rankings in the club settings.
- Updated the backend to handle the new settings in the updateClubSettings method.
- Implemented automatic ranking updates for clubs based on the new settings in the autoUpdateRatingsService.
- Enhanced the frontend to support the new settings, including validation and user interface updates for better user experience.
This commit is contained in:
Torsten Schulz (local)
2026-03-12 10:25:49 +01:00
parent 595e2eb141
commit ad09a45b17
8 changed files with 198 additions and 118 deletions

View File

@@ -737,7 +737,14 @@
"saved": "Gespeichert",
"saveFailed": "Speichern fehlgeschlagen",
"loadFailed": "Einstellungen konnten nicht geladen werden",
"noClubSelected": "Bitte wählen Sie zuerst einen Verein aus."
"noClubSelected": "Bitte wählen Sie zuerst einen Verein aus.",
"myTischtennisRankings": "myTischtennis TTR/QTTR-Ranglisten",
"myTischtennisRankingsHint": "Automatischer Abruf der Vereins-Rangliste für TTR- und QTTR-Updates der Mitglieder.",
"autoFetchRankings": "Ranglisten automatisch abrufen",
"myTischtennisClubId": "myTischtennis Vereinsnummer",
"myTischtennisClubIdPlaceholder": "z. B. 43030",
"myTischtennisFedNickname": "Verbandskürzel",
"myTischtennisFedNicknamePlaceholder": "z. B. HeTTV"
},
"predefinedActivities": {
"title": "Vordefinierte Aktivitäten",

View File

@@ -51,6 +51,27 @@
<input v-model="associationMemberNumber" class="text-input" :placeholder="$t('clubSettings.associationMemberNumberPlaceholder')" />
</section>
<section v-if="currentClub && !loading" class="card">
<h2>{{ $t('clubSettings.myTischtennisRankings') }}</h2>
<p class="hint">{{ $t('clubSettings.myTischtennisRankingsHint') }}</p>
<div class="rankings-row">
<label class="checkbox-label">
<input type="checkbox" v-model="autoFetchRankings" />
{{ $t('clubSettings.autoFetchRankings') }}
</label>
</div>
<div v-if="autoFetchRankings" class="rankings-fields">
<div class="field-group">
<label>{{ $t('clubSettings.myTischtennisClubId') }}</label>
<input v-model="myTischtennisClubId" class="text-input" :placeholder="$t('clubSettings.myTischtennisClubIdPlaceholder')" />
</div>
<div class="field-group">
<label>{{ $t('clubSettings.myTischtennisFedNickname') }}</label>
<input v-model="myTischtennisFedNickname" class="text-input" :placeholder="$t('clubSettings.myTischtennisFedNicknamePlaceholder')" />
</div>
</div>
</section>
<section v-if="currentClub && !loading" class="card actions-card">
<div class="actions">
<button class="btn btn-primary" @click="save">{{ $t('clubSettings.save') }}</button>
@@ -91,6 +112,9 @@ export default {
activeTab: 'settings',
greeting: '',
associationMemberNumber: '',
myTischtennisClubId: '',
myTischtennisFedNickname: '',
autoFetchRankings: false,
saved: false,
loading: false,
loadError: null,
@@ -112,6 +136,9 @@ export default {
if (!this.currentClub) {
this.greeting = '';
this.associationMemberNumber = '';
this.myTischtennisClubId = '';
this.myTischtennisFedNickname = '';
this.autoFetchRankings = false;
this.loadError = null;
return;
}
@@ -122,10 +149,16 @@ export default {
const club = response.data;
this.greeting = club?.greetingText ?? '';
this.associationMemberNumber = club?.associationMemberNumber ?? '';
this.myTischtennisClubId = club?.myTischtennisClubId ?? '';
this.myTischtennisFedNickname = club?.myTischtennisFedNickname ?? '';
this.autoFetchRankings = !!club?.autoFetchRankings;
} catch (e) {
this.loadError = this.$t('clubSettings.loadFailed');
this.greeting = '';
this.associationMemberNumber = '';
this.myTischtennisClubId = '';
this.myTischtennisFedNickname = '';
this.autoFetchRankings = false;
} finally {
this.loading = false;
}
@@ -139,6 +172,9 @@ export default {
await apiClient.put(`/clubs/${this.currentClub}/settings`, {
greetingText: this.greeting,
associationMemberNumber: this.associationMemberNumber,
myTischtennisClubId: this.myTischtennisClubId || null,
myTischtennisFedNickname: this.myTischtennisFedNickname || null,
autoFetchRankings: this.autoFetchRankings,
});
this.saved = true;
setTimeout(() => (this.saved = false), 1500);
@@ -174,6 +210,11 @@ export default {
font-size: 14px;
}
.text-input { width: 100%; border: 1px solid #ddd; border-radius: 6px; padding: 8px; font-size: 14px; }
.rankings-row { margin-bottom: 12px; }
.rankings-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 12px; }
.field-group label { display: block; margin-bottom: 4px; font-weight: 500; color: #333; }
.checkbox-label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.checkbox-label input[type="checkbox"] { width: auto; }
.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); }