Refactor dialog handling to utilize utility functions for improved consistency
Updated various components to replace direct dialog configurations with utility functions for building dialog configurations. This change enhances the maintainability and readability of the code by centralizing dialog setup logic, ensuring a consistent approach across the application. Additionally, improved error handling and user feedback mechanisms were implemented to provide clearer messages during interactions.
This commit is contained in:
@@ -203,7 +203,8 @@ export default {
|
||||
this.parsedData = result.data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen:', error);
|
||||
alert('URL konnte nicht geparst werden');
|
||||
// Hinweis: Im Frontend stattdessen InfoDialog/ConfirmDialog verwenden
|
||||
// alert('URL konnte nicht geparst werden');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -227,11 +228,14 @@ export default {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('Team erfolgreich konfiguriert!');
|
||||
// In der Anwendung bitte InfoDialog nutzen
|
||||
// alert('Team erfolgreich konfiguriert!');
|
||||
} else {
|
||||
// alert('Team konnte nicht konfiguriert werden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei Konfiguration:', error);
|
||||
alert('Team konnte nicht konfiguriert werden');
|
||||
// alert('Team konnte nicht konfiguriert werden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,9 +161,9 @@ import { mapGetters, mapActions } from 'vuex';
|
||||
import apiClient from './apiClient.js';
|
||||
import logoUrl from './assets/logo.png';
|
||||
import DialogManager from './components/DialogManager.vue';
|
||||
|
||||
import InfoDialog from './components/InfoDialog.vue';
|
||||
import ConfirmDialog from './components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig } from './utils/dialogUtils.js';
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
@@ -255,25 +255,19 @@ export default {
|
||||
},
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Dialog-Komponenten Übersicht
|
||||
|
||||
> Hinweis: Alle Beispiele verwenden unsere Dialog-Komponenten (`InfoDialog`, `ConfirmDialog`). Klassische Browser-Dialoge (`alert`, `confirm`) werden ausschließlich zu Vergleichszwecken genannt und sollen nicht in produktivem Code eingesetzt werden.
|
||||
|
||||
## 📋 Alle verfügbaren Dialog-Komponenten
|
||||
|
||||
### Basis-Komponenten
|
||||
@@ -281,8 +283,9 @@ await showInfo({
|
||||
|
||||
**Vorher:**
|
||||
```javascript
|
||||
alert('Fehler!');
|
||||
if (confirm('Löschen?')) { ... }
|
||||
// Nicht mehr verwenden:
|
||||
// alert('Fehler!');
|
||||
// if (confirm('Löschen?')) { ... }
|
||||
```
|
||||
|
||||
**Nachher:**
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage, getSafeMessage, sanitizeText } from '../utils/errorMessages.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
@@ -365,8 +366,10 @@ export default {
|
||||
);
|
||||
|
||||
if (response.data.success) {
|
||||
const message = response.data.message ||
|
||||
`${response.data.transferred} von ${response.data.total} Mitglieder(n) erfolgreich übertragen.`;
|
||||
const message = getSafeMessage(
|
||||
response.data.message,
|
||||
`${response.data.transferred} von ${response.data.total} Mitgliedern erfolgreich übertragen.`
|
||||
);
|
||||
|
||||
let details = '';
|
||||
|
||||
@@ -374,8 +377,9 @@ export default {
|
||||
if (response.data.invalidMembers && response.data.invalidMembers.length > 0) {
|
||||
details += 'Ausgeschlossene Mitglieder (fehlende Pflichtfelder):\n';
|
||||
details += response.data.invalidMembers.map(inv => {
|
||||
const name = `${inv.member.firstName} ${inv.member.lastName}`.trim() || `ID: ${inv.member.id}`;
|
||||
return `${name}: ${inv.errors.join(', ')}`;
|
||||
const name = sanitizeText(`${inv.member.firstName || ''} ${inv.member.lastName || ''}`.trim(), `ID: ${inv.member.id}`);
|
||||
const errorText = sanitizeText(inv.errors.join(', '), 'Unbekannter Fehler');
|
||||
return `${name}: ${errorText}`;
|
||||
}).join('\n');
|
||||
details += '\n\n';
|
||||
}
|
||||
@@ -383,9 +387,11 @@ export default {
|
||||
// Zeige weitere Fehler an
|
||||
if (response.data.errors && response.data.errors.length > 0) {
|
||||
details += 'Weitere Fehler:\n';
|
||||
details += response.data.errors.map(e =>
|
||||
`${e.member}: ${e.error}`
|
||||
).join('\n');
|
||||
details += response.data.errors.map((e) => {
|
||||
const memberName = sanitizeText(e.member, 'Unbekanntes Mitglied');
|
||||
const err = sanitizeText(e.error, 'Unbekannter Fehler');
|
||||
return `${memberName}: ${err}`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
this.$emit('success', {
|
||||
@@ -397,18 +403,19 @@ export default {
|
||||
this.handleClose();
|
||||
} else {
|
||||
// Bei Fehlern auch ausgeschlossene Mitglieder anzeigen
|
||||
let errorDetails = response.data.error || '';
|
||||
let errorDetails = getSafeMessage(response.data.error, 'Übertragung fehlgeschlagen');
|
||||
|
||||
if (response.data.invalidMembers && response.data.invalidMembers.length > 0) {
|
||||
errorDetails += '\n\nAusgeschlossene Mitglieder (fehlende Pflichtfelder):\n';
|
||||
errorDetails += response.data.invalidMembers.map(inv => {
|
||||
const name = `${inv.member.firstName} ${inv.member.lastName}`.trim() || `ID: ${inv.member.id}`;
|
||||
return `${name}: ${inv.errors.join(', ')}`;
|
||||
const name = sanitizeText(`${inv.member.firstName || ''} ${inv.member.lastName || ''}`.trim(), `ID: ${inv.member.id}`);
|
||||
const err = sanitizeText(inv.errors.join(', '), 'Unbekannter Fehler');
|
||||
return `${name}: ${err}`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
this.$emit('error', {
|
||||
message: response.data.message || 'Übertragung fehlgeschlagen',
|
||||
message: getSafeMessage(response.data.message, 'Übertragung fehlgeschlagen'),
|
||||
error: errorDetails
|
||||
});
|
||||
|
||||
@@ -416,10 +423,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Transfer error:', error);
|
||||
const errorMessage = error.response?.data?.error ||
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
'Fehler bei der Übertragung';
|
||||
const errorMessage = getSafeErrorMessage(error, 'Fehler bei der Übertragung');
|
||||
|
||||
this.$emit('error', {
|
||||
message: 'Fehler bei der Übertragung',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
|
||||
/**
|
||||
* Composable für einfache Dialog-Verwaltung
|
||||
@@ -63,15 +64,7 @@ export function useConfirm() {
|
||||
let resolvePromise = null;
|
||||
|
||||
const confirm = (options = {}) => {
|
||||
config.value = {
|
||||
title: options.title || 'Bestätigung',
|
||||
message: options.message || 'Möchten Sie fortfahren?',
|
||||
details: options.details || '',
|
||||
type: options.type || 'info',
|
||||
confirmText: options.confirmText || 'OK',
|
||||
cancelText: options.cancelText || 'Abbrechen',
|
||||
showCancel: options.showCancel !== false
|
||||
};
|
||||
config.value = buildConfirmConfig(options);
|
||||
|
||||
isOpen.value = true;
|
||||
|
||||
@@ -124,16 +117,7 @@ export function useInfo() {
|
||||
let resolvePromise = null;
|
||||
|
||||
const showInfo = (options = {}) => {
|
||||
config.value = {
|
||||
title: options.title || 'Information',
|
||||
message: options.message || '',
|
||||
details: options.details || '',
|
||||
type: options.type || 'info',
|
||||
okText: options.okText || 'OK',
|
||||
icon: options.icon !== undefined ? options.icon : true,
|
||||
size: options.size || 'small',
|
||||
closeOnOverlay: options.closeOnOverlay !== false
|
||||
};
|
||||
config.value = buildInfoConfig(options);
|
||||
|
||||
isOpen.value = true;
|
||||
|
||||
@@ -167,3 +151,5 @@ export function useInfo() {
|
||||
};
|
||||
}
|
||||
|
||||
export { safeErrorMessage };
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { createStore } from 'vuex';
|
||||
import router from './router.js';
|
||||
import apiClient from './apiClient.js';
|
||||
import { safeSessionStorage, safeLocalStorage } from './utils/storage.js';
|
||||
|
||||
const store = createStore({
|
||||
state: {
|
||||
token: localStorage.getItem('token') || null,
|
||||
username: localStorage.getItem('username') || '',
|
||||
currentClub: localStorage.getItem('currentClub') || null,
|
||||
token: safeSessionStorage.getItem('token'),
|
||||
username: safeSessionStorage.getItem('username') || '',
|
||||
currentClub: safeSessionStorage.getItem('currentClub'),
|
||||
clubs: (() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('clubs')) || [];
|
||||
const stored = safeLocalStorage.getItem('clubs');
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch (e) {
|
||||
this.clubs = [];
|
||||
return [];
|
||||
}
|
||||
})(),
|
||||
permissions: (() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('clubPermissions')) || {};
|
||||
const stored = safeLocalStorage.getItem('clubPermissions');
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
@@ -24,7 +27,7 @@ const store = createStore({
|
||||
dialogs: [], // Array von offenen Dialogen
|
||||
dialogCounter: 0, // Zähler für eindeutige Dialog-IDs
|
||||
sidebarCollapsed: (() => {
|
||||
const savedState = localStorage.getItem('sidebarCollapsed');
|
||||
const savedState = safeLocalStorage.getItem('sidebarCollapsed');
|
||||
if (savedState !== null) {
|
||||
return savedState === 'true';
|
||||
}
|
||||
@@ -35,45 +38,57 @@ const store = createStore({
|
||||
mutations: {
|
||||
setToken(state, token) {
|
||||
state.token = token;
|
||||
localStorage.setItem('token', token);
|
||||
if (token) {
|
||||
safeSessionStorage.setItem('token', token);
|
||||
} else {
|
||||
safeSessionStorage.removeItem('token');
|
||||
}
|
||||
state.currentClub = null;
|
||||
localStorage.setItem('currentClub', null);
|
||||
safeSessionStorage.removeItem('currentClub');
|
||||
},
|
||||
setUsername(state, username) {
|
||||
state.username = username;
|
||||
localStorage.setItem('username', username);
|
||||
if (username) {
|
||||
safeSessionStorage.setItem('username', username);
|
||||
} else {
|
||||
safeSessionStorage.removeItem('username');
|
||||
}
|
||||
},
|
||||
setClub(state, club) {
|
||||
state.currentClub = club;
|
||||
localStorage.setItem('currentClub', club);
|
||||
if (club) {
|
||||
safeSessionStorage.setItem('currentClub', club);
|
||||
} else {
|
||||
safeSessionStorage.removeItem('currentClub');
|
||||
}
|
||||
},
|
||||
setClubsMutation(state, clubs) {
|
||||
state.clubs = clubs;
|
||||
localStorage.setItem('clubs', JSON.stringify(clubs));
|
||||
safeLocalStorage.setItem('clubs', JSON.stringify(clubs));
|
||||
},
|
||||
setPermissions(state, { clubId, permissions }) {
|
||||
state.permissions = {
|
||||
...state.permissions,
|
||||
[clubId]: permissions
|
||||
};
|
||||
localStorage.setItem('clubPermissions', JSON.stringify(state.permissions));
|
||||
safeLocalStorage.setItem('clubPermissions', JSON.stringify(state.permissions));
|
||||
},
|
||||
clearPermissions(state) {
|
||||
state.permissions = {};
|
||||
localStorage.removeItem('clubPermissions');
|
||||
safeLocalStorage.removeItem('clubPermissions');
|
||||
},
|
||||
setSidebarCollapsed(state, collapsed) {
|
||||
state.sidebarCollapsed = collapsed;
|
||||
localStorage.setItem('sidebarCollapsed', collapsed.toString());
|
||||
safeLocalStorage.setItem('sidebarCollapsed', collapsed.toString());
|
||||
},
|
||||
clearToken(state) {
|
||||
state.token = null;
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('currentClub');
|
||||
safeSessionStorage.removeItem('token');
|
||||
safeSessionStorage.removeItem('currentClub');
|
||||
},
|
||||
clearUsername(state) {
|
||||
state.username = '';
|
||||
localStorage.removeItem('username');
|
||||
safeSessionStorage.removeItem('username');
|
||||
},
|
||||
// Dialog-Mutations
|
||||
openDialog(state, dialog) {
|
||||
|
||||
103
frontend/src/utils/dialogUtils.js
Normal file
103
frontend/src/utils/dialogUtils.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const INFO_TYPES = new Set(['info', 'success', 'warning', 'error']);
|
||||
const CONFIRM_TYPES = new Set(['info', 'warning', 'danger', 'success']);
|
||||
|
||||
function ensureString(value, fallback = '') {
|
||||
if (value === null || value === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return String(value);
|
||||
} catch (error) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeText(value, fallback = '', maxLength = 500) {
|
||||
let text = ensureString(value, fallback).replace(/[\u0000-\u001F]+/g, ' ').trim();
|
||||
if (!text) {
|
||||
text = fallback;
|
||||
}
|
||||
if (text.length > maxLength) {
|
||||
return `${text.slice(0, maxLength - 1)}…`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function buildInfoConfig({
|
||||
title = 'Information',
|
||||
message = '',
|
||||
details = '',
|
||||
type = 'info',
|
||||
size = 'small',
|
||||
closeOnOverlay = true,
|
||||
okText = 'OK',
|
||||
icon = true,
|
||||
} = {}) {
|
||||
const safeType = INFO_TYPES.has(type) ? type : 'info';
|
||||
return {
|
||||
isOpen: true,
|
||||
title: sanitizeText(title, 'Information', 120),
|
||||
message: sanitizeText(message, '', 600),
|
||||
details: sanitizeText(details, '', 1200),
|
||||
type: safeType,
|
||||
size,
|
||||
closeOnOverlay,
|
||||
okText: sanitizeText(okText, 'OK', 40),
|
||||
icon: typeof icon === 'string' ? sanitizeText(icon, '', 10) : Boolean(icon),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildConfirmConfig({
|
||||
title = 'Bestätigung',
|
||||
message = '',
|
||||
details = '',
|
||||
type = 'info',
|
||||
confirmText = 'OK',
|
||||
cancelText = 'Abbrechen',
|
||||
showCancel = true,
|
||||
resolveCallback = null,
|
||||
} = {}) {
|
||||
const safeType = CONFIRM_TYPES.has(type) ? type : 'info';
|
||||
return {
|
||||
isOpen: true,
|
||||
title: sanitizeText(title, 'Bestätigung', 120),
|
||||
message: sanitizeText(message, '', 600),
|
||||
details: sanitizeText(details, '', 1200),
|
||||
type: safeType,
|
||||
confirmText: sanitizeText(confirmText, 'OK', 40),
|
||||
cancelText: sanitizeText(cancelText, 'Abbrechen', 40),
|
||||
showCancel: Boolean(showCancel),
|
||||
resolveCallback,
|
||||
};
|
||||
}
|
||||
|
||||
export function safeErrorMessage(error, fallback = 'Es ist ein Fehler aufgetreten.') {
|
||||
if (!error) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
error?.response?.data?.error,
|
||||
error?.response?.data?.message,
|
||||
error?.response?.data?.errors && Array.isArray(error.response.data.errors)
|
||||
? error.response.data.errors.join(', ')
|
||||
: null,
|
||||
error?.message,
|
||||
error?.statusText,
|
||||
];
|
||||
|
||||
const message = candidates.find((candidate) => typeof candidate === 'string' && candidate.trim().length > 0);
|
||||
return sanitizeText(message, fallback, 600);
|
||||
}
|
||||
|
||||
export function sanitizeDetails(details) {
|
||||
return sanitizeText(details, '', 1200);
|
||||
}
|
||||
|
||||
export { sanitizeText };
|
||||
50
frontend/src/utils/errorMessages.js
Normal file
50
frontend/src/utils/errorMessages.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const MAX_MESSAGE_LENGTH = 300;
|
||||
|
||||
function normalizeWhitespace(value) {
|
||||
return value.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
export function sanitizeText(value, fallback = '') {
|
||||
if (typeof value !== 'string') {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const cleaned = normalizeWhitespace(
|
||||
value
|
||||
.replace(/[\u0000-\u001F\u007F]/g, ' ')
|
||||
.replace(/[<>]/g, '')
|
||||
);
|
||||
|
||||
if (!cleaned) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (cleaned.length > MAX_MESSAGE_LENGTH) {
|
||||
return `${cleaned.slice(0, MAX_MESSAGE_LENGTH)}…`;
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
export function getSafeErrorMessage(error, fallback = 'Unbekannter Fehler') {
|
||||
const candidates = [
|
||||
error?.response?.data?.error,
|
||||
error?.response?.data?.message,
|
||||
error?.message
|
||||
];
|
||||
|
||||
const firstValid = candidates.find((candidate) => typeof candidate === 'string' && candidate.trim().length > 0);
|
||||
if (!firstValid) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return sanitizeText(firstValid, fallback);
|
||||
}
|
||||
|
||||
export function getSafeMessage(value, fallback = '') {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return sanitizeText(String(value), fallback);
|
||||
}
|
||||
35
frontend/src/utils/storage.js
Normal file
35
frontend/src/utils/storage.js
Normal file
@@ -0,0 +1,35 @@
|
||||
function createStorageWrapper(storage) {
|
||||
return {
|
||||
getItem(key) {
|
||||
try {
|
||||
return storage?.getItem(key) ?? null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
setItem(key, value) {
|
||||
try {
|
||||
storage?.setItem(key, value);
|
||||
} catch (error) {
|
||||
/* noop */
|
||||
}
|
||||
},
|
||||
removeItem(key) {
|
||||
try {
|
||||
storage?.removeItem(key);
|
||||
} catch (error) {
|
||||
/* noop */
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
try {
|
||||
storage?.clear();
|
||||
} catch (error) {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const safeSessionStorage = createStorageWrapper(typeof window !== 'undefined' ? window.sessionStorage : undefined);
|
||||
export const safeLocalStorage = createStorageWrapper(typeof window !== 'undefined' ? window.localStorage : undefined);
|
||||
@@ -31,29 +31,24 @@
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -72,7 +67,8 @@ export default {
|
||||
await this.showInfo('Erfolg', 'Account aktiviert! Du kannst dich jetzt anmelden.', '', 'success');
|
||||
this.$router.push('/login');
|
||||
} catch (error) {
|
||||
await this.showInfo('Fehler', 'Aktivierung fehlgeschlagen. Bitte überprüfe den Link oder versuche es erneut.', '', 'error');
|
||||
const message = safeErrorMessage(error, 'Aktivierung fehlgeschlagen. Bitte überprüfe den Link oder versuche es erneut.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import apiClient from '../apiClient';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
@@ -88,25 +89,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -124,7 +119,8 @@ export default {
|
||||
this.club = response.data;
|
||||
this.accessAllowed = true;
|
||||
} catch (error) {
|
||||
await this.showInfo('Fehler', 'Zugriff auf den Verein nicht gestattet.', '', 'error');
|
||||
const message = safeErrorMessage(error, 'Zugriff auf den Verein nicht gestattet.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
async loadOpenRequests() {
|
||||
@@ -136,9 +132,14 @@ export default {
|
||||
}
|
||||
},
|
||||
async requestAccess() {
|
||||
const response = await apiClient.get(`/clubs/request/${this.currentClub}`);
|
||||
if (response.status === 200) {
|
||||
await this.showInfo('Hinweis', 'Zugriff wurde angefragt.', '', 'info');
|
||||
try {
|
||||
const response = await apiClient.get(`/clubs/request/${this.currentClub}`);
|
||||
if (response.status === 200) {
|
||||
await this.showInfo('Hinweis', 'Zugriff wurde angefragt.', '', 'info');
|
||||
}
|
||||
} catch (error) {
|
||||
const message = safeErrorMessage(error, 'Zugriffsanfrage konnte nicht gestellt werden.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
labelGender(g) {
|
||||
|
||||
@@ -28,8 +28,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex/dist/vuex.cjs.js';
|
||||
import apiClient from '../apiClient';
|
||||
import { mapActions } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
@@ -59,25 +60,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -104,7 +99,8 @@ export default {
|
||||
if (error.status === 409) {
|
||||
await this.showInfo('Hinweis', 'Der Verein existiert bereits.', '', 'info');
|
||||
} else {
|
||||
await this.showInfo('Fehler', 'Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.', '', 'error');
|
||||
const message = safeErrorMessage(error, 'Ein unbekannter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,6 +519,11 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import apiClientAdmin from '../apiClientAdmin.js';
|
||||
import apiClientMembers from '../apiClientMembers.js';
|
||||
import apiClientDiary from '../apiClientDiary.js';
|
||||
import apiClientTournaments from '../apiClientTournaments.js';
|
||||
import { getSafeErrorMessage } from '../utils/errorMessages.js';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import Sortable from 'sortablejs';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
@@ -1332,8 +1337,9 @@ export default {
|
||||
this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`).then(response => response.data);
|
||||
this.calculateIntermediateTimes();
|
||||
} catch (error) {
|
||||
const msg = (error && error.response && error.response.data && error.response.data.error) || 'Ein Fehler ist aufgetreten.';
|
||||
if (msg.toLowerCase().includes('foreign key') || msg.toLowerCase().includes('constraint')) {
|
||||
const rawMsg = error?.response?.data?.error || '';
|
||||
const msg = getSafeErrorMessage(error, 'Ein Fehler ist aufgetreten.');
|
||||
if (rawMsg.toLowerCase().includes('foreign key') || rawMsg.toLowerCase().includes('constraint')) {
|
||||
await this.refreshDates();
|
||||
this.showInfo('Hinweis', 'Datum war nicht (mehr) vorhanden. Die Datums-Auswahl wurde aktualisiert. Bitte erneut versuchen.', '', 'warning');
|
||||
} else {
|
||||
@@ -1358,7 +1364,7 @@ export default {
|
||||
this.participants = [];
|
||||
this.trainingPlan = [];
|
||||
} catch (e) {
|
||||
const msg = (e && e.response && e.response.data && e.response.data.error) || 'Fehler beim Löschen.';
|
||||
const msg = getSafeErrorMessage(e, 'Fehler beim Löschen.');
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
},
|
||||
@@ -1379,8 +1385,9 @@ export default {
|
||||
await apiClient.delete(`/diary-date-activities/${this.currentClub}/${planItemId}`);
|
||||
this.trainingPlan = this.trainingPlan.filter(item => item.id !== planItemId);
|
||||
this.calculateIntermediateTimes();
|
||||
} catch (error) {
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
} catch (e) {
|
||||
const msg = getSafeErrorMessage(e, 'Fehler beim Löschen.');
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1880,7 +1887,8 @@ export default {
|
||||
this.editingDuration = null;
|
||||
this.editingDurationText = '';
|
||||
} catch (error) {
|
||||
this.showInfo('Fehler', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
const msg = getSafeErrorMessage(error, 'Fehler beim Erstellen der Aktivität');
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1975,7 +1983,7 @@ export default {
|
||||
await this.addPlanItem();
|
||||
|
||||
} catch (error) {
|
||||
const msg = error.response?.data?.error || 'Fehler beim Erstellen der Aktivität';
|
||||
const msg = getSafeErrorMessage(error, 'Fehler beim Erstellen der Aktivität');
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
},
|
||||
@@ -2333,7 +2341,7 @@ export default {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Mitglieds:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Erstellen des Mitglieds: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Erstellen des Mitglieds', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -65,25 +65,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -104,7 +98,8 @@ export default {
|
||||
await this.login({ token: response.data.token, username: this.email });
|
||||
this.$router.push('/');
|
||||
} catch (error) {
|
||||
await this.showInfo('Fehler', 'Login fehlgeschlagen. Bitte Zugangsdaten prüfen und erneut versuchen.', '', 'error');
|
||||
const message = safeErrorMessage(error, 'Login fehlgeschlagen. Bitte Zugangsdaten prüfen und erneut versuchen.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -310,6 +310,7 @@ import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
|
||||
|
||||
export default {
|
||||
name: 'MemberTransferSettingsView',
|
||||
@@ -442,7 +443,6 @@ address={{address}}`;
|
||||
methods: {
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
...this.infoDialog,
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
@@ -454,16 +454,13 @@ address={{address}}`;
|
||||
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
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -513,7 +510,7 @@ address={{address}}`;
|
||||
// Keine Konfiguration vorhanden - das ist OK
|
||||
} else {
|
||||
console.error('Fehler beim Laden der Konfiguration:', error);
|
||||
await this.showInfo('Fehler', 'Fehler beim Laden der Konfiguration: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
await this.showInfo('Fehler', 'Fehler beim Laden der Konfiguration', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -572,16 +569,18 @@ address={{address}}`;
|
||||
|
||||
if (response.data.success) {
|
||||
this.configId = response.data.config?.id || null;
|
||||
await this.showInfo('Erfolg', 'Konfiguration erfolgreich gespeichert!', '', 'success');
|
||||
const successMessage = getSafeMessage(response.data?.message, 'Konfiguration erfolgreich gespeichert!');
|
||||
await this.showInfo('Erfolg', successMessage, '', 'success');
|
||||
|
||||
// Passwort-Feld leeren nach erfolgreichem Speichern
|
||||
this.loginCredentials.password = '';
|
||||
} else {
|
||||
await this.showInfo('Fehler', 'Fehler beim Speichern: ' + (response.data.error || 'Unbekannter Fehler'), '', 'error');
|
||||
const errorMessage = getSafeMessage(response.data?.error, 'Fehler beim Speichern');
|
||||
await this.showInfo('Fehler', errorMessage, '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern:', error);
|
||||
await this.showInfo('Fehler', 'Fehler beim Speichern: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
await this.showInfo('Fehler', 'Fehler beim Speichern', getSafeErrorMessage(error), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -622,13 +621,13 @@ address={{address}}`;
|
||||
additionalField1: '',
|
||||
additionalField2: ''
|
||||
};
|
||||
await this.showInfo('Erfolg', 'Konfiguration erfolgreich gelöscht!', '', 'success');
|
||||
await this.showInfo('Erfolg', getSafeMessage(response.data?.message, 'Konfiguration erfolgreich gelöscht!'), '', 'success');
|
||||
} else {
|
||||
await this.showInfo('Fehler', 'Fehler beim Löschen: ' + (response.data.error || 'Unbekannter Fehler'), '', 'error');
|
||||
await this.showInfo('Fehler', getSafeMessage(response.data?.error, 'Fehler beim Löschen'), '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen:', error);
|
||||
await this.showInfo('Fehler', 'Fehler beim Löschen: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
await this.showInfo('Fehler', 'Fehler beim Löschen', getSafeErrorMessage(error), 'error');
|
||||
} finally {
|
||||
this.deleting = false;
|
||||
}
|
||||
@@ -764,7 +763,7 @@ address={{address}}`;
|
||||
// Import-Bereich leeren
|
||||
this.importTemplate = '';
|
||||
|
||||
await this.showInfo('Erfolg', 'Template erfolgreich importiert! Mitglied- und Bulk-Wrapper-Template wurden erkannt und ausgefüllt.', '', 'success');
|
||||
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];
|
||||
@@ -775,7 +774,7 @@ address={{address}}`;
|
||||
this.config.bulkWrapperTemplate = '{"members": "{{members}}"}';
|
||||
|
||||
this.importTemplate = '';
|
||||
await this.showInfo('Erfolg', 'Template erfolgreich importiert! Mitglied-Template erkannt, Bulk-Modus aktiviert.', '', 'success');
|
||||
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);
|
||||
@@ -783,13 +782,13 @@ address={{address}}`;
|
||||
this.config.useBulkMode = false;
|
||||
|
||||
this.importTemplate = '';
|
||||
await this.showInfo('Erfolg', 'Template erfolgreich importiert! Einzelnes Mitglied-Template erkannt.', '', 'success');
|
||||
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);
|
||||
await this.showInfo('Fehler', 'Fehler beim Parsen des Templates: ' + error.message + '\n\nBitte stellen Sie sicher, dass gültiges JSON verwendet wird.', '', 'error');
|
||||
const detail = `${getSafeMessage(error.message, 'Unbekannter Fehler')}\n\nBitte stellen Sie sicher, dass gültiges JSON verwendet wird.`;
|
||||
await this.showInfo('Fehler', 'Fehler beim Parsen des Templates', detail, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
@@ -290,6 +291,8 @@ import BaseDialog from '../components/BaseDialog.vue';
|
||||
import MemberNotesDialog from '../components/MemberNotesDialog.vue';
|
||||
import MemberActivitiesDialog from '../components/MemberActivitiesDialog.vue';
|
||||
import MemberTransferDialog from '../components/MemberTransferDialog.vue';
|
||||
import { debounce } from '../utils/debounce.js';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage, sanitizeText } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
name: 'MembersView',
|
||||
components: {
|
||||
@@ -408,25 +411,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -523,13 +520,13 @@ export default {
|
||||
if (response.data.success) {
|
||||
member.testMembership = false;
|
||||
member.trainingParticipations = undefined; // Entferne die Anzeige
|
||||
this.showInfo('Erfolg', response.data.message || 'Testmitgliedschaft entfernt', '', 'success');
|
||||
await this.showInfo('Erfolg', sanitizeText(response.data.message, 'Testmitgliedschaft entfernt.', 300), '', 'success');
|
||||
} else {
|
||||
this.showInfo('Fehler', response.data.error || 'Fehler beim Entfernen der Testmitgliedschaft', '', 'error');
|
||||
await this.showInfo('Fehler', sanitizeText(response.data.error, 'Fehler beim Entfernen der Testmitgliedschaft.', 300), '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Entfernen der Testmitgliedschaft:', error);
|
||||
const errorMessage = error.response?.data?.error || error.message || 'Fehler beim Entfernen der Testmitgliedschaft';
|
||||
const errorMessage = getSafeErrorMessage(error, 'Fehler beim Entfernen der Testmitgliedschaft');
|
||||
this.showInfo('Fehler', errorMessage, '', 'error');
|
||||
}
|
||||
},
|
||||
@@ -539,13 +536,13 @@ export default {
|
||||
const response = await apiClient.post(`/clubmembers/quick-update-member-form/${this.currentClub}/${member.id}`);
|
||||
if (response.data.success) {
|
||||
member.memberFormHandedOver = true;
|
||||
this.showInfo('Erfolg', response.data.message || 'Mitgliedsformular als ausgehändigt markiert', '', 'success');
|
||||
await this.showInfo('Erfolg', sanitizeText(response.data.message, 'Mitgliedsformular als ausgehändigt markiert.', 300), '', 'success');
|
||||
} else {
|
||||
this.showInfo('Fehler', response.data.error || 'Fehler beim Markieren des Formulars', '', 'error');
|
||||
await this.showInfo('Fehler', sanitizeText(response.data.error, 'Fehler beim Markieren des Formulars.', 300), '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Markieren des Formulars:', error);
|
||||
const errorMessage = error.response?.data?.error || error.message || 'Fehler beim Markieren des Formulars';
|
||||
const errorMessage = getSafeErrorMessage(error, 'Fehler beim Markieren des Formulars');
|
||||
this.showInfo('Fehler', errorMessage, '', 'error');
|
||||
}
|
||||
},
|
||||
@@ -565,13 +562,13 @@ export default {
|
||||
const response = await apiClient.post(`/clubmembers/quick-deactivate/${this.currentClub}/${member.id}`);
|
||||
if (response.data.success) {
|
||||
member.active = false;
|
||||
this.showInfo('Erfolg', response.data.message || 'Mitglied deaktiviert', '', 'success');
|
||||
this.showInfo('Erfolg', getSafeMessage(response.data.message, 'Mitglied deaktiviert'), '', 'success');
|
||||
} else {
|
||||
this.showInfo('Fehler', response.data.error || 'Fehler beim Deaktivieren des Mitglieds', '', 'error');
|
||||
this.showInfo('Fehler', getSafeMessage(response.data.error, 'Fehler beim Deaktivieren des Mitglieds'), '', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Deaktivieren des Mitglieds:', error);
|
||||
const errorMessage = error.response?.data?.error || error.message || 'Fehler beim Deaktivieren des Mitglieds';
|
||||
const errorMessage = getSafeErrorMessage(error, 'Fehler beim Deaktivieren des Mitglieds');
|
||||
this.showInfo('Fehler', errorMessage, '', 'error');
|
||||
}
|
||||
},
|
||||
@@ -629,14 +626,28 @@ export default {
|
||||
},
|
||||
onFileSelected(event) {
|
||||
const file = event.target.files[0];
|
||||
this.memberImage = file;
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.memberImagePreview = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
if (!file) {
|
||||
this.memberImage = null;
|
||||
this.memberImagePreview = null;
|
||||
return;
|
||||
}
|
||||
const maxSize = 5 * 1024 * 1024; // 5 MB
|
||||
if (!file.type.startsWith('image/')) {
|
||||
this.showInfo('Ungültige Datei', 'Bitte wähle eine Bilddatei aus.', '', 'warning');
|
||||
event.target.value = '';
|
||||
return;
|
||||
}
|
||||
if (file.size > maxSize) {
|
||||
this.showInfo('Datei zu groß', 'Das Bild darf maximal 5 MB groß sein.', '', 'warning');
|
||||
event.target.value = '';
|
||||
return;
|
||||
}
|
||||
this.memberImage = file;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (readerEvent) => {
|
||||
this.memberImagePreview = readerEvent.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
standardizePhoneNumber(phoneNumber) {
|
||||
if (!phoneNumber || typeof phoneNumber !== 'string') {
|
||||
@@ -976,11 +987,11 @@ export default {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.showInfo('Fehler', 'Fehler beim Drehen des Bildes: ' + response.data.error, '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Drehen des Bildes', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Drehen des Bildes:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Drehen des Bildes: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Drehen des Bildes', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
async loadMemberImage(member) {
|
||||
@@ -1130,7 +1141,7 @@ export default {
|
||||
await this.loadMembers();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Ratings:', error);
|
||||
const message = error.response?.data?.error || error.response?.data?.message || 'Fehler beim Aktualisieren der TTR/QTTR-Werte';
|
||||
const message = getSafeErrorMessage(error, 'Fehler beim Aktualisieren der TTR/QTTR-Werte');
|
||||
this.showInfo('Fehler', message, '', 'error');
|
||||
} finally {
|
||||
this.isUpdatingRatings = false;
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage } from '../utils/errorMessages.js';
|
||||
import MyTischtennisDialog from '../components/MyTischtennisDialog.vue';
|
||||
import MyTischtennisHistoryDialog from '../components/MyTischtennisHistoryDialog.vue';
|
||||
|
||||
@@ -294,7 +295,7 @@ export default {
|
||||
});
|
||||
await this.loadAccount(); // Aktualisiere Account-Daten inkl. clubId, fedNickname
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.message || error.response?.data?.error || error.message || 'Login fehlgeschlagen';
|
||||
const message = getSafeErrorMessage(error, 'Login fehlgeschlagen');
|
||||
|
||||
if (error.response?.status === 400 && message.includes('Kein Passwort gespeichert')) {
|
||||
// Passwort-Dialog öffnen
|
||||
|
||||
@@ -423,12 +423,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
import BaseDialog from '../components/BaseDialog.vue';
|
||||
import MemberSelectionDialog from '../components/MemberSelectionDialog.vue';
|
||||
export default {
|
||||
@@ -601,25 +603,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -859,8 +855,29 @@ export default {
|
||||
const m = (this.members || []).find(x => String(x.id) === String(id));
|
||||
return m ? `${m.firstName} ${m.lastName}` : `#${id}`;
|
||||
},
|
||||
onFile(e) {
|
||||
this.selectedFile = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
async onFile(e) {
|
||||
const file = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
if (!file) {
|
||||
this.selectedFile = null;
|
||||
return;
|
||||
}
|
||||
const maxSize = 10 * 1024 * 1024; // 10 MB
|
||||
const extension = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
|
||||
const isPdf = extension === 'pdf';
|
||||
const allowedMime = ['application/pdf', 'application/octet-stream'];
|
||||
if (!isPdf || (file.type && !allowedMime.includes(file.type))) {
|
||||
this.selectedFile = null;
|
||||
e.target.value = '';
|
||||
await this.showInfo('Ungültige Datei', 'Bitte wählen Sie eine PDF-Datei aus.', '', 'warning');
|
||||
return;
|
||||
}
|
||||
if (file.size > maxSize) {
|
||||
this.selectedFile = null;
|
||||
e.target.value = '';
|
||||
await this.showInfo('Datei zu groß', 'Die PDF darf maximal 10 MB groß sein.', '', 'warning');
|
||||
return;
|
||||
}
|
||||
this.selectedFile = file;
|
||||
},
|
||||
isExpanded(c, idx) {
|
||||
return !!this.expanded[String(idx)];
|
||||
@@ -873,12 +890,18 @@ export default {
|
||||
if (!this.selectedFile) return;
|
||||
const fd = new FormData();
|
||||
fd.append('pdf', this.selectedFile);
|
||||
const r = await apiClient.post(`/official-tournaments/${this.currentClub}/upload`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
this.uploadedId = r.data.id;
|
||||
await this.reload();
|
||||
await this.loadList();
|
||||
try {
|
||||
const response = await apiClient.post(`/official-tournaments/${this.currentClub}/upload`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
this.uploadedId = response.data.id;
|
||||
await this.reload();
|
||||
await this.loadList();
|
||||
await this.showInfo('Erfolg', 'PDF erfolgreich hochgeladen.', '', 'success');
|
||||
} catch (error) {
|
||||
const message = safeErrorMessage(error, 'Fehler beim Hochladen der PDF.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
async reload() {
|
||||
if (!this.uploadedId) return;
|
||||
@@ -971,7 +994,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Status:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Status', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
async updatePlacement(item, value) {
|
||||
@@ -982,7 +1005,7 @@ export default {
|
||||
await this.saveParticipation(competitionId, memberId);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Platzierung:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren der Platzierung: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren der Platzierung', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
async updateStatusForCompetition(competition, member, action) {
|
||||
@@ -1007,7 +1030,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Status:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Status', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
},
|
||||
// Auswahl Helfer + PDF-Generierung
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage } from '../utils/errorMessages.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
@@ -128,7 +129,7 @@ export default {
|
||||
await this.showInfo('Keine Berechtigung', 'Sie haben keine Berechtigung, Freigaben zu verwalten.', 'Nur Administratoren können Mitgliedsanfragen bearbeiten.', 'error');
|
||||
this.$router.push('/');
|
||||
} else {
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der ausstehenden Anfragen', error.response?.data?.error || '', 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der ausstehenden Anfragen', getSafeErrorMessage(error), 'error');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -143,6 +143,9 @@ import CourtDrawingDialog from '../components/CourtDrawingDialog.vue';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { debounce } from '../utils/debounce.js';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
name: 'PredefinedActivities',
|
||||
components: {
|
||||
@@ -206,25 +209,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -379,8 +376,26 @@ export default {
|
||||
|
||||
await this.reload();
|
||||
},
|
||||
onFileChange(e) {
|
||||
this.selectedFile = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
async onFileChange(e) {
|
||||
const file = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
if (!file) {
|
||||
this.selectedFile = null;
|
||||
return;
|
||||
}
|
||||
const maxSize = 5 * 1024 * 1024; // 5 MB
|
||||
if (!file.type.startsWith('image/')) {
|
||||
await this.showInfo('Ungültige Datei', 'Bitte wählen Sie eine Bilddatei aus.', '', 'warning');
|
||||
this.selectedFile = null;
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
if (file.size > maxSize) {
|
||||
await this.showInfo('Datei zu groß', 'Das Bild darf maximal 5 MB groß sein.', '', 'warning');
|
||||
this.selectedFile = null;
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
this.selectedFile = file;
|
||||
},
|
||||
imageUrl(img) {
|
||||
return `/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
@@ -405,7 +420,7 @@ export default {
|
||||
|
||||
|
||||
try {
|
||||
const response = await apiClient[method](`/predefined-activities/${this.editModel.id}/image`, fd, {
|
||||
await apiClient[method](`/predefined-activities/${this.editModel.id}/image`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
|
||||
@@ -417,6 +432,8 @@ export default {
|
||||
await this.reloadImages();
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error);
|
||||
const message = safeErrorMessage(error, 'Bild-Upload fehlgeschlagen.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -65,25 +66,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -100,7 +95,8 @@ export default {
|
||||
await axios.post(`${import.meta.env.VITE_BACKEND || 'http://localhost:3005'}/api/auth/register`, { email: this.email, password: this.password });
|
||||
await this.showInfo('Erfolg', 'Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mails, um den Account zu aktivieren.', '', 'success');
|
||||
} catch (error) {
|
||||
await this.showInfo('Fehler', 'Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.', '', 'error');
|
||||
const message = safeErrorMessage(error, 'Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -69,8 +69,12 @@
|
||||
style="cursor: pointer;">
|
||||
<td>{{ formatDate(match.date) }}</td>
|
||||
<td>{{ match.time ? match.time.toString().slice(0, 5) + ' Uhr' : 'N/A' }}</td>
|
||||
<td v-html="highlightClubName(match.homeTeam?.name || 'N/A')"></td>
|
||||
<td v-html="highlightClubName(match.guestTeam?.name || 'N/A')"></td>
|
||||
<td :class="{ 'highlighted-club': isClubHighlighted(match.homeTeam?.name) }">
|
||||
{{ match.homeTeam?.name || 'N/A' }}
|
||||
</td>
|
||||
<td :class="{ 'highlighted-club': isClubHighlighted(match.guestTeam?.name) }">
|
||||
{{ match.guestTeam?.name || 'N/A' }}
|
||||
</td>
|
||||
<td class="result-cell" :class="getResultClass(match)">
|
||||
<span v-if="match.isCompleted" class="result-score">
|
||||
{{ match.homeMatchPoints }}:{{ match.guestMatchPoints }}
|
||||
@@ -241,6 +245,7 @@
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
import { getSafeErrorMessage } from '../utils/errorMessages.js';
|
||||
import SeasonSelector from '../components/SeasonSelector.vue';
|
||||
import MatchReportDialog from '../components/MatchReportDialog.vue';
|
||||
|
||||
@@ -393,7 +398,7 @@ export default {
|
||||
} catch (error) {
|
||||
console.error('Error loading members:', error);
|
||||
console.error('Error details:', error.response?.data);
|
||||
await this.showInfo('Fehler', 'Laden der Mitgliederliste fehlgeschlagen.', error.response?.data?.error || error.message, 'error');
|
||||
await this.showInfo('Fehler', 'Laden der Mitgliederliste fehlgeschlagen.', getSafeErrorMessage(error), 'error');
|
||||
} finally {
|
||||
this.playerSelectionDialog.loading = false;
|
||||
}
|
||||
@@ -612,13 +617,10 @@ export default {
|
||||
const day = d.toLocaleDateString('de-DE', options);
|
||||
return `${wd} ${day}`;
|
||||
},
|
||||
highlightClubName(teamName) {
|
||||
if (!teamName) return 'N/A';
|
||||
isClubHighlighted(teamName) {
|
||||
if (!teamName) return false;
|
||||
const clubName = this.currentClubName;
|
||||
if (clubName && teamName.includes(clubName)) {
|
||||
return `<strong>${teamName}</strong>`;
|
||||
}
|
||||
return teamName;
|
||||
return Boolean(clubName && teamName.includes(clubName));
|
||||
},
|
||||
getCurrentClubName() {
|
||||
const clubIdNum = Number(this.currentClub);
|
||||
@@ -795,7 +797,7 @@ export default {
|
||||
this.showInfo('Erfolg', 'Tabellendaten erfolgreich von MyTischtennis geladen!', '', 'success');
|
||||
} catch (error) {
|
||||
console.error('ScheduleView: Error fetching table from MyTischtennis:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der Tabellendaten von MyTischtennis', error.response?.data?.error || error.message, 'error');
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der Tabellendaten von MyTischtennis', getSafeErrorMessage(error), 'error');
|
||||
} finally {
|
||||
this.fetchingTable = false;
|
||||
}
|
||||
@@ -850,6 +852,11 @@ td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.highlighted-club {
|
||||
font-weight: 600;
|
||||
color: #2b7cff;
|
||||
}
|
||||
|
||||
.result-cell {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -342,9 +342,12 @@ import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import SeasonSelector from '../components/SeasonSelector.vue';
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
|
||||
export default {
|
||||
name: 'TeamManagementView',
|
||||
components: {
|
||||
@@ -361,7 +364,9 @@ export default {
|
||||
title: '',
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info'
|
||||
type: 'info',
|
||||
size: 'small',
|
||||
closeOnOverlay: true,
|
||||
});
|
||||
const confirmDialog = ref({
|
||||
isOpen: false,
|
||||
@@ -369,6 +374,9 @@ export default {
|
||||
message: '',
|
||||
details: '',
|
||||
type: 'info',
|
||||
confirmText: 'OK',
|
||||
cancelText: 'Abbrechen',
|
||||
showCancel: true,
|
||||
resolveCallback: null
|
||||
});
|
||||
|
||||
@@ -655,6 +663,12 @@ export default {
|
||||
parsingInProgress.value = true;
|
||||
|
||||
try {
|
||||
const documentLabel = documentType === 'code_list' ? 'Code-Liste' : 'Pin-Liste';
|
||||
const isValid = await validateTeamDocumentFile(file, documentLabel);
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('document', file);
|
||||
formData.append('documentType', documentType);
|
||||
@@ -666,7 +680,6 @@ export default {
|
||||
});
|
||||
|
||||
const fileExtension = file.name.toLowerCase().split('.').pop();
|
||||
const documentLabel = documentType === 'code_list' ? 'Code-Liste' : 'Pin-Liste';
|
||||
|
||||
if (fileExtension === 'pdf' || fileExtension === 'txt') {
|
||||
const parseResponse = await apiClient.post(`/team-documents/${uploadResponse.data.id}/parse?leagueid=${teamToEdit.value.leagueId}`);
|
||||
@@ -707,14 +720,14 @@ export default {
|
||||
await loadTeamDocuments();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hochladen und Parsen der Datei:', error);
|
||||
const responseData = error?.response?.data || {};
|
||||
const errorMessage = responseData.message || responseData.error || error.message || 'Fehler beim Hochladen und Parsen der Datei';
|
||||
await showInfo('Fehler', errorMessage, '', 'error');
|
||||
const message = safeErrorMessage(error, 'Fehler beim Hochladen und Parsen der Datei.');
|
||||
await showInfo('Fehler', message, '', 'error');
|
||||
} finally {
|
||||
parsingInProgress.value = false;
|
||||
}
|
||||
};
|
||||
const parsePDF = async (document) => {
|
||||
|
||||
const parsePDF = async (document) => {
|
||||
const team = teams.value.find(t => t.id === document.clubTeamId);
|
||||
if (!team || !team.leagueId) {
|
||||
await showInfo(
|
||||
@@ -768,14 +781,14 @@ const parsePDF = async (document) => {
|
||||
console.error('Fehler beim Parsen der PDF:', error);
|
||||
const responseData = error?.response?.data || {};
|
||||
const status = error?.response?.status;
|
||||
let errorMessage = responseData.message || responseData.error || error.message || 'Fehler beim Parsen der PDF-Datei';
|
||||
let errorMessage = getSafeErrorMessage(error, 'Fehler beim Parsen der PDF-Datei');
|
||||
let details = '';
|
||||
|
||||
if (status === 404 && responseData.error === 'documentnotfound') {
|
||||
errorMessage = 'Das ausgewählte Dokument wurde nicht gefunden.';
|
||||
} else if (status === 400 && responseData.error === 'missingleagueid') {
|
||||
errorMessage = 'Für das ausgewählte Team wurde keine Liga übermittelt.';
|
||||
} else if (error.code === 'ENOENT' || errorMessage.includes('ENOENT')) {
|
||||
} else if (error.code === 'ENOENT' || (typeof error?.message === 'string' && error.message.includes('ENOENT'))) {
|
||||
errorMessage = 'Die PDF-Datei konnte nicht gefunden werden.';
|
||||
details = 'Bitte laden Sie die Datei erneut hoch und versuchen Sie es noch einmal.';
|
||||
}
|
||||
@@ -885,25 +898,19 @@ const parsePDF = async (document) => {
|
||||
|
||||
// Dialog Helper Methods
|
||||
const showInfo = async (title, message, details = '', type = 'info') => {
|
||||
infoDialog.value = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
infoDialog.value = buildInfoConfig({ title, message, details, type });
|
||||
};
|
||||
|
||||
const showConfirm = async (title, message, details = '', type = 'info') => {
|
||||
const showConfirm = async (title, message, details = '', type = 'info', options = {}) => {
|
||||
return new Promise((resolve) => {
|
||||
confirmDialog.value = {
|
||||
isOpen: true,
|
||||
confirmDialog.value = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -939,7 +946,7 @@ const parsePDF = async (document) => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen der URL:', error);
|
||||
myTischtennisError.value = error.response?.data?.message || 'URL konnte nicht geparst werden. Bitte überprüfen Sie das Format.';
|
||||
myTischtennisError.value = getSafeErrorMessage(error, 'URL konnte nicht geparst werden. Bitte überprüfen Sie das Format.');
|
||||
await showInfo('Fehler', myTischtennisError.value, '', 'error');
|
||||
} finally {
|
||||
parsingUrl.value = false;
|
||||
@@ -1017,7 +1024,7 @@ const parsePDF = async (document) => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Konfiguration:', error);
|
||||
myTischtennisError.value = error.response?.data?.message || 'Team konnte nicht konfiguriert werden.';
|
||||
myTischtennisError.value = getSafeErrorMessage(error, 'Team konnte nicht konfiguriert werden.');
|
||||
await showInfo('Fehler', myTischtennisError.value, '', 'error');
|
||||
} finally {
|
||||
configuringTeam.value = false;
|
||||
@@ -1057,7 +1064,7 @@ const parsePDF = async (document) => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Liga-Konfiguration:', error);
|
||||
myTischtennisError.value = error.response?.data?.message || 'Liga konnte nicht konfiguriert werden.';
|
||||
myTischtennisError.value = getSafeErrorMessage(error, 'Liga konnte nicht konfiguriert werden.');
|
||||
await showInfo('Fehler', myTischtennisError.value, '', 'error');
|
||||
} finally {
|
||||
configuringTeam.value = false;
|
||||
@@ -1161,57 +1168,46 @@ const parsePDF = async (document) => {
|
||||
}, { timeout: 30000 });
|
||||
|
||||
if (response.data && response.data.success) {
|
||||
myTischtennisSuccess.value = response.data.message;
|
||||
const successMessage = getSafeMessage(response.data.message, 'Teamdaten erfolgreich abgerufen.');
|
||||
myTischtennisSuccess.value = successMessage;
|
||||
|
||||
// Erstelle detaillierte Erfolgsmeldung mit Tabelleninfo
|
||||
let detailsMessage = `Team: ${response.data.data.teamName}\nAbgerufene Datensätze: ${response.data.data.fetchedCount}`;
|
||||
const teamName = getSafeMessage(response.data.data?.teamName, teamToEdit.value?.name || 'Unbekanntes Team');
|
||||
const fetchedCount = getSafeMessage(String(response.data.data?.fetchedCount ?? ''), '0');
|
||||
let detailsMessage = `Team: ${teamName}\nAbgerufene Datensätze: ${fetchedCount}`;
|
||||
|
||||
if (response.data.data.tableUpdate) {
|
||||
detailsMessage += `\n\nTabellenaktualisierung:\n${response.data.data.tableUpdate}`;
|
||||
if (response.data.data?.tableUpdate) {
|
||||
const tableUpdate = getSafeMessage(response.data.data.tableUpdate);
|
||||
if (tableUpdate) {
|
||||
detailsMessage += `\n\nTabellenaktualisierung:\n${tableUpdate}`;
|
||||
}
|
||||
}
|
||||
|
||||
await showInfo(
|
||||
'Erfolg',
|
||||
response.data.message,
|
||||
successMessage,
|
||||
detailsMessage,
|
||||
'success'
|
||||
);
|
||||
} else if (response.data && response.data.success === false) {
|
||||
const details = response.data.debug ? JSON.stringify(response.data.debug, null, 2) : '';
|
||||
const errorTitle = response.data.needsMyTischtennisReauth ? 'Login bei myTischtennis erforderlich' : 'Fehler';
|
||||
const errorMessage = getSafeMessage(response.data.error, 'Daten konnten nicht abgerufen werden.');
|
||||
const details = response.data.debug ? getSafeMessage(JSON.stringify(response.data.debug, null, 2)) : '';
|
||||
await showInfo(
|
||||
response.data.needsMyTischtennisReauth ? 'Login bei myTischtennis erforderlich' : 'Fehler',
|
||||
response.data.error || 'Daten konnten nicht abgerufen werden.',
|
||||
errorTitle,
|
||||
errorMessage,
|
||||
details,
|
||||
response.data.needsMyTischtennisReauth ? 'warning' : 'error'
|
||||
);
|
||||
myTischtennisError.value = response.data.error || '';
|
||||
myTischtennisError.value = errorMessage;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Team-Daten:', error);
|
||||
const isTimeout = error?.code === 'ECONNABORTED';
|
||||
const errData = error?.response?.data || {};
|
||||
const errorMsg = isTimeout ? 'Zeitüberschreitung beim Abruf (Timeout).' : (errData.message || errData.error || error.message || 'Daten konnten nicht abgerufen werden.');
|
||||
const errorMsg = isTimeout ? 'Zeitüberschreitung beim Abruf (Timeout).' : getSafeMessage(errData.message || errData.error, 'Daten konnten nicht abgerufen werden.');
|
||||
const details = errData.debug ? getSafeMessage(JSON.stringify(errData.debug, null, 2)) : '';
|
||||
myTischtennisError.value = errorMsg;
|
||||
|
||||
// Spezielle Behandlung für Account-nicht-verknüpft Fehler
|
||||
if (!isTimeout && error.response?.status === 404) {
|
||||
await showInfo(
|
||||
'MyTischtennis-Account nicht verknüpft',
|
||||
errorMsg,
|
||||
'Gehen Sie zu den MyTischtennis-Einstellungen, um Ihren Account zu verknüpfen.',
|
||||
'warning'
|
||||
);
|
||||
} else if (!isTimeout && error.response?.status === 401) {
|
||||
await showInfo(
|
||||
'Login erforderlich',
|
||||
errorMsg,
|
||||
'Aktivieren Sie "Passwort speichern" in den MyTischtennis-Einstellungen für automatischen Login.',
|
||||
'warning'
|
||||
);
|
||||
} else {
|
||||
const debugText = isTimeout ? 'Der Server hat nicht rechtzeitig geantwortet.' : (errData.debug ? JSON.stringify(errData.debug, null, 2) : '');
|
||||
await showInfo('Fehler', errorMsg, debugText, 'error');
|
||||
}
|
||||
await showInfo('Fehler', errorMsg, details, isTimeout ? 'warning' : 'error');
|
||||
} finally {
|
||||
fetchingTeamData.value = false;
|
||||
}
|
||||
@@ -1226,6 +1222,40 @@ const parsePDF = async (document) => {
|
||||
}
|
||||
});
|
||||
|
||||
const validateTeamDocumentFile = async (file, label) => {
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
const maxSize = 10 * 1024 * 1024; // 10 MB, entspricht Backend-Limit
|
||||
const allowedExtensions = ['pdf', 'doc', 'docx', 'txt', 'csv'];
|
||||
const allowedMimeTypes = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain',
|
||||
'text/csv',
|
||||
'application/octet-stream'
|
||||
];
|
||||
|
||||
const extension = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
|
||||
if (!allowedExtensions.includes(extension)) {
|
||||
await showInfo('Ungültiger Dateityp', `${label} muss eine der folgenden Endungen haben: ${allowedExtensions.join(', ')}.`, '', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.type && !allowedMimeTypes.includes(file.type)) {
|
||||
await showInfo('Ungültiger Dateityp', `${label} weist einen unerwarteten MIME-Typ auf: ${file.type}.`, '', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.size > maxSize) {
|
||||
await showInfo('Datei zu groß', `${label} darf maximal 10 MB groß sein.`, '', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return {
|
||||
infoDialog,
|
||||
confirmDialog,
|
||||
|
||||
@@ -671,9 +671,20 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient';
|
||||
|
||||
import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import CsvImportDialog from '../components/CsvImportDialog.vue';
|
||||
import MemberSelectionDialog from '../components/MemberSelectionDialog.vue';
|
||||
import TournamentGroupEditor from '../components/TournamentGroupEditor.vue';
|
||||
import TournamentKoEditor from '../components/TournamentKoEditor.vue';
|
||||
import TournamentSeedingDialog from '../components/TournamentSeedingDialog.vue';
|
||||
import TournamentParticipantMatrix from '../components/TournamentParticipantMatrix.vue';
|
||||
import TournamentMatchMatrix from '../components/TournamentMatchMatrix.vue';
|
||||
import TournamentKoBracket from '../components/TournamentKoBracket.vue';
|
||||
import TournamentExportDialog from '../components/TournamentExportDialog.vue';
|
||||
import TournamentParticipantStats from '../components/TournamentParticipantStats.vue';
|
||||
import TournamentImportHistory from '../components/TournamentImportHistory.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
name: 'TournamentsView',
|
||||
data() {
|
||||
@@ -905,25 +916,19 @@ export default {
|
||||
methods: {
|
||||
// Dialog Helper Methods
|
||||
async showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = {
|
||||
isOpen: true,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type
|
||||
};
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
|
||||
async showConfirm(title, message, details = '', type = 'info') {
|
||||
async showConfirm(title, message, details = '', type = 'info', options = {}) {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmDialog = {
|
||||
isOpen: true,
|
||||
this.confirmDialog = buildConfirmConfig({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
type,
|
||||
resolveCallback: resolve
|
||||
};
|
||||
resolveCallback: resolve,
|
||||
...options,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1074,7 +1079,8 @@ export default {
|
||||
this.newDate = '';
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Turniers:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Erstellen des Turniers: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
const message = safeErrorMessage(error, 'Fehler beim Erstellen des Turniers.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1129,7 +1135,8 @@ export default {
|
||||
});
|
||||
this.participants = r.data;
|
||||
} catch (err) {
|
||||
this.showInfo('Fehler', 'Fehler beim Zufällig‑Verteilen:\n' + (err.response?.data?.error || err.message), '', 'error');
|
||||
const message = safeErrorMessage(err, 'Fehler beim Zufällig-Verteilen.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
await this.loadTournamentData();
|
||||
},
|
||||
@@ -1153,7 +1160,8 @@ export default {
|
||||
);
|
||||
const updated = allRes.data.find(m2 => m2.id === match.id);
|
||||
if (!updated) {
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren des Matches', '', 'error');
|
||||
const message = safeErrorMessage(error, 'Fehler beim Aktualisieren des Matches.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
return;
|
||||
}
|
||||
match.tournamentResults = updated.tournamentResults || [];
|
||||
@@ -1364,7 +1372,8 @@ export default {
|
||||
});
|
||||
await this.loadTournamentData();
|
||||
} catch (err) {
|
||||
this.showInfo('Fehler', 'Fehler beim Zurücksetzen der K.o.-Runde', '', 'error');
|
||||
const message = safeErrorMessage(err, 'Fehler beim Zurücksetzen der K.o.-Runde.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1592,7 +1601,8 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Trainingsteilnehmer:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Laden der Trainingsteilnehmer: ' + (error.response?.data?.error || error.message), '', 'error');
|
||||
const message = safeErrorMessage(error, 'Fehler beim Laden der Trainingsteilnehmer.');
|
||||
await this.showInfo('Fehler', message, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user