Add MyTischtennis fetch log functionality and new endpoints

Enhance MyTischtennis integration by introducing fetch log capabilities. Implement new controller methods to retrieve fetch logs and latest successful fetches for users. Update routes to include these new endpoints. Modify the MyTischtennis model to support fetch logs and ensure proper logging of fetch operations in various services. Update frontend components to display fetch statistics, improving user experience and data visibility.
This commit is contained in:
Torsten Schulz (local)
2025-10-14 23:07:57 +02:00
parent 7549fb5730
commit 36bf99c013
10 changed files with 584 additions and 25 deletions

View File

@@ -51,6 +51,58 @@
<button class="btn-danger" @click="deleteAccount">Account trennen</button>
</div>
</div>
<!-- Fetch Statistics Section -->
<div class="info-section fetch-stats-section" v-if="account">
<h2>Datenabruf-Statistiken</h2>
<div v-if="loadingStats" class="loading-stats">Lade Statistiken...</div>
<div v-else-if="latestFetches" class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-content">
<h3>Spielerwertungen</h3>
<div v-if="latestFetches.ratings">
<p class="stat-date">{{ formatDateRelative(latestFetches.ratings.lastFetch) }}</p>
<p class="stat-detail">{{ latestFetches.ratings.recordsProcessed }} Spieler aktualisiert</p>
<p class="stat-time" v-if="latestFetches.ratings.executionTime">{{ latestFetches.ratings.executionTime }}ms</p>
</div>
<p v-else class="stat-never">Noch nie abgerufen</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏓</div>
<div class="stat-content">
<h3>Spielergebnisse</h3>
<div v-if="latestFetches.match_results">
<p class="stat-date">{{ formatDateRelative(latestFetches.match_results.lastFetch) }}</p>
<p class="stat-detail">{{ latestFetches.match_results.recordsProcessed }} Ergebnisse</p>
<p class="stat-time" v-if="latestFetches.match_results.executionTime">{{ latestFetches.match_results.executionTime }}ms</p>
</div>
<p v-else class="stat-never">Noch nie abgerufen</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">📋</div>
<div class="stat-content">
<h3>Ligatabellen</h3>
<div v-if="latestFetches.league_table">
<p class="stat-date">{{ formatDateRelative(latestFetches.league_table.lastFetch) }}</p>
<p class="stat-detail">{{ latestFetches.league_table.recordsProcessed }} Teams</p>
<p class="stat-time" v-if="latestFetches.league_table.executionTime">{{ latestFetches.league_table.executionTime }}ms</p>
</div>
<p v-else class="stat-never">Noch nie abgerufen</p>
</div>
</div>
</div>
<button class="btn-secondary refresh-stats-btn" @click="loadLatestFetches">
🔄 Statistiken aktualisieren
</button>
</div>
</div>
<div v-else class="no-account">
@@ -141,13 +193,16 @@ export default {
resolveCallback: null
},
loading: true,
loadingStats: false,
account: null,
latestFetches: null,
showDialog: false,
showHistoryDialog: false
};
},
mounted() {
this.loadAccount();
this.loadLatestFetches();
},
methods: {
// Dialog Helper Methods
@@ -247,23 +302,34 @@ export default {
},
async deleteAccount() {
if (!confirm('Möchten Sie die Verknüpfung zum myTischtennis-Account wirklich trennen?')) {
return;
}
const confirmed = await this.showConfirm(
'Account trennen',
'Möchten Sie die Verknüpfung zum myTischtennis-Account wirklich trennen?',
'',
'danger'
);
if (!confirmed) return;
try {
await apiClient.delete('/mytischtennis/account');
this.account = null;
this.$store.dispatch('showMessage', {
text: 'myTischtennis-Account erfolgreich getrennt',
type: 'success'
});
this.showInfo('Erfolg', 'myTischtennis-Account erfolgreich getrennt', '', 'success');
} catch (error) {
console.error('Fehler beim Löschen des Accounts:', error);
this.$store.dispatch('showMessage', {
text: 'Fehler beim Trennen des Accounts',
type: 'error'
});
this.showInfo('Fehler', 'Fehler beim Trennen des Accounts', error.message, 'error');
}
},
async loadLatestFetches() {
this.loadingStats = true;
try {
const response = await apiClient.get('/mytischtennis/latest-fetches');
this.latestFetches = response.data.latestFetches;
} catch (error) {
console.error('Fehler beim Laden der Fetch-Statistiken:', error);
} finally {
this.loadingStats = false;
}
},
@@ -277,6 +343,31 @@ export default {
hour: '2-digit',
minute: '2-digit'
});
},
formatDateRelative(dateString) {
if (!dateString) return 'Nie';
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Gerade eben';
if (diffMins < 60) return `vor ${diffMins} Min.`;
if (diffHours < 24) return `vor ${diffHours} Std.`;
if (diffDays === 1) return 'Gestern';
if (diffDays < 7) return `vor ${diffDays} Tagen`;
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
}
};
@@ -403,6 +494,85 @@ h1 {
background-color: #545b62;
}
/* Fetch Statistics */
.fetch-stats-section {
margin-top: 2rem;
}
.loading-stats {
text-align: center;
padding: 2rem;
color: #666;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.stat-card {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
display: flex;
align-items: flex-start;
gap: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.stat-icon {
font-size: 2.5rem;
line-height: 1;
}
.stat-content {
flex: 1;
}
.stat-content h3 {
margin: 0 0 0.5rem 0;
font-size: 1rem;
color: #333;
font-weight: 600;
}
.stat-date {
font-weight: 600;
color: #28a745;
margin: 0.25rem 0;
}
.stat-detail {
font-size: 0.9rem;
color: #666;
margin: 0.25rem 0;
}
.stat-time {
font-size: 0.8rem;
color: #999;
margin: 0.25rem 0;
}
.stat-never {
font-style: italic;
color: #999;
margin: 0.25rem 0;
}
.refresh-stats-btn {
width: 100%;
margin-top: 1rem;
}
.btn-danger {
background-color: #dc3545;
color: white;