refactor: remove tour feature and Tjing import
Tour functionality has moved to its own project (HyzrTour). Removes all tour-related code, Tjing integration, and associated views/styles/scripts. Keeps the saveCourseToDB ON CONFLICT fix.
This commit is contained in:
@@ -54,88 +54,6 @@ async function scrapeCourses() {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchTjing() {
|
||||
var input = document.getElementById('tjing-search');
|
||||
var query = input.value.trim();
|
||||
if (query.length < 2) return;
|
||||
|
||||
var btn = document.getElementById('tjing-search-btn');
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
var response = await fetch('/api/tjing/search?q=' + encodeURIComponent(query));
|
||||
var courses = await response.json();
|
||||
var container = document.getElementById('tjing-results');
|
||||
container.textContent = '';
|
||||
|
||||
if (courses.length === 0) {
|
||||
var p = document.createElement('p');
|
||||
p.className = 'no-layouts';
|
||||
p.textContent = 'No courses found on Tjing';
|
||||
container.appendChild(p);
|
||||
return;
|
||||
}
|
||||
|
||||
courses.forEach(function(course) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'tjing-result';
|
||||
|
||||
var info = document.createElement('div');
|
||||
info.className = 'tjing-result-info';
|
||||
|
||||
var name = document.createElement('span');
|
||||
name.className = 'tjing-result-name';
|
||||
name.textContent = course.name;
|
||||
|
||||
var addr = document.createElement('span');
|
||||
addr.className = 'tjing-result-address';
|
||||
addr.textContent = course.address || '';
|
||||
|
||||
info.appendChild(name);
|
||||
info.appendChild(addr);
|
||||
|
||||
var importBtn = document.createElement('button');
|
||||
importBtn.className = 'btn';
|
||||
importBtn.textContent = 'Import';
|
||||
importBtn.addEventListener('click', function() { importFromTjing(course.id, importBtn); });
|
||||
|
||||
item.appendChild(info);
|
||||
item.appendChild(importBtn);
|
||||
container.appendChild(item);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error searching Tjing:', error);
|
||||
alert('Failed to search Tjing');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function importFromTjing(tjingId, btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Importing...';
|
||||
|
||||
try {
|
||||
var response = await fetch('/api/tjing/import/' + tjingId, { method: 'POST' });
|
||||
var data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
btn.textContent = 'Imported!';
|
||||
btn.style.background = 'var(--green)';
|
||||
htmx.ajax('GET', '/partials/course-table', '#courses-table');
|
||||
} else {
|
||||
alert(data.error || 'Failed to import');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Import';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error importing from Tjing:', error);
|
||||
alert('Failed to import course');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Import';
|
||||
}
|
||||
}
|
||||
|
||||
async function scrapeLayouts(courseId, courseName) {
|
||||
const icon = document.querySelector(`#row-${courseId} .refresh-icon`);
|
||||
icon.classList.add('spinning');
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
var currentPdgaNumber = null;
|
||||
|
||||
function initTour(code) {
|
||||
// Check if player already joined (stored in localStorage)
|
||||
var stored = localStorage.getItem('tour_' + code);
|
||||
if (stored) {
|
||||
var data = JSON.parse(stored);
|
||||
currentPdgaNumber = data.pdgaNumber;
|
||||
showResultSection();
|
||||
}
|
||||
}
|
||||
|
||||
function showResultSection() {
|
||||
var joinSection = document.getElementById('join-section');
|
||||
var resultSection = document.getElementById('result-section');
|
||||
if (joinSection) joinSection.style.display = 'none';
|
||||
if (resultSection) resultSection.style.display = '';
|
||||
}
|
||||
|
||||
async function joinTour() {
|
||||
var pdgaNumber = document.getElementById('pdga-number').value.trim();
|
||||
var playerName = document.getElementById('player-name').value.trim();
|
||||
|
||||
if (!pdgaNumber || !playerName) {
|
||||
alert('Please enter your PDGA number and name');
|
||||
return;
|
||||
}
|
||||
|
||||
var code = window.location.pathname.split('/').pop();
|
||||
|
||||
try {
|
||||
var res = await fetch('/api/tours/' + code + '/join', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ pdgaNumber: pdgaNumber, playerName: playerName })
|
||||
});
|
||||
|
||||
var data = await res.json();
|
||||
if (data.success) {
|
||||
currentPdgaNumber = pdgaNumber;
|
||||
localStorage.setItem('tour_' + code, JSON.stringify({ pdgaNumber: pdgaNumber, playerName: playerName }));
|
||||
showResultSection();
|
||||
// Refresh leaderboard
|
||||
htmx.ajax('GET', '/partials/tour-leaderboard/' + code, { target: '#leaderboard-container' });
|
||||
} else {
|
||||
alert(data.error || 'Failed to join tour');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error joining tour:', err);
|
||||
alert('Failed to join tour');
|
||||
}
|
||||
}
|
||||
|
||||
async function recordResult(code) {
|
||||
var tourCourseId = document.getElementById('result-course').value;
|
||||
var totalStrokes = document.getElementById('result-strokes').value;
|
||||
|
||||
if (!tourCourseId || !totalStrokes) {
|
||||
alert('Please select a course and enter your strokes');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPdgaNumber) {
|
||||
alert('Please join the tour first');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var res = await fetch('/api/tours/' + code + '/results', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
pdgaNumber: currentPdgaNumber,
|
||||
tourCourseId: parseInt(tourCourseId),
|
||||
totalStrokes: parseInt(totalStrokes)
|
||||
})
|
||||
});
|
||||
|
||||
var data = await res.json();
|
||||
if (data.success) {
|
||||
document.getElementById('result-strokes').value = '';
|
||||
// Refresh leaderboard
|
||||
htmx.ajax('GET', '/partials/tour-leaderboard/' + code, { target: '#leaderboard-container' });
|
||||
} else {
|
||||
alert(data.error || 'Failed to record result');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error recording result:', err);
|
||||
alert('Failed to record result');
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
var coursesData = [];
|
||||
|
||||
async function loadCourses() {
|
||||
try {
|
||||
var res = await fetch('/api/tours/courses-with-layouts');
|
||||
coursesData = await res.json();
|
||||
updateCourseDropdowns();
|
||||
} catch (err) {
|
||||
console.error('Failed to load courses:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function populateSelectWithCourses(select) {
|
||||
select.textContent = '';
|
||||
var defaultOpt = document.createElement('option');
|
||||
defaultOpt.value = '';
|
||||
defaultOpt.textContent = 'Select a course...';
|
||||
select.appendChild(defaultOpt);
|
||||
|
||||
coursesData.forEach(function(course) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = course.id;
|
||||
opt.textContent = course.name + ' (' + course.city + ')';
|
||||
select.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCourseDropdowns() {
|
||||
document.querySelectorAll('.course-select').forEach(function(select) {
|
||||
var currentValue = select.value;
|
||||
populateSelectWithCourses(select);
|
||||
select.value = currentValue;
|
||||
});
|
||||
}
|
||||
|
||||
function onCourseChange(courseSelect) {
|
||||
var index = courseSelect.dataset.index;
|
||||
var layoutSelect = courseSelect.parentElement.querySelector('.layout-select');
|
||||
var manualFields = courseSelect.parentElement.querySelector('.manual-layout');
|
||||
var courseId = parseInt(courseSelect.value);
|
||||
|
||||
layoutSelect.textContent = '';
|
||||
layoutSelect.disabled = true;
|
||||
|
||||
if (manualFields) {
|
||||
manualFields.style.display = 'none';
|
||||
}
|
||||
|
||||
var defaultOpt = document.createElement('option');
|
||||
defaultOpt.value = '';
|
||||
defaultOpt.textContent = 'Select layout...';
|
||||
layoutSelect.appendChild(defaultOpt);
|
||||
|
||||
if (!courseId) return;
|
||||
|
||||
var course = coursesData.find(function(c) { return c.id === courseId; });
|
||||
if (!course) return;
|
||||
|
||||
if (course.layouts.length > 0) {
|
||||
course.layouts.forEach(function(layout) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = layout.id;
|
||||
opt.textContent = layout.name + ' (Par ' + layout.par + ')';
|
||||
layoutSelect.appendChild(opt);
|
||||
});
|
||||
|
||||
// Add "custom" option at the end
|
||||
var customOpt = document.createElement('option');
|
||||
customOpt.value = 'custom';
|
||||
customOpt.textContent = '+ Add custom layout...';
|
||||
layoutSelect.appendChild(customOpt);
|
||||
} else {
|
||||
// No layouts — show "custom" as the only real option
|
||||
var customOpt = document.createElement('option');
|
||||
customOpt.value = 'custom';
|
||||
customOpt.textContent = '+ Add custom layout...';
|
||||
layoutSelect.appendChild(customOpt);
|
||||
}
|
||||
|
||||
layoutSelect.disabled = false;
|
||||
}
|
||||
|
||||
function onLayoutChange(layoutSelect) {
|
||||
var manualFields = layoutSelect.parentElement.querySelector('.manual-layout');
|
||||
if (layoutSelect.value === 'custom') {
|
||||
if (manualFields) manualFields.style.display = '';
|
||||
} else {
|
||||
if (manualFields) manualFields.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function createManualLayoutFields() {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'manual-layout';
|
||||
div.style.display = 'none';
|
||||
|
||||
var nameInput = document.createElement('input');
|
||||
nameInput.type = 'text';
|
||||
nameInput.className = 'input layout-name-input';
|
||||
nameInput.placeholder = 'Layout name';
|
||||
|
||||
var parInput = document.createElement('input');
|
||||
parInput.type = 'number';
|
||||
parInput.className = 'input layout-par-input';
|
||||
parInput.placeholder = 'Par';
|
||||
parInput.min = '1';
|
||||
parInput.style.width = '80px';
|
||||
|
||||
div.appendChild(nameInput);
|
||||
div.appendChild(parInput);
|
||||
return div;
|
||||
}
|
||||
|
||||
var courseIndex = 1;
|
||||
|
||||
function createCourseEntry(index) {
|
||||
var entry = document.createElement('div');
|
||||
entry.className = 'course-entry';
|
||||
|
||||
var courseSelect = document.createElement('select');
|
||||
courseSelect.className = 'input course-select';
|
||||
courseSelect.dataset.index = index;
|
||||
populateSelectWithCourses(courseSelect);
|
||||
courseSelect.addEventListener('change', function() { onCourseChange(courseSelect); });
|
||||
|
||||
var layoutSelect = document.createElement('select');
|
||||
layoutSelect.className = 'input layout-select';
|
||||
layoutSelect.dataset.index = index;
|
||||
layoutSelect.disabled = true;
|
||||
var defaultOpt = document.createElement('option');
|
||||
defaultOpt.value = '';
|
||||
defaultOpt.textContent = 'Select course first';
|
||||
layoutSelect.appendChild(defaultOpt);
|
||||
layoutSelect.addEventListener('change', function() { onLayoutChange(layoutSelect); });
|
||||
|
||||
var manualFields = createManualLayoutFields();
|
||||
|
||||
entry.appendChild(courseSelect);
|
||||
entry.appendChild(layoutSelect);
|
||||
entry.appendChild(manualFields);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
function addCourseEntry() {
|
||||
var container = document.getElementById('course-selector');
|
||||
var entry = createCourseEntry(courseIndex);
|
||||
|
||||
var removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'btn-remove';
|
||||
removeBtn.type = 'button';
|
||||
var icon = document.createElement('i');
|
||||
icon.className = 'fas fa-times';
|
||||
removeBtn.appendChild(icon);
|
||||
removeBtn.addEventListener('click', function() { entry.remove(); });
|
||||
|
||||
entry.appendChild(removeBtn);
|
||||
container.appendChild(entry);
|
||||
|
||||
courseIndex++;
|
||||
}
|
||||
|
||||
async function createTour() {
|
||||
var name = document.getElementById('tour-name').value.trim();
|
||||
var startDate = document.getElementById('tour-start').value;
|
||||
var endDate = document.getElementById('tour-end').value;
|
||||
|
||||
if (!name || !startDate || !endDate) {
|
||||
alert('Please fill in name and dates');
|
||||
return;
|
||||
}
|
||||
|
||||
var courses = [];
|
||||
var valid = true;
|
||||
document.querySelectorAll('.course-entry').forEach(function(entry) {
|
||||
var courseId = entry.querySelector('.course-select').value;
|
||||
var layoutSelect = entry.querySelector('.layout-select');
|
||||
var layoutValue = layoutSelect.value;
|
||||
|
||||
if (!courseId) return;
|
||||
|
||||
if (layoutValue === 'custom') {
|
||||
var layoutName = entry.querySelector('.layout-name-input').value.trim();
|
||||
var par = entry.querySelector('.layout-par-input').value;
|
||||
if (!layoutName || !par) {
|
||||
alert('Please fill in layout name and par for custom layouts');
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
courses.push({ courseId: parseInt(courseId), layoutName: layoutName, par: parseInt(par) });
|
||||
} else if (layoutValue) {
|
||||
courses.push({ courseId: parseInt(courseId), layoutId: parseInt(layoutValue) });
|
||||
}
|
||||
});
|
||||
|
||||
if (!valid) return;
|
||||
|
||||
if (courses.length === 0) {
|
||||
alert('Please select at least one course with a layout');
|
||||
return;
|
||||
}
|
||||
|
||||
var btn = document.getElementById('create-tour-btn');
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
var res = await fetch('/api/tours', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name, startDate: startDate, endDate: endDate, courses: courses })
|
||||
});
|
||||
|
||||
var data = await res.json();
|
||||
if (data.success) {
|
||||
var link = document.getElementById('tour-link');
|
||||
link.href = '/tours/' + data.code;
|
||||
link.textContent = window.location.origin + '/tours/' + data.code;
|
||||
document.getElementById('tour-created').style.display = '';
|
||||
} else {
|
||||
alert(data.error || 'Failed to create tour');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error creating tour:', err);
|
||||
alert('Failed to create tour');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function goToTour() {
|
||||
var code = document.getElementById('tour-code').value.trim().toUpperCase();
|
||||
if (code) {
|
||||
window.location.href = '/tours/' + code;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCourses();
|
||||
|
||||
// Replace the initial static course entry with a dynamic one
|
||||
var container = document.getElementById('course-selector');
|
||||
container.textContent = '';
|
||||
container.appendChild(createCourseEntry(0));
|
||||
|
||||
// Delegate change events
|
||||
container.addEventListener('change', function(e) {
|
||||
if (e.target.classList.contains('course-select')) {
|
||||
onCourseChange(e.target);
|
||||
}
|
||||
if (e.target.classList.contains('layout-select')) {
|
||||
onLayoutChange(e.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user