Add member transfer configuration and UI enhancements

Introduced MemberTransferConfig model and integrated it into the backend, allowing for the storage and retrieval of member transfer settings. Updated server routes to include member transfer configuration endpoints. Enhanced the frontend with a new MemberTransferDialog component for user interaction, added a dedicated route for member transfer settings, and updated the App.vue to include a link for accessing these settings. Improved the loading state and configuration handling in the dialog for better user experience.
This commit is contained in:
Torsten Schulz (local)
2025-11-05 15:30:12 +01:00
parent 5bba9522b3
commit 1f47a11091
11 changed files with 1826 additions and 28 deletions

View File

@@ -22,6 +22,10 @@
<span class="dropdown-icon">🔐</span>
Berechtigungen
</router-link>
<router-link v-if="hasPermission('members', 'write')" to="/member-transfer-settings" class="dropdown-item" @click="userDropdownOpen = false">
<span class="dropdown-icon">📤</span>
Mitgliederübertragung
</router-link>
<router-link v-if="isAdmin" to="/logs" class="dropdown-item" @click="userDropdownOpen = false">
<span class="dropdown-icon">📋</span>
System-Logs

View File

@@ -7,20 +7,41 @@
:close-on-overlay="false"
@close="handleClose"
>
<div class="transfer-form">
<div v-if="loadingConfig" class="loading-config">
Gespeicherte Konfiguration wird geladen...
</div>
<div v-else class="transfer-form">
<div class="form-section">
<h4>Server-Konfiguration</h4>
<div class="form-group">
<label for="server">Server-Basis-URL:</label>
<input
type="text"
id="server"
v-model="config.server"
placeholder="https://example.com"
class="form-input"
readonly
/>
<span class="hint">Aus Einstellungen geladen (nur lesend)</span>
</div>
</div>
<div class="form-section">
<h4>Login-Konfiguration</h4>
<div class="form-group">
<label for="loginEndpoint">Login-Endpoint URL:</label>
<label for="loginEndpoint">Login-Endpoint Pfad:</label>
<input
type="text"
id="loginEndpoint"
v-model="config.loginEndpoint"
placeholder="https://example.com/api/login"
placeholder="/api/auth/login"
class="form-input"
/>
<span class="hint">Optional: Falls für die Übertragung ein Login erforderlich ist</span>
<span class="hint">Optional: Relativer Pfad zum Login-Endpoint</span>
</div>
<div class="form-group">
@@ -71,17 +92,18 @@
<div class="form-section">
<h4>Übertragungs-Konfiguration</h4>
<div class="form-group">
<label for="transferEndpoint">Übertragungs-Endpoint URL: <span class="required">*</span></label>
<input
type="text"
id="transferEndpoint"
v-model="config.transferEndpoint"
placeholder="https://example.com/api/members"
class="form-input"
required
/>
</div>
<div class="form-group">
<label for="transferEndpoint">Übertragungs-Endpoint Pfad: <span class="required">*</span></label>
<input
type="text"
id="transferEndpoint"
v-model="config.transferEndpoint"
placeholder="/api/members/bulk"
class="form-input"
required
/>
<span class="hint">Relativer Pfad zum Übertragungs-Endpoint</span>
</div>
<div class="form-row">
<div class="form-group">
@@ -182,7 +204,7 @@ export default {
...mapGetters(['currentClub']),
isValid() {
return !!(this.config.transferEndpoint && this.config.transferTemplate);
return !!(this.config.server && this.config.transferEndpoint && this.config.transferTemplate);
},
additionalField1Placeholder() {
@@ -213,6 +235,7 @@ export default {
data() {
return {
config: {
server: '',
loginEndpoint: '',
loginFormat: 'json',
transferEndpoint: '',
@@ -227,10 +250,57 @@ export default {
additionalField1: '',
additionalField2: ''
},
isTransferring: false
isTransferring: false,
loadingConfig: false
};
},
watch: {
modelValue(newVal) {
if (newVal) {
this.loadSavedConfig();
}
}
},
methods: {
async loadSavedConfig() {
if (!this.currentClub) {
return;
}
this.loadingConfig = true;
try {
const response = await apiClient.get(`/member-transfer-config/${this.currentClub}`);
if (response.data.success && response.data.config) {
const savedConfig = response.data.config;
// Konfiguration aus gespeicherten Daten laden
this.config.server = savedConfig.server || '';
this.config.loginEndpoint = savedConfig.loginEndpoint || '';
this.config.loginFormat = savedConfig.loginFormat || 'json';
this.config.transferEndpoint = savedConfig.transferEndpoint || '';
this.config.transferMethod = savedConfig.transferMethod || 'POST';
this.config.transferFormat = savedConfig.transferFormat || 'json';
this.config.transferTemplate = savedConfig.transferTemplate || '';
this.config.useBulkMode = savedConfig.useBulkMode || false;
// Login-Credentials (Passwort wird nicht zurückgegeben, nur wenn vorhanden)
if (savedConfig.loginCredentials) {
this.loginCredentials.username = savedConfig.loginCredentials.username || '';
this.loginCredentials.password = ''; // Passwort wird nicht zurückgegeben
// Zusätzliche Felder können nicht direkt zugewiesen werden, da sie verschlüsselt sind
// Benutzer muss diese neu eingeben
}
}
} catch (error) {
// Keine Konfiguration vorhanden - das ist OK, Dialog bleibt leer
if (error.response?.status !== 404) {
console.error('Fehler beim Laden der gespeicherten Konfiguration:', error);
}
} finally {
this.loadingConfig = false;
}
},
handleClose() {
this.$emit('update:modelValue', false);
this.resetForm();
@@ -238,6 +308,7 @@ export default {
resetForm() {
this.config = {
server: '',
loginEndpoint: '',
loginFormat: 'json',
transferEndpoint: '',
@@ -295,14 +366,20 @@ export default {
transferEndpoint: this.config.transferEndpoint,
transferMethod: this.config.transferMethod,
transferFormat: this.config.transferFormat,
transferTemplate: this.config.transferTemplate
transferTemplate: this.config.transferTemplate,
useBulkMode: this.config.useBulkMode
};
// Login-Konfiguration nur hinzufügen, wenn Endpoint vorhanden
if (this.config.loginEndpoint) {
transferConfig.loginEndpoint = this.config.loginEndpoint;
transferConfig.loginFormat = this.config.loginFormat;
transferConfig.loginCredentials = loginCredentials;
// Nur Login-Credentials hinzufügen, wenn welche eingegeben wurden
// Wenn keine eingegeben wurden, werden die gespeicherten verwendet (Backend holt diese)
if (Object.keys(loginCredentials).length > 0) {
transferConfig.loginCredentials = loginCredentials;
}
}
const response = await apiClient.post(
@@ -401,6 +478,12 @@ export default {
font-family: inherit;
}
.form-input[readonly] {
background-color: #f8f9fa;
cursor: not-allowed;
color: #6c757d;
}
.form-textarea {
font-family: 'Courier New', monospace;
resize: vertical;
@@ -489,6 +572,12 @@ export default {
border-radius: 3px;
}
.loading-config {
text-align: center;
padding: 2rem;
color: #666;
}
.btn-primary {
background-color: #007bff;
color: white;

View File

@@ -17,6 +17,7 @@ import MyTischtennisAccount from './views/MyTischtennisAccount.vue';
import TeamManagementView from './views/TeamManagementView.vue';
import PermissionsView from './views/PermissionsView.vue';
import LogsView from './views/LogsView.vue';
import MemberTransferSettingsView from './views/MemberTransferSettingsView.vue';
import Impressum from './views/Impressum.vue';
import Datenschutz from './views/Datenschutz.vue';
@@ -39,6 +40,7 @@ const routes = [
{ path: '/team-management', component: TeamManagementView },
{ path: '/permissions', component: PermissionsView },
{ path: '/logs', component: LogsView },
{ path: '/member-transfer-settings', component: MemberTransferSettingsView },
{ path: '/impressum', component: Impressum },
{ path: '/datenschutz', component: Datenschutz },
];

File diff suppressed because it is too large Load Diff