diff --git a/courses.html b/courses.html deleted file mode 100644 index 3ecd07b..0000000 --- a/courses.html +++ /dev/null @@ -1,612 +0,0 @@ - - -
- - -Error loading courses. Please try again.
'; + } +} + +function searchCourses() { + const searchInput = document.getElementById('course-search'); + const searchTerm = searchInput.value.toLowerCase().trim(); + + if (!searchTerm) { + displayCourses(allCourses); + updateSearchInfo(allCourses.length, allCourses.length); + return; + } + + const filtered = allCourses.filter(course => { + return course.name.toLowerCase().includes(searchTerm) || + course.city.toLowerCase().includes(searchTerm); + }); + + displayCourses(filtered); + updateSearchInfo(filtered.length, allCourses.length); +} + +function updateSearchInfo(showing, total) { + const infoDiv = document.getElementById('search-results-info'); + if (showing === total) { + infoDiv.textContent = `Showing all ${total} courses`; + } else { + infoDiv.textContent = `Showing ${showing} of ${total} courses`; + } +} + +function displayCourses(courses) { + const tableDiv = document.getElementById('courses-table'); + + if (courses.length === 0) { + tableDiv.innerHTML = 'No courses found. Click "Scrape Courses" to load Swedish courses from PDGA.
'; + return; + } + + let tableHTML = ` +| Course Name | +City | +Last Updated | +Actions | +
|---|---|---|---|
|
+ ${course.name}
+ ${course.city}
+ |
+ ${course.city} | +${lastUpdated} | ++ + | +
|
+
+
+ Click to load layouts...
+ |
+ |||
No ratings found.
'; + return; + } + + let tableHTML = ` +| Rank | +Player Name | +PDGA # | +Rating | +Change | +Predicted | +
|---|---|---|---|---|---|
| ${index + 1} | +
+ ${player.name}
+ PDGA #${player.pdgaNumber}
+ |
+ #${player.pdgaNumber} | +
+
+
+
+
+
+
+ |
+ ${ratingChangeText} | +
+
+ ${player.predictedRating || 'N/A'}
+
+
+
+
+ |
+
|
+
+
+
+ Rating History for ${player.name}
+
+
+
+
+
+ Click to load rating history...
+ |
+ |||||
Is this the correct player you want to add?
++ ${message} +
++ Please check the PDGA number and try again. +
+ `; + footer.innerHTML = ` + + `; + + modal.style.display = 'flex'; +} + +function showInfoModal(message) { + const modal = document.getElementById('add-player-modal'); + const header = document.getElementById('add-player-modal-header'); + const body = document.getElementById('add-player-modal-body'); + const footer = document.getElementById('add-player-modal-footer'); + + header.textContent = 'Information'; + body.innerHTML = ` ++ ${message} +
+ `; + footer.innerHTML = ` + + `; + + modal.style.display = 'flex'; +} + +async function confirmAddPlayer() { + if (!pendingPlayerData) { + closeAddPlayerModal(); + return; + } + + const modal = document.getElementById('add-player-modal'); + const body = document.getElementById('add-player-modal-body'); + const footer = document.getElementById('add-player-modal-footer'); + + body.innerHTML = 'Adding player...
'; + footer.innerHTML = ''; + + try { + const response = await fetch('/api/add-player', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ pdgaNumber: pendingPlayerData.pdgaNumber }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to add player'); + } + + body.innerHTML = ` +
+
+ ${data.player.name} has been added successfully!
+
+ ${error.message} +
+ `; + footer.innerHTML = ` + + `; + } +} + +function closeAddPlayerModal(event) { + const modal = document.getElementById('add-player-modal'); + modal.style.display = 'none'; + pendingPlayerData = null; +} diff --git a/public/js/progress.js b/public/js/progress.js new file mode 100644 index 0000000..0db252e --- /dev/null +++ b/public/js/progress.js @@ -0,0 +1,101 @@ +function fetchRatingsWithProgress() { + const progressSection = document.getElementById('progress-section'); + const progressBar = document.getElementById('progress-bar'); + const progressText = document.getElementById('progress-text'); + const tableDiv = document.getElementById('ratings-table'); + + progressSection.style.display = 'block'; + tableDiv.innerHTML = ''; + + const eventSource = new EventSource('/api/ratings/progress'); + + eventSource.onmessage = function(event) { + const data = JSON.parse(event.data); + + if (data.status === 'loading') { + const percentage = Math.round((data.current / data.total) * 100); + progressBar.style.width = `${percentage}%`; + progressBar.textContent = `${percentage}%`; + progressText.textContent = `Loading player ${data.current}/${data.total}: PDGA #${data.pdgaNumber}`; + } else if (data.status === 'completed') { + const percentage = Math.round((data.current / data.total) * 100); + progressBar.style.width = `${percentage}%`; + progressBar.textContent = `${percentage}%`; + progressText.textContent = `Loaded ${data.name} (${data.current}/${data.total})`; + } else if (data.status === 'error') { + const percentage = Math.round((data.current / data.total) * 100); + progressBar.style.width = `${percentage}%`; + progressBar.textContent = `${percentage}%`; + progressText.textContent = `Error loading PDGA #${data.pdgaNumber} (${data.current}/${data.total})`; + } else if (data.status === 'complete') { + progressSection.style.display = 'none'; + displayRatings(data.ratings); + eventSource.close(); + } else if (data.status === 'error') { + progressSection.style.display = 'none'; + tableDiv.innerHTML = 'Error loading ratings. Please try again.
'; + eventSource.close(); + } + }; + + eventSource.onerror = function() { + progressSection.style.display = 'none'; + tableDiv.innerHTML = 'Connection error. Please refresh the page.
'; + eventSource.close(); + }; +} + +function loadAllPlayers() { + const button = document.getElementById('load-all-btn'); + const originalText = button.textContent; + button.textContent = 'Loading...'; + button.style.pointerEvents = 'none'; + + try { + const progressSection = document.getElementById('progress-section'); + const progressBar = document.getElementById('progress-bar'); + const progressText = document.getElementById('progress-text'); + const tableDiv = document.getElementById('ratings-table'); + + progressSection.style.display = 'block'; + tableDiv.innerHTML = ''; + + const eventSource = new EventSource('/api/load-all-players'); + + eventSource.onmessage = function(event) { + const data = JSON.parse(event.data); + + if (data.status === 'loading' || data.status === 'completed' || data.status === 'error') { + const percentage = Math.round((data.current / data.total) * 100); + progressBar.style.width = `${percentage}%`; + progressBar.textContent = `${percentage}%`; + + if (data.status === 'loading') { + progressText.textContent = `Loading player ${data.current}/${data.total}: PDGA #${data.pdgaNumber}`; + } else if (data.status === 'completed') { + progressText.textContent = `Loaded ${data.name} (${data.current}/${data.total})`; + } else if (data.status === 'error') { + progressText.textContent = `Error loading PDGA #${data.pdgaNumber} (${data.current}/${data.total})`; + } + } else if (data.status === 'complete') { + progressSection.style.display = 'none'; + displayRatings(data.ratings); + eventSource.close(); + button.textContent = originalText; + button.style.pointerEvents = 'auto'; + } + }; + + eventSource.onerror = function() { + progressSection.style.display = 'none'; + tableDiv.innerHTML = 'Connection error. Please refresh the page.
'; + eventSource.close(); + button.textContent = originalText; + button.style.pointerEvents = 'auto'; + }; + } catch (error) { + console.error('Error loading all players:', error); + button.textContent = originalText; + button.style.pointerEvents = 'auto'; + } +} diff --git a/public/js/tooltips.js b/public/js/tooltips.js new file mode 100644 index 0000000..6eab87e --- /dev/null +++ b/public/js/tooltips.js @@ -0,0 +1,24 @@ +function setupTooltip(element, tooltip, getText) { + element.addEventListener('mouseenter', (e) => { + tooltip.textContent = getText(); + tooltip.style.display = 'block'; + tooltip.style.left = `${e.clientX + 15}px`; + tooltip.style.top = `${e.clientY - 35}px`; + }); + + element.addEventListener('mousemove', (e) => { + tooltip.style.left = `${e.clientX + 15}px`; + tooltip.style.top = `${e.clientY - 35}px`; + }); + + element.addEventListener('mouseleave', () => { + tooltip.style.display = 'none'; + }); +} + +function replaceWithTooltip(element, tooltip, getText) { + const newElement = element.cloneNode(true); + element.parentNode.replaceChild(newElement, element); + setupTooltip(newElement, tooltip, getText); + return newElement; +} diff --git a/server.js b/server.js index cff026f..f5df138 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,8 @@ const pageRoutes = require('./src/routes/pages'); const app = express(); const PORT = 3000; +app.set('view engine', 'ejs'); +app.set('views', path.join(__dirname, 'views/pages')); app.use(express.static('public')); app.use(express.json()); diff --git a/src/routes/pages.js b/src/routes/pages.js index 33cfb9b..2eb677f 100644 --- a/src/routes/pages.js +++ b/src/routes/pages.js @@ -1,13 +1,17 @@ const express = require('express'); -const path = require('path'); const router = express.Router(); router.get('/', (req, res) => { - res.sendFile(path.join(__dirname, '../../index.html')); + res.render('index'); }); +router.get('/courses', (req, res) => { + res.render('courses'); +}); + +// Keep old URL working router.get('/courses.html', (req, res) => { - res.sendFile(path.join(__dirname, '../../courses.html')); + res.redirect('/courses'); }); module.exports = router; diff --git a/views/pages/courses.ejs b/views/pages/courses.ejs new file mode 100644 index 0000000..c8d72ed --- /dev/null +++ b/views/pages/courses.ejs @@ -0,0 +1,31 @@ +<% var body = ` +