// ── Tab switching ────────────────────────────────── function initCourseTabs() { var tabs = document.querySelectorAll('.action-tab'); tabs.forEach(function(tab) { tab.addEventListener('click', function() { tabs.forEach(function(t) { t.classList.remove('is-active'); t.setAttribute('aria-selected', 'false'); }); tab.classList.add('is-active'); tab.setAttribute('aria-selected', 'true'); document.querySelectorAll('.action-pane').forEach(function(pane) { pane.hidden = true; pane.classList.remove('is-active'); }); var targetId = 'tab-pane-' + tab.dataset.tab; var pane = document.getElementById(targetId); if (pane) { pane.hidden = false; pane.classList.add('is-active'); } }); }); } // ── Live filter ──────────────────────────────────── function initCourseLiveFilter() { var input = document.getElementById('course-filter-input'); if (!input) return; input.addEventListener('input', function() { var q = input.value.toLowerCase().trim(); var rows = document.querySelectorAll('.course-row'); var visible = 0; rows.forEach(function(row) { var name = row.dataset.courseName || ''; var city = row.dataset.courseCity || ''; var match = !q || name.includes(q) || city.includes(q); row.hidden = !match; // Keep the expanded content sibling in sync var next = row.nextElementSibling; if (next && next.classList.contains('expanded-content')) { next.hidden = !match; } if (match) visible++; }); var visibleEl = document.getElementById('visible-count'); if (visibleEl) visibleEl.textContent = visible; }); } // ── Count display ────────────────────────────────── function initCourseCounts() { var grid = document.querySelector('.course-grid'); var total = grid ? parseInt(grid.dataset.totalCount || '0', 10) : 0; var rows = document.querySelectorAll('.course-row'); var visible = 0; rows.forEach(function(r) { if (!r.hidden) visible++; }); var totalEl = document.getElementById('total-count'); var visibleEl = document.getElementById('visible-count'); if (totalEl) totalEl.textContent = total; if (visibleEl) visibleEl.textContent = visible || total; } // ── Course row expand/collapse ───────────────────── function toggleCourseLayouts(courseId) { var row = document.querySelector('.course-row[data-course-id="' + courseId + '"]'); var content = document.getElementById('course-layouts-' + courseId); if (!row || !content) return; var isOpen = content.classList.contains('is-open'); if (isOpen) { content.classList.remove('is-open'); row.classList.remove('row-open'); } else { content.classList.add('is-open'); row.classList.add('row-open'); // Lazy-load layouts on first expand var cell = content.querySelector('.expanded-cell'); if (cell && cell.dataset.loaded !== 'true') { cell.dataset.loaded = 'true'; htmx.ajax('GET', '/partials/course-layouts/' + courseId, { target: cell, swap: 'innerHTML' }); } } } // ── Mobile course card toggle ────────────────────── var openMobileCourseId = null; function toggleMobileCourseLayouts(courseId) { var card = document.getElementById('m-course-' + courseId); if (!card) return; var isOpen = card.classList.contains('is-open'); // Close previously open card if (openMobileCourseId !== null && openMobileCourseId !== courseId) { var prevCard = document.getElementById('m-course-' + openMobileCourseId); if (prevCard) { prevCard.classList.remove('is-open'); prevCard.setAttribute('aria-expanded', 'false'); } openMobileCourseId = null; } if (isOpen) { card.classList.remove('is-open'); card.setAttribute('aria-expanded', 'false'); openMobileCourseId = null; return; } card.classList.add('is-open'); card.setAttribute('aria-expanded', 'true'); openMobileCourseId = courseId; // Lazy-load layouts on first expand var container = document.getElementById('m-layouts-container-' + courseId); if (container && container.dataset.loaded !== 'true') { htmx.ajax('GET', '/partials/course-layouts/' + courseId, { target: '#m-layouts-container-' + courseId, swap: 'innerHTML' }); container.dataset.loaded = 'true'; } } // ── Inactive layouts toggle ──────────────────────── function toggleInactiveLayouts(btn) { var body = btn.nextElementSibling; if (!body) return; var isOpen = btn.classList.contains('is-open'); btn.classList.toggle('is-open', !isOpen); btn.setAttribute('aria-expanded', String(!isOpen)); body.hidden = isOpen; } // ── Scrape courses ───────────────────────────────── async function scrapeCourses() { var btn = document.getElementById('scrape-courses-btn'); if (btn) { btn.disabled = true; btn.textContent = 'Scraping...'; } try { var response = await fetch('/api/scrape-courses', { method: 'POST' }); var data = await response.json(); if (data.success) { alert(data.message); htmx.trigger(document.body, 'refresh'); } else { alert('Failed to scrape courses'); } } catch (error) { console.error('Error scraping courses:', error); alert('Error scraping courses'); } finally { if (btn) { btn.disabled = false; btn.textContent = 'Scrape Courses'; } } } // ── Scrape layouts for a course ──────────────────── async function scrapeLayouts(courseId, btn) { if (btn) btn.classList.add('spinning'); try { var response = await fetch('/api/scrape-layouts/' + courseId, { method: 'POST' }); var data = await response.json(); if (response.status === 409) { alert(data.message || 'Scrape already in progress for this course. Please wait.'); } else if (data.success) { // Reload expanded layout content if currently open var content = document.getElementById('course-layouts-' + courseId); if (content && content.classList.contains('is-open')) { var cell = content.querySelector('.expanded-cell'); if (cell) { cell.dataset.loaded = 'true'; htmx.ajax('GET', '/partials/course-layouts/' + courseId, { target: cell, swap: 'innerHTML' }); } } alert(data.message); } else { alert('Failed to scrape layouts'); } } catch (error) { console.error('Error scraping layouts:', error); alert('Error scraping layouts'); } finally { if (btn) btn.classList.remove('spinning'); } } // ── Tjing search ─────────────────────────────────── async function searchTjing() { var input = document.getElementById('tjing-search-input'); var btn = document.getElementById('tjing-search-btn'); var container = document.getElementById('tjing-results'); if (!input || !container) return; var q = input.value.trim(); if (!q) return; btn.disabled = true; // Clear previous results safely while (container.firstChild) { container.removeChild(container.firstChild); } try { var response = await fetch('/api/tjing/search?q=' + encodeURIComponent(q)); var data; try { data = await response.json(); } catch (e) { var errP = document.createElement('p'); errP.className = 'tjing-error'; errP.textContent = 'Invalid response from server.'; container.appendChild(errP); return; } if (!response.ok || data.error) { var errP2 = document.createElement('p'); errP2.className = 'tjing-error'; errP2.textContent = 'Error: ' + (data.error || 'Search failed'); container.appendChild(errP2); return; } var results = data.results || []; if (results.length === 0) { var noResults = document.createElement('p'); noResults.className = 'tjing-error'; noResults.textContent = 'No courses found on Tjing.'; container.appendChild(noResults); return; } results.forEach(function(course) { var item = document.createElement('div'); item.className = 'tjing-result'; var info = document.createElement('div'); info.className = 'tjing-result-info'; var nameSpan = document.createElement('span'); nameSpan.className = 'tjing-result-name'; nameSpan.textContent = course.name || ''; var addrSpan = document.createElement('span'); addrSpan.className = 'tjing-result-address'; addrSpan.textContent = course.address || ''; info.appendChild(nameSpan); info.appendChild(addrSpan); var importBtn = document.createElement('button'); importBtn.className = 'btn-pill'; importBtn.textContent = 'Import'; (function(id, b) { b.addEventListener('click', function() { importFromTjing(id, b); }); })(course.id, importBtn); item.appendChild(info); item.appendChild(importBtn); container.appendChild(item); }); } catch (error) { console.error('Error searching Tjing:', error); var errFallback = document.createElement('p'); errFallback.className = 'tjing-error'; errFallback.textContent = 'Failed to search Tjing.'; container.appendChild(errFallback); } finally { btn.disabled = false; } } // ── Tjing import ─────────────────────────────────── async function importFromTjing(tjingId, btn) { btn.disabled = true; btn.textContent = 'Importing…'; try { var response = await fetch('/api/tjing/import/' + encodeURIComponent(tjingId), { method: 'POST' }); var data; try { data = await response.json(); } catch (e) { btn.textContent = 'Error'; btn.disabled = false; return; } if (!response.ok || data.error) { btn.textContent = 'Error: ' + (data.error || 'Import failed'); btn.disabled = false; return; } btn.textContent = 'Imported ✓'; // Trigger table reload htmx.trigger(document.body, 'refresh'); } catch (error) { console.error('Error importing from Tjing:', error); btn.textContent = 'Failed'; btn.disabled = false; } } // ── Init ─────────────────────────────────────────── function initAll() { initCourseTabs(); initCourseLiveFilter(); initCourseCounts(); } document.addEventListener('DOMContentLoaded', initAll); document.addEventListener('htmx:afterSwap', function(evt) { if (evt.detail && evt.detail.target && evt.detail.target.id === 'course-table-region') { initCourseLiveFilter(); initCourseCounts(); } });