eb77b1f32b
Search and import courses with layouts from Tjing's GraphQL API. Total par is calculated from individual hole data. Courses are saved with a tjing.se link as unique identifier to prevent duplicates.
170 lines
5.6 KiB
JavaScript
170 lines
5.6 KiB
JavaScript
function toggleAccordion(accordionId) {
|
|
const content = document.getElementById(accordionId);
|
|
const icon = document.getElementById(`${accordionId}-icon`);
|
|
|
|
if (content.classList.contains('expanded')) {
|
|
content.classList.remove('expanded');
|
|
icon.classList.remove('expanded');
|
|
} else {
|
|
content.classList.add('expanded');
|
|
icon.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
function toggleCourseLayouts(courseId) {
|
|
const layoutsRow = document.getElementById(`layouts-${courseId}`);
|
|
const layoutsContainer = document.getElementById(`layouts-container-${courseId}`);
|
|
|
|
if (layoutsRow.style.display === 'table-row') {
|
|
layoutsRow.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
layoutsRow.style.display = 'table-row';
|
|
|
|
if (layoutsContainer.dataset.loaded === 'true') {
|
|
return;
|
|
}
|
|
|
|
htmx.ajax('GET', `/partials/course-layouts/${courseId}`, {target: `#layouts-container-${courseId}`, swap: 'innerHTML'});
|
|
layoutsContainer.dataset.loaded = 'true';
|
|
}
|
|
|
|
async function scrapeCourses() {
|
|
const btn = document.getElementById('scrape-courses-btn');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Scraping...';
|
|
|
|
try {
|
|
const response = await fetch('/api/scrape-courses', { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert(data.message);
|
|
htmx.ajax('GET', '/partials/course-table', '#courses-table');
|
|
} else {
|
|
alert('Failed to scrape courses');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error scraping courses:', error);
|
|
alert('Error scraping courses');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Scrape Courses';
|
|
}
|
|
}
|
|
|
|
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');
|
|
|
|
try {
|
|
const response = await fetch(`/api/scrape-layouts/${courseId}`, { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (response.status === 409) {
|
|
alert(data.message || 'Scrape already in progress for this course. Please wait.');
|
|
} else if (data.success) {
|
|
const layoutsContainer = document.getElementById(`layouts-container-${courseId}`);
|
|
layoutsContainer.dataset.loaded = 'false';
|
|
|
|
const layoutsRow = document.getElementById(`layouts-${courseId}`);
|
|
if (layoutsRow.style.display === 'table-row') {
|
|
htmx.ajax('GET', `/partials/course-layouts/${courseId}`, {target: `#layouts-container-${courseId}`, swap: 'innerHTML'});
|
|
layoutsContainer.dataset.loaded = 'true';
|
|
}
|
|
|
|
alert(data.message);
|
|
} else {
|
|
alert('Failed to scrape layouts');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error scraping layouts:', error);
|
|
alert('Error scraping layouts');
|
|
} finally {
|
|
icon.classList.remove('spinning');
|
|
}
|
|
}
|