UI fixes: Match per playlist, Match All button, local/external labels, preserve tab on reload

This commit is contained in:
2026-02-03 18:27:29 -05:00
parent 08af650d6c
commit 1492778b14
2 changed files with 135 additions and 24 deletions

View File

@@ -911,16 +911,35 @@
document.getElementById('restart-banner').classList.remove('active');
}
// Tab switching
// Tab switching with URL hash support
function switchTab(tabName) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
const tab = document.querySelector(`.tab[data-tab="${tabName}"]`);
const content = document.getElementById('tab-' + tabName);
if (tab && content) {
tab.classList.add('active');
content.classList.add('active');
window.location.hash = tabName;
}
}
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById('tab-' + tab.dataset.tab).classList.add('active');
switchTab(tab.dataset.tab);
});
});
// Restore tab from URL hash on page load
window.addEventListener('load', () => {
const hash = window.location.hash.substring(1);
if (hash) {
switchTab(hash);
}
});
// Toast notification
function showToast(message, type = 'success') {
const toast = document.createElement('div');
@@ -1345,7 +1364,9 @@
const data = await res.json();
if (res.ok) {
showToast(data.message, 'success');
showToast(`${data.message}`, 'success');
// Refresh the playlists table after a delay to show updated counts
setTimeout(fetchPlaylists, 2000);
} else {
showToast(data.error || 'Failed to match tracks', 'error');
}
@@ -1355,13 +1376,17 @@
}
async function matchAllPlaylists() {
if (!confirm('Match tracks for ALL playlists? This may take a few minutes.')) return;
try {
showToast('Matching tracks for all playlists...', 'success');
const res = await fetch('/api/admin/playlists/match-all', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast(data.message, 'success');
showToast(`${data.message}`, 'success');
// Refresh the playlists table after a delay to show updated counts
setTimeout(fetchPlaylists, 3000);
} else {
showToast(data.error || 'Failed to match tracks', 'error');
}
@@ -1516,19 +1541,28 @@
return;
}
document.getElementById('tracks-list').innerHTML = data.tracks.map(t => `
<div class="track-item">
<span class="track-position">${t.position + 1}</span>
<div class="track-info">
<h4>${escapeHtml(t.title)}</h4>
<span class="artists">${escapeHtml(t.artists.join(', '))}</span>
document.getElementById('tracks-list').innerHTML = data.tracks.map(t => {
let statusBadge = '';
if (t.isLocal === true) {
statusBadge = '<span class="status-badge success" style="font-size:0.75rem;padding:2px 8px;margin-left:8px;"><span class="status-dot"></span>Local</span>';
} else if (t.isLocal === false) {
statusBadge = '<span class="status-badge warning" style="font-size:0.75rem;padding:2px 8px;margin-left:8px;"><span class="status-dot"></span>External</span>';
}
return `
<div class="track-item">
<span class="track-position">${t.position + 1}</span>
<div class="track-info">
<h4>${escapeHtml(t.title)}${statusBadge}</h4>
<span class="artists">${escapeHtml(t.artists.join(', '))}</span>
</div>
<div class="track-meta">
${t.album ? escapeHtml(t.album) : ''}
${t.isrc ? '<br><small>ISRC: ' + t.isrc + '</small>' : ''}
</div>
</div>
<div class="track-meta">
${t.album ? escapeHtml(t.album) : ''}
${t.isrc ? '<br><small>ISRC: ' + t.isrc + '</small>' : ''}
</div>
</div>
`).join('');
`;
}).join('');
} catch (error) {
document.getElementById('tracks-list').innerHTML = '<p style="text-align:center;color:var(--error);padding:40px;">Failed to load tracks</p>';
}