feat: add tooltips, refresh & match button, and matching warning banner

This commit is contained in:
2026-02-07 02:36:48 -05:00
parent f03aa0be35
commit 42b4e0e399

View File

@@ -644,12 +644,18 @@
<!-- Active Playlists Tab --> <!-- Active Playlists Tab -->
<div class="tab-content" id="tab-playlists"> <div class="tab-content" id="tab-playlists">
<!-- Warning Banner (hidden by default) -->
<div id="matching-warning-banner" style="display:none;background:#f59e0b;color:#000;padding:16px;border-radius:8px;margin-bottom:16px;font-weight:600;text-align:center;box-shadow:0 4px 6px rgba(0,0,0,0.1);">
⚠️ TRACK MATCHING IN PROGRESS - Please wait for matching to complete before making changes to playlists or mappings!
</div>
<div class="card"> <div class="card">
<h2> <h2>
Active Spotify Playlists Active Spotify Playlists
<div class="actions"> <div class="actions">
<button onclick="matchAllPlaylists()">Match All Tracks</button> <button onclick="matchAllPlaylists()" title="Match tracks for all playlists against your local library and external providers. This may take several minutes.">Match All Tracks</button>
<button onclick="refreshPlaylists()">Refresh All</button> <button onclick="refreshPlaylists()" title="Fetch the latest playlist data from Spotify without re-matching tracks.">Refresh All</button>
<button onclick="refreshAndMatchAll()" title="Clear caches, fetch fresh data from Spotify, and match all tracks. This is a full rebuild and may take several minutes." style="background:var(--accent);border-color:var(--accent);">Refresh & Match All</button>
</div> </div>
</h2> </h2>
<p style="color: var(--text-secondary); margin-bottom: 12px;"> <p style="color: var(--text-secondary); margin-bottom: 12px;">
@@ -1910,6 +1916,9 @@
if (!confirm(`Clear cache and rebuild for "${name}"?\n\nThis will:\n• Clear Redis cache\n• Delete file caches\n• Rebuild with latest Spotify IDs\n\nThis may take a minute.`)) return; if (!confirm(`Clear cache and rebuild for "${name}"?\n\nThis will:\n• Clear Redis cache\n• Delete file caches\n• Rebuild with latest Spotify IDs\n\nThis may take a minute.`)) return;
try { try {
// Show warning banner
document.getElementById('matching-warning-banner').style.display = 'block';
showToast(`Clearing cache for ${name}...`, 'info'); showToast(`Clearing cache for ${name}...`, 'info');
const res = await fetch(`/api/admin/playlists/${encodeURIComponent(name)}/clear-cache`, { method: 'POST' }); const res = await fetch(`/api/admin/playlists/${encodeURIComponent(name)}/clear-cache`, { method: 'POST' });
const data = await res.json(); const data = await res.json();
@@ -1917,17 +1926,26 @@
if (res.ok) { if (res.ok) {
showToast(`${data.message} (Cleared ${data.clearedKeys} cache keys, ${data.clearedFiles} files)`, 'success', 5000); showToast(`${data.message} (Cleared ${data.clearedKeys} cache keys, ${data.clearedFiles} files)`, 'success', 5000);
// Refresh the playlists table after a delay to show updated counts // Refresh the playlists table after a delay to show updated counts
setTimeout(fetchPlaylists, 3000); setTimeout(() => {
fetchPlaylists();
// Hide warning banner after refresh
document.getElementById('matching-warning-banner').style.display = 'none';
}, 3000);
} else { } else {
showToast(data.error || 'Failed to clear cache', 'error'); showToast(data.error || 'Failed to clear cache', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
} }
} catch (error) { } catch (error) {
showToast('Failed to clear cache', 'error'); showToast('Failed to clear cache', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
} }
} }
async function matchPlaylistTracks(name) { async function matchPlaylistTracks(name) {
try { try {
// Show warning banner
document.getElementById('matching-warning-banner').style.display = 'block';
showToast(`Matching tracks for ${name}...`, 'success'); showToast(`Matching tracks for ${name}...`, 'success');
const res = await fetch(`/api/admin/playlists/${encodeURIComponent(name)}/match`, { method: 'POST' }); const res = await fetch(`/api/admin/playlists/${encodeURIComponent(name)}/match`, { method: 'POST' });
const data = await res.json(); const data = await res.json();
@@ -1935,12 +1953,18 @@
if (res.ok) { if (res.ok) {
showToast(`${data.message}`, 'success'); showToast(`${data.message}`, 'success');
// Refresh the playlists table after a delay to show updated counts // Refresh the playlists table after a delay to show updated counts
setTimeout(fetchPlaylists, 2000); setTimeout(() => {
fetchPlaylists();
// Hide warning banner after refresh
document.getElementById('matching-warning-banner').style.display = 'none';
}, 2000);
} else { } else {
showToast(data.error || 'Failed to match tracks', 'error'); showToast(data.error || 'Failed to match tracks', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
} }
} catch (error) { } catch (error) {
showToast('Failed to match tracks', 'error'); showToast('Failed to match tracks', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
} }
} }
@@ -1948,6 +1972,9 @@
if (!confirm('Match tracks for ALL playlists? This may take a few minutes.')) return; if (!confirm('Match tracks for ALL playlists? This may take a few minutes.')) return;
try { try {
// Show warning banner
document.getElementById('matching-warning-banner').style.display = 'block';
showToast('Matching tracks for all playlists...', 'success'); showToast('Matching tracks for all playlists...', 'success');
const res = await fetch('/api/admin/playlists/match-all', { method: 'POST' }); const res = await fetch('/api/admin/playlists/match-all', { method: 'POST' });
const data = await res.json(); const data = await res.json();
@@ -1955,12 +1982,61 @@
if (res.ok) { if (res.ok) {
showToast(`${data.message}`, 'success'); showToast(`${data.message}`, 'success');
// Refresh the playlists table after a delay to show updated counts // Refresh the playlists table after a delay to show updated counts
setTimeout(fetchPlaylists, 2000); setTimeout(() => {
fetchPlaylists();
// Hide warning banner after refresh
document.getElementById('matching-warning-banner').style.display = 'none';
}, 2000);
} else { } else {
showToast(data.error || 'Failed to match tracks', 'error'); showToast(data.error || 'Failed to match tracks', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
} }
} catch (error) { } catch (error) {
showToast('Failed to match tracks', 'error'); showToast('Failed to match tracks', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
}
}
async function refreshAndMatchAll() {
if (!confirm('Clear caches, refresh from Spotify, and match all tracks?\n\nThis will:\n• Clear all playlist caches\n• Fetch fresh data from Spotify\n• Match all tracks against local library and external providers\n\nThis may take several minutes.')) return;
try {
// Show warning banner
document.getElementById('matching-warning-banner').style.display = 'block';
showToast('Starting full refresh and match...', 'info', 3000);
// Step 1: Clear all caches
showToast('Step 1/3: Clearing caches...', 'info', 2000);
await fetch('/api/admin/cache/clear', { method: 'POST' });
// Step 2: Refresh playlists from Spotify
showToast('Step 2/3: Fetching from Spotify...', 'info', 2000);
await fetch('/api/admin/playlists/refresh', { method: 'POST' });
// Wait a bit for Spotify fetch to complete
await new Promise(resolve => setTimeout(resolve, 3000));
// Step 3: Match all tracks
showToast('Step 3/3: Matching all tracks...', 'info', 2000);
const res = await fetch('/api/admin/playlists/match-all', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast(`✓ Full refresh and match complete!`, 'success', 5000);
// Refresh the playlists table after a delay
setTimeout(() => {
fetchPlaylists();
// Hide warning banner after refresh
document.getElementById('matching-warning-banner').style.display = 'none';
}, 2000);
} else {
showToast(data.error || 'Failed to match tracks', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
}
} catch (error) {
showToast('Failed to complete refresh and match', 'error');
document.getElementById('matching-warning-banner').style.display = 'none';
} }
} }