diff --git a/web/src/dashboard.ts b/web/src/dashboard.ts index 5f7f8b8..aeed763 100644 --- a/web/src/dashboard.ts +++ b/web/src/dashboard.ts @@ -236,19 +236,8 @@ async function switchLibrary(libraryId: string): Promise { } } -function renderDashboardCollections(sections: SectionData[]): void { - const container = document.getElementById( - "collections-container", - ) as HTMLElement; - if (!container) return; - - // Preserve wood paneling attribute - const currentWood = container.getAttribute("data-wood"); - - // Replace content while still invisible (opacity-0 from switchLibrary) - container.innerHTML = sections - .map( - (section) => ` +function renderSectionHTML(section: SectionData): string { + return `
@@ -299,9 +288,20 @@ function renderDashboardCollections(sections: SectionData[]): void {
- `, - ) - .join(""); + `; +} + +function renderDashboardCollections(sections: SectionData[]): void { + const container = document.getElementById( + "collections-container", + ) as HTMLElement; + if (!container) return; + + // Preserve wood paneling attribute + const currentWood = container.getAttribute("data-wood"); + + // Replace content while still invisible (opacity-0 from switchLibrary) + container.innerHTML = sections.map((section) => renderSectionHTML(section)).join(""); // Step 5: Fade in new content (300ms for smoother entrance) container.classList.remove("duration-150"); @@ -329,6 +329,7 @@ function renderBookCard(book: BookInfo): string {
@@ -505,6 +506,84 @@ function initDashboard() { } }); } + + window.addEventListener("bookhoard:scan-complete", async () => { + console.log("[dashboard] scan-complete event received"); + const librarySelect = document.getElementById( + "library-select", + ) as HTMLSelectElement; + const libraryId = librarySelect?.value; + console.log("[dashboard] libraryId:", libraryId); + if (!libraryId) return; + + try { + const response = await fetch( + `/api/dashboard/sections?library_id=${libraryId}`, + { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + "Content-Type": "application/json", + }, + }, + ); + console.log("[dashboard] fetch status:", response.status); + if (!response.ok) return; + const data = await response.json(); + console.log("[dashboard] sections:", data.sections?.length, JSON.stringify(data.sections?.map((s: SectionData) => ({ id: s.id, items: s.items?.length })))); + + const container = document.getElementById( + "collections-container", + ) as HTMLElement; + if (!container) { + console.log("[dashboard] no collections-container found"); + return; + } + + for (const section of data.sections as SectionData[]) { + const track = document.getElementById( + `carousel-track-${section.id}`, + ); + + if (!track) { + console.log("[dashboard] creating new section:", section.id, section.title); + container.insertAdjacentHTML( + "beforeend", + renderSectionHTML(section), + ); + continue; + } + + const existing = new Set( + Array.from(track.querySelectorAll("[data-media-item-id]")).map( + (el) => el.dataset.mediaItemId, + ), + ); + console.log("[dashboard] section:", section.id, "existing:", existing.size, "api items:", section.items.length); + + let added = false; + const newItems: string[] = []; + for (const item of section.items) { + if (existing.has(item.media_item_id)) continue; + newItems.push(renderBookCard(item)); + added = true; + } + + if (added) { + track.insertAdjacentHTML("afterbegin", newItems.join("")); + track.scrollLeft = 0; + + const placeholder = track.querySelector( + '.text-center.py-8', + ); + if (placeholder) { + placeholder.remove(); + } + } + } + } catch (err) { + console.error("[dashboard] scan-complete handler error:", err); + } + }); } export { initDashboard };