Add interactive rating display and controls to dashboard

This commit is contained in:
2026-01-26 11:49:51 -05:00
parent f35defd139
commit 1bda408448
+63 -4
View File
@@ -129,21 +129,80 @@ templ Dashboard(user User) {
});
}
function renderEbooks(ebooks) {
async function renderEbooks(ebooks) {
const container = document.getElementById('ebooks-container');
container.innerHTML = '';
ebooks.forEach(ebook => {
for (const ebook of ebooks) {
const card = document.createElement('div');
card.className = 'card p-4 rounded-lg border hover:shadow-lg transition-shadow';
card.style.cssText = 'background-color: var(--bg-secondary); border-color: var(--border);';
const coverUrl = ebook.cover_image_path ? ebook.cover_image_path : '/static/placeholder-book.svg';
// Get user's rating for this ebook
const rating = await getEbookRating(ebook.id);
const ratingStars = renderRatingStars(ebook.id, rating);
card.innerHTML = '<div class="flex flex-col h-full"><div class="flex-1"><div class="aspect-w-3 aspect-h-4 mb-3 overflow-hidden rounded"><img src="' + coverUrl + '" alt="Cover" class="w-full h-48 object-cover rounded" onerror="this.src=\'/static/placeholder-book.svg\'"></div><h3 class="font-semibold text-lg mb-1 line-clamp-2" style="color: var(--text-primary)">' + ebook.title + '</h3>' + (ebook.author ? '<p class="text-sm mb-2" style="color: var(--text-secondary)">by ' + ebook.author + '</p>' : '') + '<div class="flex justify-between items-center mt-4"><div class="flex space-x-2"><button onclick="viewEbook(\'' + ebook.id + '\')" class="px-3 py-1 text-sm border rounded hover:opacity-80" style="border-color: var(--border); color: var(--text-secondary)">View</button></div></div></div>';
card.innerHTML = '<div class="flex flex-col h-full"><div class="flex-1"><div class="aspect-w-3 aspect-h-4 mb-3 overflow-hidden rounded"><img src="' + coverUrl + '" alt="Cover" class="w-full h-48 object-cover rounded" onerror="this.src=\'/static/placeholder-book.svg\'"></div><h3 class="font-semibold text-lg mb-1 line-clamp-2" style="color: var(--text-primary)">' + ebook.title + '</h3>' + (ebook.author ? '<p class="text-sm mb-2" style="color: var(--text-secondary)">by ' + ebook.author + '</p>' : '') + '<div class="mb-2" id="rating-' + ebook.id + '">' + ratingStars + '</div><div class="flex justify-between items-center mt-4"><div class="flex space-x-2"><button onclick="viewEbook(\'' + ebook.id + '\')" class="px-3 py-1 text-sm border rounded hover:opacity-80" style="border-color: var(--border); color: var(--text-secondary)">View</button></div></div></div>';
container.appendChild(card);
});
}
}
async function getEbookRating(ebookId) {
try {
const response = await fetch(`/api/ebooks/${ebookId}/rating`, {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
return data.rating || 0;
}
return 0;
} catch (error) {
console.error('Error fetching rating:', error);
return 0;
}
}
function renderRatingStars(ebookId, currentRating) {
let stars = '';
for (let i = 1; i <= 5; i++) {
const filled = i <= currentRating;
const starClass = filled ? 'text-yellow-400' : 'text-gray-400';
stars += `<span onclick="setRating('${ebookId}', ${i})" class="cursor-pointer text-xl ${starClass} hover:text-yellow-300 transition-colors">${filled ? '★' : '☆'}</span>`;
}
return stars;
}
async function setRating(ebookId, rating) {
try {
const response = await fetch(`/api/ebooks/${ebookId}/rating`, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ rating: rating })
});
if (response.ok) {
const data = await response.json();
const ratingContainer = document.getElementById(`rating-${ebookId}`);
if (ratingContainer) {
ratingContainer.innerHTML = renderRatingStars(ebookId, data.rating);
}
} else {
console.error('Error setting rating:', await response.text());
}
} catch (error) {
console.error('Error setting rating:', error);
}
}
function logout() {