diff --git a/public/js/leaderboard.js b/public/js/leaderboard.js index 6846f5f..e6b37b6 100644 --- a/public/js/leaderboard.js +++ b/public/js/leaderboard.js @@ -54,7 +54,7 @@ function saveLocationSelection(locationId, locationName) { name: cleanName, timestamp: new Date().toISOString() }; - + setCookie('ninjacross_last_location', JSON.stringify(locationData), 90); lastSelectedLocation = { id: locationId, name: cleanName }; console.log('💾 Location saved to cookie:', cleanName); @@ -84,21 +84,21 @@ function showNotification(timeData) { const notificationBubble = document.getElementById('notificationBubble'); const notificationTitle = document.getElementById('notificationTitle'); const notificationSubtitle = document.getElementById('notificationSubtitle'); - + // Format the time data const playerName = timeData.player_name || (currentLanguage === 'de' ? 'Unbekannter Spieler' : 'Unknown Player'); const locationName = timeData.location_name || (currentLanguage === 'de' ? 'Unbekannter Standort' : 'Unknown Location'); const timeString = timeData.recorded_time || '--:--'; - + // Update notification content const newTimeText = currentLanguage === 'de' ? 'Neue Zeit von' : 'New time from'; notificationTitle.textContent = `🏁 ${newTimeText} ${playerName}!`; notificationSubtitle.textContent = `${timeString} • ${locationName}`; - + // Show notification notificationBubble.classList.remove('hide'); notificationBubble.classList.add('show'); - + // Auto-hide after 5 seconds setTimeout(() => { hideNotification(); @@ -109,7 +109,7 @@ function hideNotification() { const notificationBubble = document.getElementById('notificationBubble'); notificationBubble.classList.remove('show'); notificationBubble.classList.add('hide'); - + // Remove hide class after animation setTimeout(() => { notificationBubble.classList.remove('hide'); @@ -120,7 +120,7 @@ function hideNotification() { async function checkAuth() { try { const { data: { session } } = await supabase.auth.getSession(); - + if (session) { // User is logged in, show dashboard button document.getElementById('adminLoginBtn').style.display = 'none'; @@ -163,18 +163,18 @@ async function loadLocations() { if (!response.ok) { throw new Error('Failed to fetch locations'); } - + const responseData = await response.json(); const locations = responseData.data || responseData; // Handle both formats const locationSelect = document.getElementById('locationSelect'); - + // Store locations globally for distance calculations locationsData = locations; - + // Clear existing options and set default placeholder const placeholderText = currentLanguage === 'de' ? '📍 Bitte Standort auswählen' : '📍 Please select location'; locationSelect.innerHTML = ``; - + // Add locations from database locations.forEach(location => { const option = document.createElement('option'); @@ -182,12 +182,12 @@ async function loadLocations() { option.textContent = `📍 ${location.name}`; locationSelect.appendChild(option); }); - + // Load and set last selected location const lastLocation = loadLastSelectedLocation(); if (lastLocation) { // Find the option that matches the last location name - const matchingOption = Array.from(locationSelect.options).find(option => + const matchingOption = Array.from(locationSelect.options).find(option => option.textContent === `📍 ${lastLocation.name}` || option.value === lastLocation.name ); if (matchingOption) { @@ -199,7 +199,7 @@ async function loadLocations() { loadData(); } } - + } catch (error) { console.error('Error loading locations:', error); } @@ -210,14 +210,14 @@ function calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371; // Earth's radius in kilometers const dLat = toRadians(lat2 - lat1); const dLon = toRadians(lon2 - lon1); - + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const distance = R * c; // Distance in kilometers - + return distance; } @@ -229,10 +229,10 @@ function toRadians(degrees) { async function findNearestLocation() { const btn = document.getElementById('findLocationBtn'); const locationSelect = document.getElementById('locationSelect'); - + // Check if geolocation is supported if (!navigator.geolocation) { - const errorMsg = currentLanguage === 'de' ? + const errorMsg = currentLanguage === 'de' ? 'Geolocation wird von diesem Browser nicht unterstützt.' : 'Geolocation is not supported by this browser.'; showLocationError(errorMsg); @@ -265,9 +265,9 @@ async function findNearestLocation() { const locationsWithDistance = locationsData.map(location => ({ ...location, distance: calculateDistance( - userLat, - userLon, - parseFloat(location.latitude), + userLat, + userLon, + parseFloat(location.latitude), parseFloat(location.longitude) ) })); @@ -279,7 +279,7 @@ async function findNearestLocation() { // Select the nearest location in the dropdown locationSelect.value = nearestLocation.name; - + // Trigger change event to update the leaderboard locationSelect.dispatchEvent(new Event('change')); @@ -289,27 +289,27 @@ async function findNearestLocation() { } catch (error) { console.error('Error getting location:', error); let errorMessage = currentLanguage === 'de' ? 'Standort konnte nicht ermittelt werden.' : 'Location could not be determined.'; - + if (error.code) { - switch(error.code) { + switch (error.code) { case error.PERMISSION_DENIED: - errorMessage = currentLanguage === 'de' ? + errorMessage = currentLanguage === 'de' ? 'Standortzugriff wurde verweigert. Bitte erlaube den Standortzugriff in den Browser-Einstellungen.' : 'Location access was denied. Please allow location access in browser settings.'; break; case error.POSITION_UNAVAILABLE: - errorMessage = currentLanguage === 'de' ? + errorMessage = currentLanguage === 'de' ? 'Standortinformationen sind nicht verfügbar.' : 'Location information is not available.'; break; case error.TIMEOUT: - errorMessage = currentLanguage === 'de' ? + errorMessage = currentLanguage === 'de' ? 'Zeitüberschreitung beim Abrufen des Standorts.' : 'Timeout while retrieving location.'; break; } } - + showLocationError(errorMessage); } finally { // Reset button state @@ -324,17 +324,17 @@ function showLocationSuccess(locationName, distance) { const notificationBubble = document.getElementById('notificationBubble'); const notificationTitle = document.getElementById('notificationTitle'); const notificationSubtitle = document.getElementById('notificationSubtitle'); - + // Update notification content const locationFoundText = currentLanguage === 'de' ? 'Standort gefunden!' : 'Location found!'; const distanceText = currentLanguage === 'de' ? 'km entfernt' : 'km away'; notificationTitle.textContent = `📍 ${locationFoundText}`; notificationSubtitle.textContent = `${locationName} (${distance.toFixed(1)} ${distanceText})`; - + // Show notification notificationBubble.classList.remove('hide'); notificationBubble.classList.add('show'); - + // Auto-hide after 4 seconds setTimeout(() => { hideNotification(); @@ -346,19 +346,19 @@ function showLocationError(message) { const notificationBubble = document.getElementById('notificationBubble'); const notificationTitle = document.getElementById('notificationTitle'); const notificationSubtitle = document.getElementById('notificationSubtitle'); - + // Change notification style to error notificationBubble.style.background = 'linear-gradient(135deg, #dc3545, #c82333)'; - + // Update notification content const errorText = currentLanguage === 'de' ? 'Fehler' : 'Error'; notificationTitle.textContent = `❌ ${errorText}`; notificationSubtitle.textContent = message; - + // Show notification notificationBubble.classList.remove('hide'); notificationBubble.classList.add('show'); - + // Auto-hide after 6 seconds setTimeout(() => { hideNotification(); @@ -371,10 +371,10 @@ function showLocationError(message) { function showLocationSelectionPrompt() { const rankingList = document.getElementById('rankingList'); const emptyTitle = currentLanguage === 'de' ? 'Standort auswählen' : 'Select Location'; - const emptyDescription = currentLanguage === 'de' ? + const emptyDescription = currentLanguage === 'de' ? 'Bitte wähle einen Standort aus dem Dropdown-Menü aus
oder nutze den "📍 Mein Standort" Button, um automatisch
den nächstgelegenen Standort zu finden.' : 'Please select a location from the dropdown menu
or use the "📍 My Location" button to automatically
find the nearest location.'; - + rankingList.innerHTML = `
📍
@@ -382,12 +382,12 @@ function showLocationSelectionPrompt() {
${emptyDescription}
`; - + // Reset stats to show no data document.getElementById('totalPlayers').textContent = '0'; document.getElementById('bestTime').textContent = '--:--'; document.getElementById('totalRecords').textContent = '0'; - + // Update current selection display updateCurrentSelection(); } @@ -397,13 +397,13 @@ async function loadData() { try { const location = document.getElementById('locationSelect').value; const period = document.querySelector('.time-tab.active').dataset.period; - + // Don't load data if no location is selected if (!location || location === '') { showLocationSelectionPrompt(); return; } - + // Build query parameters const params = new URLSearchParams(); if (location && location !== 'all') { @@ -412,26 +412,26 @@ async function loadData() { if (period && period !== 'all') { params.append('period', period); } - + // Fetch times with player and location data from local database const response = await fetch(`/api/v1/public/times-with-details?${params.toString()}`); if (!response.ok) { throw new Error('Failed to fetch times'); } - + const times = await response.json(); - + // Convert to the format expected by the leaderboard const leaderboardData = times.map(time => { const { minutes, seconds, milliseconds } = time.recorded_time; const timeString = `${minutes}:${seconds.toString().padStart(2, '0')}.${milliseconds}`; - const playerName = time.player ? - `${time.player.firstname} ${time.player.lastname}` : - (currentLanguage === 'de' ? 'Unbekannter Spieler' : 'Unknown Player'); - const locationName = time.location ? time.location.name : - (currentLanguage === 'de' ? 'Unbekannter Standort' : 'Unknown Location'); + const playerName = time.player ? + `${time.player.firstname} ${time.player.lastname}` : + (currentLanguage === 'de' ? 'Unbekannter Spieler' : 'Unknown Player'); + const locationName = time.location ? time.location.name : + (currentLanguage === 'de' ? 'Unbekannter Standort' : 'Unknown Location'); const date = new Date(time.created_at).toISOString().split('T')[0]; - + return { name: playerName, time: timeString, @@ -450,7 +450,7 @@ async function loadData() { updateLeaderboard(leaderboardData); updateStats(leaderboardData); updateCurrentSelection(); - + } catch (error) { console.error('Error loading data:', error); // Fallback to sample data if API fails @@ -470,7 +470,7 @@ function loadSampleData() { { name: "Carsten Graf", time: "01:11.945", date: "2025-09-02", location: "Test" }, { name: "Carsten Graf", time: "01:11.945", date: "2025-09-02", location: "Ulm Donaubad" } ]; - + updateLeaderboard(sampleData); updateStats(sampleData); updateCurrentSelection(); @@ -485,7 +485,7 @@ function updateStats(data) { const totalPlayers = new Set(data.map(item => item.name)).size; const bestTime = data.length > 0 ? data[0].time : '--:--'; const totalRecords = data.length; - + document.getElementById('totalPlayers').textContent = totalPlayers; document.getElementById('bestTime').textContent = bestTime; document.getElementById('totalRecords').textContent = totalRecords; @@ -494,13 +494,13 @@ function updateStats(data) { function updateCurrentSelection() { const location = document.getElementById('locationSelect').value; const period = document.querySelector('.time-tab.active').dataset.period; - + // Get the display text from the selected option const locationSelect = document.getElementById('locationSelect'); const selectedLocationOption = locationSelect.options[locationSelect.selectedIndex]; - const locationDisplay = selectedLocationOption ? selectedLocationOption.textContent : + const locationDisplay = selectedLocationOption ? selectedLocationOption.textContent : (currentLanguage === 'de' ? '📍 Bitte Standort auswählen' : '📍 Please select location'); - + const periodIcons = currentLanguage === 'de' ? { 'today': '📅 Heute', 'week': '📊 Diese Woche', @@ -512,24 +512,24 @@ function updateCurrentSelection() { 'month': '📈 This Month', 'all': '♾️ All Times' }; - - document.getElementById('currentSelection').textContent = + + document.getElementById('currentSelection').textContent = `${locationDisplay} • ${periodIcons[period]}`; - + const lastSyncText = currentLanguage === 'de' ? 'Letzter Sync' : 'Last Sync'; - document.getElementById('lastUpdated').textContent = + document.getElementById('lastUpdated').textContent = `${lastSyncText}: ${new Date().toLocaleTimeString(currentLanguage === 'de' ? 'de-DE' : 'en-US')}`; } function updateLeaderboard(data) { const rankingList = document.getElementById('rankingList'); - + if (data.length === 0) { const emptyTitle = currentLanguage === 'de' ? 'Keine Rekorde gefunden' : 'No records found'; - const emptyDescription = currentLanguage === 'de' ? + const emptyDescription = currentLanguage === 'de' ? 'Für diese Filtereinstellungen liegen noch keine Zeiten vor.
Versuche es mit einem anderen Zeitraum oder Standort.' : 'No times available for these filter settings.
Try a different time period or location.'; - + rankingList.innerHTML = `
🏁
@@ -539,17 +539,17 @@ function updateLeaderboard(data) { `; return; } - + rankingList.innerHTML = data.map((player, index) => { const rank = index + 1; let positionClass = ''; let trophy = ''; - + if (rank === 1) { positionClass = 'gold'; trophy = '👑'; } else if (rank === 2) { - positionClass = 'silver'; + positionClass = 'silver'; trophy = '🥈'; } else if (rank === 3) { positionClass = 'bronze'; @@ -557,12 +557,12 @@ function updateLeaderboard(data) { } else if (rank <= 10) { trophy = '⭐'; } - + const formatDate = new Date(player.date).toLocaleDateString(currentLanguage === 'de' ? 'de-DE' : 'en-US', { day: '2-digit', month: 'short' }); - + return `
#${rank}
@@ -583,7 +583,7 @@ function updateLeaderboard(data) { // Event Listeners Setup function setupEventListeners() { // Location select event listener - document.getElementById('locationSelect').addEventListener('change', function() { + document.getElementById('locationSelect').addEventListener('change', function () { // Save location selection to cookie const selectedOption = this.options[this.selectedIndex]; if (selectedOption.value) { @@ -592,10 +592,10 @@ function setupEventListeners() { // Load data loadData(); }); - + // Time tab event listeners document.querySelectorAll('.time-tab').forEach(tab => { - tab.addEventListener('click', function() { + tab.addEventListener('click', function () { // Remove active class from all tabs document.querySelectorAll('.time-tab').forEach(t => t.classList.remove('active')); // Add active class to clicked tab @@ -639,19 +639,19 @@ function translateElement(element, language) { function changeLanguage() { const languageSelect = document.getElementById('languageSelect'); currentLanguage = languageSelect.value; - + // Save language preference localStorage.setItem('ninjacross_language', currentLanguage); - + // Translate all elements with data attributes const elementsToTranslate = document.querySelectorAll('[data-de][data-en]'); elementsToTranslate.forEach(element => { translateElement(element, currentLanguage); }); - + // Update dynamic content updateDynamicContent(); - + console.log(`🌐 Language changed to: ${currentLanguage}`); } @@ -660,26 +660,26 @@ function updateDynamicContent() { // Update location select placeholder const locationSelect = document.getElementById('locationSelect'); if (locationSelect && locationSelect.options[0]) { - locationSelect.options[0].textContent = currentLanguage === 'de' ? + locationSelect.options[0].textContent = currentLanguage === 'de' ? '📍 Bitte Standort auswählen' : '📍 Please select location'; } - + // Update find location button const findLocationBtn = document.getElementById('findLocationBtn'); if (findLocationBtn) { - findLocationBtn.textContent = currentLanguage === 'de' ? + findLocationBtn.textContent = currentLanguage === 'de' ? '📍 Mein Standort' : '📍 My Location'; - findLocationBtn.title = currentLanguage === 'de' ? + findLocationBtn.title = currentLanguage === 'de' ? 'Nächstgelegenen Standort finden' : 'Find nearest location'; } - + // Update refresh button const refreshBtn = document.querySelector('.refresh-btn'); if (refreshBtn) { - refreshBtn.textContent = currentLanguage === 'de' ? + refreshBtn.textContent = currentLanguage === 'de' ? '⚡ Live Update' : '⚡ Live Update'; } - + // Update notification elements const notificationTitle = document.getElementById('notificationTitle'); const notificationSubtitle = document.getElementById('notificationSubtitle'); @@ -687,13 +687,13 @@ function updateDynamicContent() { notificationTitle.textContent = currentLanguage === 'de' ? 'Neue Zeit!' : 'New Time!'; } if (notificationSubtitle) { - notificationSubtitle.textContent = currentLanguage === 'de' ? + notificationSubtitle.textContent = currentLanguage === 'de' ? 'Ein neuer Rekord wurde erstellt' : 'A new record has been created'; } - + // Update current selection display updateCurrentSelection(); - + // Reload data to update any dynamic content if (document.getElementById('locationSelect').value) { loadData(); @@ -715,16 +715,16 @@ function loadLanguagePreference() { } // Start the application when DOM is loaded -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { loadLanguagePreference(); changeLanguage(); // Apply saved language init(); startAutoRefresh(); - + // Add cookie settings button functionality const cookieSettingsBtn = document.getElementById('cookie-settings-footer'); if (cookieSettingsBtn) { - cookieSettingsBtn.addEventListener('click', function() { + cookieSettingsBtn.addEventListener('click', function () { if (window.cookieConsent) { window.cookieConsent.resetConsent(); }