init
This commit is contained in:
396
public/js/admin.js
Normal file
396
public/js/admin.js
Normal file
@@ -0,0 +1,396 @@
|
||||
// public/js/admin.js
|
||||
|
||||
// --- DOM Elements ---
|
||||
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');
|
||||
const logoutButton = document.getElementById('logout-button');
|
||||
const welcomeMessage = document.getElementById('welcome-message');
|
||||
|
||||
// Tournament elements
|
||||
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 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');
|
||||
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');
|
||||
|
||||
|
||||
// --- State ---
|
||||
let authToken = localStorage.getItem('authToken'); // Store token in local storage
|
||||
let currentUser = JSON.parse(localStorage.getItem('currentUser')); // Store user info
|
||||
|
||||
// --- API Base URL ---
|
||||
// Use relative URL for API calls, assuming frontend and backend are on the same origin
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// --- Utility Functions ---
|
||||
|
||||
// Function to display messages (error or success)
|
||||
const showMessage = (element, message, isError = true) => {
|
||||
element.textContent = message;
|
||||
element.className = isError ? 'error-message' : 'success-message'; // Use CSS classes
|
||||
element.classList.remove('hidden');
|
||||
// Optional: Auto-hide after a few seconds
|
||||
// setTimeout(() => element.classList.add('hidden'), 5000);
|
||||
};
|
||||
|
||||
// Function to hide messages
|
||||
const hideMessage = (element) => {
|
||||
element.classList.add('hidden');
|
||||
element.textContent = '';
|
||||
};
|
||||
|
||||
// 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.');
|
||||
}
|
||||
|
||||
// Check if response is OK (status code 200-299)
|
||||
if (!response.ok) {
|
||||
// Try to parse error message from response body
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await response.json();
|
||||
} catch (parseError) {
|
||||
// If parsing fails, use status text
|
||||
errorData = { message: response.statusText };
|
||||
}
|
||||
console.error('API Error:', response.status, errorData);
|
||||
throw new Error(errorData.message || `HTTP-Fehler: ${response.status}`);
|
||||
}
|
||||
|
||||
// Handle responses with no content (e.g., DELETE 204)
|
||||
if (response.status === 204) {
|
||||
return null; // Or return a success indicator if needed
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- Authentication Functions ---
|
||||
|
||||
const handleLogin = async (event) => {
|
||||
event.preventDefault(); // Prevent default form submission
|
||||
hideMessage(loginError); // Hide previous errors
|
||||
|
||||
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) {
|
||||
// Check if the logged-in user has the 'admin' role
|
||||
if (data.user.role !== 'admin') {
|
||||
showMessage(loginError, 'Zugriff verweigert. Nur Administratoren können sich hier anmelden.');
|
||||
return; // Prevent non-admins from logging in to the admin panel
|
||||
}
|
||||
|
||||
// Store token and user info
|
||||
authToken = data.token;
|
||||
currentUser = data.user;
|
||||
localStorage.setItem('authToken', authToken);
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||
|
||||
// Update UI
|
||||
updateUIBasedOnAuthState();
|
||||
loadTournaments(); // Load initial data for admin
|
||||
|
||||
// Clear form
|
||||
loginForm.reset();
|
||||
|
||||
} else {
|
||||
// Should be caught by fetchAPI, but as a fallback
|
||||
showMessage(loginError, data.message || 'Login fehlgeschlagen.');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(loginError, `Login fehlgeschlagen: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
authToken = null;
|
||||
currentUser = null;
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('currentUser');
|
||||
updateUIBasedOnAuthState();
|
||||
// Clear any sensitive data displayed on the page
|
||||
tournamentList.innerHTML = ''; // Clear tournament list
|
||||
// Add similar clearing for other sections if needed
|
||||
};
|
||||
|
||||
// --- UI Update Functions ---
|
||||
|
||||
const updateUIBasedOnAuthState = () => {
|
||||
if (authToken && currentUser && currentUser.role === 'admin') {
|
||||
// Logged in as Admin
|
||||
loginSection.classList.add('hidden');
|
||||
adminContent.classList.remove('hidden');
|
||||
logoutButton.classList.remove('hidden');
|
||||
welcomeMessage.textContent = `Willkommen, ${currentUser.username}! (Admin)`;
|
||||
welcomeMessage.classList.remove('hidden');
|
||||
} else {
|
||||
// Logged out or not an Admin
|
||||
loginSection.classList.remove('hidden');
|
||||
adminContent.classList.add('hidden');
|
||||
logoutButton.classList.add('hidden');
|
||||
welcomeMessage.classList.add('hidden');
|
||||
// If logged in but not admin, show appropriate message
|
||||
if (currentUser && currentUser.role !== 'admin') {
|
||||
showMessage(loginError, 'Sie sind angemeldet, haben aber keine Admin-Berechtigung für diese Seite.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- Tournament Functions ---
|
||||
|
||||
// Reset and hide the tournament form
|
||||
const resetAndHideTournamentForm = () => {
|
||||
tournamentForm.reset(); // Clear form fields
|
||||
tournamentIdInput.value = ''; // Clear hidden ID field
|
||||
tournamentFormTitle.textContent = 'Neues Turnier hinzufügen'; // Reset title
|
||||
saveTournamentButton.textContent = 'Speichern'; // Reset button text
|
||||
hideMessage(tournamentFormError); // Hide any previous form errors
|
||||
tournamentForm.classList.add('hidden'); // Hide the form
|
||||
};
|
||||
|
||||
// Show the tournament form for adding or editing
|
||||
const showTournamentForm = (tournament = null) => {
|
||||
resetAndHideTournamentForm(); // Start clean
|
||||
|
||||
if (tournament) {
|
||||
// Editing existing tournament
|
||||
tournamentFormTitle.textContent = 'Turnier bearbeiten';
|
||||
saveTournamentButton.textContent = 'Änderungen speichern';
|
||||
tournamentIdInput.value = tournament.tournament_id;
|
||||
tournamentNameInput.value = tournament.name || '';
|
||||
// Format date correctly for input type="date" (YYYY-MM-DD)
|
||||
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';
|
||||
tournamentDescriptionInput.value = tournament.description || '';
|
||||
// Populate other fields as needed (max_players, status, etc.)
|
||||
} else {
|
||||
// Adding new tournament - title and button text are already set by reset
|
||||
}
|
||||
|
||||
tournamentForm.classList.remove('hidden'); // Show the form
|
||||
tournamentNameInput.focus(); // Focus the first field
|
||||
};
|
||||
|
||||
// Load tournaments from the API and display them
|
||||
const loadTournaments = async () => {
|
||||
if (!authToken) return; // Don't load if not logged in
|
||||
|
||||
loadingTournaments.classList.remove('hidden');
|
||||
tournamentList.innerHTML = ''; // Clear existing list
|
||||
hideMessage(tournamentListMessage);
|
||||
|
||||
try {
|
||||
const tournaments = await fetchAPI('/tournaments');
|
||||
|
||||
if (tournaments.length === 0) {
|
||||
tournamentList.innerHTML = '<tr><td colspan="6">Keine Turniere gefunden.</td></tr>';
|
||||
} else {
|
||||
tournaments.forEach(tournament => {
|
||||
const row = tournamentList.insertRow();
|
||||
row.dataset.id = tournament.tournament_id; // Store ID for easier access
|
||||
|
||||
// Format date for display (DD.MM.YYYY or leave empty)
|
||||
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">Bearbeiten</button>
|
||||
<button class="delete-tournament danger">Löschen</button>
|
||||
</td>
|
||||
`;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(tournamentListMessage, `Fehler beim Laden der Turniere: ${error.message}`);
|
||||
tournamentList.innerHTML = `<tr><td colspan="6">Fehler beim Laden der Daten.</td></tr>`; // Indicate error in table
|
||||
} finally {
|
||||
loadingTournaments.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle saving (creating or updating) a tournament
|
||||
const handleSaveTournament = async (event) => {
|
||||
event.preventDefault();
|
||||
hideMessage(tournamentFormError);
|
||||
|
||||
const tournamentId = tournamentIdInput.value;
|
||||
const isEditing = !!tournamentId; // Check if we are editing
|
||||
|
||||
// Gather data from form
|
||||
const tournamentData = {
|
||||
name: tournamentNameInput.value.trim(),
|
||||
date: tournamentDateInput.value || null, // Send null if empty
|
||||
location: tournamentLocationInput.value.trim() || null,
|
||||
tournament_type: tournamentTypeInput.value,
|
||||
game_type: tournamentGameTypeInput.value,
|
||||
description: tournamentDescriptionInput.value.trim() || null,
|
||||
// Add other fields (max_players, status) here if they are in the form
|
||||
};
|
||||
|
||||
// Basic validation
|
||||
if (!tournamentData.name) {
|
||||
showMessage(tournamentFormError, 'Turniername ist erforderlich.');
|
||||
return;
|
||||
}
|
||||
|
||||
const method = isEditing ? 'PUT' : 'POST';
|
||||
const endpoint = isEditing ? `/tournaments/${tournamentId}` : '/tournaments';
|
||||
|
||||
try {
|
||||
const savedTournament = await fetchAPI(endpoint, {
|
||||
method: method,
|
||||
body: JSON.stringify(tournamentData),
|
||||
});
|
||||
|
||||
showMessage(tournamentListMessage, `Turnier erfolgreich ${isEditing ? 'aktualisiert' : 'gespeichert'}.`, false); // Success message
|
||||
resetAndHideTournamentForm();
|
||||
loadTournaments(); // Refresh the list
|
||||
|
||||
} catch (error) {
|
||||
showMessage(tournamentFormError, `Fehler beim Speichern des Turniers: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle deleting a tournament
|
||||
const handleDeleteTournament = async (tournamentId, tournamentName) => {
|
||||
if (!tournamentId) return;
|
||||
|
||||
// Confirmation dialog
|
||||
if (!confirm(`Möchten Sie das Turnier "${tournamentName}" wirklich löschen? Alle zugehörigen Spiele und Daten gehen verloren!`)) {
|
||||
return; // User cancelled
|
||||
}
|
||||
|
||||
hideMessage(tournamentListMessage);
|
||||
|
||||
try {
|
||||
await fetchAPI(`/tournaments/${tournamentId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
showMessage(tournamentListMessage, `Turnier "${tournamentName}" erfolgreich gelöscht.`, false); // Success message
|
||||
loadTournaments(); // Refresh the list
|
||||
|
||||
} catch (error) {
|
||||
showMessage(tournamentListMessage, `Fehler beim Löschen des Turniers: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- Event Listeners ---
|
||||
|
||||
// Login form submission
|
||||
loginForm.addEventListener('submit', handleLogin);
|
||||
|
||||
// Logout button click
|
||||
logoutButton.addEventListener('click', logout);
|
||||
|
||||
// Show "Add Tournament" form button
|
||||
showAddTournamentFormButton.addEventListener('click', () => showTournamentForm());
|
||||
|
||||
// Cancel button in tournament form
|
||||
cancelTournamentButton.addEventListener('click', resetAndHideTournamentForm);
|
||||
|
||||
// Tournament form submission (Save/Update)
|
||||
tournamentForm.addEventListener('submit', handleSaveTournament);
|
||||
|
||||
// Event delegation for Edit and Delete buttons in the tournament list
|
||||
tournamentList.addEventListener('click', async (event) => {
|
||||
const target = event.target;
|
||||
const row = target.closest('tr'); // Find the table row
|
||||
if (!row) return; // Click wasn't inside a row
|
||||
|
||||
const tournamentId = row.dataset.id;
|
||||
if (!tournamentId) return; // Row doesn't have an ID
|
||||
|
||||
if (target.classList.contains('edit-tournament')) {
|
||||
// Edit button clicked
|
||||
hideMessage(tournamentListMessage); // Hide list messages when editing
|
||||
try {
|
||||
// Fetch the specific tournament data to pre-fill the form accurately
|
||||
const tournamentToEdit = await fetchAPI(`/tournaments/${tournamentId}`);
|
||||
showTournamentForm(tournamentToEdit);
|
||||
} catch (error) {
|
||||
showMessage(tournamentListMessage, `Fehler beim Laden der Turnierdaten zum Bearbeiten: ${error.message}`);
|
||||
}
|
||||
} else if (target.classList.contains('delete-tournament')) {
|
||||
// Delete button clicked
|
||||
const tournamentName = row.cells[0].textContent; // Get name from the first cell
|
||||
handleDeleteTournament(tournamentId, tournamentName);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- Initial Load ---
|
||||
|
||||
// Check authentication state and update UI on page load
|
||||
updateUIBasedOnAuthState();
|
||||
|
||||
// Load initial data if logged in as admin
|
||||
if (authToken && currentUser && currentUser.role === 'admin') {
|
||||
loadTournaments();
|
||||
// Load other data (players, users) here as well
|
||||
}
|
Reference in New Issue
Block a user