Refactor alert handling to use dialog components for improved user feedback
Updated various components to replace alert calls with showInfo and showConfirm dialog methods, enhancing user experience by providing more informative and styled feedback. This change standardizes the way messages are displayed across the application, improving consistency and clarity in user interactions.
This commit is contained in:
@@ -322,7 +322,7 @@ export default {
|
||||
},
|
||||
|
||||
handleLogout() {
|
||||
alert('Deine Sitzung ist abgelaufen. Du wirst abgemeldet.');
|
||||
this.showInfo('Hinweis', 'Deine Sitzung ist abgelaufen. Du wirst abgemeldet.', '', 'warning');
|
||||
this.logout();
|
||||
clearInterval(this.sessionInterval);
|
||||
this.$router.push('/login');
|
||||
|
||||
@@ -69,10 +69,10 @@ export default {
|
||||
try {
|
||||
const activationCode = this.$route.params.activationCode;
|
||||
await axios.get(`/api/auth/activate/${activationCode}`);
|
||||
alert('Account activated! You can now log in.');
|
||||
await this.showInfo('Erfolg', 'Account aktiviert! Du kannst dich jetzt anmelden.', '', 'success');
|
||||
this.$router.push('/login');
|
||||
} catch (error) {
|
||||
alert('Aktivierung fehlgeschlagen');
|
||||
await this.showInfo('Fehler', 'Aktivierung fehlgeschlagen. Bitte überprüfe den Link oder versuche es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -124,7 +124,7 @@ export default {
|
||||
this.club = response.data;
|
||||
this.accessAllowed = true;
|
||||
} catch (error) {
|
||||
alert('Zugriff auf den Verein nicht gestattet');
|
||||
await this.showInfo('Fehler', 'Zugriff auf den Verein nicht gestattet.', '', 'error');
|
||||
}
|
||||
},
|
||||
async loadOpenRequests() {
|
||||
@@ -138,7 +138,7 @@ export default {
|
||||
async requestAccess() {
|
||||
const response = await apiClient.get(`/clubs/request/${this.currentClub}`);
|
||||
if (response.status === 200) {
|
||||
alert('Zugriff wurde angefragt');
|
||||
await this.showInfo('Hinweis', 'Zugriff wurde angefragt.', '', 'info');
|
||||
}
|
||||
},
|
||||
labelGender(g) {
|
||||
|
||||
@@ -92,7 +92,7 @@ export default {
|
||||
...mapActions(['setClubs', 'setCurrentClub']),
|
||||
async createClub() {
|
||||
if (this.clubName.trim().length < 3) {
|
||||
alert('Bitte gib dem Verein einen Aussagekräftigen Namen');
|
||||
await this.showInfo('Hinweis', 'Bitte gib dem Verein einen aussagekräftigen Namen.', '', 'warning');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -102,9 +102,9 @@ export default {
|
||||
this.setCurrentClub(newClub.data);
|
||||
} catch (error) {
|
||||
if (error.status === 409) {
|
||||
alert('Der Verein existiert bereits.');
|
||||
await this.showInfo('Hinweis', 'Der Verein existiert bereits.', '', 'info');
|
||||
} else {
|
||||
alert('Ein unbekannter Fehler ist aufgetreten.');
|
||||
await this.showInfo('Fehler', 'Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.', '', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export default {
|
||||
await this.login({ token: response.data.token, username: this.email });
|
||||
this.$router.push('/');
|
||||
} catch (error) {
|
||||
alert('Login fehlgeschlagen');
|
||||
await this.showInfo('Fehler', 'Login fehlgeschlagen. Bitte Zugangsdaten prüfen und erneut versuchen.', '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -281,15 +281,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
:confirm-text="confirmDialog.confirmText"
|
||||
:cancel-text="confirmDialog.cancelText"
|
||||
:show-cancel="confirmDialog.showCancel"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'MemberTransferSettingsView',
|
||||
components: {
|
||||
InfoDialog,
|
||||
ConfirmDialog
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentClub']),
|
||||
|
||||
@@ -351,6 +378,24 @@ address={{address}}`;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
},
|
||||
confirmDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
confirmText: 'OK',
|
||||
cancelText: 'Abbrechen',
|
||||
showCancel: true,
|
||||
resolveCallback: null
|
||||
},
|
||||
loading: true,
|
||||
saving: false,
|
||||
deleting: false,
|
||||
@@ -395,6 +440,42 @@ address={{address}}`;
|
||||
await this.loadConfig();
|
||||
},
|
||||
methods: {
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
...this.infoDialog,
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
...this.confirmDialog,
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
confirmText: options.confirmText || 'OK',
|
||||
cancelText: options.cancelText || 'Abbrechen',
|
||||
showCancel: options.showCancel !== false,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleConfirmResult(confirmed) {
|
||||
if (this.confirmDialog.resolveCallback) {
|
||||
this.confirmDialog.resolveCallback(confirmed);
|
||||
this.confirmDialog.resolveCallback = null;
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
|
||||
async loadConfig() {
|
||||
if (!this.currentClub) {
|
||||
this.loading = false;
|
||||
@@ -432,7 +513,7 @@ address={{address}}`;
|
||||
// Keine Konfiguration vorhanden - das ist OK
|
||||
} else {
|
||||
console.error('Fehler beim Laden der Konfiguration:', error);
|
||||
alert('Fehler beim Laden der Konfiguration: ' + (error.response?.data?.error || error.message));
|
||||
await this.showInfo('Fehler', 'Fehler beim Laden der Konfiguration: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -491,16 +572,16 @@ address={{address}}`;
|
||||
|
||||
if (response.data.success) {
|
||||
this.configId = response.data.config?.id || null;
|
||||
alert('Konfiguration erfolgreich gespeichert!');
|
||||
await this.showInfo('Erfolg', 'Konfiguration erfolgreich gespeichert!', '', 'success');
|
||||
|
||||
// Passwort-Feld leeren nach erfolgreichem Speichern
|
||||
this.loginCredentials.password = '';
|
||||
} else {
|
||||
alert('Fehler beim Speichern: ' + (response.data.error || 'Unbekannter Fehler'));
|
||||
await this.showInfo('Fehler', 'Fehler beim Speichern: ' + (response.data.error || 'Unbekannter Fehler'), '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern:', error);
|
||||
alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message));
|
||||
await this.showInfo('Fehler', 'Fehler beim Speichern: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -511,7 +592,8 @@ address={{address}}`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Möchten Sie die Konfiguration wirklich löschen?')) {
|
||||
const confirmed = await this.showConfirm('Konfiguration löschen', 'Möchten Sie die Konfiguration wirklich löschen?', '', 'danger');
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -540,13 +622,13 @@ address={{address}}`;
|
||||
additionalField1: '',
|
||||
additionalField2: ''
|
||||
};
|
||||
alert('Konfiguration erfolgreich gelöscht!');
|
||||
await this.showInfo('Erfolg', 'Konfiguration erfolgreich gelöscht!', '', 'success');
|
||||
} else {
|
||||
alert('Fehler beim Löschen: ' + (response.data.error || 'Unbekannter Fehler'));
|
||||
await this.showInfo('Fehler', 'Fehler beim Löschen: ' + (response.data.error || 'Unbekannter Fehler'), '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen:', error);
|
||||
alert('Fehler beim Löschen: ' + (error.response?.data?.error || error.message));
|
||||
await this.showInfo('Fehler', 'Fehler beim Löschen: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
} finally {
|
||||
this.deleting = false;
|
||||
}
|
||||
@@ -682,7 +764,7 @@ address={{address}}`;
|
||||
// Import-Bereich leeren
|
||||
this.importTemplate = '';
|
||||
|
||||
alert('Template erfolgreich importiert! Das Mitglied-Template und das Bulk-Wrapper-Template wurden automatisch erkannt und ausgefüllt.');
|
||||
await this.showInfo('Erfolg', 'Template erfolgreich importiert! Mitglied- und Bulk-Wrapper-Template wurden erkannt und ausgefüllt.', '', 'success');
|
||||
} else if (Array.isArray(parsed) && parsed.length > 0) {
|
||||
// Direktes Array-Format (ohne Wrapper)
|
||||
const firstMember = parsed[0];
|
||||
@@ -693,7 +775,7 @@ address={{address}}`;
|
||||
this.config.bulkWrapperTemplate = '{"members": "{{members}}"}';
|
||||
|
||||
this.importTemplate = '';
|
||||
alert('Template erfolgreich importiert! Das Mitglied-Template wurde erkannt. Bulk-Modus wurde aktiviert.');
|
||||
await this.showInfo('Erfolg', 'Template erfolgreich importiert! Mitglied-Template erkannt, Bulk-Modus aktiviert.', '', 'success');
|
||||
} else if (typeof parsed === 'object' && parsed !== null) {
|
||||
// Einzelnes Mitglied-Objekt
|
||||
const memberTemplateWithPlaceholders = this.convertValuesToPlaceholders(parsed);
|
||||
@@ -701,13 +783,13 @@ address={{address}}`;
|
||||
this.config.useBulkMode = false;
|
||||
|
||||
this.importTemplate = '';
|
||||
alert('Template erfolgreich importiert! Einzelnes Mitglied-Template erkannt.');
|
||||
await this.showInfo('Erfolg', 'Template erfolgreich importiert! Einzelnes Mitglied-Template erkannt.', '', 'success');
|
||||
} else {
|
||||
throw new Error('Ungültiges Template-Format');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen des Templates:', error);
|
||||
alert('Fehler beim Parsen des Templates: ' + error.message + '\n\nBitte stellen Sie sicher, dass es sich um gültiges JSON handelt.');
|
||||
await this.showInfo('Fehler', 'Fehler beim Parsen des Templates: ' + error.message + '\n\nBitte stellen Sie sicher, dass gültiges JSON verwendet wird.', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -551,7 +551,13 @@ export default {
|
||||
},
|
||||
|
||||
async quickDeactivateMember(member) {
|
||||
if (!confirm(`Möchten Sie "${member.firstName} ${member.lastName}" wirklich deaktivieren?`)) {
|
||||
const confirmed = await this.showConfirm(
|
||||
'Mitglied deaktivieren',
|
||||
`Möchten Sie "${member.firstName} ${member.lastName}" wirklich deaktivieren?`,
|
||||
'',
|
||||
'warning'
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1117,7 +1123,7 @@ export default {
|
||||
const response = await apiClient.post(`/clubmembers/update-ratings/${this.currentClub}`);
|
||||
|
||||
if (response.data.message) {
|
||||
alert(response.data.message);
|
||||
await this.showInfo('Hinweis', response.data.message, '', 'info');
|
||||
}
|
||||
|
||||
// Mitglieder neu laden um aktualisierte Werte anzuzeigen
|
||||
|
||||
@@ -1270,7 +1270,13 @@ export default {
|
||||
},
|
||||
|
||||
async removeTournament(t) {
|
||||
if (!confirm(`Turnier wirklich löschen?\n${t.title || 'Ohne Titel'} (ID ${t.id})`)) return;
|
||||
const confirmed = await this.showConfirm(
|
||||
'Turnier löschen',
|
||||
`Turnier wirklich löschen?\n${t.title || 'Ohne Titel'} (ID ${t.id})`,
|
||||
'',
|
||||
'danger'
|
||||
);
|
||||
if (!confirmed) return;
|
||||
await apiClient.delete(`/official-tournaments/${this.currentClub}/${t.id}`);
|
||||
if (String(this.uploadedId) === String(t.id)) {
|
||||
this.parsed = null;
|
||||
|
||||
@@ -128,6 +128,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
:title="infoDialog.title"
|
||||
:message="infoDialog.message"
|
||||
:details="infoDialog.details"
|
||||
:type="infoDialog.type"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model="confirmDialog.isOpen"
|
||||
:title="confirmDialog.title"
|
||||
:message="confirmDialog.message"
|
||||
:details="confirmDialog.details"
|
||||
:type="confirmDialog.type"
|
||||
:confirm-text="confirmDialog.confirmText"
|
||||
:cancel-text="confirmDialog.cancelText"
|
||||
:show-cancel="confirmDialog.showCancel"
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -136,13 +157,72 @@ import { ref, computed, onMounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { usePermissions } from '../composables/usePermissions.js';
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'PermissionsView',
|
||||
components: {
|
||||
InfoDialog,
|
||||
ConfirmDialog
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const { isOwner, isAdmin, can } = usePermissions();
|
||||
|
||||
const infoDialog = ref({
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
});
|
||||
const confirmDialog = ref({
|
||||
isOpen: false,
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
confirmText: 'OK',
|
||||
cancelText: 'Abbrechen',
|
||||
showCancel: true,
|
||||
resolveCallback: null
|
||||
});
|
||||
|
||||
const showInfo = async (title, message, details = '', type = 'info') => {
|
||||
infoDialog.value = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
};
|
||||
|
||||
const showConfirm = async (title, message, details = '', type = 'info', options = {}) => {
|
||||
return new Promise((resolve) => {
|
||||
confirmDialog.value = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
confirmText: options.confirmText || 'OK',
|
||||
cancelText: options.cancelText || 'Abbrechen',
|
||||
showCancel: options.showCancel !== false,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirmResult = (confirmed) => {
|
||||
if (confirmDialog.value.resolveCallback) {
|
||||
confirmDialog.value.resolveCallback(confirmed);
|
||||
confirmDialog.value.resolveCallback = null;
|
||||
}
|
||||
confirmDialog.value.isOpen = false;
|
||||
};
|
||||
|
||||
const currentClub = computed(() => store.getters.currentClub);
|
||||
const members = ref([]);
|
||||
const availableRoles = ref([]);
|
||||
@@ -201,7 +281,7 @@ export default {
|
||||
await loadData();
|
||||
} catch (err) {
|
||||
console.error('Error updating role:', err);
|
||||
alert(err.response?.data?.error || 'Fehler beim Aktualisieren der Rolle');
|
||||
await showInfo('Fehler', err.response?.data?.error || 'Fehler beim Aktualisieren der Rolle', '', 'error');
|
||||
// Reload to revert changes
|
||||
await loadData();
|
||||
}
|
||||
@@ -211,7 +291,13 @@ export default {
|
||||
const newStatus = member.approved === false ? true : false;
|
||||
const action = newStatus ? 'aktivieren' : 'deaktivieren';
|
||||
|
||||
if (!confirm(`Möchten Sie ${member.user?.email} wirklich ${action}?`)) {
|
||||
const confirmed = await showConfirm(
|
||||
'Status ändern',
|
||||
`Möchten Sie ${member.user?.email} wirklich ${action}?`,
|
||||
'',
|
||||
'warning'
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -309,7 +395,7 @@ export default {
|
||||
await loadData(true);
|
||||
} catch (err) {
|
||||
console.error('Error saving permissions:', err);
|
||||
alert(err.response?.data?.error || 'Fehler beim Speichern der Berechtigungen');
|
||||
await showInfo('Fehler', err.response?.data?.error || 'Fehler beim Speichern der Berechtigungen', '', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -468,6 +554,11 @@ export default {
|
||||
isReadOnly,
|
||||
isOwner,
|
||||
isAdmin,
|
||||
infoDialog,
|
||||
confirmDialog,
|
||||
showInfo,
|
||||
showConfirm,
|
||||
handleConfirmResult,
|
||||
updateMemberRole,
|
||||
toggleMemberStatus,
|
||||
openPermissionsDialog,
|
||||
|
||||
@@ -306,7 +306,13 @@ export default {
|
||||
async mergeSelected() {
|
||||
if (!this.canMerge) return;
|
||||
const src = this.mergeSourceId; const tgt = this.mergeTargetId;
|
||||
if (!confirm(`Eintrag #${src} in #${tgt} zusammenführen?\nAlle Verknüpfungen werden auf das Ziel umgebogen, die Quelle wird gelöscht.`)) return;
|
||||
const confirmed = await this.showConfirm(
|
||||
'Aktivitäten zusammenführen',
|
||||
`Eintrag #${src} in #${tgt} zusammenführen?`,
|
||||
'Alle Verknüpfungen werden auf das Ziel umgebogen, die Quelle wird gelöscht.',
|
||||
'warning'
|
||||
);
|
||||
if (!confirmed) return;
|
||||
await apiClient.post('/predefined-activities/merge', { sourceId: src, targetId: tgt });
|
||||
this.mergeSourceId = '';
|
||||
this.mergeTargetId = '';
|
||||
|
||||
@@ -98,9 +98,9 @@ export default {
|
||||
async register() {
|
||||
try {
|
||||
await axios.post(`${import.meta.env.VITE_BACKEND || 'http://localhost:3005'}/api/auth/register`, { email: this.email, password: this.password });
|
||||
alert('Registration successful! Please check your email to activate your account.');
|
||||
await this.showInfo('Erfolg', 'Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mails, um den Account zu aktivieren.', '', 'success');
|
||||
} catch (error) {
|
||||
alert('Registrierung fehlgeschlagen');
|
||||
await this.showInfo('Fehler', 'Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -655,7 +655,7 @@ export default {
|
||||
|
||||
pdfGen.save('Spielpläne.pdf');
|
||||
} else {
|
||||
alert('Keine Matches gefunden, um PDF zu generieren.');
|
||||
await this.showInfo('Hinweis', 'Keine Matches gefunden, um PDF zu generieren.', '', 'info');
|
||||
}
|
||||
},
|
||||
getUniqueLocations() {
|
||||
|
||||
@@ -517,7 +517,13 @@ export default {
|
||||
};
|
||||
|
||||
const deleteTeam = async (team) => {
|
||||
if (!confirm(`Möchten Sie das Club-Team "${team.name}" wirklich löschen?`)) {
|
||||
const confirmed = await showConfirm(
|
||||
'Club-Team löschen',
|
||||
`Möchten Sie das Club-Team "${team.name}" wirklich löschen?`,
|
||||
'',
|
||||
'danger'
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1579,16 +1579,16 @@ export default {
|
||||
// Lade Turnierdaten neu
|
||||
await this.loadTournamentData();
|
||||
} else {
|
||||
alert('Keine gültigen Teilnehmer im heutigen Trainingstag gefunden!');
|
||||
await this.showInfo('Hinweis', 'Keine gültigen Teilnehmer im heutigen Trainingstag gefunden!', '', 'info');
|
||||
}
|
||||
} else {
|
||||
alert('Keine Teilnehmer im heutigen Trainingstag gefunden!');
|
||||
await this.showInfo('Hinweis', 'Keine Teilnehmer im heutigen Trainingstag gefunden!', '', 'info');
|
||||
}
|
||||
} else {
|
||||
alert('Kein Trainingstag für heute gefunden!');
|
||||
await this.showInfo('Hinweis', 'Kein Trainingstag für heute gefunden!', '', 'info');
|
||||
}
|
||||
} else {
|
||||
alert('Kein Trainingstag für heute gefunden!');
|
||||
await this.showInfo('Hinweis', 'Kein Trainingstag für heute gefunden!', '', 'info');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Trainingsteilnehmer:', error);
|
||||
|
||||
Reference in New Issue
Block a user