diff --git a/templates/dashboard.templ b/templates/dashboard.templ index b5f06aa..623b26e 100644 --- a/templates/dashboard.templ +++ b/templates/dashboard.templ @@ -1,601 +1,272 @@ package templates -templ Dashboard(user User) { - - - - - - Dashboard - Bookhoard - - - - - - - - @Header(user, "/dashboard") +import ( + "bookhoard/internal/handlers" + "fmt" +) -
- -
-
-

Your Media Library

-

Browse and manage your media collections

-
-
+templ Dashboard(user User, sections []handlers.SectionData, libData []LibraryData, currentLibraryID string) { + + + + + + Dashboard - Bookhoard + + + + + + + + + @Header(user, "/dashboard") - -
- -
+ +
+
+
+ + +
- - - -
-
- - - - - Filters - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- - -
-
-
- -
-
-
- - -
-
-
+ +
- - + +
+ for _, section := range sections { + @CollectionCarousel(section) + } +
- -
- -
-
+ + @DashboardSettingsModal(sections) + + +} - -
- Loading your media libraries... -
-
+templ CollectionCarousel(section handlers.SectionData) { +
+ +
+
+ { section.Icon } +
+

{ section.Title }

+ if section.Description != "" { +

{ section.Description }

+ } +
+
- - - -} \ No newline at end of file +
+ + +
+
+
+} diff --git a/templates/dashboard_templ.go b/templates/dashboard_templ.go index aaa3105..4c13243 100644 --- a/templates/dashboard_templ.go +++ b/templates/dashboard_templ.go @@ -8,7 +8,12 @@ package templates import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -func Dashboard(user User) templ.Component { +import ( + "bookhoard/internal/handlers" + "fmt" +) + +func Dashboard(user User, sections []handlers.SectionData, libData []LibraryData, currentLibraryID string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -29,7 +34,7 @@ func Dashboard(user User) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Dashboard - Bookhoard") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Dashboard - Bookhoard") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -37,7 +42,545 @@ func Dashboard(user User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Your Media Library

Browse and manage your media collections

Filters
Loading your media libraries...
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, section := range sections { + templ_7745c5c3_Err = CollectionCarousel(section).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = DashboardSettingsModal(sections).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func CollectionCarousel(section handlers.SectionData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 86, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 88, Col: 85} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if section.Description != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(section.Description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 90, Col: 83} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if section.ViewAllURL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "View All →") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, item := range section.Items { + templ_7745c5c3_Err = BookCard(item).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(section.Items) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

No items in this collection

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func BookCard(item handlers.BookInfo) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if item.CoverImagePath != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 171, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if item.Author != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(item.Author) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 176, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func DashboardSettingsModal(sections []handlers.SectionData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var23 := templ.GetChildren(ctx) + if templ_7745c5c3_Var23 == nil { + templ_7745c5c3_Var23 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

Customize Dashboard

Drag to reorder collections, toggle visibility with the switch.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, section := range sections { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 210, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 212, Col: 85} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if section.IsSystem { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "System") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if section.IsSystem { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/src/dashboard.ts b/web/src/dashboard.ts new file mode 100644 index 0000000..f8dab4d --- /dev/null +++ b/web/src/dashboard.ts @@ -0,0 +1,332 @@ +// Dashboard functionality with unified collections architecture +// Procedural/imperative style (no OOP) + +import type { SectionData, BookInfo } from './types/api'; + +const SCROLL_AMOUNT = 300; + +function scrollCarousel(collectionId: string, direction: number): void { + const track = document.getElementById(`carousel-track-${collectionId}`) as HTMLElement; + if (!track) return; + + const scrollAmount = direction * SCROLL_AMOUNT; + track.scrollBy({ left: scrollAmount, behavior: 'smooth' }); +} + +function openDashboardSettings(): void { + const modal = document.getElementById('dashboard-settings-modal') as HTMLElement; + if (modal) { + modal.classList.remove('hidden'); + } +} + +function closeDashboardSettings(): void { + const modal = document.getElementById('dashboard-settings-modal') as HTMLElement; + if (modal) { + modal.classList.add('hidden'); + } +} + +function toggleCollectionVisibility(collectionId: string): void { + const checkbox = document.querySelector(`input[data-collection-id="${collectionId}"]`) as HTMLInputElement; + if (checkbox) { + checkbox.checked = !checkbox.checked; + } +} + +async function saveDashboardSettings(): Promise { + const collectionList = document.getElementById('collection-list') as HTMLElement; + if (!collectionList) return; + + const collectionItems = collectionList.querySelectorAll('[data-collection-id]') as NodeListOf; + const hiddenCollections: string[] = []; + const collectionOrder: string[] = []; + + collectionItems.forEach((item) => { + const collectionId = item.dataset.collectionId; + const checkbox = item.querySelector('input[type="checkbox"]') as HTMLInputElement; + + if (collectionId) { + collectionOrder.push(collectionId); + if (checkbox && !checkbox.checked) { + hiddenCollections.push(collectionId); + } + } + }); + + const itemsPerCollection = (document.querySelector('#items-count-display') as HTMLElement)?.textContent || '20'; + + try { + const response = await (window as any).api.put('/dashboard/preferences', { + library_id: new URLSearchParams(window.location.search).get('library_id') || '', + hidden_collections: hiddenCollections, + collection_order: collectionOrder, + items_per_section: parseInt(itemsPerCollection), + }); + + if (response.ok) { + (window as any).showToast.success('Dashboard settings saved'); + closeDashboardSettings(); + window.location.reload(); + } + } catch (error) { + (window as any).showToast.error('Failed to save settings'); + console.error('Save dashboard settings error:', error); + } +} + +async function restoreSystemCollection(collectionName: string, collectionTitle: string): Promise { + if (!confirm(`Are you sure you want to reset "${collectionTitle}" to its default state? Any customizations will be lost.`)) { + return; + } + + try { + const response = await (window as any).api.post('/dashboard/restore-system-collection', { + collection_name: collectionName, + }); + + if (response.ok) { + (window as any).showToast.success(`"${collectionTitle}" restored to defaults`); + setTimeout(() => window.location.reload(), 1000); + } + } catch (error) { + (window as any).showToast.error('Failed to restore system collection'); + console.error('Restore system collection error:', error); + } +} + +async function switchLibrary(libraryId: string): Promise { + const container = document.getElementById('collections-container') as HTMLElement; + const loading = document.getElementById('loading-spinner') as HTMLElement; + + if (!container || !loading) return; + + loading.classList.remove('hidden'); + + try { + const response = await fetch(`/api/dashboard/sections?library_id=${libraryId}`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error('Failed to load sections'); + } + + const data = await response.json(); + renderCollections(data.sections); + } catch (error) { + (window as any).showToast.error('Failed to load library'); + console.error('Switch library error:', error); + } finally { + loading.classList.add('hidden'); + } +} + +function renderCollections(sections: SectionData[]): void { + const container = document.getElementById('collections-container') as HTMLElement; + if (!container) return; + + container.innerHTML = sections.map(section => ` +
+
+
+ ${section.icon} +
+

${section.title}

+ ${section.description ? `

${section.description}

` : ''} +
+
+ ${section.view_all_url ? `View All →` : ''} +
+ + +
+ `).join(''); +} + +function renderBookCard(book: BookInfo): string { + const coverUrl = book.cover_image_path || '/static/placeholder-book.svg'; + + return ` +
+
+ ${book.title} +
+

+ ${book.title} +

+ ${book.author ? `

${book.author}

` : ''} +
+ `; +} + +function viewBook(bookId: string): void { + console.log('View book:', bookId); +} + +function reloadPage(): void { + window.location.reload(); +} + +function updateItemsCount(input: HTMLInputElement, targetId: string): void { + const display = document.getElementById(targetId) as HTMLElement; + if (display) { + display.textContent = input.value; + } +} + +function initDragAndDrop(): void { + const collectionList = document.getElementById('collection-list') as HTMLElement; + if (!collectionList) return; + + let draggedItem: HTMLElement | null = null; + + collectionList.addEventListener('dragstart', (e: Event) => { + const target = e.target as HTMLElement; + if (target.classList.contains('collection-item')) { + draggedItem = target; + target.style.opacity = '0.5'; + } + }); + + collectionList.addEventListener('dragend', (e: Event) => { + const target = e.target as HTMLElement; + if (target.classList.contains('collection-item')) { + target.style.opacity = '1'; + draggedItem = null; + } + }); + + collectionList.addEventListener('dragover', (e: Event) => { + e.preventDefault(); + const target = e.target as HTMLElement; + if (target.classList.contains('collection-item') && target !== draggedItem && draggedItem) { + const rect = target.getBoundingClientRect(); + const midY = rect.top + rect.height / 2; + if ((e as DragEvent).clientY < midY) { + target.parentNode?.insertBefore(draggedItem, target); + } else { + if (target.parentNode) { + target.parentNode.insertBefore(draggedItem, target.nextSibling); + } + } + } + }); +} + +document.addEventListener('DOMContentLoaded', () => { + initDragAndDrop(); + + document.addEventListener('click', (e: Event) => { + const target = e.target as HTMLElement; + const actionElem = target.closest('[data-action]') as HTMLElement; + const action = actionElem?.getAttribute('data-action'); + + switch (action) { + case 'scroll-carousel': { + const collectionId = target.dataset.collectionId || actionElem?.dataset.collectionId; + const direction = parseInt(target.dataset.direction || actionElem?.dataset.direction || '0'); + if (collectionId) scrollCarousel(collectionId, direction); + break; + } + + case 'open-dashboard-settings': + openDashboardSettings(); + break; + + case 'close-dashboard-settings': + closeDashboardSettings(); + break; + + case 'toggle-collection-visibility': { + const checkbox = target as HTMLInputElement; + const colId = checkbox.dataset.collectionId; + if (colId) toggleCollectionVisibility(colId); + break; + } + + case 'save-dashboard-settings': + saveDashboardSettings(); + break; + + case 'restore-system-collection': { + const colName = actionElem?.dataset.collectionName || target.dataset.collectionName; + const colTitle = actionElem?.dataset.collectionTitle || target.dataset.collectionTitle || 'System Collection'; + if (colName) restoreSystemCollection(colName, colTitle); + break; + } + + case 'view-book': { + const bookId = target.dataset.bookId || actionElem?.dataset.bookId; + if (bookId) viewBook(bookId); + break; + } + + case 'reload-page': + reloadPage(); + break; + + case 'switch-library': { + const select = target as HTMLSelectElement; + if (select.value) switchLibrary(select.value); + break; + } + + case 'update-items-count': { + const input = target as HTMLInputElement; + const displayTarget = input.getAttribute('target'); + if (displayTarget) updateItemsCount(input, displayTarget); + break; + } + } + }); +}); + +export {};