gemini enhancements

This commit is contained in:
MLH
2025-04-08 21:29:54 +02:00
parent f59480d91e
commit a0050736ca
12 changed files with 1481 additions and 1984 deletions

View File

@@ -7,397 +7,256 @@
<link rel="stylesheet" href="/css/style.css">
<style>
/* Optional: Zusätzliche Stile für die Zuschaueransicht */
.spectator-section h2 {
border-bottom: 2px solid #007bff;
padding-bottom: 5px;
margin-bottom: 15px;
}
.match-card { /* Beispiel für Live-Spiel-Anzeige */
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
background-color: #f8f9fa;
}
.match-card .players {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 10px;
}
.match-card .details {
font-size: 0.9em;
color: #555;
}
.match-card .score { /* Für Live-Score später */
font-size: 1.2em;
font-weight: bold;
text-align: center;
margin: 10px 0;
}
#player-list table th, #player-list table td {
padding: 8px; /* Etwas kompakter für Spielerliste */
}
.spectator-section h2 { border-bottom: 2px solid #007bff; padding-bottom: 5px; margin-bottom: 15px; }
.match-card { border: 1px solid #ddd; border-radius: 5px; padding: 15px; margin-bottom: 15px; background-color: #f8f9fa; }
.match-card .players { font-size: 1.1em; font-weight: bold; margin-bottom: 10px; }
.match-card .details { font-size: 0.9em; color: #555; }
.match-card .score { font-size: 1.2em; font-weight: bold; text-align: center; margin: 10px 0; }
#player-list table th, #player-list table td { padding: 8px; }
footer { margin-top: 30px; padding-top: 15px; border-top: 1px solid #ccc; text-align: center; font-size: 0.9em; color: #666; }
footer a { color: #666; }
#no-active-tournament-spectator { font-weight: bold; color: #17a2b8; text-align: center; padding: 20px; background-color: #e9ecef; border-radius: 5px;}
</style>
</head>
<body>
<nav>
<a href="/admin.html">Admin</a>
<a href="/referee.html">Schiedsrichter</a>
<a href="/spectator.html" class="active">Zuschauer</a>
</nav>
</nav>
<div class="container">
<h1>Turnierübersicht</h1>
<p id="no-active-tournament-spectator" class="hidden">Derzeit findet kein aktives Turnier statt.</p>
<section class="content-section spectator-section">
<h2>Turnier auswählen</h2>
<label for="select-tournament">Aktuelles/Vergangenes Turnier:</label>
<select id="select-tournament" disabled>
<option value="">-- Bitte Turnier wählen --</option>
</select>
<p id="loading-tournaments-spectator" class="loading-indicator hidden">Lade Turniere...</p>
<div id="tournament-info" class="hidden" style="margin-top: 15px; padding: 10px; background-color: #e9ecef; border-radius: 5px;">
<h3 id="selected-tournament-name" style="margin: 0 0 5px 0;"></h3>
<section id="tournament-info-header" class="content-section spectator-section hidden" style="padding-bottom: 0; margin-bottom: 0;">
<div id="tournament-info" style="padding: 10px 10px 15px 10px; background-color: #e9ecef; border-radius: 5px;">
<h2 id="selected-tournament-name" style="margin: 0 0 5px 0; border-bottom: none;">Turniername</h2>
<p id="selected-tournament-details" style="margin: 0; font-size: 0.9em;"></p>
<p id="selected-tournament-description" style="margin: 5px 0 0 0; font-size: 0.9em;"></p>
</div>
</section>
</div>
</section>
<div id="tournament-display" class="hidden">
<section class="content-section spectator-section">
<h2><span class="live-indicator" style="color: red;"></span> Live Spiele / Aktuelle Runde</h2>
<div id="live-matches-list">
<p><i>Keine Live-Spiele gefunden oder Turnier nicht ausgewählt.</i></p>
</div>
<h2><span class="live-indicator" style="color: red;"></span> Live Spiele</h2>
<div id="live-matches-list"><p><i>Keine Live-Spiele gefunden.</i></p></div>
<p id="loading-live-matches" class="loading-indicator hidden">Lade Live-Spiele...</p>
<div id="live-matches-error" class="message-area error-message hidden"></div>
</section>
<section class="content-section spectator-section">
<h2>Turnierbaum / Gruppenphase</h2>
<div id="bracket-groups-visualization">
<p><i>Visualisierung des Turnierbaums oder der Gruppenphase ist für dieses Turnier nicht verfügbar oder noch nicht implementiert.</i></p>
</div>
<div id="bracket-groups-visualization"><p><i>Visualisierung noch nicht implementiert.</i></p></div>
<p id="loading-bracket" class="loading-indicator hidden">Lade Turnierbaum/Gruppen...</p>
<div id="bracket-error" class="message-area error-message hidden"></div>
</section>
<section class="content-section spectator-section">
<h2>Alle Spiele (Zeitplan)</h2>
<div id="all-matches-list">
<p><i>Keine Spiele gefunden oder Turnier nicht ausgewählt.</i></p>
</div>
<div id="all-matches-list"><p><i>Keine Spiele gefunden.</i></p></div>
<p id="loading-all-matches" class="loading-indicator hidden">Lade alle Spiele...</p>
<div id="all-matches-error" class="message-area error-message hidden"></div>
</section>
<section class="content-section spectator-section">
<h2>Spieler</h2>
<h2>Teilnehmer dieses Turniers</h2>
<div>
<label for="filter-player">Spieler suchen:</label>
<input type="text" id="filter-player" placeholder="Namen oder Verein eingeben...">
<small style="display: block; margin-top: 5px;"><i>Hinweis: Zeigt aktuell alle Spieler im System, nicht nur die des ausgewählten Turniers.</i></small>
</div>
<div id="player-list" style="margin-top: 15px;">
<p><i>Keine Spieler gefunden.</i></p>
</div>
<p id="loading-players" class="loading-indicator hidden">Lade Spieler...</p>
<div id="player-list" style="margin-top: 15px;"><p><i>Keine Teilnehmer gefunden.</i></p></div>
<p id="loading-players" class="loading-indicator hidden">Lade Teilnehmer...</p>
<div id="player-list-error" class="message-area error-message hidden"></div>
</section>
<section class="content-section spectator-section">
<h2>Hinweise & Benachrichtigungen</h2>
<div id="notifications-area">
<p><i>Keine aktuellen Hinweise für dieses Turnier vorhanden oder Funktion noch nicht implementiert.</i></p>
</div>
<div id="notifications-area"><p><i>Keine aktuellen Hinweise oder Funktion noch nicht implementiert.</i></p></div>
<p id="loading-notifications" class="loading-indicator hidden">Lade Hinweise...</p>
<div id="notifications-error" class="message-area error-message hidden"></div>
</section>
</div> <div id="spectator-error" class="message-area error-message hidden"></div> </div> <script>
</div> <div id="spectator-error" class="message-area error-message hidden"></div> </div> <footer>
<a href="/admin.html">Admin-Bereich</a>
</footer>
<script>
// --- DOM Elements ---
const selectTournament = document.getElementById('select-tournament');
const loadingTournamentsSpectator = document.getElementById('loading-tournaments-spectator');
const spectatorError = document.getElementById('spectator-error');
const noActiveTournamentMessageSpec = document.getElementById('no-active-tournament-spectator');
const tournamentInfoHeader = document.getElementById('tournament-info-header');
const tournamentInfo = document.getElementById('tournament-info');
const selectedTournamentName = document.getElementById('selected-tournament-name');
const selectedTournamentDetails = document.getElementById('selected-tournament-details');
const selectedTournamentDescription = document.getElementById('selected-tournament-description');
const tournamentDisplay = document.getElementById('tournament-display');
const spectatorError = document.getElementById('spectator-error');
// ... (get elements for live matches, all matches, players, bracket, notifications as before)
const liveMatchesList = document.getElementById('live-matches-list');
const loadingLiveMatches = document.getElementById('loading-live-matches');
const liveMatchesError = document.getElementById('live-matches-error');
const bracketViz = document.getElementById('bracket-groups-visualization');
const loadingBracket = document.getElementById('loading-bracket');
const bracketError = document.getElementById('bracket-error');
const allMatchesList = document.getElementById('all-matches-list');
const loadingAllMatches = document.getElementById('loading-all-matches');
const allMatchesError = document.getElementById('all-matches-error');
const filterPlayerInput = document.getElementById('filter-player');
const playerListDiv = document.getElementById('player-list');
const loadingPlayers = document.getElementById('loading-players');
const playerListError = document.getElementById('player-list-error');
const notificationsArea = document.getElementById('notifications-area');
const loadingNotifications = document.getElementById('loading-notifications');
const notificationsError = document.getElementById('notifications-error');
// --- State ---
let allPlayersCache = []; // Cache all players fetched once
let currentTournamentId = null;
let activeTournamentId = null;
let tournamentPlayersCacheSpec = []; // Cache players for the active tournament
// --- API Base URL ---
const API_BASE_URL_SPEC = '/api';
// --- Helper Functions ---
const showMessageSpec = (element, message, isError = true) => {
if (!element) return;
element.textContent = message;
element.className = `message-area ${isError ? 'error-message' : 'success-message'}`;
element.classList.remove('hidden');
};
const hideMessageSpec = (element) => {
if (element) {
element.classList.add('hidden');
element.textContent = '';
}
};
const setLoadingSpec = (element, isLoading) => {
if (element) {
element.classList.toggle('hidden', !isLoading);
}
}
// --- Helper Functions (showMessageSpec, hideMessageSpec, setLoadingSpec) ---
const showMessageSpec = (element, message, isError = true) => { if (!element) return; element.textContent = message; element.className = `message-area ${isError ? 'error-message' : 'success-message'}`; element.classList.remove('hidden'); };
const hideMessageSpec = (element) => { if (element) { element.classList.add('hidden'); element.textContent = ''; } };
const setLoadingSpec = (element, isLoading) => { if (element) { element.classList.toggle('hidden', !isLoading); } };
// --- API Fetch Function (Spectator - No Auth Header) ---
// Spectator endpoints should ideally be public.
const fetchAPISpec = async (endpoint, options = {}) => {
const fetchAPISpec = async (endpoint, options = {}) => { /* ... (same as spectator_html_v2) ... */
const headers = { 'Content-Type': 'application/json', ...options.headers };
// No Authorization header for public spectator view
try {
const response = await fetch(`${API_BASE_URL_SPEC}${endpoint}`, { ...options, headers });
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 */ }
throw new Error(errorData.message);
}
if (response.status === 204) return null;
return await response.json();
} catch (error) {
console.error('API Fetch Error (Spectator):', error);
throw error;
}
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 (Spectator):', error); throw error; }
};
// --- Core Functions ---
// Load available tournaments into the dropdown
const loadAvailableTournaments = async () => {
setLoadingSpec(loadingTournamentsSpectator, true);
selectTournament.disabled = true;
// Find active tournament and load its data
const loadActiveTournamentData = async () => {
console.log("Suche aktives Turnier...");
hideMessageSpec(spectatorError);
try {
// Assuming /api/tournaments is accessible without auth
const tournaments = await fetchAPISpec('/tournaments');
noActiveTournamentMessageSpec.classList.add('hidden');
tournamentInfoHeader.classList.add('hidden');
tournamentDisplay.classList.add('hidden');
activeTournamentId = null;
selectTournament.innerHTML = '<option value="">-- Bitte Turnier wählen --</option>'; // Reset
if (tournaments.length === 0) {
selectTournament.innerHTML += '<option value="" disabled>Keine Turniere verfügbar</option>';
} else {
// Sort tournaments, e.g., by date descending
tournaments.sort((a, b) => (b.date || '').localeCompare(a.date || ''));
tournaments.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') : 'Datum n.a.'})`;
selectTournament.appendChild(option);
});
selectTournament.disabled = false;
try {
const tournaments = await fetchAPISpec('/tournaments');
const runningTournament = tournaments.find(t => t.status === 'running');
if (!runningTournament) {
noActiveTournamentMessageSpec.classList.remove('hidden');
console.log("Kein aktives Turnier gefunden.");
return; // Stop if no tournament is running
}
activeTournamentId = runningTournament.tournament_id;
console.log(`Aktives Turnier gefunden: ${runningTournament.name} (${activeTournamentId})`);
// Display basic info and load dependent data
displayTournamentHeader(runningTournament);
await loadTournamentDependentData(activeTournamentId);
} catch (error) {
showMessageSpec(spectatorError, `Fehler beim Laden der Turniere: ${error.message}`);
selectTournament.innerHTML += '<option value="" disabled>Fehler beim Laden</option>';
} finally {
setLoadingSpec(loadingTournamentsSpectator, false);
showMessageSpec(spectatorError, `Fehler beim Laden des Turniers: ${error.message}`);
}
};
// Load details and related data for the selected tournament
const loadTournamentDetails = async (tournamentId) => {
console.log("Lade Details für Turnier:", tournamentId);
currentTournamentId = tournamentId; // Store current ID
hideMessageSpec(spectatorError);
tournamentInfo.classList.add('hidden');
tournamentDisplay.classList.add('hidden'); // Hide sections until loaded
setLoadingSpec(loadingLiveMatches, true);
setLoadingSpec(loadingBracket, true); // Show loading even if not implemented
setLoadingSpec(loadingAllMatches, true);
setLoadingSpec(loadingNotifications, true); // Show loading even if not implemented
try {
// 1. Fetch basic tournament info
const tournament = await fetchAPISpec(`/tournaments/${tournamentId}`);
selectedTournamentName.textContent = tournament.name || 'Unbekanntes Turnier';
const dateStr = tournament.date ? new Date(tournament.date).toLocaleDateString('de-DE') : 'N/A';
const typeStr = tournament.tournament_type === 'knockout' ? 'KO-System' : 'Gruppenphase';
selectedTournamentDetails.textContent = `Ort: ${tournament.location || 'N/A'} | Datum: ${dateStr} | Typ: ${typeStr}`;
selectedTournamentDescription.textContent = tournament.description || '';
tournamentInfo.classList.remove('hidden');
// 2. Trigger loading of related data concurrently
await Promise.all([
loadLiveMatches(tournamentId),
loadAllMatches(tournamentId),
loadBracketInfo(tournamentId), // Placeholder call
loadNotifications(tournamentId) // Placeholder call
]);
tournamentDisplay.classList.remove('hidden'); // Show the details sections
} catch (error) {
showMessageSpec(spectatorError, `Fehler beim Laden der Turnierdetails: ${error.message}`);
tournamentDisplay.classList.add('hidden'); // Hide details on error
}
// Display header info for the loaded tournament
const displayTournamentHeader = (tournament) => {
if (!tournament) return;
selectedTournamentName.textContent = tournament.name || 'Unbekanntes Turnier';
const dateStr = tournament.date ? new Date(tournament.date).toLocaleDateString('de-DE') : 'N/A';
const typeStr = tournament.tournament_type === 'knockout' ? 'KO-System' : 'Gruppenphase';
selectedTournamentDetails.textContent = `Ort: ${tournament.location || 'N/A'} | Datum: ${dateStr} | Typ: ${typeStr}`;
selectedTournamentDescription.textContent = tournament.description || '';
tournamentInfoHeader.classList.remove('hidden');
};
// Load and display live matches
const loadLiveMatches = async (tournamentId) => {
setLoadingSpec(loadingLiveMatches, true);
liveMatchesList.innerHTML = '';
hideMessageSpec(liveMatchesError);
// Load all data sections for the active tournament
const loadTournamentDependentData = async (tournamentId) => {
if (!tournamentId) return;
tournamentDisplay.classList.remove('hidden'); // Show the main display area
// Trigger loading concurrently
await Promise.all([
loadLiveMatches(tournamentId),
loadAllMatches(tournamentId),
loadTournamentPlayers(tournamentId), // Use new function
loadBracketInfo(tournamentId), // Placeholder
loadNotifications(tournamentId) // Placeholder
]);
};
// Load and display live matches (same as spectator_html_v2)
const loadLiveMatches = async (tournamentId) => { /* ... (same as spectator_html_v2) ... */
setLoadingSpec(loadingLiveMatches, true); liveMatchesList.innerHTML = ''; hideMessageSpec(liveMatchesError);
try {
const matches = await fetchAPISpec(`/matches?tournamentId=${tournamentId}&status=ongoing`);
if (matches.length === 0) {
liveMatchesList.innerHTML = '<p><i>Aktuell keine laufenden Spiele.</i></p>';
} else {
if (matches.length === 0) { liveMatchesList.innerHTML = '<p><i>Aktuell keine laufenden Spiele.</i></p>'; }
else {
matches.forEach(match => {
const card = document.createElement('div');
card.className = 'match-card';
// TODO: Display live score if available (requires backend changes)
card.innerHTML = `
<div class="players">${match.player1_name || 'N/A'} vs ${match.player2_name || 'N/A'}</div>
<div class="details">
Runde: ${match.round || '?'} | Tisch: ${match.table_number || '?'}
</div>
`;
const card = document.createElement('div'); card.className = 'match-card';
card.innerHTML = `<div class="players">${match.player1_name || 'N/A'} vs ${match.player2_name || 'N/A'}</div> <div class="details">Runde: ${match.round || '?'} | Tisch: ${match.table_number || '?'}</div>`;
liveMatchesList.appendChild(card);
});
}
} catch (error) {
showMessageSpec(liveMatchesError, `Fehler beim Laden der Live-Spiele: ${error.message}`);
liveMatchesList.innerHTML = '<p><i>Fehler beim Laden der Live-Spiele.</i></p>';
} finally {
setLoadingSpec(loadingLiveMatches, false);
}
} catch (error) { showMessageSpec(liveMatchesError, `Fehler Live-Spiele: ${error.message}`); liveMatchesList.innerHTML = '<p><i>Fehler beim Laden.</i></p>'; }
finally { setLoadingSpec(loadingLiveMatches, false); }
};
// Load and display all matches (schedule)
const loadAllMatches = async (tournamentId) => {
setLoadingSpec(loadingAllMatches, true);
allMatchesList.innerHTML = '';
hideMessageSpec(allMatchesError);
// Load and display all matches (schedule) (same as spectator_html_v2)
const loadAllMatches = async (tournamentId) => { /* ... (same as spectator_html_v2) ... */
setLoadingSpec(loadingAllMatches, true); allMatchesList.innerHTML = ''; hideMessageSpec(allMatchesError);
try {
const matches = await fetchAPISpec(`/matches?tournamentId=${tournamentId}`);
// Sort matches: by round, then match number, then scheduled time
matches.sort((a, b) => {
if ((a.round || 999) < (b.round || 999)) return -1;
if ((a.round || 999) > (b.round || 999)) return 1;
if ((a.match_number_in_round || 999) < (b.match_number_in_round || 999)) return -1;
if ((a.match_number_in_round || 999) > (b.match_number_in_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;
matches.sort((a, b) => { /* Sorting logic */
if ((a.round || 999) < (b.round || 999)) return -1; if ((a.round || 999) > (b.round || 999)) return 1;
if ((a.match_number_in_round || 999) < (b.match_number_in_round || 999)) return -1; if ((a.match_number_in_round || 999) > (b.match_number_in_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;
});
if (matches.length === 0) {
allMatchesList.innerHTML = '<p><i>Keine Spiele für dieses Turnier gefunden.</i></p>';
} else {
const table = document.createElement('table');
table.innerHTML = `
<thead>
<tr>
<th>Runde</th>
<th>Spiel Nr.</th>
<th>Spieler 1</th>
<th>Spieler 2</th>
<th>Ergebnis</th>
<th>Status</th>
<th>Geplant</th>
<th>Tisch</th>
</tr>
</thead>
<tbody></tbody>
`;
if (matches.length === 0) { allMatchesList.innerHTML = '<p><i>Keine Spiele für dieses Turnier gefunden.</i></p>'; }
else {
const table = document.createElement('table'); table.innerHTML = `<thead><tr><th>Runde</th><th>Spiel Nr.</th><th>Spieler 1</th><th>Spieler 2</th><th>Ergebnis</th><th>Status</th><th>Geplant</th><th>Tisch</th></tr></thead><tbody></tbody>`;
const tbody = table.querySelector('tbody');
matches.forEach(match => {
const row = tbody.insertRow();
const scheduled = match.scheduled_time ? new Date(match.scheduled_time).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short'}) : '-';
// TODO: Fetch and display actual scores/result string more accurately
let resultPlaceholder = '-';
if (match.status === 'finished') {
resultPlaceholder = match.winner_name ? `${match.winner_name} gewinnt` : 'Beendet';
// Ideally fetch sets: const sets = await fetchAPISpec(`/matches/${match.match_id}/sets`) ... display score string
}
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>
`;
});
allMatchesList.appendChild(table);
const row = tbody.insertRow(); const scheduled = match.scheduled_time ? new Date(match.scheduled_time).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short'}) : '-';
let resultPlaceholder = '-'; if (match.status === 'finished') { resultPlaceholder = 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>`;
}); allMatchesList.appendChild(table);
}
} catch (error) { showMessageSpec(allMatchesError, `Fehler Spielplan: ${error.message}`); allMatchesList.innerHTML = '<p><i>Fehler beim Laden.</i></p>'; }
finally { setLoadingSpec(loadingAllMatches, false); }
};
// Load and display players FOR THIS TOURNAMENT
const loadTournamentPlayers = async (tournamentId) => {
setLoadingSpec(loadingPlayers, true);
hideMessageSpec(playerListError);
tournamentPlayersCacheSpec = []; // Clear cache before loading new tournament's players
try {
// Use the new endpoint to get only players linked to this tournament
tournamentPlayersCacheSpec = await fetchAPISpec(`/tournaments/${tournamentId}/players`);
tournamentPlayersCacheSpec.sort((a,b) => (a.last_name || '').localeCompare(b.last_name || '')); // Sort by last name
filterAndRenderPlayersSpec(); // Initial render without filter
} catch (error) {
showMessageSpec(allMatchesError, `Fehler beim Laden des Spielplans: ${error.message}`);
allMatchesList.innerHTML = '<p><i>Fehler beim Laden des Spielplans.</i></p>';
showMessageSpec(playerListError, `Fehler beim Laden der Teilnehmer: ${error.message}`);
tournamentPlayersCacheSpec = []; // Ensure cache is empty on error
filterAndRenderPlayersSpec(); // Render empty state
} finally {
setLoadingSpec(loadingAllMatches, false);
setLoadingSpec(loadingPlayers, false);
}
};
// Load and display players (currently ALL players)
const loadAndDisplayPlayers = async () => {
// Only fetch all players once if not already cached
if (allPlayersCache.length === 0) {
setLoadingSpec(loadingPlayers, true);
hideMessageSpec(playerListError);
try {
// Fetch ALL players - backend endpoint for tournament-specific players is needed
console.warn("Fetching ALL players for spectator view. Backend should provide tournament-specific player list.");
allPlayersCache = await fetchAPISpec('/players');
allPlayersCache.sort((a,b) => (a.last_name || '').localeCompare(b.last_name || '')); // Sort by last name
} catch (error) {
showMessageSpec(playerListError, `Fehler beim Laden der Spielerliste: ${error.message}`);
allPlayersCache = []; // Ensure cache is empty on error
} finally {
setLoadingSpec(loadingPlayers, false);
}
}
// Filter and display based on the current cache and search term
filterAndRenderPlayers();
};
// Filter and render the player list based on search term
const filterAndRenderPlayers = () => {
// Filter and render the TOURNAMENT player list based on search term
const filterAndRenderPlayersSpec = () => {
const searchTerm = filterPlayerInput.value.trim().toLowerCase();
const filteredPlayers = allPlayersCache.filter(p => {
const filteredPlayers = tournamentPlayersCacheSpec.filter(p => {
return !searchTerm ||
(p.first_name && p.first_name.toLowerCase().includes(searchTerm)) ||
(p.last_name && p.last_name.toLowerCase().includes(searchTerm)) ||
@@ -406,7 +265,7 @@
playerListDiv.innerHTML = ''; // Clear previous list
if (filteredPlayers.length === 0) {
playerListDiv.innerHTML = '<p><i>Keine Spieler entsprechen dem Suchbegriff.</i></p>';
playerListDiv.innerHTML = searchTerm ? '<p><i>Keine Teilnehmer entsprechen dem Suchbegriff.</i></p>' : '<p><i>Keine Teilnehmer für dieses Turnier gefunden.</i></p>';
} else {
const table = document.createElement('table');
table.innerHTML = `
@@ -414,79 +273,55 @@
<tr>
<th>Nachname</th>
<th>Vorname</th>
<th>Geschlecht</th> {/* Added Header */}
<th>Verein</th>
<th>QTTR</th>
<th>Altersklasse</th>
<th>Altersklasse</th> {/* Added Header - assuming it comes from players table */}
</tr>
</thead>
<tbody></tbody>`;
const tbody = table.querySelector('tbody');
filteredPlayers.forEach(player => {
const row = tbody.insertRow();
let genderText = '-';
if (player.gender === 'm') genderText = 'M';
else if (player.gender === 'w') genderText = 'W';
else if (player.gender === 'd') genderText = 'D';
row.innerHTML = `
<td>${player.last_name || '-'}</td>
<td>${player.first_name || '-'}</td>
<td>${genderText}</td> {/* Display Gender */}
<td>${player.club || '-'}</td>
<td>${player.qttr_points || '-'}</td>
<td>${player.age_class || '-'}</td>
`;
<td>${player.age_class || '-'}</td> {/* Display Age Class */}
`;
});
playerListDiv.appendChild(table);
}
};
// Placeholder for loading bracket info
const loadBracketInfo = async (tournamentId) => {
setLoadingSpec(loadingBracket, true);
bracketViz.innerHTML = '<p><i>Turnierbaum-Visualisierung wird geladen... (Funktion noch nicht implementiert)</i></p>';
hideMessageSpec(bracketError);
// TODO: Implement API call to fetch bracket data
// Example: const bracketData = await fetchAPISpec(`/tournaments/${tournamentId}/bracket`);
// TODO: Use a library (e.g., jquery-bracket, d3) or custom rendering to display the bracket
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate loading
setLoadingSpec(loadingBracket, false);
};
// Placeholder for loading bracket info (same as spectator_html_v2)
const loadBracketInfo = async (tournamentId) => { /* ... (placeholder) ... */ setLoadingSpec(loadingBracket, true); bracketViz.innerHTML = '<p><i>...</i></p>'; hideMessageSpec(bracketError); await new Promise(r => setTimeout(r, 100)); setLoadingSpec(loadingBracket, false); bracketViz.innerHTML = '<p><i>Visualisierung noch nicht implementiert.</i></p>'; };
// Placeholder for loading notifications
const loadNotifications = async (tournamentId) => {
setLoadingSpec(loadingNotifications, true);
notificationsArea.innerHTML = '<p><i>Hinweise werden geladen... (Funktion noch nicht implementiert)</i></p>';
hideMessageSpec(notificationsError);
// TODO: Implement API call to fetch notifications
// Example: const notes = await fetchAPISpec(`/notifications?tournamentId=${tournamentId}&target=spectators`);
// TODO: Display notifications
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate loading
setLoadingSpec(loadingNotifications, false);
};
// Placeholder for loading notifications (same as spectator_html_v2)
const loadNotifications = async (tournamentId) => { /* ... (placeholder) ... */ setLoadingSpec(loadingNotifications, true); notificationsArea.innerHTML = '<p><i>...</i></p>'; hideMessageSpec(notificationsError); await new Promise(r => setTimeout(r, 100)); setLoadingSpec(loadingNotifications, false); notificationsArea.innerHTML = '<p><i>Keine Hinweise oder Funktion nicht implementiert.</i></p>';};
// --- Event Listeners ---
selectTournament.addEventListener('change', (event) => {
const selectedId = event.target.value;
if (selectedId) {
loadTournamentDetails(selectedId);
} else {
currentTournamentId = null;
tournamentInfo.classList.add('hidden');
tournamentDisplay.classList.add('hidden');
hideMessageSpec(spectatorError);
}
});
// Player filter input (debounced)
let playerFilterTimeout;
let playerFilterTimeoutSpec;
filterPlayerInput.addEventListener('input', () => {
clearTimeout(playerFilterTimeout);
playerFilterTimeout = setTimeout(() => {
filterAndRenderPlayers(); // Filter based on cached players
clearTimeout(playerFilterTimeoutSpec);
playerFilterTimeoutSpec = setTimeout(() => {
filterAndRenderPlayersSpec(); // Filter based on cached tournament players
}, 300);
});
// --- Initial Load ---
document.addEventListener('DOMContentLoaded', () => {
loadAvailableTournaments();
loadAndDisplayPlayers(); // Load global player list initially
loadActiveTournamentData(); // Automatically load data for the running tournament
});
</script>