// Watchlist Scan History modal (#831 round 2). // // Every scan run is persisted server-side (watchlist_scan_runs) with its full // track ledger — the tracks the run ADDED to the wishlist plus the found-but- // skipped ones. This modal lists past runs (newest first) and expands each // into its ledger. Note: this is what the watchlist PUT IN THE WISHLIST — the // watchlist never downloads; downloaded tracks live in Download Origins. let _wlhModalEl = null; let _wlhRuns = []; const _wlhEventsCache = new Map(); function openWatchlistHistoryModal() { if (!_wlhModalEl) { _wlhModalEl = document.createElement('div'); _wlhModalEl.className = 'modal-overlay origin-modal-overlay'; _wlhModalEl.innerHTML = `

Scan History

Every watchlist scan and the tracks it added to your wishlist.

`; _wlhModalEl.addEventListener('click', (e) => { if (e.target === _wlhModalEl) closeWatchlistHistoryModal(); }); document.body.appendChild(_wlhModalEl); } _wlhModalEl.classList.remove('hidden'); _wlhLoadRuns(); } function closeWatchlistHistoryModal() { if (_wlhModalEl) _wlhModalEl.classList.add('hidden'); } async function _wlhLoadRuns() { const body = document.getElementById('wlh-modal-body'); body.innerHTML = '
Loading…
'; try { const resp = await fetch('/api/watchlist/scan/history?limit=50'); const data = await resp.json(); if (!data.success) throw new Error(data.error || 'Failed to load'); _wlhRuns = data.runs || []; _wlhRenderRuns(); } catch (err) { body.innerHTML = `
Couldn't load: ${escapeHtml(err.message)}
`; } } function _wlhRenderRuns() { const body = document.getElementById('wlh-modal-body'); if (!_wlhRuns.length) { body.innerHTML = '
No scans recorded yet. Run a watchlist scan and it will appear here.
'; return; } body.innerHTML = _wlhRuns.map(r => { const when = _wlhFormatDate(r.completed_at || r.started_at); const cancelled = r.status === 'cancelled'; return `
`; }).join(''); } async function toggleWatchlistHistoryRun(runId, btn) { const runEl = btn.closest('.wlh-run'); const bodyEl = runEl.querySelector('.wlh-run-body'); const caret = btn.querySelector('.origin-group-caret'); const open = bodyEl.style.display !== 'none'; if (open) { bodyEl.style.display = 'none'; if (caret) caret.textContent = '▸'; return; } bodyEl.style.display = ''; if (caret) caret.textContent = '▾'; if (!_wlhEventsCache.has(runId)) { bodyEl.innerHTML = '
Loading…
'; try { const resp = await fetch(`/api/watchlist/scan/history/${encodeURIComponent(runId)}/tracks`); const data = await resp.json(); _wlhEventsCache.set(runId, data.success ? (data.events || []) : []); } catch (e) { _wlhEventsCache.set(runId, []); } } bodyEl.innerHTML = _wlhRenderEvents(_wlhEventsCache.get(runId)); } function _wlhRenderEvents(events) { if (!events.length) { return '
No new tracks were found by this scan.
'; } const added = events.filter(e => e.status === 'added'); const skipped = events.filter(e => e.status !== 'added'); const row = (e) => `
${escapeHtml(e.track_name || '')}
${escapeHtml(e.artist_name || '')}${e.album_name ? ' — ' + escapeHtml(e.album_name) : ''}
${e.status === 'added' ? 'added' : 'skipped'}
`; const section = (label, list) => list.length ? `
${label} (${list.length})
${list.map(row).join('')}` : ''; return section('Added to wishlist', added) + section('Found but skipped — already queued or blocklisted', skipped); } function _wlhFormatDate(ts) { if (!ts) return 'Unknown time'; try { const d = new Date(ts); if (isNaN(d.getTime())) return ts; return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', }); } catch (e) { return ts; } } window.openWatchlistHistoryModal = openWatchlistHistoryModal; window.closeWatchlistHistoryModal = closeWatchlistHistoryModal; window.toggleWatchlistHistoryRun = toggleWatchlistHistoryRun;