gemini enhancements
This commit is contained in:
@@ -7,28 +7,20 @@
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
/* Zusätzliche Stile für die Ergebniseingabe */
|
||||
.set-score-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px; /* Abstand zwischen Elementen */
|
||||
.set-score-row { display: flex; align-items: center; margin-bottom: 10px; gap: 10px; }
|
||||
.set-score-row label { width: 50px; margin-bottom: 0; }
|
||||
.set-score-row input[type="number"] { width: 60px; margin-bottom: 0; text-align: center; }
|
||||
.set-score-row span { margin: 0 5px; }
|
||||
#match-details-players { font-weight: bold; margin-bottom: 15px; }
|
||||
/* Stile für die Match-Liste */
|
||||
#match-list-referee .match-item {
|
||||
border: 1px solid #eee; padding: 10px; margin-bottom: 10px; border-radius: 4px;
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
}
|
||||
.set-score-row label {
|
||||
width: 50px; /* Feste Breite für "Satz X:" */
|
||||
margin-bottom: 0; /* Label neben Inputs */
|
||||
}
|
||||
.set-score-row input[type="number"] {
|
||||
width: 60px; /* Schmalere Input-Felder für Punkte */
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.set-score-row span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
#match-details-players {
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#match-list-referee .match-item span { font-size: 0.9em; }
|
||||
#match-list-referee .match-item button { padding: 5px 10px; font-size: 0.9em; }
|
||||
#no-active-tournament-referee { font-weight: bold; color: #dc3545; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -46,14 +38,8 @@
|
||||
<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>
|
||||
<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>
|
||||
@@ -61,13 +47,14 @@
|
||||
|
||||
<div id="referee-content" class="hidden">
|
||||
<section class="content-section">
|
||||
<h2>Spiel auswählen & Ergebnisse eintragen</h2>
|
||||
<h2>Aktuelles Turnier: <span id="active-tournament-name">Lädt...</span></h2>
|
||||
<p id="no-active-tournament-referee" class="hidden">Derzeit ist kein Turnier als 'laufend' markiert.</p>
|
||||
|
||||
<div id="match-selection">
|
||||
<label for="select-match">Aktives/Geplantes Spiel auswählen:</label>
|
||||
<select id="select-match" disabled>
|
||||
<option value="">-- Bitte Spiel wählen --</option>
|
||||
</select>
|
||||
<h3>Verfügbare Spiele (Geplant / Laufend)</h3>
|
||||
<div id="match-list-referee">
|
||||
<p><i>Wähle ein Spiel aus der Liste, um Ergebnisse einzutragen.</i></p>
|
||||
</div>
|
||||
<p id="loading-matches" class="loading-indicator hidden">Lade verfügbare Spiele...</p>
|
||||
<div id="match-load-error" class="message-area error-message hidden"></div>
|
||||
</div>
|
||||
@@ -76,8 +63,7 @@
|
||||
<h3>Ergebnisse eintragen</h3>
|
||||
<div id="match-details-players">Spieler: Lädt...</div>
|
||||
<form id="score-form">
|
||||
<div id="set-inputs">
|
||||
</div>
|
||||
<div id="set-inputs"></div>
|
||||
<div style="margin-top: 15px;">
|
||||
<button type="button" id="add-set-button">Weiteren Satz hinzufügen</button>
|
||||
<button type="submit">Ergebnis speichern</button>
|
||||
@@ -97,7 +83,9 @@
|
||||
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 activeTournamentNameSpan = document.getElementById('active-tournament-name');
|
||||
const noActiveTournamentMessage = document.getElementById('no-active-tournament-referee');
|
||||
const matchListRefereeDiv = document.getElementById('match-list-referee'); // Div to list matches
|
||||
const loadingMatches = document.getElementById('loading-matches');
|
||||
const matchLoadError = document.getElementById('match-load-error');
|
||||
const scoreEntry = document.getElementById('score-entry');
|
||||
@@ -111,397 +99,238 @@
|
||||
// --- State ---
|
||||
let authTokenRef = localStorage.getItem('authToken');
|
||||
let currentUserRef = JSON.parse(localStorage.getItem('currentUser'));
|
||||
let currentSelectedMatch = null; // Store details of the selected match
|
||||
let currentSelectedMatch = null; // Store details of the selected match for scoring
|
||||
let activeTournamentId = null; // Store the ID of the currently running tournament
|
||||
|
||||
// --- API Base URL ---
|
||||
const API_BASE_URL_REF = '/api';
|
||||
|
||||
// --- Helper Functions ---
|
||||
const showMessageRef = (element, message, isError = true, autohide = false) => {
|
||||
if (!element) return;
|
||||
element.textContent = message;
|
||||
element.className = `message-area ${isError ? 'error-message' : 'success-message'}`;
|
||||
element.classList.remove('hidden');
|
||||
if (autohide) {
|
||||
setTimeout(() => hideMessageRef(element), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
const hideMessageRef = (element) => {
|
||||
if (element) {
|
||||
element.classList.add('hidden');
|
||||
element.textContent = '';
|
||||
}
|
||||
};
|
||||
|
||||
const setLoadingRef = (element, isLoading) => {
|
||||
if (element) {
|
||||
element.classList.toggle('hidden', !isLoading);
|
||||
}
|
||||
}
|
||||
// --- Helper Functions (showMessageRef, hideMessageRef, setLoadingRef) ---
|
||||
const showMessageRef = (element, message, isError = true, autohide = false) => { if (!element) return; element.textContent = message; element.className = `message-area ${isError ? 'error-message' : 'success-message'}`; element.classList.remove('hidden'); if (autohide) { setTimeout(() => hideMessageRef(element), 5000); } };
|
||||
const hideMessageRef = (element) => { if (element) { element.classList.add('hidden'); element.textContent = ''; } };
|
||||
const setLoadingRef = (element, isLoading) => { if (element) { element.classList.toggle('hidden', !isLoading); } };
|
||||
|
||||
// --- API Fetch Function ---
|
||||
const fetchAPIRef = async (endpoint, options = {}) => {
|
||||
const headers = { 'Content-Type': 'application/json', ...options.headers };
|
||||
if (authTokenRef) {
|
||||
headers['Authorization'] = `Bearer ${authTokenRef}`;
|
||||
}
|
||||
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(); // Logout on auth error
|
||||
throw new Error('Authentifizierung fehlgeschlagen oder Token abgelaufen.');
|
||||
}
|
||||
if (!response.ok) {
|
||||
let errorData = { message: `HTTP-Fehler: ${response.status} ${response.statusText}` };
|
||||
try {
|
||||
const parsedError = await response.json();
|
||||
if (parsedError && parsedError.message) errorData.message = parsedError.message;
|
||||
} catch (e) { /* Ignore parsing error */ }
|
||||
throw new Error(errorData.message);
|
||||
}
|
||||
if (response.status === 204) return null; // Handle No Content
|
||||
if (response.status === 401 || response.status === 403) { logoutRef(); throw new Error('Authentifizierung fehlgeschlagen.'); }
|
||||
if (!response.ok) { let e = { m: `HTTP-Fehler: ${response.status} ${response.statusText}` }; try { const p = await response.json(); if (p && p.message) e.m = p.message; } catch (err) {} throw new Error(e.m); }
|
||||
if (response.status === 204) return null;
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API Fetch Error (Referee):', error);
|
||||
throw error; // Re-throw for handling in calling function
|
||||
}
|
||||
} catch (error) { console.error('API Fetch Error (Referee):', error); throw error; }
|
||||
};
|
||||
|
||||
// --- Authentication ---
|
||||
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;
|
||||
}
|
||||
|
||||
const handleLoginRef = async (event) => { /* ... (same as referee_html_v2) ... */
|
||||
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();
|
||||
const data = await fetchAPIRef('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) });
|
||||
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(); loadActiveTournamentAndMatches(); loginFormRef.reset(); // Load data for active tournament
|
||||
} 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.');
|
||||
logoutRef(); // Log out if wrong role but valid login somehow occurred
|
||||
} else {
|
||||
showMessageRef(loginErrorRef, data.message || 'Login fehlgeschlagen.');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessageRef(loginErrorRef, `Login fehlgeschlagen: ${error.message}`);
|
||||
}
|
||||
showMessageRef(loginErrorRef, 'Zugriff verweigert. Nur Schiedsrichter oder Admins.'); logoutRef();
|
||||
} 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');
|
||||
currentSelectedMatch = null;
|
||||
const logoutRef = () => { /* ... (same as referee_html_v2) ... */
|
||||
authTokenRef = null; currentUserRef = null; activeTournamentId = null; currentSelectedMatch = null;
|
||||
localStorage.removeItem('authToken'); localStorage.removeItem('currentUser');
|
||||
updateUIRef(); matchListRefereeDiv.innerHTML = ''; scoreEntry.classList.add('hidden');
|
||||
activeTournamentNameSpan.textContent = 'Unbekannt'; noActiveTournamentMessage.classList.add('hidden');
|
||||
};
|
||||
|
||||
const updateUIRef = () => {
|
||||
const updateUIRef = () => { /* ... (same as referee_html_v2) ... */
|
||||
const isAllowedUser = authTokenRef && currentUserRef && (currentUserRef.role === 'referee' || currentUserRef.role === 'admin');
|
||||
loginSectionRef.classList.toggle('hidden', isAllowedUser);
|
||||
refereeContent.classList.toggle('hidden', !isAllowedUser);
|
||||
logoutButtonRef.classList.toggle('hidden', !isAllowedUser);
|
||||
welcomeMessageRef.classList.toggle('hidden', !isAllowedUser);
|
||||
if (isAllowedUser) {
|
||||
welcomeMessageRef.textContent = `Willkommen, ${currentUserRef.username}! (${currentUserRef.role})`;
|
||||
} else {
|
||||
if (currentUserRef) { // Logged in but wrong role
|
||||
showMessageRef(loginErrorRef, 'Sie sind angemeldet, haben aber keine Schiedsrichter-Berechtigung für diese Seite.');
|
||||
}
|
||||
}
|
||||
loginSectionRef.classList.toggle('hidden', isAllowedUser); refereeContent.classList.toggle('hidden', !isAllowedUser);
|
||||
logoutButtonRef.classList.toggle('hidden', !isAllowedUser); welcomeMessageRef.classList.toggle('hidden', !isAllowedUser);
|
||||
if (isAllowedUser) { welcomeMessageRef.textContent = `Willkommen, ${currentUserRef.username}! (${currentUserRef.role})`; }
|
||||
else { if (currentUserRef) { showMessageRef(loginErrorRef, 'Keine Berechtigung für diese Seite.'); } }
|
||||
};
|
||||
|
||||
// --- Referee Specific Functions ---
|
||||
|
||||
const loadRefereeMatches = async () => {
|
||||
console.log("Lade Spiele für Schiedsrichter...");
|
||||
// Find the active tournament and load its matches
|
||||
const loadActiveTournamentAndMatches = async () => {
|
||||
console.log("Suche aktives Turnier...");
|
||||
setLoadingRef(loadingMatches, true);
|
||||
selectMatch.disabled = true;
|
||||
selectMatch.innerHTML = '<option value="">Lade Spiele...</option>';
|
||||
matchListRefereeDiv.innerHTML = ''; // Clear previous list
|
||||
hideMessageRef(matchLoadError);
|
||||
scoreEntry.classList.add('hidden'); // Hide score entry while loading matches
|
||||
currentSelectedMatch = null;
|
||||
|
||||
let allRelevantMatches = [];
|
||||
noActiveTournamentMessage.classList.add('hidden');
|
||||
activeTournamentNameSpan.textContent = 'Lädt...';
|
||||
activeTournamentId = null; // Reset active tournament ID
|
||||
scoreEntry.classList.add('hidden'); // Hide score entry initially
|
||||
|
||||
try {
|
||||
// Inefficient approach: Fetch all tournaments, then fetch relevant matches for each.
|
||||
// A dedicated backend endpoint would be much better.
|
||||
console.warn("Fetching all tournaments to find matches - consider optimizing backend.");
|
||||
const tournaments = await fetchAPIRef('/tournaments'); // Assumes this endpoint is available
|
||||
const tournaments = await fetchAPIRef('/tournaments');
|
||||
const runningTournament = tournaments.find(t => t.status === 'running');
|
||||
|
||||
// Array to hold promises for fetching matches for each tournament
|
||||
const matchFetchPromises = tournaments.map(tournament =>
|
||||
fetchAPIRef(`/matches?tournamentId=${tournament.tournament_id}&status=scheduled`)
|
||||
.catch(e => { console.error(`Error fetching scheduled matches for ${tournament.name}:`, e); return []; }) // Ignore errors for single tournament fetch
|
||||
);
|
||||
const matchFetchPromisesOngoing = tournaments.map(tournament =>
|
||||
fetchAPIRef(`/matches?tournamentId=${tournament.tournament_id}&status=ongoing`)
|
||||
.catch(e => { console.error(`Error fetching ongoing matches for ${tournament.name}:`, e); return []; }) // Ignore errors for single tournament fetch
|
||||
);
|
||||
|
||||
|
||||
// Wait for all fetches to complete
|
||||
const resultsScheduled = await Promise.all(matchFetchPromises);
|
||||
const resultsOngoing = await Promise.all(matchFetchPromisesOngoing);
|
||||
|
||||
// Flatten the results and add tournament name for display
|
||||
resultsScheduled.forEach((matches, index) => {
|
||||
matches.forEach(match => {
|
||||
match.tournament_name = tournaments[index].name; // Add tournament name
|
||||
allRelevantMatches.push(match);
|
||||
});
|
||||
});
|
||||
resultsOngoing.forEach((matches, index) => {
|
||||
matches.forEach(match => {
|
||||
match.tournament_name = tournaments[index].name; // Add tournament name
|
||||
// Avoid duplicates if a match was somehow fetched twice
|
||||
if (!allRelevantMatches.some(m => m.match_id === match.match_id)) {
|
||||
allRelevantMatches.push(match);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Sort matches (e.g., by tournament, then round, then time)
|
||||
allRelevantMatches.sort((a, b) => {
|
||||
if (a.tournament_name < b.tournament_name) return -1;
|
||||
if (a.tournament_name > b.tournament_name) return 1;
|
||||
if ((a.round || 999) < (b.round || 999)) return -1; // Treat null rounds as last
|
||||
if ((a.round || 999) > (b.round || 999)) return 1;
|
||||
const timeA = a.scheduled_time ? new Date(a.scheduled_time).getTime() : Infinity;
|
||||
const timeB = b.scheduled_time ? new Date(b.scheduled_time).getTime() : Infinity;
|
||||
return timeA - timeB;
|
||||
});
|
||||
|
||||
// Populate dropdown
|
||||
selectMatch.innerHTML = '<option value="">-- Bitte Spiel wählen --</option>'; // Reset
|
||||
if (allRelevantMatches.length === 0) {
|
||||
selectMatch.innerHTML += '<option value="" disabled>Keine aktiven/geplanten Spiele gefunden</option>';
|
||||
} else {
|
||||
allRelevantMatches.forEach(match => {
|
||||
const option = document.createElement('option');
|
||||
option.value = match.match_id;
|
||||
const timeStr = match.scheduled_time ? `(${new Date(match.scheduled_time).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })})` : '';
|
||||
option.textContent = `[${match.tournament_name}] Rd ${match.round || '?'} Tisch ${match.table_number || '?'} - ${match.player1_name || 'N/A'} vs ${match.player2_name || 'N/A'} ${timeStr}`;
|
||||
selectMatch.appendChild(option);
|
||||
});
|
||||
selectMatch.disabled = false;
|
||||
if (!runningTournament) {
|
||||
activeTournamentNameSpan.textContent = 'Kein Aktives';
|
||||
noActiveTournamentMessage.classList.remove('hidden');
|
||||
setLoadingRef(loadingMatches, false);
|
||||
return; // Stop if no tournament is running
|
||||
}
|
||||
|
||||
activeTournamentId = runningTournament.tournament_id;
|
||||
activeTournamentNameSpan.textContent = runningTournament.name;
|
||||
console.log(`Aktives Turnier gefunden: ${runningTournament.name} (${activeTournamentId})`);
|
||||
|
||||
// Now load matches for this tournament
|
||||
await loadRefereeMatches(activeTournamentId);
|
||||
|
||||
} catch (error) {
|
||||
showMessageRef(matchLoadError, `Fehler beim Laden der Spiele: ${error.message}`);
|
||||
selectMatch.innerHTML = '<option value="" disabled>Fehler beim Laden</option>';
|
||||
} finally {
|
||||
setLoadingRef(loadingMatches, false);
|
||||
showMessageRef(matchLoadError, `Fehler beim Laden des aktiven Turniers: ${error.message}`);
|
||||
activeTournamentNameSpan.textContent = 'Fehler';
|
||||
setLoadingRef(loadingMatches, false);
|
||||
}
|
||||
};
|
||||
|
||||
// Display the score entry form for the selected match
|
||||
const displayScoreEntryForm = async (matchId) => {
|
||||
console.log("Zeige Formular für Match ID:", matchId);
|
||||
hideMessageRef(scoreError);
|
||||
hideMessageRef(scoreSuccess);
|
||||
setInputs.innerHTML = '<div>Lade Spieldetails...</div>'; // Placeholder
|
||||
scoreEntry.classList.remove('hidden');
|
||||
// Load scheduled/ongoing matches for the given tournament ID
|
||||
const loadRefereeMatches = async (tournamentId) => {
|
||||
if (!tournamentId) return; // Should not happen if called correctly
|
||||
setLoadingRef(loadingMatches, true); // Already set, but safe to repeat
|
||||
matchListRefereeDiv.innerHTML = ''; // Clear previous list
|
||||
hideMessageRef(matchLoadError);
|
||||
|
||||
try {
|
||||
// Fetch full match details, including existing sets and player names
|
||||
// Fetch scheduled and ongoing matches for the active tournament
|
||||
const scheduledPromise = fetchAPIRef(`/matches?tournamentId=${tournamentId}&status=scheduled`);
|
||||
const ongoingPromise = fetchAPIRef(`/matches?tournamentId=${tournamentId}&status=ongoing`);
|
||||
|
||||
const [scheduledMatches, ongoingMatches] = await Promise.all([scheduledPromise, ongoingPromise]);
|
||||
const allRelevantMatches = [...scheduledMatches, ...ongoingMatches];
|
||||
|
||||
// Sort matches (e.g., by round, then time)
|
||||
allRelevantMatches.sort((a, b) => { /* ... sorting logic ... */
|
||||
if ((a.round || 999) < (b.round || 999)) return -1; if ((a.round || 999) > (b.round || 999)) return 1;
|
||||
const timeA = a.scheduled_time ? new Date(a.scheduled_time).getTime() : Infinity; const timeB = b.scheduled_time ? new Date(b.scheduled_time).getTime() : Infinity;
|
||||
return timeA - timeB;
|
||||
});
|
||||
|
||||
// Render matches as list items with buttons
|
||||
if (allRelevantMatches.length === 0) {
|
||||
matchListRefereeDiv.innerHTML = '<p><i>Keine geplanten oder laufenden Spiele für dieses Turnier gefunden.</i></p>';
|
||||
} else {
|
||||
allRelevantMatches.forEach(match => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'match-item';
|
||||
const timeStr = match.scheduled_time ? `(${new Date(match.scheduled_time).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })})` : '';
|
||||
item.innerHTML = `
|
||||
<span>
|
||||
Rd ${match.round || '?'} Tisch ${match.table_number || '?'} - ${match.player1_name || 'N/A'} vs ${match.player2_name || 'N/A'} ${timeStr} [${match.status}]
|
||||
</span>
|
||||
<button class="score-match-button" data-match-id="${match.match_id}">Ergebnis eintragen</button>
|
||||
`;
|
||||
matchListRefereeDiv.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
showMessageRef(matchLoadError, `Fehler beim Laden der Spiele: ${error.message}`);
|
||||
matchListRefereeDiv.innerHTML = '<p><i>Fehler beim Laden der Spieleliste.</i></p>';
|
||||
} finally {
|
||||
setLoadingRef(loadingMatches, false);
|
||||
}
|
||||
};
|
||||
|
||||
// Display the score entry form (same as referee_html_v2)
|
||||
const displayScoreEntryForm = async (matchId) => { /* ... (same as referee_html_v2) ... */
|
||||
console.log("Zeige Formular für Match ID:", matchId);
|
||||
hideMessageRef(scoreError); hideMessageRef(scoreSuccess);
|
||||
setInputs.innerHTML = '<div>Lade Spieldetails...</div>';
|
||||
scoreEntry.classList.remove('hidden');
|
||||
currentSelectedMatch = null; // Reset selected match before loading
|
||||
try {
|
||||
currentSelectedMatch = await fetchAPIRef(`/matches/${matchId}`);
|
||||
if (!currentSelectedMatch) {
|
||||
throw new Error("Spieldetails konnten nicht geladen werden.");
|
||||
}
|
||||
|
||||
if (!currentSelectedMatch) { throw new Error("Spieldetails konnten nicht geladen werden."); }
|
||||
matchDetailsPlayers.textContent = `Spieler: ${currentSelectedMatch.player1_name || 'Spieler 1'} vs ${currentSelectedMatch.player2_name || 'Spieler 2'}`;
|
||||
|
||||
// Dynamically create input fields based on game type (best of 5 default) and existing scores
|
||||
setInputs.innerHTML = ''; // Clear placeholder/previous inputs
|
||||
const maxSets = 5; // Assume best of 5 for now
|
||||
const existingSets = currentSelectedMatch.sets || [];
|
||||
|
||||
setInputs.innerHTML = '';
|
||||
const maxSets = 5; const existingSets = currentSelectedMatch.sets || [];
|
||||
for (let i = 1; i <= maxSets; i++) {
|
||||
const existingSet = existingSets.find(s => s.set_number === i);
|
||||
createSetInputRow(i, existingSet?.player1_score, existingSet?.player2_score);
|
||||
}
|
||||
|
||||
// Scroll to score entry
|
||||
scoreEntry.scrollIntoView({ behavior: 'smooth' });
|
||||
} catch (error) {
|
||||
showMessageRef(scoreError, `Fehler beim Laden der Spieldetails: ${error.message}`);
|
||||
matchDetailsPlayers.textContent = 'Fehler';
|
||||
setInputs.innerHTML = ''; // Clear on error
|
||||
currentSelectedMatch = null; // Reset selected match on error
|
||||
matchDetailsPlayers.textContent = 'Fehler'; setInputs.innerHTML = ''; currentSelectedMatch = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to create a row of set score inputs
|
||||
const createSetInputRow = (setNumber, score1 = '', score2 = '') => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'set-score-row';
|
||||
div.dataset.setNumber = setNumber; // Store set number
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `set-${setNumber}-p1`;
|
||||
label.textContent = `Satz ${setNumber}:`;
|
||||
|
||||
const input1 = document.createElement('input');
|
||||
input1.type = 'number';
|
||||
input1.id = `set-${setNumber}-p1`;
|
||||
input1.min = "0";
|
||||
input1.placeholder = "P1";
|
||||
input1.value = score1; // Pre-fill if score exists
|
||||
input1.dataset.player = "1";
|
||||
|
||||
const separator = document.createElement('span');
|
||||
separator.textContent = "-";
|
||||
|
||||
const input2 = document.createElement('input');
|
||||
input2.type = 'number';
|
||||
input2.id = `set-${setNumber}-p2`;
|
||||
input2.min = "0";
|
||||
input2.placeholder = "P2";
|
||||
input2.value = score2; // Pre-fill if score exists
|
||||
input2.dataset.player = "2";
|
||||
|
||||
div.appendChild(label);
|
||||
div.appendChild(input1);
|
||||
div.appendChild(separator);
|
||||
div.appendChild(input2);
|
||||
setInputs.appendChild(div);
|
||||
// Helper to create a row of set score inputs (same as referee_html_v2)
|
||||
const createSetInputRow = (setNumber, score1 = '', score2 = '') => { /* ... (same as referee_html_v2) ... */
|
||||
const div = document.createElement('div'); div.className = 'set-score-row'; div.dataset.setNumber = setNumber;
|
||||
const label = document.createElement('label'); label.htmlFor = `set-${setNumber}-p1`; label.textContent = `Satz ${setNumber}:`;
|
||||
const input1 = document.createElement('input'); input1.type = 'number'; input1.id = `set-${setNumber}-p1`; input1.min = "0"; input1.placeholder = "P1"; input1.value = score1; input1.dataset.player = "1";
|
||||
const separator = document.createElement('span'); separator.textContent = "-";
|
||||
const input2 = document.createElement('input'); input2.type = 'number'; input2.id = `set-${setNumber}-p2`; input2.min = "0"; input2.placeholder = "P2"; input2.value = score2; input2.dataset.player = "2";
|
||||
div.appendChild(label); div.appendChild(input1); div.appendChild(separator); div.appendChild(input2); setInputs.appendChild(div);
|
||||
};
|
||||
|
||||
// Handle adding another set input row (same as referee_html_v2)
|
||||
const handleAddSet = () => { /* ... (same as referee_html_v2) ... */
|
||||
const currentSetCount = setInputs.children.length; createSetInputRow(currentSetCount + 1);
|
||||
};
|
||||
|
||||
// Handle adding another set input row
|
||||
const handleAddSet = () => {
|
||||
const currentSetCount = setInputs.children.length;
|
||||
createSetInputRow(currentSetCount + 1);
|
||||
};
|
||||
|
||||
// Handle saving the scores
|
||||
const handleSaveScore = async (event) => {
|
||||
// Handle saving the scores (same as referee_html_v2)
|
||||
const handleSaveScore = async (event) => { /* ... (same as referee_html_v2, maybe refresh list on success) ... */
|
||||
event.preventDefault();
|
||||
const matchId = selectMatch.value;
|
||||
if (!matchId || !currentSelectedMatch) {
|
||||
showMessageRef(scoreError, "Kein gültiges Spiel ausgewählt.");
|
||||
return;
|
||||
}
|
||||
|
||||
hideMessageRef(scoreError);
|
||||
hideMessageRef(scoreSuccess);
|
||||
|
||||
// Collect scores from input fields
|
||||
const setsPayload = [];
|
||||
// Use currentSelectedMatch.match_id instead of selectMatch.value
|
||||
const matchId = currentSelectedMatch?.match_id;
|
||||
if (!matchId) { showMessageRef(scoreError, "Kein gültiges Spiel ausgewählt."); return; }
|
||||
hideMessageRef(scoreError); hideMessageRef(scoreSuccess);
|
||||
const setsPayload = []; let formIsValid = true;
|
||||
const setRows = setInputs.querySelectorAll('.set-score-row');
|
||||
let formIsValid = true;
|
||||
|
||||
setRows.forEach((row) => {
|
||||
const setNumber = parseInt(row.dataset.setNumber);
|
||||
const inputs = row.querySelectorAll('input[type="number"]');
|
||||
const score1Input = inputs[0];
|
||||
const score2Input = inputs[1];
|
||||
const score1 = score1Input.value.trim();
|
||||
const score2 = score2Input.value.trim();
|
||||
|
||||
// Add scores only if both are entered and valid numbers
|
||||
setRows.forEach((row) => { /* ... validation logic ... */
|
||||
const setNumber = parseInt(row.dataset.setNumber); const inputs = row.querySelectorAll('input[type="number"]');
|
||||
const score1Input = inputs[0]; const score2Input = inputs[1];
|
||||
const score1 = score1Input.value.trim(); const score2 = score2Input.value.trim();
|
||||
score1Input.style.borderColor = ''; score2Input.style.borderColor = ''; // Reset border
|
||||
if (score1 !== '' && score2 !== '') {
|
||||
const p1Score = parseInt(score1);
|
||||
const p2Score = parseInt(score2);
|
||||
if (!isNaN(p1Score) && !isNaN(p2Score) && p1Score >= 0 && p2Score >= 0) {
|
||||
setsPayload.push({ set_number: setNumber, player1_score: p1Score, player2_score: p2Score });
|
||||
} else {
|
||||
showMessageRef(scoreError, `Ungültige Eingabe in Satz ${setNumber}. Bitte nur positive Zahlen eingeben.`);
|
||||
formIsValid = false;
|
||||
// Highlight invalid inputs (optional)
|
||||
score1Input.style.borderColor = 'red';
|
||||
score2Input.style.borderColor = 'red';
|
||||
}
|
||||
} else if (score1 !== '' || score2 !== '') {
|
||||
// Only one score entered for the set
|
||||
showMessageRef(scoreError, `Bitte beide Punktzahlen für Satz ${setNumber} eingeben oder beide leer lassen.`);
|
||||
formIsValid = false;
|
||||
score1Input.style.borderColor = 'red';
|
||||
score2Input.style.borderColor = 'red';
|
||||
} else {
|
||||
// Both empty, reset border (optional)
|
||||
score1Input.style.borderColor = '';
|
||||
score2Input.style.borderColor = '';
|
||||
}
|
||||
const p1Score = parseInt(score1); const p2Score = parseInt(score2);
|
||||
if (!isNaN(p1Score) && !isNaN(p2Score) && p1Score >= 0 && p2Score >= 0) { setsPayload.push({ set_number: setNumber, player1_score: p1Score, player2_score: p2Score }); }
|
||||
else { showMessageRef(scoreError, `Ungültige Eingabe in Satz ${setNumber}.`); formIsValid = false; score1Input.style.borderColor = 'red'; score2Input.style.borderColor = 'red'; }
|
||||
} else if (score1 !== '' || score2 !== '') { showMessageRef(scoreError, `Bitte beide Punktzahlen für Satz ${setNumber} eingeben.`); formIsValid = false; score1Input.style.borderColor = 'red'; score2Input.style.borderColor = 'red'; }
|
||||
});
|
||||
|
||||
if (!formIsValid) {
|
||||
return; // Stop if validation failed
|
||||
}
|
||||
|
||||
if (setsPayload.length === 0) {
|
||||
showMessageRef(scoreError, "Keine gültigen Satzergebnisse zum Speichern eingegeben.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formIsValid) return;
|
||||
if (setsPayload.length === 0) { showMessageRef(scoreError, "Keine gültigen Satzergebnisse zum Speichern."); return; }
|
||||
console.log("Speichere Ergebnis für Match", matchId, setsPayload);
|
||||
|
||||
try {
|
||||
// Call the API endpoint to update scores
|
||||
const result = await fetchAPIRef(`/matches/${matchId}/score`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ sets: setsPayload })
|
||||
});
|
||||
|
||||
showMessageRef(scoreSuccess, 'Ergebnis erfolgreich gespeichert.', false, true); // Autohide success message
|
||||
// Update the displayed form with potentially recalculated winner/status from response
|
||||
const result = await fetchAPIRef(`/matches/${matchId}/score`, { method: 'POST', body: JSON.stringify({ sets: setsPayload }) });
|
||||
showMessageRef(scoreSuccess, 'Ergebnis erfolgreich gespeichert.', false, true);
|
||||
if (result && result.match) {
|
||||
currentSelectedMatch = result.match; // Update local match data
|
||||
// Re-display the form with updated data (especially if winner/status changed)
|
||||
// Update local match data and redisplay form
|
||||
currentSelectedMatch = result.match;
|
||||
matchDetailsPlayers.textContent = `Spieler: ${currentSelectedMatch.player1_name || 'Spieler 1'} vs ${currentSelectedMatch.player2_name || 'Spieler 2'}`;
|
||||
setInputs.innerHTML = ''; // Clear old inputs
|
||||
const maxSets = 5;
|
||||
const existingSets = currentSelectedMatch.sets || [];
|
||||
for (let i = 1; i <= maxSets; i++) {
|
||||
const existingSet = existingSets.find(s => s.set_number === i);
|
||||
createSetInputRow(i, existingSet?.player1_score, existingSet?.player2_score);
|
||||
setInputs.innerHTML = ''; const maxSets = 5; const existingSets = currentSelectedMatch.sets || [];
|
||||
for (let i = 1; i <= maxSets; i++) { const es = existingSets.find(s => s.set_number === i); createSetInputRow(i, es?.player1_score, es?.player2_score); }
|
||||
// Refresh the match list as the status might have changed
|
||||
if (activeTournamentId) {
|
||||
loadRefereeMatches(activeTournamentId);
|
||||
}
|
||||
// Optionally update the match list dropdown if status changed
|
||||
// loadRefereeMatches(); // Could reload the whole list, maybe too much?
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
showMessageRef(scoreError, `Fehler beim Speichern: ${error.message}`);
|
||||
}
|
||||
};
|
||||
} 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'); // Hide form if default option selected
|
||||
currentSelectedMatch = null;
|
||||
// Event delegation for match selection buttons
|
||||
matchListRefereeDiv.addEventListener('click', (event) => {
|
||||
if (event.target.classList.contains('score-match-button')) {
|
||||
const matchId = event.target.dataset.matchId;
|
||||
if (matchId) {
|
||||
displayScoreEntryForm(matchId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -513,7 +342,7 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
updateUIRef();
|
||||
if (authTokenRef && currentUserRef && (currentUserRef.role === 'referee' || currentUserRef.role === 'admin')) {
|
||||
loadRefereeMatches();
|
||||
loadActiveTournamentAndMatches(); // Load data for active tournament
|
||||
}
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user