document.addEventListener('DOMContentLoaded', () => { const leaderboardBody = document.getElementById('leaderboard-body'); const loadingIndicator = document.getElementById('loading-indicator'); const lastUpdatedElement = document.getElementById('last-updated'); const modal = document.getElementById('code-modal'); const modalUsername = document.getElementById('modal-username'); const modalCode = document.getElementById('modal-code'); const closeModalButton = document.querySelector('.close-button'); // Fetch from the FastAPI backend endpoint const API_URL = '/api/leaderboard'; const REFRESH_INTERVAL_MS = 60 * 1000; // 60 seconds async function fetchData() { try { // Fetch directly from our backend API const response = await fetch(API_URL); if (!response.ok) { // Try to get error message from backend if available let errorMsg = `HTTP error! status: ${response.status}`; try { const errorData = await response.json(); errorMsg = errorData.detail || errorMsg; } catch (e) { /* Ignore if response is not JSON */ } throw new Error(errorMsg); } const data = await response.json(); // The data is expected to be the already sorted list of objects return data; } catch (error) { console.error("Failed to fetch leaderboard data:", error); lastUpdatedElement.textContent = `Error: ${error.message}`; // Display error return null; // Return null to indicate failure } } // No sorting needed here - backend does it // function sortData(data) { ... } // REMOVED function formatTimestamp(isoString) { // Keep this helper function try { const date = new Date(isoString); return date.toISOString().slice(0, 19).replace('T', ' '); } catch (e) { return 'Invalid Date'; } } function renderLeaderboard(leaderboardData) { // Clear previous content leaderboardBody.innerHTML = ''; if (!leaderboardData || leaderboardData.length === 0) { // Check if the loading indicator is still displayed, means fetch failed on load if (loadingIndicator.style.display !== 'none') { leaderboardBody.innerHTML = '
No data available or failed to load. Check logs.
'; } else { // If fetch failed during refresh, keep old data and show error in footer? Or clear? // Let's clear for now and rely on error message in footer. leaderboardBody.innerHTML = '
Failed to refresh data.
'; } return; } leaderboardData.forEach((entry, index) => { const rank = index + 1; const row = document.createElement('div'); row.className = 'leaderboard-row'; row.setAttribute('data-rank', rank); row.innerHTML = `
#${rank}
${entry.username}
${entry.score} pts
${formatTimestamp(entry.timestamp)}
`; const button = row.querySelector('.view-code-btn'); button._codeData = entry.code; // Store code directly button.addEventListener('click', handleViewCodeClick); leaderboardBody.appendChild(row); }); } function handleViewCodeClick(event) { const button = event.currentTarget; const username = button.getAttribute('data-username'); const code = button._codeData; showCodeModal(username, code); } function showCodeModal(username, code) { modalUsername.textContent = `Code from ${username}`; modalCode.textContent = code || '// No code submitted or available'; if (typeof hljs !== 'undefined') { modalCode.className = 'language-python'; hljs.highlightElement(modalCode); } modal.style.display = 'block'; } function hideCodeModal() { modal.style.display = 'none'; modalCode.textContent = ''; modalUsername.textContent = ''; } async function updateLeaderboard() { if (!leaderboardBody.hasChildNodes()) { loadingIndicator.style.display = 'flex'; } else { lastUpdatedElement.textContent = 'Updating...'; } const leaderboardData = await fetchData(); // Fetches already sorted data loadingIndicator.style.display = 'none'; // Hide loading indicator regardless of success/fail if (leaderboardData) { renderLeaderboard(leaderboardData); // Only update timestamp on success lastUpdatedElement.textContent = `Last updated: ${new Date().toLocaleTimeString()}`; } else { // Error occurred, message already set in fetchData // Optionally render empty state if needed if (!leaderboardBody.hasChildNodes()) { renderLeaderboard(null); // Show 'No data' message if initial load failed } } } // --- Event Listeners --- closeModalButton.addEventListener('click', hideCodeModal); window.addEventListener('click', (event) => { if (event.target === modal) { hideCodeModal(); } }); // --- Initial Load and Auto-Refresh --- updateLeaderboard(); // Initial load setInterval(updateLeaderboard, REFRESH_INTERVAL_MS); // Refresh every 60 seconds });