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:
Torsten Schulz (local)
2025-11-10 14:20:34 +01:00
parent d94238f6df
commit 4cfa03834e
14 changed files with 233 additions and 36 deletions

View File

@@ -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');

View File

@@ -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');
}
},
},

View File

@@ -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) {

View File

@@ -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');
}
}
}

View File

@@ -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');
}
},
},

View File

@@ -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');
}
},

View File

@@ -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

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 = '';

View File

@@ -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');
}
},
},

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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);