// ==================== 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); 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'); return; } 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 `