// ==================== Reading Log Functions ==================== let booksPerMonthChart = null; let genresChart = null; async function loadReadingStats() { try { const response = await fetch('/api/reading-log/stats'); if (response.status === 401) { window.location.href = '/login'; return; } const stats = await response.json(); // Hide loading, show stats document.getElementById('stats-loading').classList.add('hidden'); document.getElementById('stats-container').classList.remove('hidden'); // Update stat cards document.getElementById('stat-total-books').textContent = stats.total_books || 0; document.getElementById('stat-total-hours').textContent = stats.total_hours || 0; document.getElementById('stat-avg-rating').textContent = stats.average_rating ? `${stats.average_rating}/5` : 'N/A'; document.getElementById('stat-streak').textContent = stats.current_streak || 0; // Render charts renderBooksPerMonthChart(stats.books_by_month || []); renderGenresChart(stats.books_by_genre || []); // Render recent books renderRecentBooks(stats.recent_books || []); } catch (error) { console.error('Error loading stats:', error); // Hide loading indicators even on error document.getElementById('stats-loading').classList.add('hidden'); document.getElementById('books-loading').classList.add('hidden'); showMessage('Error loading statistics: ' + error.message, 'error'); } } function renderBooksPerMonthChart(booksPerMonth) { const ctx = document.getElementById('books-per-month-chart'); if (!ctx) return; // Destroy existing chart if (booksPerMonthChart) { booksPerMonthChart.destroy(); } // Prepare data - show last 12 months const labels = booksPerMonth.slice(-12).map(item => `${item.month_name} ${item.year}`); const data = booksPerMonth.slice(-12).map(item => item.count); booksPerMonthChart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Books Finished', data: data, backgroundColor: 'rgba(99, 102, 241, 0.7)', borderColor: 'rgba(99, 102, 241, 1)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: true, scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } }, plugins: { legend: { display: false } } } }); } function renderGenresChart(booksByGenre) { const ctx = document.getElementById('genres-chart'); if (!ctx) return; // Destroy existing chart if (genresChart) { genresChart.destroy(); } // Show top 8 genres const topGenres = booksByGenre.slice(0, 8); const labels = topGenres.map(item => item.genre); const data = topGenres.map(item => item.count); // Generate colors const colors = [ 'rgba(239, 68, 68, 0.7)', 'rgba(249, 115, 22, 0.7)', 'rgba(234, 179, 8, 0.7)', 'rgba(34, 197, 94, 0.7)', 'rgba(20, 184, 166, 0.7)', 'rgba(59, 130, 246, 0.7)', 'rgba(99, 102, 241, 0.7)', 'rgba(168, 85, 247, 0.7)' ]; genresChart = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ data: data, backgroundColor: colors, borderWidth: 2, borderColor: '#ffffff' }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { position: 'right' } } } }); } function renderRecentBooks(recentBooks) { const listEl = document.getElementById('books-list'); const loadingEl = document.getElementById('books-loading'); const emptyEl = document.getElementById('books-empty'); loadingEl.classList.add('hidden'); if (recentBooks.length === 0) { emptyEl.classList.remove('hidden'); listEl.classList.add('hidden'); return; } emptyEl.classList.add('hidden'); listEl.classList.remove('hidden'); const html = recentBooks.map(book => { const finishedDate = new Date(book.finished_at).toLocaleDateString(); const ratingStars = book.rating ? '★'.repeat(book.rating) + '☆'.repeat(5 - book.rating) : 'Not rated'; return `
${book.book_id ? `${book.title}` : ''}

${book.title}

by ${book.author}

Finished: ${finishedDate} ${book.listening_duration ? `${book.listening_duration}h` : ''}
${ratingStars}
`; }).join(''); listEl.innerHTML = html; } async function promptRating(sessionId) { const rating = prompt('Rate this book (1-5 stars):'); if (!rating) return; const ratingNum = parseInt(rating); if (isNaN(ratingNum) || ratingNum < 1 || ratingNum > 5) { showMessage('Please enter a rating between 1 and 5', 'error'); return; } try { const formData = new FormData(); formData.append('rating', ratingNum); const response = await fetch(`/api/sessions/${sessionId}/rating`, { method: 'PUT', body: formData }); if (response.status === 401) { window.location.href = '/login'; return; } const data = await response.json(); if (data.status === 'success') { showMessage('Rating updated successfully!', 'success'); loadReadingStats(); // Reload stats } else { showMessage(data.message || 'Failed to update rating', 'error'); } } catch (error) { showMessage('Error updating rating: ' + error.message, 'error'); } }