Example: Chad Brown = CB, John Smith = JS

Dashboard

Total Rounds
0
Season 2026
Best Score
Average Score
Current season
Favorite Course
Most rounds played

Recent Rounds

Date Course Score Temp Conditions Wind Notes
No scores yet

Log Score

Minneapolis Weather
Select a date to fetch weather

Score History

Date Course Score Temp Conditions Wind Notes
No scores yet

Player Leaderboard

Performance Analytics

Scores Over Time
Average by Player
Rounds by Course
if (weatherCache[date]) return weatherCache[date]; try { const url = `https://archive-api.open-meteo.com/v1/archive?latitude=44.9537&longitude=-93.0900&start_date=${date}&end_date=${date}&daily=temperature_2m_max,weather_code,wind_speed_10m_max,wind_direction_10m_dominant&temperature_unit=fahrenheit&timezone=America/Chicago`; const response = await fetch(url); const data = await response.json(); if (!data.daily || !data.daily.temperature_2m_max) throw new Error("Invalid response"); const temp = Math.round(data.daily.temperature_2m_max[0]); const weatherCode = data.daily.weather_code[0]; const windSpeed = Math.round(data.daily.wind_speed_10m_max[0]); const windDir = data.daily.wind_direction_10m_dominant[0]; let conditions = "Clear"; if (weatherCode >= 45 && weatherCode <= 48) conditions = "Fog"; else if (weatherCode >= 51 && weatherCode <= 65) conditions = "Rainy"; else if (weatherCode >= 71 && weatherCode <= 86) conditions = "Rainy"; const windDirLabels = {0: "N", 45: "NE", 90: "E", 135: "SE", 180: "S", 225: "SW", 270: "W", 315: "NW"}; const directions = Object.keys(windDirLabels).map(Number); const closest = directions.reduce((prev, curr) => Math.abs(curr - windDir) < Math.abs(prev - windDir) ? curr : prev); const windDirLabel = windDirLabels[closest]; weatherCache[date] = {temp, windSpeed, windDirection: windDirLabel, conditions}; return weatherCache[date]; } catch (error) { throw error; } } document.getElementById('scoreDate').addEventListener('change', async (e) => { if (e.target.value) { document.getElementById('weatherDisplay').textContent = 'Loading...'; try { const w = await fetchWeather(e.target.value); document.getElementById('weatherDisplay').textContent = `${w.temp}°F | ${w.conditions} | Wind: ${w.windSpeed} mph ${w.windDirection}`; } catch (error) { document.getElementById('weatherDisplay').textContent = '❌ Weather unavailable'; } } }); async function addScore() { const date = document.getElementById('scoreDate').value; const course = document.getElementById('course').value.trim(); const score = document.getElementById('score').value.trim(); const notes = document.getElementById('notes').value.trim(); if (!date || !course || !score) { alert('Fill in: Date, Course, Score'); return; } try { const weather = weatherCache[date] || {temp: 70, windSpeed: 10, windDirection: 'N', conditions: 'Unknown'}; const newScore = { golfer: currentUser, date, course, score, notes, temp: weather.temp, windSpeed: weather.windSpeed, windDirection: weather.windDirection, conditions: weather.conditions }; const scoresRef = ref(db, SCORES_REF); const newScoreRef = push(scoresRef); await set(newScoreRef, newScore); document.getElementById('scoreDate').valueAsDate = new Date(); document.getElementById('course').value = ''; document.getElementById('score').value = ''; document.getElementById('notes').value = ''; document.getElementById('weatherDisplay').textContent = 'Select a date to fetch weather'; alert('✅ Score saved!'); } catch (error) { console.error('Error adding score:', error); alert('Error adding score'); } } function updateUI() { document.getElementById('headerUserName').textContent = currentUser; document.getElementById('headerInitials').textContent = currentUser; document.getElementById('scoreDate').valueAsDate = new Date(); const scores = currentUserData.scores; document.getElementById('totalRounds').textContent = scores.length; if (scores.length > 0) { const scoreVals = scores.map(s => parseInt(s.score)); const best = Math.min(...scoreVals); const bestRound = scores.find(s => parseInt(s.score) === best); document.getElementById('bestScore').textContent = best; document.getElementById('bestScoreCourse').textContent = bestRound.course; const avg = (scoreVals.reduce((a, b) => a + b, 0) / scoreVals.length).toFixed(1); document.getElementById('avgScore').textContent = avg; const courseCounts = {}; scores.forEach(s => { courseCounts[s.course] = (courseCounts[s.course] || 0) + 1; }); const fav = Object.entries(courseCounts).sort((a, b) => b[1] - a[1])[0][0]; document.getElementById('favCourse').textContent = fav; } renderTables(); } function renderTables() { const tbody = document.getElementById('tableBody'); const recent = document.getElementById('recentRoundsTable'); tbody.innerHTML = ''; recent.innerHTML = ''; if (currentUserData.scores.length === 0) { tbody.innerHTML = 'No scores yet'; recent.innerHTML = 'No scores yet'; return; } const sorted = [...currentUserData.scores].sort((a, b) => new Date(b.date) - new Date(a.date)); sorted.forEach(s => { const row = document.createElement('tr'); row.innerHTML = ` ${s.date} ${s.course} ${s.score} ${s.temp}°F ${s.conditions} ${s.windSpeed} mph ${s.windDirection} ${s.notes || '—'} `; tbody.appendChild(row); if (sorted.indexOf(s) < 5) { const row2 = document.createElement('tr'); row2.innerHTML = ` ${s.date} ${s.course} ${s.score} ${s.temp}°F ${s.conditions} ${s.windSpeed} mph ${s.windDirection} ${s.notes || '—'} `; recent.appendChild(row2); } }); renderLeaderboard(); } function renderLeaderboard() { const container = document.getElementById('leaderboardContainer'); container.innerHTML = ''; // Group all scores by player const playerStats = {}; allScores.forEach(score => { if (!playerStats[score.golfer]) { playerStats[score.golfer] = []; } playerStats[score.golfer].push(score); }); Object.entries(playerStats).forEach(([initials, scores]) => { if (scores.length === 0) return; const scoreVals = scores.map(s => parseInt(s.score)); const best = Math.min(...scoreVals); const worst = Math.max(...scoreVals); const avg = (scoreVals.reduce((a, b) => a + b, 0) / scoreVals.length).toFixed(1); const courseCounts = {}; scores.forEach(s => { courseCounts[s.course] = (courseCounts[s.course] || 0) + 1; }); const numCourses = Object.keys(courseCounts).length; const card = document.createElement('div'); card.className = 'player-card'; card.onclick = function() { document.querySelectorAll('.player-card-inner.flipped').forEach(c => { if (c !== this.querySelector('.player-card-inner')) c.classList.remove('flipped'); }); this.querySelector('.player-card-inner').classList.toggle('flipped'); }; const bestRound = scores.reduce((a, b) => parseInt(a.score) < parseInt(b.score) ? a : b); card.innerHTML = `
${initials}
Rounds ${scores.length}
Best Score ${best}
Average ${avg}
Worst Score ${worst}
Courses ${numCourses}
Best Round
Course ${bestRound.course}
Score ${bestRound.score}
Date ${bestRound.date}
Temp ${bestRound.temp}°F
Conditions ${bestRound.conditions}
`; container.appendChild(card); }); } function updateCharts() { if (allScores.length === 0) return; const playerData = {}; allScores.forEach(s => { if (!playerData[s.golfer]) playerData[s.golfer] = []; playerData[s.golfer].push({date: s.date, score: parseInt(s.score)}); }); Object.keys(playerData).forEach(p => { playerData[p].sort((a, b) => new Date(a.date) - new Date(b.date)); }); const colors = ['#10b981', '#3b82f6', '#f59e0b', '#ef4444', '#8b5cf6']; const datasets = Object.entries(playerData).map(([name, scores], i) => ({ label: name, data: scores.map(s => s.score), borderColor: colors[i % colors.length], backgroundColor: colors[i % colors.length] + '40', tension: 0.4, fill: true })); const allDates = [...new Set(allScores.map(s => s.date))].sort(); if (window.scoresChartInstance) window.scoresChartInstance.destroy(); window.scoresChartInstance = new Chart(document.getElementById('scoresChart'), { type: 'line', data: {labels: allDates, datasets}, options: {responsive: true, maintainAspectRatio: false, plugins: {legend: {labels: {color: '#f1f5f9'}}}, scales: {y: {ticks: {color: '#cbd5e1'}, grid: {color: '#1e293b'}}, x: {ticks: {color: '#cbd5e1'}, grid: {color: '#1e293b'}}}} }); const avgData = Object.entries(playerData).map(([name, scores]) => { const avg = scores.reduce((a, s) => a + s.score, 0) / scores.length; return avg.toFixed(1); }); if (window.avgChartInstance) window.avgChartInstance.destroy(); window.avgChartInstance = new Chart(document.getElementById('avgChart'), { type: 'bar', data: {labels: Object.keys(playerData), datasets: [{label: 'Average Score', data: avgData, backgroundColor: colors}]}, options: {responsive: true, maintainAspectRatio: false, plugins: {legend: {labels: {color: '#f1f5f9'}}}, scales: {y: {ticks: {color: '#cbd5e1'}, grid: {color: '#1e293b'}}, x: {ticks: {color: '#cbd5e1'}, grid: {color: '#1e293b'}}}} }); const courseCounts = {}; allScores.forEach(s => { courseCounts[s.course] = (courseCounts[s.course] || 0) + 1; }); if (window.courseChartInstance) window.courseChartInstance.destroy(); window.courseChartInstance = new Chart(document.getElementById('courseChart'), { type: 'doughnut', data: {labels: Object.keys(courseCounts), datasets: [{data: Object.values(courseCounts), backgroundColor: colors}]}, options: {responsive: true, maintainAspectRatio: false, plugins: {legend: {labels: {color: '#f1f5f9'}}}} }); } document.getElementById('username').addEventListener('keydown', e => { if (e.key === 'Enter') login(); }); // Start loading scores from Firebase loadScoresFromFirebase();