Update server port and enhance participant management features
Changed the server port from 3000 to 3005 for local development. Enhanced the participant management functionality by adding a new endpoint to update participant group assignments, including error handling for non-existent participants. Updated the participant model to include a groupId reference, and modified the participant retrieval logic to include group information. Additionally, improved the frontend API client to accommodate the new backend structure and added filtering options in the MembersView for better user experience.
This commit is contained in:
@@ -2,7 +2,7 @@ import axios from 'axios';
|
||||
import store from './store';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: `${import.meta.env.VITE_BACKEND}/api`,
|
||||
baseURL: `${import.meta.env.VITE_BACKEND || 'http://localhost:3005'}/api`,
|
||||
});
|
||||
|
||||
apiClient.interceptors.request.use(config => {
|
||||
|
||||
@@ -848,6 +848,17 @@ export default {
|
||||
this.participants = response.data.map(participant => participant.memberId);
|
||||
// Map für memberId -> participantId speichern
|
||||
this.participantMapByMemberId = response.data.reduce((map, p) => { map[p.memberId] = p.id; return map; }, {});
|
||||
// Map für memberId -> groupId speichern und mit Reaktivität initialisieren
|
||||
this.memberGroupsMap = {};
|
||||
response.data.forEach(p => {
|
||||
if (p.groupId) {
|
||||
if (this.$set) {
|
||||
this.$set(this.memberGroupsMap, p.memberId, p.groupId);
|
||||
} else {
|
||||
this.memberGroupsMap[p.memberId] = p.groupId;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async loadActivities(dateId) {
|
||||
@@ -1391,12 +1402,19 @@ export default {
|
||||
},
|
||||
async createGroups() {
|
||||
try {
|
||||
// Erstelle die gewünschte Anzahl Gruppen mit Namen 1 bis X
|
||||
for (let i = 1; i <= this.newGroupCount; i++) {
|
||||
// Bestimme Startnummer basierend auf vorhandenen Gruppen
|
||||
const existingNumbers = (this.groups || [])
|
||||
.map(g => parseInt((g.name || '').trim(), 10))
|
||||
.filter(n => Number.isFinite(n));
|
||||
const startNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
|
||||
|
||||
// Erstelle die gewünschte Anzahl Gruppen mit fortlaufender Nummerierung
|
||||
for (let i = 0; i < this.newGroupCount; i++) {
|
||||
const groupNumber = startNumber + i;
|
||||
const form = {
|
||||
clubid: this.currentClub,
|
||||
dateid: this.date.id,
|
||||
name: i.toString(),
|
||||
name: groupNumber.toString(),
|
||||
lead: '', // Leiter wird leer gelassen
|
||||
}
|
||||
await apiClient.post('/group', form);
|
||||
@@ -1933,14 +1951,25 @@ export default {
|
||||
|
||||
async updateMemberGroup(memberId, groupId) {
|
||||
try {
|
||||
// Hier würde normalerweise ein API-Call gemacht werden
|
||||
// Für jetzt speichern wir es nur lokal
|
||||
this.memberGroupsMap[memberId] = groupId || '';
|
||||
const selectedGroupId = groupId || '';
|
||||
|
||||
// TODO: API-Call zum Speichern der Teilnehmer-Gruppenzuordnung
|
||||
// await apiClient.put(`/participants/${this.date.id}/${memberId}/group`, { groupId });
|
||||
// Verwende Vue.set für Reaktivität (Vue 2)
|
||||
if (this.$set) {
|
||||
this.$set(this.memberGroupsMap, memberId, selectedGroupId);
|
||||
} else {
|
||||
// Vue 3 oder Fallback
|
||||
this.memberGroupsMap = {
|
||||
...this.memberGroupsMap,
|
||||
[memberId]: selectedGroupId
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`Teilnehmer ${memberId} wurde Gruppe ${groupId} zugewiesen`);
|
||||
// API-Call zum Speichern der Teilnehmer-Gruppenzuordnung
|
||||
await apiClient.put(`/participants/${this.date.id}/${memberId}/group`, {
|
||||
groupId: selectedGroupId || null
|
||||
});
|
||||
|
||||
console.log(`Teilnehmer ${memberId} wurde Gruppe ${selectedGroupId} zugewiesen`);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Teilnehmer-Gruppenzuordnung:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Aktualisieren der Teilnehmer-Gruppenzuordnung', '', 'error');
|
||||
|
||||
@@ -98,7 +98,7 @@ export default {
|
||||
...mapActions(['login']),
|
||||
async executeLogin() {
|
||||
try {
|
||||
const response = await axios.post(`${import.meta.env.VITE_BACKEND}/api/auth/login`, { email: this.email, password: this.password }, {
|
||||
const response = await axios.post(`${import.meta.env.VITE_BACKEND || 'http://localhost:3005'}/api/auth/login`, { email: this.email, password: this.password }, {
|
||||
timeout: 5000,
|
||||
});
|
||||
await this.login({ token: response.data.token, username: this.email });
|
||||
|
||||
@@ -68,11 +68,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" v-model="showInactiveMembers">
|
||||
<span>Inaktive Mitglieder anzeigen</span>
|
||||
</label>
|
||||
<div class="filters-section">
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" v-model="showInactiveMembers">
|
||||
<span>Inaktive Mitglieder anzeigen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="filter-controls">
|
||||
<div class="filter-group">
|
||||
<label>Altersklasse:</label>
|
||||
<select v-model="selectedAgeGroup" class="filter-select">
|
||||
<option value="">Alle</option>
|
||||
<option value="adult">Erwachsene (20+)</option>
|
||||
<option value="J19">J19 (19 und jünger)</option>
|
||||
<option value="J17">J17 (17 und jünger)</option>
|
||||
<option value="J15">J15 (15 und jünger)</option>
|
||||
<option value="J13">J13 (13 und jünger)</option>
|
||||
<option value="J11">J11 (11 und jünger)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Geschlecht:</label>
|
||||
<select v-model="selectedGender" class="filter-select">
|
||||
<option value="">Alle</option>
|
||||
<option value="female">Weiblich</option>
|
||||
<option value="male">Männlich</option>
|
||||
<option value="diverse">Divers</option>
|
||||
<option value="unknown">Unbekannt</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button @click="clearFilters" class="btn-clear-filters">Filter zurücksetzen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table>
|
||||
@@ -90,7 +120,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="member in members" :key="member.id">
|
||||
<template v-for="member in filteredMembers" :key="member.id">
|
||||
<tr v-if="member.active || showInactiveMembers" class="member-row" :class="{ 'row-inactive': !member.active, 'row-test': member.testMembership }" @click="editMember(member)">
|
||||
<td>
|
||||
<img v-if="member.imageUrl" :src="member.imageUrl" alt="Mitgliedsbild"
|
||||
@@ -237,6 +267,32 @@ export default {
|
||||
|
||||
inactiveMembersCount() {
|
||||
return this.members.filter(member => !member.active).length;
|
||||
},
|
||||
|
||||
filteredMembers() {
|
||||
return this.members.filter(member => {
|
||||
// Inaktive Mitglieder Filter
|
||||
if (!member.active && !this.showInactiveMembers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Altersklasse Filter
|
||||
if (this.selectedAgeGroup) {
|
||||
const age = this.getAge(member.birthDate);
|
||||
if (!this.matchesAgeGroup(age, this.selectedAgeGroup)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Geschlecht Filter
|
||||
if (this.selectedGender) {
|
||||
if (member.gender !== this.selectedGender) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -283,7 +339,9 @@ export default {
|
||||
isUpdatingRatings: false,
|
||||
showMemberInfo: false,
|
||||
showActivitiesModal: false,
|
||||
selectedMemberForActivities: null
|
||||
selectedMemberForActivities: null,
|
||||
selectedAgeGroup: '',
|
||||
selectedGender: ''
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -618,6 +676,60 @@ export default {
|
||||
} finally {
|
||||
this.isUpdatingRatings = false;
|
||||
}
|
||||
},
|
||||
|
||||
getAge(birthDate) {
|
||||
if (!birthDate) return null;
|
||||
|
||||
const birth = new Date(birthDate);
|
||||
if (isNaN(birth.getTime())) return null;
|
||||
|
||||
const today = new Date();
|
||||
const age = today.getFullYear() - birth.getFullYear();
|
||||
const monthDiff = today.getMonth() - birth.getMonth();
|
||||
|
||||
// Korrektur für Geburtstag in diesem Jahr
|
||||
return monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())
|
||||
? age - 1
|
||||
: age;
|
||||
},
|
||||
|
||||
matchesAgeGroup(age, ageGroup) {
|
||||
if (age === null) return false;
|
||||
|
||||
// Tischtennis-Logik: Altersklassen basieren auf Jahrgängen mit Stichtag 1. Januar
|
||||
// J19 = 19 Jahre und jünger am 1. Januar des aktuellen Jahres
|
||||
// J17 = 17 Jahre und jünger am 1. Januar des aktuellen Jahres
|
||||
// etc.
|
||||
|
||||
switch (ageGroup) {
|
||||
case 'adult':
|
||||
return age > 19; // Erwachsene = älter als 19
|
||||
|
||||
case 'J19':
|
||||
return age <= 19; // J19 = 19 und jünger
|
||||
|
||||
case 'J17':
|
||||
return age <= 17; // J17 = 17 und jünger
|
||||
|
||||
case 'J15':
|
||||
return age <= 15; // J15 = 15 und jünger
|
||||
|
||||
case 'J13':
|
||||
return age <= 13; // J13 = 13 und jünger
|
||||
|
||||
case 'J11':
|
||||
return age <= 11; // J11 = 11 und jünger
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
clearFilters() {
|
||||
this.selectedAgeGroup = '';
|
||||
this.selectedGender = '';
|
||||
this.showInactiveMembers = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -948,4 +1060,78 @@ table td {
|
||||
.btn-activities:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* Filter Styles */
|
||||
.filters-section {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-group .checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-group .checkbox-item input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
font-size: 0.9em;
|
||||
min-width: 120px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.btn-clear-filters {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-clear-filters:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -358,7 +358,7 @@ export default {
|
||||
this.selectedFile = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
},
|
||||
imageUrl(img) {
|
||||
return `http://localhost:3000/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
return `/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
},
|
||||
async uploadImage() {
|
||||
if (!this.editModel || !this.editModel.id || !this.selectedFile) {
|
||||
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
|
||||
async register() {
|
||||
try {
|
||||
await axios.post(`${import.meta.env.VITE_BACKEND}/api/auth/register`, { email: this.email, password: this.password });
|
||||
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.');
|
||||
} catch (error) {
|
||||
alert('Registrierung fehlgeschlagen');
|
||||
|
||||
Reference in New Issue
Block a user