diff --git a/backend/controllers/officialTournamentController.js b/backend/controllers/officialTournamentController.js
index cf88a4f..9e1db96 100644
--- a/backend/controllers/officialTournamentController.js
+++ b/backend/controllers/officialTournamentController.js
@@ -160,6 +160,71 @@ export const upsertCompetitionMember = async (req, res) => {
}
};
+export const updateParticipantStatus = async (req, res) => {
+ try {
+ const { authcode: userToken } = req.headers;
+ const { clubId, id } = req.params; // id = tournamentId
+ await checkAccess(userToken, clubId);
+ const { competitionId, memberId, action } = req.body;
+
+ if (!competitionId || !memberId || !action) {
+ return res.status(400).json({ error: 'competitionId, memberId and action required' });
+ }
+
+ const [row] = await OfficialCompetitionMember.findOrCreate({
+ where: { competitionId, memberId },
+ defaults: {
+ tournamentId: id,
+ competitionId,
+ memberId,
+ wants: false,
+ registered: false,
+ participated: false,
+ placement: null,
+ }
+ });
+
+ // Status-Update basierend auf Aktion
+ switch (action) {
+ case 'register':
+ // Von "möchte teilnehmen" zu "angemeldet"
+ row.wants = true;
+ row.registered = true;
+ row.participated = false;
+ break;
+ case 'participate':
+ // Von "angemeldet" zu "hat gespielt"
+ row.wants = true;
+ row.registered = true;
+ row.participated = true;
+ break;
+ case 'reset':
+ // Zurück zu "möchte teilnehmen"
+ row.wants = true;
+ row.registered = false;
+ row.participated = false;
+ break;
+ default:
+ return res.status(400).json({ error: 'Invalid action. Use: register, participate, or reset' });
+ }
+
+ await row.save();
+ return res.status(200).json({
+ success: true,
+ id: row.id,
+ status: {
+ wants: row.wants,
+ registered: row.registered,
+ participated: row.participated,
+ placement: row.placement
+ }
+ });
+ } catch (e) {
+ console.error('[updateParticipantStatus] Error:', e);
+ res.status(500).json({ error: 'Failed to update participant status' });
+ }
+};
+
export const listOfficialTournaments = async (req, res) => {
try {
const { authcode: userToken } = req.headers;
diff --git a/backend/routes/officialTournamentRoutes.js b/backend/routes/officialTournamentRoutes.js
index 2a3c012..16d1108 100644
--- a/backend/routes/officialTournamentRoutes.js
+++ b/backend/routes/officialTournamentRoutes.js
@@ -1,7 +1,7 @@
import express from 'express';
import multer from 'multer';
import { authenticate } from '../middleware/authMiddleware.js';
-import { uploadTournamentPdf, getParsedTournament, listOfficialTournaments, deleteOfficialTournament, upsertCompetitionMember, listClubParticipations } from '../controllers/officialTournamentController.js';
+import { uploadTournamentPdf, getParsedTournament, listOfficialTournaments, deleteOfficialTournament, upsertCompetitionMember, listClubParticipations, updateParticipantStatus } from '../controllers/officialTournamentController.js';
const router = express.Router();
const upload = multer({ storage: multer.memoryStorage() });
@@ -14,6 +14,7 @@ router.post('/:clubId/upload', upload.single('pdf'), uploadTournamentPdf);
router.get('/:clubId/:id', getParsedTournament);
router.delete('/:clubId/:id', deleteOfficialTournament);
router.post('/:clubId/:id/participation', upsertCompetitionMember);
+router.post('/:clubId/:id/status', updateParticipantStatus);
export default router;
diff --git a/frontend/src/views/OfficialTournaments.vue b/frontend/src/views/OfficialTournaments.vue
index a117abf..ebdded5 100644
--- a/frontend/src/views/OfficialTournaments.vue
+++ b/frontend/src/views/OfficialTournaments.vue
@@ -163,8 +163,8 @@
Mitglied |
Konkurrenz |
Startzeit |
- Angemeldet |
- Teilgenommen |
+ Status |
+ Aktion |
Platzierung |
@@ -175,9 +175,53 @@
{{ group.memberName }} |
{{ item.competitionName }} |
{{ item.start }} |
- {{ item.registered ? 'Ja' : 'Nein' }} |
- {{ item.participated ? 'Ja' : 'Nein' }} |
- {{ item.placement || '–' }} |
+
+ Hat gespielt
+ Angemeldet
+ Möchte teilnehmen
+ Nicht interessiert
+ |
+
+
+
+
+
+ |
+
+
+ {{ item.placement || '–' }}
+ |
@@ -756,6 +800,43 @@ export default {
this.getParticipation(c.id, m.id).placement = v || null;
this.saveParticipation(c.id, m.id);
},
+ async updateStatus(item, action) {
+ try {
+ const [competitionId, memberId] = item.key.split('-');
+ const response = await apiClient.post(`/official-tournaments/${this.currentClub}/${this.uploadedId}/status`, {
+ competitionId: parseInt(competitionId),
+ memberId: parseInt(memberId),
+ action: action
+ });
+
+ if (response.data.success) {
+ // Aktualisiere den lokalen Status
+ const key = `${competitionId}-${memberId}`;
+ const participation = this.getParticipation(competitionId, memberId);
+ participation.wants = response.data.status.wants;
+ participation.registered = response.data.status.registered;
+ participation.participated = response.data.status.participated;
+ participation.placement = response.data.status.placement;
+
+ // Aktualisiere auch die participationMap
+ this.participationMap[key] = participation;
+ }
+ } catch (error) {
+ console.error('Fehler beim Aktualisieren des Status:', error);
+ alert('Fehler beim Aktualisieren des Status: ' + (error.response?.data?.error || error.message));
+ }
+ },
+ async updatePlacement(item, value) {
+ try {
+ const [competitionId, memberId] = item.key.split('-');
+ const participation = this.getParticipation(competitionId, memberId);
+ participation.placement = value.trim() || null;
+ await this.saveParticipation(competitionId, memberId);
+ } catch (error) {
+ console.error('Fehler beim Aktualisieren der Platzierung:', error);
+ alert('Fehler beim Aktualisieren der Platzierung: ' + (error.response?.data?.error || error.message));
+ }
+ },
// Auswahl Helfer + PDF-Generierung
openMemberDialog() { this.showMemberDialog = true; },
closeMemberDialog() { this.showMemberDialog = false; },
@@ -1024,6 +1105,112 @@ th, td { border-bottom: 1px solid var(--border-color); padding: 0.5rem; text-ali
.dialog-col h4 { margin: 0 0 .5rem 0; }
.members-col .check-item span.active { font-weight: bold; }
.recommendations-col .check-item { padding: .15rem 0; }
+
+/* Status Management Styles */
+.status-cell {
+ text-align: center;
+ vertical-align: middle;
+}
+
+.status-badge {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ text-align: center;
+ min-width: 120px;
+}
+
+.status-badge.status-played {
+ background-color: #d4edda;
+ color: #155724;
+ border: 1px solid #c3e6cb;
+}
+
+.status-badge.status-registered {
+ background-color: #d1ecf1;
+ color: #0c5460;
+ border: 1px solid #bee5eb;
+}
+
+.status-badge.status-wants {
+ background-color: #fff3cd;
+ color: #856404;
+ border: 1px solid #ffeaa7;
+}
+
+.status-badge.status-none {
+ background-color: #f8d7da;
+ color: #721c24;
+ border: 1px solid #f5c6cb;
+}
+
+.action-cell {
+ text-align: center;
+ vertical-align: middle;
+}
+
+.btn-status {
+ padding: 0.4rem 0.8rem;
+ border: none;
+ border-radius: 4px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.5s ease;
+ min-width: 100px;
+}
+
+.btn-status:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.btn-status:active {
+ transform: translateY(0);
+}
+
+.btn-register {
+ background-color: #007bff;
+ color: white;
+}
+
+.btn-register:hover {
+ background-color: #0056b3;
+}
+
+.btn-participate {
+ background-color: #28a745;
+ color: white;
+}
+
+.btn-participate:hover {
+ background-color: #1e7e34;
+}
+
+.btn-reset {
+ background-color: #6c757d;
+ color: white;
+}
+
+.btn-reset:hover {
+ background-color: #545b62;
+}
+
+.placement-input {
+ width: 120px;
+ padding: 0.25rem 0.5rem;
+ border: 1px solid #ced4da;
+ border-radius: 4px;
+ font-size: 0.85rem;
+}
+
+.placement-input:focus {
+ outline: none;
+ border-color: #007bff;
+ box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
+}