Files
TTurnier/public/js/admin.js
2025-04-07 22:26:47 +02:00

915 lines
38 KiB
JavaScript

// public/js/admin.js
// --- Helper Functions (Keep from previous version) ---
const showMessage = (element, message, isError = true, autohide = false) => {
if (!element) {
console.warn("Attempted to show message on non-existent element:", message);
return;
}
element.textContent = message;
element.className = `message-area ${isError ? 'error-message' : 'success-message'}`; // Use CSS classes
element.classList.remove('hidden');
if (autohide) {
setTimeout(() => hideMessage(element), 5000);
}
};
const hideMessage = (element) => {
if (element) {
element.classList.add('hidden');
element.textContent = '';
}
};
const setLoading = (element, isLoading) => {
if (element) {
element.classList.toggle('hidden', !isLoading);
}
}
// --- DOM Elements ---
// Auth
const loginSection = document.getElementById('login-section');
const adminContent = document.getElementById('admin-content');
const loginForm = document.getElementById('login-form');
const loginUsernameInput = document.getElementById('login-username');
const loginPasswordInput = document.getElementById('login-password');
const loginError = document.getElementById('login-error'); // Specific error element for login form
const logoutButton = document.getElementById('logout-button');
const welcomeMessage = document.getElementById('welcome-message');
// Tournaments
const tournamentForm = document.getElementById('tournament-form');
const tournamentFormTitle = document.getElementById('tournament-form-title');
const tournamentIdInput = document.getElementById('tournament-id');
const tournamentNameInput = document.getElementById('tournament-name');
const tournamentDateInput = document.getElementById('tournament-date');
const tournamentLocationInput = document.getElementById('tournament-location');
const tournamentTypeInput = document.getElementById('tournament-type');
const tournamentGameTypeInput = document.getElementById('tournament-game-type');
const tournamentMaxPlayersInput = document.getElementById('tournament-max-players');
const tournamentStatusInput = document.getElementById('tournament-status');
const tournamentDescriptionInput = document.getElementById('tournament-description');
const saveTournamentButton = document.getElementById('save-tournament-button');
const cancelTournamentButton = document.getElementById('cancel-tournament-button');
const tournamentFormError = document.getElementById('tournament-form-error'); // Specific error element
const showAddTournamentFormButton = document.getElementById('show-add-tournament-form');
const tournamentList = document.getElementById('tournament-list');
const tournamentTable = document.getElementById('tournament-table');
const loadingTournaments = document.getElementById('loading-tournaments');
const tournamentListMessage = document.getElementById('tournament-list-message'); // General message area for list
// Players
const playerManagementSection = document.getElementById('player-management');
const playerSearchInput = document.getElementById('player-search');
const showAddPlayerFormButton = document.getElementById('show-add-player-form');
const playerForm = document.getElementById('player-form');
const playerFormTitle = document.getElementById('player-form-title');
const playerIdInput = document.getElementById('player-id');
const playerFirstNameInput = document.getElementById('player-first-name');
const playerLastNameInput = document.getElementById('player-last-name');
const playerClubInput = document.getElementById('player-club');
const playerQttrInput = document.getElementById('player-qttr');
const playerAgeClassInput = document.getElementById('player-age-class');
const savePlayerButton = document.getElementById('save-player-button');
const cancelPlayerButton = document.getElementById('cancel-player-button');
const playerFormError = document.getElementById('player-form-error');
const playerList = document.getElementById('player-list');
const playerTable = document.getElementById('player-table');
const loadingPlayers = document.getElementById('loading-players');
const playerListMessage = document.getElementById('player-list-message');
// Users
const userManagementSection = document.getElementById('user-management');
const showAddUserFormButton = document.getElementById('show-add-user-form');
const userForm = document.getElementById('user-form');
const userFormTitle = document.getElementById('user-form-title');
const userIdInput = document.getElementById('user-id');
const userUsernameInput = document.getElementById('user-username');
const userPasswordInput = document.getElementById('user-password');
const userRoleInput = document.getElementById('user-role');
const saveUserButton = document.getElementById('save-user-button');
const cancelUserButton = document.getElementById('cancel-user-button');
const userFormError = document.getElementById('user-form-error');
const userList = document.getElementById('user-list');
const userTable = document.getElementById('user-table');
const loadingUsers = document.getElementById('loading-users');
const userListMessage = document.getElementById('user-list-message');
// Matches
const matchManagementSection = document.getElementById('match-management');
const selectTournamentForMatches = document.getElementById('select-tournament-for-matches');
const showAddMatchFormButton = document.getElementById('show-add-match-form');
const matchForm = document.getElementById('match-form');
const matchFormTitle = document.getElementById('match-form-title');
const matchIdInput = document.getElementById('match-id');
const matchFormTournamentIdInput = document.getElementById('match-form-tournament-id');
const matchRoundInput = document.getElementById('match-round');
const matchNumberInRoundInput = document.getElementById('match-number-in-round');
const matchPlayer1Select = document.getElementById('match-player1');
const matchPlayer2Select = document.getElementById('match-player2');
const matchScheduledTimeInput = document.getElementById('match-scheduled-time');
const matchTableNumberInput = document.getElementById('match-table-number');
const matchStatusInput = document.getElementById('match-status');
const saveMatchButton = document.getElementById('save-match-button');
const cancelMatchButton = document.getElementById('cancel-match-button');
const matchFormError = document.getElementById('match-form-error');
const matchList = document.getElementById('match-list');
const matchTable = document.getElementById('match-table');
const loadingMatches = document.getElementById('loading-matches');
const matchListMessage = document.getElementById('match-list-message');
const selectTournamentPrompt = document.getElementById('select-tournament-prompt');
// --- State ---
let authToken = localStorage.getItem('authToken'); // Store token in local storage
let currentUser = JSON.parse(localStorage.getItem('currentUser')); // Store user info
let currentPlayers = []; // Cache players for dropdowns
let currentTournaments = []; // Cache tournaments for dropdowns
// --- API Base URL ---
const API_BASE_URL = '/api';
// --- Utility Functions --- (showMessage, hideMessage, setLoading defined above)
// Function to make authenticated API requests
const fetchAPI = async (endpoint, options = {}) => {
const headers = {
'Content-Type': 'application/json',
...options.headers, // Allow overriding headers
};
// Add Authorization header if token exists
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}
try {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
headers,
});
// Handle token expiration or invalid token
if (response.status === 401 || response.status === 403) {
console.warn('Authentication error:', response.status);
logout(); // Log out the user if token is invalid/expired
throw new Error('Authentifizierung fehlgeschlagen oder Token abgelaufen.');
}
// Try to parse error message from response body for non-OK responses
if (!response.ok) {
let errorData = { message: `HTTP-Fehler: ${response.status} ${response.statusText}` }; // Default error
try {
// Attempt to parse JSON error response from backend
const parsedError = await response.json();
if (parsedError && parsedError.message) {
errorData.message = parsedError.message;
}
} catch (parseError) {
// Ignore if response is not JSON or empty
}
console.error('API Error:', response.status, errorData);
throw new Error(errorData.message);
}
// Handle responses with no content (e.g., DELETE 204)
if (response.status === 204) {
return null; // Indicate success with no content
}
// Parse JSON response body for other successful responses
return await response.json();
} catch (error) {
console.error('Fetch API Error:', error);
// Re-throw the error so it can be caught by the calling function
throw error; // Propagate the specific error message
}
};
// --- Authentication Functions ---
const handleLogin = async (event) => {
event.preventDefault();
hideMessage(loginError);
const username = loginUsernameInput.value.trim();
const password = loginPasswordInput.value.trim();
if (!username || !password) {
showMessage(loginError, 'Bitte Benutzername und Passwort eingeben.');
return;
}
try {
const data = await fetchAPI('/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
if (data.token && data.user) {
if (data.user.role !== 'admin') {
showMessage(loginError, 'Zugriff verweigert. Nur Administratoren können sich hier anmelden.');
logout(); // Clear any potentially stored invalid token
return;
}
authToken = data.token;
currentUser = data.user;
localStorage.setItem('authToken', authToken);
localStorage.setItem('currentUser', JSON.stringify(currentUser));
updateUIBasedOnAuthState();
loadInitialData(); // Load data needed for admin view
loginForm.reset();
} else {
showMessage(loginError, data.message || 'Login fehlgeschlagen.');
}
} catch (error) {
// Use the error message thrown by fetchAPI
showMessage(loginError, `Login fehlgeschlagen: ${error.message}`);
}
};
const logout = () => {
authToken = null;
currentUser = null;
localStorage.removeItem('authToken');
localStorage.removeItem('currentUser');
updateUIBasedOnAuthState();
// Clear dynamic content
tournamentList.innerHTML = '';
playerList.innerHTML = '';
userList.innerHTML = '';
matchList.innerHTML = '';
selectTournamentForMatches.innerHTML = '<option value="">-- Turnier wählen --</option>';
// Hide forms
resetAndHideTournamentForm();
resetAndHidePlayerForm();
resetAndHideUserForm();
resetAndHideMatchForm();
};
// --- UI Update Functions ---
const updateUIBasedOnAuthState = () => {
const isAdminLoggedIn = authToken && currentUser && currentUser.role === 'admin';
loginSection.classList.toggle('hidden', isAdminLoggedIn);
adminContent.classList.toggle('hidden', !isAdminLoggedIn);
logoutButton.classList.toggle('hidden', !isAdminLoggedIn);
welcomeMessage.classList.toggle('hidden', !isAdminLoggedIn);
if (isAdminLoggedIn) {
welcomeMessage.textContent = `Willkommen, ${currentUser.username}! (Admin)`;
} else {
// If not logged in or wrong role, ensure login error message is cleared unless login just failed
// hideMessage(loginError); // Might hide a recent login error, handle carefully
}
};
// Load all necessary initial data after login
const loadInitialData = async () => {
if (!authToken || !currentUser || currentUser.role !== 'admin') return;
// Load concurrently for better performance
await Promise.all([
loadTournaments(),
loadPlayers(),
loadUsers()
// Matches are loaded when a tournament is selected
]);
populateTournamentSelects(); // Populate dropdowns after tournaments are loaded
};
// --- Tournament Functions ---
const resetAndHideTournamentForm = () => {
tournamentForm.reset();
tournamentIdInput.value = '';
tournamentFormTitle.textContent = 'Neues Turnier hinzufügen';
saveTournamentButton.textContent = 'Speichern';
hideMessage(tournamentFormError);
tournamentForm.classList.add('hidden');
};
const showTournamentForm = (tournament = null) => {
resetAndHideTournamentForm();
if (tournament) {
tournamentFormTitle.textContent = 'Turnier bearbeiten';
saveTournamentButton.textContent = 'Änderungen speichern';
tournamentIdInput.value = tournament.tournament_id;
tournamentNameInput.value = tournament.name || '';
tournamentDateInput.value = tournament.date ? tournament.date.split('T')[0] : '';
tournamentLocationInput.value = tournament.location || '';
tournamentTypeInput.value = tournament.tournament_type || 'knockout';
tournamentGameTypeInput.value = tournament.game_type || '11_points';
tournamentMaxPlayersInput.value = tournament.max_players || '';
tournamentStatusInput.value = tournament.status || 'planned';
tournamentDescriptionInput.value = tournament.description || '';
}
tournamentForm.classList.remove('hidden');
tournamentNameInput.focus();
};
const loadTournaments = async () => {
if (!authToken) return;
setLoading(loadingTournaments, true);
tournamentList.innerHTML = '';
hideMessage(tournamentListMessage);
try {
currentTournaments = await fetchAPI('/tournaments'); // Store fetched tournaments
if (currentTournaments.length === 0) {
tournamentList.innerHTML = '<tr><td colspan="6">Keine Turniere gefunden.</td></tr>';
} else {
currentTournaments.forEach(tournament => {
const row = tournamentList.insertRow();
row.dataset.id = tournament.tournament_id;
const displayDate = tournament.date ? new Date(tournament.date).toLocaleDateString('de-DE') : '-';
row.innerHTML = `
<td>${tournament.name || '-'}</td>
<td>${displayDate}</td>
<td>${tournament.location || '-'}</td>
<td>${tournament.tournament_type === 'knockout' ? 'KO-System' : 'Gruppenphase'}</td>
<td>${tournament.status || 'Geplant'}</td>
<td>
<button class="edit-tournament" data-id="${tournament.tournament_id}">Bearbeiten</button>
<button class="delete-tournament danger" data-id="${tournament.tournament_id}" data-name="${tournament.name}">Löschen</button>
</td>
`;
});
populateTournamentSelects(); // Update dropdowns whenever tournaments are loaded/reloaded
}
} catch (error) {
showMessage(tournamentListMessage, `Fehler beim Laden der Turniere: ${error.message}`);
tournamentList.innerHTML = `<tr><td colspan="6">Fehler beim Laden der Daten.</td></tr>`;
} finally {
setLoading(loadingTournaments, false);
}
};
const handleSaveTournament = async (event) => {
event.preventDefault();
hideMessage(tournamentFormError);
const tournamentId = tournamentIdInput.value;
const isEditing = !!tournamentId;
const tournamentData = {
name: tournamentNameInput.value.trim(),
date: tournamentDateInput.value || null,
location: tournamentLocationInput.value.trim() || null,
tournament_type: tournamentTypeInput.value,
game_type: tournamentGameTypeInput.value,
max_players: tournamentMaxPlayersInput.value ? parseInt(tournamentMaxPlayersInput.value, 10) : null,
status: tournamentStatusInput.value,
description: tournamentDescriptionInput.value.trim() || null,
};
if (!tournamentData.name) {
showMessage(tournamentFormError, 'Turniername ist erforderlich.');
return;
}
if (tournamentData.max_players !== null && isNaN(tournamentData.max_players)) {
showMessage(tournamentFormError, 'Max. Spieler muss eine Zahl sein.');
return;
}
const method = isEditing ? 'PUT' : 'POST';
const endpoint = isEditing ? `/tournaments/${tournamentId}` : '/tournaments';
try {
await fetchAPI(endpoint, { method, body: JSON.stringify(tournamentData) });
showMessage(tournamentListMessage, `Turnier erfolgreich ${isEditing ? 'aktualisiert' : 'gespeichert'}.`, false, true);
resetAndHideTournamentForm();
loadTournaments(); // Refresh list and potentially dropdowns
} catch (error) {
showMessage(tournamentFormError, `Fehler beim Speichern: ${error.message}`);
}
};
const handleDeleteTournament = async (tournamentId, tournamentName) => {
if (!tournamentId || !confirm(`Möchten Sie das Turnier "${tournamentName}" wirklich löschen?`)) return;
hideMessage(tournamentListMessage);
try {
await fetchAPI(`/tournaments/${tournamentId}`, { method: 'DELETE' });
showMessage(tournamentListMessage, `Turnier "${tournamentName}" erfolgreich gelöscht.`, false, true);
loadTournaments(); // Refresh list and dropdowns
} catch (error) {
showMessage(tournamentListMessage, `Fehler beim Löschen: ${error.message}`);
}
};
// --- Player Functions ---
const resetAndHidePlayerForm = () => {
playerForm.reset();
playerIdInput.value = '';
playerFormTitle.textContent = 'Neuen Spieler hinzufügen';
savePlayerButton.textContent = 'Speichern';
hideMessage(playerFormError);
playerForm.classList.add('hidden');
};
const showPlayerForm = (player = null) => {
resetAndHidePlayerForm();
if (player) {
playerFormTitle.textContent = 'Spieler bearbeiten';
savePlayerButton.textContent = 'Änderungen speichern';
playerIdInput.value = player.player_id;
playerFirstNameInput.value = player.first_name || '';
playerLastNameInput.value = player.last_name || '';
playerClubInput.value = player.club || '';
playerQttrInput.value = player.qttr_points || '';
playerAgeClassInput.value = player.age_class || '';
}
playerForm.classList.remove('hidden');
playerFirstNameInput.focus();
};
const loadPlayers = async (searchTerm = '') => {
if (!authToken) return;
setLoading(loadingPlayers, true);
playerList.innerHTML = '';
hideMessage(playerListMessage);
const endpoint = searchTerm ? `/players?search=${encodeURIComponent(searchTerm)}` : '/players';
try {
currentPlayers = await fetchAPI(endpoint); // Cache players
if (currentPlayers.length === 0) {
playerList.innerHTML = '<tr><td colspan="6">Keine Spieler gefunden.</td></tr>';
} else {
currentPlayers.forEach(player => {
const row = playerList.insertRow();
row.dataset.id = player.player_id;
row.innerHTML = `
<td>${player.last_name || '-'}</td>
<td>${player.first_name || '-'}</td>
<td>${player.club || '-'}</td>
<td>${player.qttr_points || '-'}</td>
<td>${player.age_class || '-'}</td>
<td>
<button class="edit-player" data-id="${player.player_id}">Bearbeiten</button>
<button class="delete-player danger" data-id="${player.player_id}" data-name="${player.first_name} ${player.last_name}">Löschen</button>
</td>
`;
});
populatePlayerSelects(); // Update player dropdowns in match form
}
} catch (error) {
showMessage(playerListMessage, `Fehler beim Laden der Spieler: ${error.message}`);
playerList.innerHTML = `<tr><td colspan="6">Fehler beim Laden der Daten.</td></tr>`;
} finally {
setLoading(loadingPlayers, false);
}
};
const handleSavePlayer = async (event) => {
event.preventDefault();
hideMessage(playerFormError);
const playerId = playerIdInput.value;
const isEditing = !!playerId;
const playerData = {
first_name: playerFirstNameInput.value.trim(),
last_name: playerLastNameInput.value.trim(),
club: playerClubInput.value.trim(),
qttr_points: playerQttrInput.value ? parseInt(playerQttrInput.value, 10) : null,
age_class: playerAgeClassInput.value.trim(),
};
if (!playerData.first_name || !playerData.last_name) {
showMessage(playerFormError, 'Vor- und Nachname sind erforderlich.');
return;
}
if (playerQttrInput.value && (isNaN(playerData.qttr_points) || playerData.qttr_points === null)) {
showMessage(playerFormError, 'QTTR-Punkte müssen eine gültige Zahl sein.');
return;
}
const method = isEditing ? 'PUT' : 'POST';
const endpoint = isEditing ? `/players/${playerId}` : '/players';
try {
await fetchAPI(endpoint, { method, body: JSON.stringify(playerData) });
showMessage(playerListMessage, `Spieler erfolgreich ${isEditing ? 'aktualisiert' : 'gespeichert'}.`, false, true);
resetAndHidePlayerForm();
loadPlayers(playerSearchInput.value.trim()); // Refresh list with current search term
} catch (error) {
showMessage(playerFormError, `Fehler beim Speichern: ${error.message}`);
}
};
const handleDeletePlayer = async (playerId, playerName) => {
if (!playerId || !confirm(`Möchten Sie den Spieler "${playerName}" wirklich löschen?`)) return;
hideMessage(playerListMessage);
try {
await fetchAPI(`/players/${playerId}`, { method: 'DELETE' });
showMessage(playerListMessage, `Spieler "${playerName}" erfolgreich gelöscht.`, false, true);
loadPlayers(playerSearchInput.value.trim()); // Refresh list
} catch (error) {
showMessage(playerListMessage, `Fehler beim Löschen: ${error.message}`);
}
};
// --- User Functions ---
const resetAndHideUserForm = () => {
userForm.reset();
userIdInput.value = '';
userFormTitle.textContent = 'Neuen Benutzer hinzufügen';
saveUserButton.textContent = 'Speichern';
userPasswordInput.required = true; // Required when adding
hideMessage(userFormError);
userForm.classList.add('hidden');
};
const showUserForm = (user = null) => {
resetAndHideUserForm();
if (user) {
userFormTitle.textContent = 'Benutzer bearbeiten';
saveUserButton.textContent = 'Änderungen speichern';
userIdInput.value = user.user_id;
userUsernameInput.value = user.username || '';
userRoleInput.value = user.role || 'spectator';
userPasswordInput.required = false; // Not required when editing (unless changing)
userPasswordInput.placeholder = "Leer lassen, um nicht zu ändern";
} else {
userPasswordInput.required = true;
userPasswordInput.placeholder = "";
}
userForm.classList.remove('hidden');
userUsernameInput.focus();
};
const loadUsers = async () => {
if (!authToken) return;
setLoading(loadingUsers, true);
userList.innerHTML = '';
hideMessage(userListMessage);
try {
const users = await fetchAPI('/users');
if (users.length === 0) {
userList.innerHTML = '<tr><td colspan="4">Keine Benutzer gefunden.</td></tr>';
} else {
users.forEach(user => {
const row = userList.insertRow();
row.dataset.id = user.user_id;
const createdAt = user.created_at ? new Date(user.created_at).toLocaleString('de-DE') : '-';
// Prevent deleting the currently logged-in admin
const disableDelete = currentUser && currentUser.userId === user.user_id;
row.innerHTML = `
<td>${user.username || '-'}</td>
<td>${user.role || '-'}</td>
<td>${createdAt}</td>
<td>
<button class="edit-user" data-id="${user.user_id}">Bearbeiten</button>
<button class="delete-user danger" data-id="${user.user_id}" data-name="${user.username}" ${disableDelete ? 'disabled title="Aktueller Admin kann nicht gelöscht werden"' : ''}>Löschen</button>
</td>
`;
});
}
} catch (error) {
showMessage(userListMessage, `Fehler beim Laden der Benutzer: ${error.message}`);
userList.innerHTML = `<tr><td colspan="4">Fehler beim Laden der Daten.</td></tr>`;
} finally {
setLoading(loadingUsers, false);
}
};
const handleSaveUser = async (event) => {
event.preventDefault();
hideMessage(userFormError);
const userId = userIdInput.value;
const isEditing = !!userId;
const userData = {
username: userUsernameInput.value.trim(),
password: userPasswordInput.value, // Send password field (empty if not changed)
role: userRoleInput.value,
};
if (!userData.username) {
showMessage(userFormError, 'Benutzername ist erforderlich.');
return;
}
if (!isEditing && !userData.password) {
showMessage(userFormError, 'Passwort ist beim Erstellen erforderlich.');
return;
}
// Basic password validation (example)
if (userData.password && userData.password.length < 6) {
showMessage(userFormError, 'Passwort muss mindestens 6 Zeichen lang sein.');
return;
}
const method = isEditing ? 'PUT' : 'POST';
const endpoint = isEditing ? `/users/${userId}` : '/users';
// Don't send empty password field if editing and not changing password
if (isEditing && !userData.password) {
delete userData.password;
}
try {
await fetchAPI(endpoint, { method, body: JSON.stringify(userData) });
showMessage(userListMessage, `Benutzer erfolgreich ${isEditing ? 'aktualisiert' : 'gespeichert'}.`, false, true);
resetAndHideUserForm();
loadUsers(); // Refresh list
} catch (error) {
showMessage(userFormError, `Fehler beim Speichern: ${error.message}`);
}
};
const handleDeleteUser = async (userId, username) => {
if (!userId || (currentUser && currentUser.userId === userId) || !confirm(`Möchten Sie den Benutzer "${username}" wirklich löschen?`)) return;
hideMessage(userListMessage);
try {
await fetchAPI(`/users/${userId}`, { method: 'DELETE' });
showMessage(userListMessage, `Benutzer "${username}" erfolgreich gelöscht.`, false, true);
loadUsers(); // Refresh list
} catch (error) {
showMessage(userListMessage, `Fehler beim Löschen: ${error.message}`);
}
};
// --- Match Functions (Structure and Placeholders) ---
// Populate dropdown selects with current data
const populateTournamentSelects = () => {
selectTournamentForMatches.innerHTML = '<option value="">-- Turnier wählen --</option>';
currentTournaments.forEach(t => {
const option = document.createElement('option');
option.value = t.tournament_id;
option.textContent = `${t.name} (${t.date ? new Date(t.date).toLocaleDateString('de-DE') : 'N/A'})`;
selectTournamentForMatches.appendChild(option);
});
};
const populatePlayerSelects = () => {
const optionsHtml = '<option value="">-- Spieler wählen --</option>' +
currentPlayers.map(p => `<option value="${p.player_id}">${p.first_name} ${p.last_name} (${p.club || 'N/A'})</option>`).join('');
matchPlayer1Select.innerHTML = optionsHtml;
matchPlayer2Select.innerHTML = optionsHtml;
};
const resetAndHideMatchForm = () => {
matchForm.reset();
matchIdInput.value = '';
matchFormTournamentIdInput.value = '';
matchFormTitle.textContent = 'Neues Spiel hinzufügen';
saveMatchButton.textContent = 'Speichern';
hideMessage(matchFormError);
matchForm.classList.add('hidden');
};
const showMatchForm = (match = null) => {
resetAndHideMatchForm();
const selectedTournamentId = selectTournamentForMatches.value;
if (!selectedTournamentId) {
showMessage(matchListMessage, "Bitte zuerst ein Turnier auswählen, um ein Spiel hinzuzufügen.", true);
return;
}
matchFormTournamentIdInput.value = selectedTournamentId; // Set hidden field
// Ensure player dropdowns are up-to-date
populatePlayerSelects();
if (match) {
// TODO: Populate form for editing a match
matchFormTitle.textContent = 'Spiel bearbeiten';
saveMatchButton.textContent = 'Änderungen speichern';
matchIdInput.value = match.match_id;
matchRoundInput.value = match.round || '';
matchNumberInRoundInput.value = match.match_number_in_round || '';
matchPlayer1Select.value = match.player1_id || '';
matchPlayer2Select.value = match.player2_id || '';
// Format datetime-local (YYYY-MM-DDTHH:mm)
matchScheduledTimeInput.value = match.scheduled_time ? match.scheduled_time.substring(0, 16) : '';
matchTableNumberInput.value = match.table_number || '';
matchStatusInput.value = match.status || 'scheduled';
}
matchForm.classList.remove('hidden');
matchRoundInput.focus();
};
const loadMatches = async (tournamentId) => {
if (!authToken || !tournamentId) {
matchList.innerHTML = '';
selectTournamentPrompt.classList.remove('hidden');
showAddMatchFormButton.disabled = true;
return;
}
selectTournamentPrompt.classList.add('hidden');
showAddMatchFormButton.disabled = false; // Enable add button
setLoading(loadingMatches, true);
matchList.innerHTML = '';
hideMessage(matchListMessage);
try {
const matches = await fetchAPI(`/matches?tournamentId=${tournamentId}`);
if (matches.length === 0) {
matchList.innerHTML = '<tr><td colspan="9">Keine Spiele für dieses Turnier gefunden.</td></tr>';
} else {
matches.forEach(match => {
const row = matchList.insertRow();
row.dataset.id = match.match_id;
const scheduled = match.scheduled_time ? new Date(match.scheduled_time).toLocaleString('de-DE') : '-';
// TODO: Fetch and display actual scores/result string
const resultPlaceholder = match.status === 'finished' ? (match.winner_name ? `${match.winner_name} gewinnt` : 'Beendet') : '-';
row.innerHTML = `
<td>${match.round || '-'}</td>
<td>${match.match_number_in_round || '-'}</td>
<td>${match.player1_name || '<i>N/A</i>'}</td>
<td>${match.player2_name || '<i>N/A</i>'}</td>
<td>${resultPlaceholder}</td> <td>${match.status || '-'}</td>
<td>${scheduled}</td>
<td>${match.table_number || '-'}</td>
<td>
<button class="edit-match" data-id="${match.match_id}">Bearbeiten</button>
<button class="delete-match danger" data-id="${match.match_id}">Löschen</button>
</td>
`;
});
}
} catch (error) {
showMessage(matchListMessage, `Fehler beim Laden der Spiele: ${error.message}`);
matchList.innerHTML = `<tr><td colspan="9">Fehler beim Laden der Daten.</td></tr>`;
} finally {
setLoading(loadingMatches, false);
}
};
const handleSaveMatch = async (event) => {
event.preventDefault();
hideMessage(matchFormError);
const matchId = matchIdInput.value;
const isEditing = !!matchId;
const tournamentId = matchFormTournamentIdInput.value;
if (!tournamentId) {
showMessage(matchFormError, "Fehler: Turnier-ID fehlt im Formular.");
return;
}
const matchData = {
tournament_id: tournamentId, // Crucial for creation
round: matchRoundInput.value ? parseInt(matchRoundInput.value, 10) : null,
match_number_in_round: matchNumberInRoundInput.value ? parseInt(matchNumberInRoundInput.value, 10) : null,
player1_id: matchPlayer1Select.value || null,
player2_id: matchPlayer2Select.value || null,
scheduled_time: matchScheduledTimeInput.value || null,
table_number: matchTableNumberInput.value.trim() || null,
status: matchStatusInput.value,
};
// Basic validation
if (matchData.player1_id && matchData.player1_id === matchData.player2_id) {
showMessage(matchFormError, "Spieler 1 und Spieler 2 dürfen nicht identisch sein.");
return;
}
if (matchData.round !== null && isNaN(matchData.round)) {
showMessage(matchFormError, "Runde muss eine Zahl sein."); return;
}
if (matchData.match_number_in_round !== null && isNaN(matchData.match_number_in_round)) {
showMessage(matchFormError, "Spiel Nr. muss eine Zahl sein."); return;
}
// Remove tournament_id if editing (it's part of the URL, not body)
if (isEditing) {
delete matchData.tournament_id;
}
const method = isEditing ? 'PUT' : 'POST';
const endpoint = isEditing ? `/matches/${matchId}` : '/matches';
try {
await fetchAPI(endpoint, { method, body: JSON.stringify(matchData) });
showMessage(matchListMessage, `Spiel erfolgreich ${isEditing ? 'aktualisiert' : 'gespeichert'}.`, false, true);
resetAndHideMatchForm();
loadMatches(tournamentId); // Refresh list for the current tournament
} catch (error) {
showMessage(matchFormError, `Fehler beim Speichern des Spiels: ${error.message}`);
}
};
const handleDeleteMatch = async (matchId) => {
if (!matchId || !confirm(`Möchten Sie dieses Spiel wirklich löschen?`)) return;
hideMessage(matchListMessage);
const currentTournamentId = selectTournamentForMatches.value; // Get current tournament to refresh list
try {
await fetchAPI(`/matches/${matchId}`, { method: 'DELETE' });
showMessage(matchListMessage, `Spiel erfolgreich gelöscht.`, false, true);
if (currentTournamentId) {
loadMatches(currentTournamentId); // Refresh list
}
} catch (error) {
showMessage(matchListMessage, `Fehler beim Löschen des Spiels: ${error.message}`);
}
};
// --- Event Listeners ---
// General Setup
document.addEventListener('DOMContentLoaded', () => {
updateUIBasedOnAuthState();
if (authToken && currentUser && currentUser.role === 'admin') {
loadInitialData();
}
});
// Auth
loginForm.addEventListener('submit', handleLogin);
logoutButton.addEventListener('click', logout);
// Tournaments
showAddTournamentFormButton.addEventListener('click', () => showTournamentForm());
cancelTournamentButton.addEventListener('click', resetAndHideTournamentForm);
tournamentForm.addEventListener('submit', handleSaveTournament);
tournamentList.addEventListener('click', (event) => {
const target = event.target;
if (target.classList.contains('edit-tournament')) {
const id = target.dataset.id;
const tournamentToEdit = currentTournaments.find(t => t.tournament_id === id);
if (tournamentToEdit) showTournamentForm(tournamentToEdit);
} else if (target.classList.contains('delete-tournament')) {
handleDeleteTournament(target.dataset.id, target.dataset.name);
}
});
// Players
showAddPlayerFormButton.addEventListener('click', () => showPlayerForm());
cancelPlayerButton.addEventListener('click', resetAndHidePlayerForm);
playerForm.addEventListener('submit', handleSavePlayer);
playerList.addEventListener('click', (event) => {
const target = event.target;
if (target.classList.contains('edit-player')) {
const id = target.dataset.id;
const playerToEdit = currentPlayers.find(p => p.player_id === id);
if (playerToEdit) showPlayerForm(playerToEdit);
} else if (target.classList.contains('delete-player')) {
handleDeletePlayer(target.dataset.id, target.dataset.name);
}
});
// Debounce search input
let playerSearchTimeout;
playerSearchInput.addEventListener('input', () => {
clearTimeout(playerSearchTimeout);
playerSearchTimeout = setTimeout(() => {
loadPlayers(playerSearchInput.value.trim());
}, 300); // Wait 300ms after user stops typing
});
// Users
showAddUserFormButton.addEventListener('click', () => showUserForm());
cancelUserButton.addEventListener('click', resetAndHideUserForm);
userForm.addEventListener('submit', handleSaveUser);
userList.addEventListener('click', async (event) => { // Mark async if fetching user data for edit
const target = event.target;
if (target.classList.contains('edit-user')) {
const id = target.dataset.id;
// Fetch the specific user data to ensure we have the latest, including role
hideMessage(userListMessage);
try {
const userToEdit = await fetchAPI(`/users/${id}`); // Fetch single user
showUserForm(userToEdit);
} catch(error) {
showMessage(userListMessage, `Fehler beim Laden der Benutzerdaten: ${error.message}`);
}
} else if (target.classList.contains('delete-user')) {
handleDeleteUser(target.dataset.id, target.dataset.name);
}
});
// Matches
selectTournamentForMatches.addEventListener('change', (event) => {
loadMatches(event.target.value);
});
showAddMatchFormButton.addEventListener('click', () => showMatchForm());
cancelMatchButton.addEventListener('click', resetAndHideMatchForm);
matchForm.addEventListener('submit', handleSaveMatch);
matchList.addEventListener('click', async (event) => {
const target = event.target;
if (target.classList.contains('edit-match')) {
const id = target.dataset.id;
hideMessage(matchListMessage);
try {
// Fetch the specific match data to pre-fill the form accurately
const matchToEdit = await fetchAPI(`/matches/${id}`); // Assumes getMatchById returns necessary fields
showMatchForm(matchToEdit);
} catch (error) {
showMessage(matchListMessage, `Fehler beim Laden der Spieldaten zum Bearbeiten: ${error.message}`);
}
} else if (target.classList.contains('delete-match')) {
handleDeleteMatch(target.dataset.id);
}
});