This commit is contained in:
MLH
2025-04-07 01:13:36 +02:00
parent 3bf2aae1a3
commit cdd3e180a0
24 changed files with 5156 additions and 0 deletions

311
public/referee.html Normal file
View File

@ -0,0 +1,311 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schiedsrichter-Bereich</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<nav>
<a href="/admin.html">Admin</a>
<a href="/referee.html" class="active">Schiedsrichter</a>
<a href="/spectator.html">Zuschauer</a>
<a href="#" id="logout-button" style="float: right;" class="hidden">Logout</a>
</nav>
<div class="container">
<h1>Schiedsrichter-Bereich</h1>
<p id="welcome-message" class="hidden">Willkommen!</p>
<div id="login-section" class="auth-form">
<h2>Login (Schiedsrichter)</h2>
<form id="login-form">
<div>
<label for="login-username">Benutzername:</label>
<input type="text" id="login-username" required>
</div>
<div>
<label for="login-password">Passwort:</label>
<input type="password" id="login-password" required>
</div>
<button type="submit">Login</button>
<div id="login-error" class="error-message hidden"></div>
</form>
</div>
<div id="referee-content" class="hidden">
<section class="content-section">
<h2>Spiel auswählen & Ergebnisse eintragen</h2>
<p>Hier kann der Schiedsrichter ein ihm zugewiesenes Spiel auswählen und die Satzergebnisse eintragen.</p>
<div id="match-selection">
<label for="select-match">Spiel auswählen:</label>
<select id="select-match" disabled>
<option value="">-- Bitte Spiel wählen --</option>
</select>
<p id="loading-matches" class="hidden">Lade Spiele...</p>
</div>
<div id="score-entry" class="hidden">
<h3>Ergebnisse für Spiel: <span id="match-details"></span></h3>
<form id="score-form">
<div id="set-inputs"></div>
<button type="button" id="add-set-button">Satz hinzufügen</button>
<button type="submit">Ergebnis speichern</button>
<div id="score-error" class="error-message hidden"></div>
<div id="score-success" class="success-message hidden"></div>
</form>
</div>
<p><i>(Funktionalität zum Laden von Spielen und Speichern von Ergebnissen muss noch implementiert werden.)</i></p>
</section>
</div> </div> <script>
// Basic JS for Referee Login/Logout (similar to admin.js but checks for 'referee' or 'admin' role)
const loginSectionRef = document.getElementById('login-section');
const refereeContent = document.getElementById('referee-content');
const loginFormRef = document.getElementById('login-form');
const loginUsernameInputRef = document.getElementById('login-username');
const loginPasswordInputRef = document.getElementById('login-password');
const loginErrorRef = document.getElementById('login-error');
const logoutButtonRef = document.getElementById('logout-button');
const welcomeMessageRef = document.getElementById('welcome-message');
const selectMatch = document.getElementById('select-match');
const scoreEntry = document.getElementById('score-entry');
const matchDetails = document.getElementById('match-details');
const setInputs = document.getElementById('set-inputs');
const addSetButton = document.getElementById('add-set-button');
const scoreForm = document.getElementById('score-form');
const scoreError = document.getElementById('score-error');
const scoreSuccess = document.getElementById('score-success');
const loadingMatches = document.getElementById('loading-matches');
let authTokenRef = localStorage.getItem('authToken');
let currentUserRef = JSON.parse(localStorage.getItem('currentUser'));
const API_BASE_URL_REF = '/api'; // Consistent API base
const showMessageRef = (element, message, isError = true) => {
element.textContent = message;
element.className = isError ? 'error-message' : 'success-message';
element.classList.remove('hidden');
};
const hideMessageRef = (element) => {
element.classList.add('hidden');
element.textContent = '';
};
const fetchAPIRef = async (endpoint, options = {}) => {
// Reusing fetchAPI logic requires it to be in a shared file or duplicated.
// For simplicity here, we assume a similar function exists or reuse admin.js's if loaded globally (not recommended).
// Here's a simplified version for demonstration:
const headers = { 'Content-Type': 'application/json', ...options.headers };
if (authTokenRef) {
headers['Authorization'] = `Bearer ${authTokenRef}`;
}
try {
const response = await fetch(`${API_BASE_URL_REF}${endpoint}`, { ...options, headers });
if (response.status === 401 || response.status === 403) {
logoutRef();
throw new Error('Authentifizierung fehlgeschlagen.');
}
if (!response.ok) {
let errorData;
try { errorData = await response.json(); } catch { errorData = { message: response.statusText }; }
throw new Error(errorData.message || `HTTP Error: ${response.status}`);
}
if (response.status === 204) return null;
return await response.json();
} catch (error) {
console.error('API Fetch Error (Referee):', error);
throw error;
}
};
const handleLoginRef = async (event) => {
event.preventDefault();
hideMessageRef(loginErrorRef);
const username = loginUsernameInputRef.value.trim();
const password = loginPasswordInputRef.value.trim();
if (!username || !password) {
showMessageRef(loginErrorRef, 'Bitte Benutzername und Passwort eingeben.');
return;
}
try {
const data = await fetchAPIRef('/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
// Allow 'admin' or 'referee' role
if (data.token && data.user && (data.user.role === 'referee' || data.user.role === 'admin')) {
authTokenRef = data.token;
currentUserRef = data.user;
localStorage.setItem('authToken', authTokenRef);
localStorage.setItem('currentUser', JSON.stringify(currentUserRef));
updateUIRef();
loadRefereeMatches(); // Load matches for the referee
loginFormRef.reset();
} else if (data.user && data.user.role !== 'referee' && data.user.role !== 'admin') {
showMessageRef(loginErrorRef, 'Zugriff verweigert. Nur Schiedsrichter oder Admins können sich hier anmelden.');
} else {
showMessageRef(loginErrorRef, data.message || 'Login fehlgeschlagen.');
}
} catch (error) {
showMessageRef(loginErrorRef, `Login fehlgeschlagen: ${error.message}`);
}
};
const logoutRef = () => {
authTokenRef = null;
currentUserRef = null;
localStorage.removeItem('authToken');
localStorage.removeItem('currentUser');
updateUIRef();
// Clear referee specific data
selectMatch.innerHTML = '<option value="">-- Bitte Spiel wählen --</option>';
selectMatch.disabled = true;
scoreEntry.classList.add('hidden');
};
const updateUIRef = () => {
if (authTokenRef && currentUserRef && (currentUserRef.role === 'referee' || currentUserRef.role === 'admin')) {
loginSectionRef.classList.add('hidden');
refereeContent.classList.remove('hidden');
logoutButtonRef.classList.remove('hidden');
welcomeMessageRef.textContent = `Willkommen, ${currentUserRef.username}! (${currentUserRef.role})`;
welcomeMessageRef.classList.remove('hidden');
} else {
loginSectionRef.classList.remove('hidden');
refereeContent.classList.add('hidden');
logoutButtonRef.classList.add('hidden');
welcomeMessageRef.classList.add('hidden');
if (currentUserRef) { // Logged in but wrong role
showMessageRef(loginErrorRef, 'Sie sind angemeldet, haben aber keine Schiedsrichter-Berechtigung für diese Seite.');
}
}
};
// --- Referee Specific Functions (Placeholders) ---
const loadRefereeMatches = async () => {
// TODO: Implement API call to fetch matches assigned to this referee (or all ongoing matches if admin)
console.log("Lade Spiele für Schiedsrichter...");
loadingMatches.classList.remove('hidden');
selectMatch.disabled = true;
try {
// Example: Fetch matches (needs backend endpoint)
// const matches = await fetchAPIRef('/matches?status=ongoing&assignee=me'); // Fictional endpoint
const matches = []; // Placeholder
selectMatch.innerHTML = '<option value="">-- Bitte Spiel wählen --</option>'; // Reset
if (matches.length === 0) {
selectMatch.innerHTML += '<option value="" disabled>Keine aktiven Spiele gefunden</option>';
} else {
matches.forEach(match => {
const option = document.createElement('option');
option.value = match.match_id;
// Construct a readable match description
option.textContent = `Runde ${match.round || '?'} - Tisch ${match.table_number || '?'} (${match.player1_name || 'N/A'} vs ${match.player2_name || 'N/A'})`;
selectMatch.appendChild(option);
});
selectMatch.disabled = false;
}
showMessageRef(document.getElementById('match-selection'), 'TODO: Lade zugewiesene/aktuelle Spiele.', false);
} catch (error) {
showMessageRef(document.getElementById('match-selection'), `Fehler beim Laden der Spiele: ${error.message}`);
} finally {
loadingMatches.classList.add('hidden');
}
};
const displayScoreEntryForm = (matchId) => {
// TODO: Fetch current scores for the selected match if they exist
console.log("Zeige Formular für Match ID:", matchId);
hideMessageRef(scoreError);
hideMessageRef(scoreSuccess);
// Placeholder: Display match identifier
matchDetails.textContent = `ID ${matchId.substring(0, 8)}...`; // Show partial ID
// TODO: Dynamically create input fields based on game type (best of 3/5 sets) and current scores
setInputs.innerHTML = `
<div>Satz 1: <input type="number" min="0" placeholder="P1"> - <input type="number" min="0" placeholder="P2"></div>
<div>Satz 2: <input type="number" min="0" placeholder="P1"> - <input type="number" min="0" placeholder="P2"></div>
<div>Satz 3: <input type="number" min="0" placeholder="P1"> - <input type="number" min="0" placeholder="P2"></div>
`;
scoreEntry.classList.remove('hidden');
};
const handleAddSet = () => {
// TODO: Add another row of set score inputs
const setNumber = setInputs.children.length + 1;
const div = document.createElement('div');
div.innerHTML = `Satz ${setNumber}: <input type="number" min="0" placeholder="P1"> - <input type="number" min="0" placeholder="P2">`;
setInputs.appendChild(div);
};
const handleSaveScore = async (event) => {
event.preventDefault();
const matchId = selectMatch.value;
if (!matchId) return;
hideMessageRef(scoreError);
hideMessageRef(scoreSuccess);
// TODO: Collect scores from input fields
const scores = [];
const setDivs = setInputs.querySelectorAll('div');
setDivs.forEach((div, index) => {
const inputs = div.querySelectorAll('input[type="number"]');
const score1 = inputs[0].value;
const score2 = inputs[1].value;
// Basic validation: Add scores only if both are entered
if (score1 !== '' && score2 !== '') {
scores.push({ set_number: index + 1, player1_score: parseInt(score1), player2_score: parseInt(score2) });
}
});
console.log("Speichere Ergebnis für Match", matchId, scores);
try {
// TODO: Implement API call to save scores
// await fetchAPIRef(`/matches/${matchId}/score`, { method: 'POST', body: JSON.stringify({ sets: scores }) });
showMessageRef(scoreSuccess, 'Ergebnis erfolgreich gespeichert (TODO: Implement API Call).', false);
// Optionally: Reload matches or update status
} catch (error) {
showMessageRef(scoreError, `Fehler beim Speichern: ${error.message}`);
}
};
// --- Event Listeners ---
loginFormRef.addEventListener('submit', handleLoginRef);
logoutButtonRef.addEventListener('click', logoutRef);
selectMatch.addEventListener('change', (event) => {
const selectedMatchId = event.target.value;
if (selectedMatchId) {
displayScoreEntryForm(selectedMatchId);
} else {
scoreEntry.classList.add('hidden');
}
});
addSetButton.addEventListener('click', handleAddSet);
scoreForm.addEventListener('submit', handleSaveScore);
// --- Initial Load ---
updateUIRef();
if (authTokenRef && currentUserRef && (currentUserRef.role === 'referee' || currentUserRef.role === 'admin')) {
loadRefereeMatches();
}
</script>
</body>
</html>