Update MyTischtennis functionality to support automatic rating updates. Introduce new autoUpdateRatings field in MyTischtennis model and enhance MyTischtennisController to handle update history retrieval. Integrate node-cron for scheduling daily updates at 6:00 AM. Update frontend components to allow users to enable/disable automatic updates and display last update timestamps.
This commit is contained in:
@@ -41,6 +41,24 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="formData.autoUpdateRatings"
|
||||
:disabled="!formData.savePassword"
|
||||
/>
|
||||
<span>Automatische Update-Ratings aktivieren</span>
|
||||
</label>
|
||||
<p class="hint">
|
||||
Täglich um 6:00 Uhr werden automatisch die neuesten Ratings von myTischtennis abgerufen.
|
||||
<strong>Erfordert gespeichertes Passwort.</strong>
|
||||
</p>
|
||||
<p v-if="!formData.savePassword" class="warning">
|
||||
⚠️ Für automatische Updates muss das myTischtennis-Passwort gespeichert werden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group" v-if="formData.password">
|
||||
<label for="app-password">Ihr App-Passwort zur Bestätigung:</label>
|
||||
<input
|
||||
@@ -90,6 +108,7 @@ export default {
|
||||
email: this.account?.email || '',
|
||||
password: '',
|
||||
savePassword: this.account?.savePassword || false,
|
||||
autoUpdateRatings: this.account?.autoUpdateRatings || false,
|
||||
userPassword: ''
|
||||
},
|
||||
saving: false,
|
||||
@@ -108,6 +127,11 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Automatische Updates erfordern gespeichertes Passwort
|
||||
if (this.formData.autoUpdateRatings && !this.formData.savePassword) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
@@ -121,7 +145,8 @@ export default {
|
||||
try {
|
||||
const payload = {
|
||||
email: this.formData.email,
|
||||
savePassword: this.formData.savePassword
|
||||
savePassword: this.formData.savePassword,
|
||||
autoUpdateRatings: this.formData.autoUpdateRatings
|
||||
};
|
||||
|
||||
// Nur password und userPassword hinzufügen, wenn ein Passwort eingegeben wurde
|
||||
@@ -243,6 +268,13 @@ export default {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #dc3545;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 0.75rem;
|
||||
background-color: #f8d7da;
|
||||
|
||||
228
frontend/src/components/MyTischtennisHistoryDialog.vue
Normal file
228
frontend/src/components/MyTischtennisHistoryDialog.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div class="modal-overlay" @click.self="$emit('close')">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>Update-Ratings History</h3>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div v-if="loading" class="loading">
|
||||
Lade History...
|
||||
</div>
|
||||
|
||||
<div v-else-if="history.length === 0" class="no-history">
|
||||
<p>Noch keine automatischen Updates durchgeführt.</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="history-list">
|
||||
<div v-for="entry in history" :key="entry.id" class="history-entry">
|
||||
<div class="history-header">
|
||||
<span class="history-date">{{ formatDate(entry.createdAt) }}</span>
|
||||
<span class="history-status" :class="entry.success ? 'success' : 'error'">
|
||||
{{ entry.success ? 'Erfolgreich' : 'Fehlgeschlagen' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="entry.message" class="history-message">
|
||||
{{ entry.message }}
|
||||
</div>
|
||||
<div v-if="entry.errorDetails" class="history-error">
|
||||
{{ entry.errorDetails }}
|
||||
</div>
|
||||
<div v-if="entry.updatedCount !== undefined" class="history-stats">
|
||||
{{ entry.updatedCount }} Ratings aktualisiert
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" @click="$emit('close')">
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'MyTischtennisHistoryDialog',
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
history: []
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadHistory();
|
||||
},
|
||||
methods: {
|
||||
async loadHistory() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const response = await apiClient.get('/mytischtennis/update-history');
|
||||
this.history = response.data.history || [];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der History:', error);
|
||||
this.history = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
max-width: 800px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.no-history {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.history-entry {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.history-date {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.history-status {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.history-status.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.history-status.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.history-message {
|
||||
margin-top: 0.5rem;
|
||||
color: #495057;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.history-error {
|
||||
margin-top: 0.5rem;
|
||||
color: #dc3545;
|
||||
font-size: 0.875rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.history-stats {
|
||||
margin-top: 0.5rem;
|
||||
color: #28a745;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
</style>
|
||||
@@ -34,9 +34,20 @@
|
||||
<span>{{ formatDate(account.lastLoginAttempt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastUpdateRatings">
|
||||
<label>Letzter Abruf:</label>
|
||||
<span>{{ formatDate(account.lastUpdateRatings) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.autoUpdateRatings !== undefined">
|
||||
<label>Automatische Updates:</label>
|
||||
<span>{{ account.autoUpdateRatings ? 'Aktiviert' : 'Deaktiviert' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn-primary" @click="openEditDialog">Account bearbeiten</button>
|
||||
<button class="btn-secondary" @click="testConnection">Erneut einloggen</button>
|
||||
<button class="btn-info" @click="openHistoryDialog" v-if="account">Update-History</button>
|
||||
<button class="btn-danger" @click="deleteAccount">Account trennen</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,6 +77,12 @@
|
||||
@close="closeDialog"
|
||||
@saved="onAccountSaved"
|
||||
/>
|
||||
|
||||
<!-- History Dialog -->
|
||||
<MyTischtennisHistoryDialog
|
||||
v-if="showHistoryDialog"
|
||||
@close="closeHistoryDialog"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -93,16 +110,18 @@
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import MyTischtennisDialog from '../components/MyTischtennisDialog.vue';
|
||||
import MyTischtennisHistoryDialog from '../components/MyTischtennisHistoryDialog.vue';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
export default {
|
||||
name: 'MyTischtennisAccount',
|
||||
components: {
|
||||
MyTischtennisDialog
|
||||
,
|
||||
InfoDialog,
|
||||
ConfirmDialog},
|
||||
MyTischtennisDialog,
|
||||
MyTischtennisHistoryDialog,
|
||||
InfoDialog,
|
||||
ConfirmDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
@@ -123,7 +142,8 @@ export default {
|
||||
},
|
||||
loading: true,
|
||||
account: null,
|
||||
showDialog: false
|
||||
showDialog: false,
|
||||
showHistoryDialog: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -186,6 +206,14 @@ export default {
|
||||
this.showDialog = false;
|
||||
},
|
||||
|
||||
openHistoryDialog() {
|
||||
this.showHistoryDialog = true;
|
||||
},
|
||||
|
||||
closeHistoryDialog() {
|
||||
this.showHistoryDialog = false;
|
||||
},
|
||||
|
||||
async onAccountSaved() {
|
||||
this.closeDialog();
|
||||
await this.loadAccount();
|
||||
@@ -383,5 +411,14 @@ h1 {
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background-color: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
background-color: #138496;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user