feat: implement QTTR status tracking and update mechanism
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 56s

This commit is contained in:
Torsten Schulz (local)
2026-08-14 11:25:44 +02:00
parent bc9e7cfe3c
commit 3f2c474913
7 changed files with 87 additions and 2 deletions

View File

@@ -10,7 +10,7 @@
</button>
<button @click="$emit('create-phone-list')">{{ $t('members.generatePhoneList') }}</button>
<button @click="$emit('open-group-photo-crop')">{{ $t('members.groupPhotoCrop') }}</button>
<button @click="$emit('update-ratings')" class="btn-update-ratings" :disabled="isUpdatingRatings">
<button @click="$emit('update-ratings')" class="btn-update-ratings" :disabled="isUpdatingRatings" :title="ratingsStatus">
{{ isUpdatingRatings ? $t('members.updating') : $t('members.updateRatings') }}
</button>
<button @click="$emit('open-transfer-dialog')" class="btn-transfer">
@@ -244,7 +244,8 @@ export default {
filteredTestMembersCount: { type: Number, required: true },
filteredMembersWithPhonesCount: { type: Number, required: true },
filteredMembersWithEmailsCount: { type: Number, required: true },
exportPreviewNames: { type: Array, required: true }
exportPreviewNames: { type: Array, required: true },
ratingsStatus: { type: String, default: '' }
},
emits: [
'toggle-new-member',

View File

@@ -3,6 +3,7 @@
<MembersOverviewSection
:member-form-is-open="memberFormIsOpen"
:is-updating-ratings="isUpdatingRatings"
:ratings-status="ratingsStatusTooltip"
:active-members-count="activeMembersCount"
:test-members-count="testMembersCount"
:inactive-members-count="inactiveMembersCount"
@@ -795,6 +796,20 @@ export default {
},
computed: {
...mapGetters(['isAuthenticated', 'currentClub', 'token', 'hasPermission']),
ratingsStatusTooltip() {
const status = this.ratingsStatus;
if (!status) return 'QTTR-Status wird geladen.';
const lines = [];
const last = this.formatQttrStatusDate(status.lastQttrValueChangeAt);
lines.push(last ? `Letzte echte QTTR-Wertänderung: ${last}` : 'Bisher keine echte QTTR-Wertänderung erkannt. Erstbefüllungen zählen nicht.');
const cutoff = this.formatQttrStatusDate(status.nextQttrCutoff);
if (cutoff) lines.push(`Nächster QTTR-Stichtag: ${cutoff.split(',')[0]}.`);
const availableFrom = this.formatQttrStatusDate(status.expectedAvailableFrom);
const availableUntil = this.formatQttrStatusDate(status.expectedAvailableUntil);
if (availableFrom && availableUntil) lines.push(`Voraussichtlich verfügbar: ${availableFrom.split(',')[0]} bis ${availableUntil.split(',')[0]} (myTischtennis veröffentlicht nach einigen Tagen; keine feste Uhrzeit).`);
return lines.join('\n');
},
activeMembersCount() {
return this.members.filter(member => member.active && !member.testMembership).length;
@@ -1227,6 +1242,7 @@ export default {
newAdultReleaseApproved: false,
newAdultReserveApproved: false,
isUpdatingRatings: false,
ratingsStatus: null,
showMemberInfo: false,
showActivitiesModal: false,
showMemberTtrHistoryDialog: false,
@@ -1281,6 +1297,7 @@ export default {
await this.loadTrainingGroups();
await this.loadMemberDataQualityRequirements();
await this.init();
await this.loadRatingsStatus();
},
methods: {
// Dialog Helper Methods
@@ -3029,6 +3046,7 @@ export default {
// Mitglieder neu laden um aktualisierte Werte anzuzeigen
await this.loadMembers();
await this.loadRatingsStatus();
} catch (error) {
console.error(this.$t('members.errorUpdatingRatings'), error);
const message = getSafeErrorMessage(error, this.$t('members.errorUpdatingRatings'));
@@ -3037,6 +3055,14 @@ export default {
this.isUpdatingRatings = false;
}
},
async loadRatingsStatus() {
if (!this.currentClub) return;
try { this.ratingsStatus = (await apiClient.get(`/clubmembers/ratings-status/${this.currentClub}`)).data; }
catch (_error) { this.ratingsStatus = null; }
},
formatQttrStatusDate(value) {
return value ? new Intl.DateTimeFormat('de-DE', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value)) : null;
},
getAge(birthDate) {
if (!birthDate) return null;