Add search history with saved results
Saves each mood search and its recommendations to SQLite per user. Recent searches appear below the results area with mood text, top movie titles, and timestamp. Click any entry to reload those results. Entries can be deleted individually. History toggleable via show/hide. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,7 @@ function showMainScreen() {
|
||||
selectedUserIds = [currentUser.id];
|
||||
loadUsers();
|
||||
loadStats();
|
||||
loadHistory();
|
||||
}
|
||||
|
||||
// --- Users ---
|
||||
@@ -113,6 +114,7 @@ async function findMovies() {
|
||||
|
||||
const data = await res.json();
|
||||
renderResults(data);
|
||||
loadHistory();
|
||||
} catch (err) {
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
document.getElementById('error-message').textContent = err.message;
|
||||
@@ -171,6 +173,86 @@ function renderResults(data) {
|
||||
document.getElementById('results').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// --- Search History ---
|
||||
|
||||
async function loadHistory() {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/history`);
|
||||
if (!res.ok) return;
|
||||
const history = await res.json();
|
||||
const list = document.getElementById('history-list');
|
||||
list.innerHTML = '';
|
||||
|
||||
if (history.length === 0) {
|
||||
document.getElementById('history-section').classList.add('hidden');
|
||||
document.getElementById('history-toggle').classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
history.forEach(entry => {
|
||||
const date = new Date(entry.created_at);
|
||||
const timeStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +
|
||||
' ' + date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
||||
const movieTitles = entry.results.slice(0, 3).map(r => r.title).join(', ');
|
||||
const more = entry.results.length > 3 ? ` +${entry.results.length - 3} more` : '';
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'history-item bg-dark-200 border border-gray-800 rounded-lg p-3 cursor-pointer hover:border-gray-600 transition-colors';
|
||||
item.innerHTML = `
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-200 truncate">"${entry.mood}"</p>
|
||||
<p class="text-xs text-gray-500 mt-1 truncate">${movieTitles}${more}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<span class="text-xs text-gray-600">${timeStr}</span>
|
||||
<button class="history-delete text-gray-700 hover:text-red-400 transition-colors text-xs" data-id="${entry.id}">×</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
item.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('history-delete')) return;
|
||||
showHistoryResults(entry);
|
||||
});
|
||||
list.appendChild(item);
|
||||
});
|
||||
|
||||
// Add delete handlers
|
||||
list.querySelectorAll('.history-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
const id = btn.dataset.id;
|
||||
await fetch(`${API}/api/history/${id}`, { method: 'DELETE' });
|
||||
loadHistory();
|
||||
});
|
||||
});
|
||||
|
||||
// Show the toggle button if history is currently hidden
|
||||
if (document.getElementById('history-section').classList.contains('hidden')) {
|
||||
document.getElementById('history-toggle').classList.remove('hidden');
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function showHistoryResults(entry) {
|
||||
document.getElementById('mood-input').value = entry.mood;
|
||||
const data = { recommendations: entry.results, meta: entry.meta };
|
||||
renderResults(data);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function toggleHistoryVisibility(show) {
|
||||
const section = document.getElementById('history-section');
|
||||
const toggle = document.getElementById('history-toggle');
|
||||
if (show) {
|
||||
section.classList.remove('hidden');
|
||||
toggle.classList.add('hidden');
|
||||
} else {
|
||||
section.classList.add('hidden');
|
||||
toggle.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// --- Library Stats ---
|
||||
|
||||
async function loadStats() {
|
||||
@@ -217,6 +299,11 @@ document.getElementById('mood-input').addEventListener('keydown', (e) => {
|
||||
});
|
||||
document.getElementById('logout-btn').addEventListener('click', logout);
|
||||
document.getElementById('refresh-btn').addEventListener('click', refreshLibrary);
|
||||
document.getElementById('toggle-history-btn').addEventListener('click', () => toggleHistoryVisibility(false));
|
||||
document.getElementById('show-history-btn').addEventListener('click', () => {
|
||||
toggleHistoryVisibility(true);
|
||||
loadHistory();
|
||||
});
|
||||
|
||||
// --- Init ---
|
||||
checkAuth();
|
||||
|
||||
Reference in New Issue
Block a user