Create a new /series/detail?name=X&library_id=Y SSR page that shows all books in a specific series, replacing the broken approach of linking to /bookshelf?series_filter=X (the bookshelf SSR handler ignores all filter query params). The series detail page features: - Back link to /series browse page - Library selector dropdown (full page navigation on change) - Series name header with book count badge - Book grid using the shared BookCard template - Empty state for series with no books Update all links to point to the new page: - Series cards on /series browse page - Series badge on book detail page - JS-rendered cards in series.ts switchLibrary Add seriesDetailPage Alpine component for the detail page's library switcher (simple navigation, no AJAX needed).
73 lines
2.7 KiB
Templ
73 lines
2.7 KiB
Templ
package templates
|
|
|
|
import (
|
|
"bookhoard/internal/handlers"
|
|
"fmt"
|
|
)
|
|
|
|
templ SeriesDetail(user User, seriesName string, books []handlers.BookInfo, libData []LibraryData, currentLibraryID string, errorMessage string) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
<title>{ seriesName } - Bookhoard</title>
|
|
<link href="/static/style.css" rel="stylesheet"/>
|
|
</head>
|
|
<body x-data="seriesDetailPage" x-init="initSeriesDetailPage()" class="theme-{ user.Theme }">
|
|
@Header(user, "/series")
|
|
<!-- Library Selector -->
|
|
<div class="sticky top-0 z-40 bg-opacity-95 backdrop-blur border-b" style="background-color: var(--bg-primary);">
|
|
<div class="w-full px-4 py-3 flex items-center justify-between">
|
|
<div class="flex items-center gap-4">
|
|
<a href="/series" class="text-sm hover:opacity-80 transition-opacity" style="color: var(--text-secondary); text-decoration: none;">
|
|
← All Series
|
|
</a>
|
|
<span style="color: var(--border);">|</span>
|
|
<label class="text-sm font-medium" style="color: var(--text-secondary)">Library:</label>
|
|
<select
|
|
id="library-select"
|
|
name="library_id"
|
|
class="px-4 py-2 rounded-lg border focus:ring-2 focus:ring-blue-500"
|
|
style="background-color: var(--bg-secondary); color: var(--text-primary);"
|
|
>
|
|
for _, lib := range libData {
|
|
if lib.ID == currentLibraryID {
|
|
<option value={ lib.ID } selected>{ lib.Name }</option>
|
|
} else {
|
|
<option value={ lib.ID }>{ lib.Name }</option>
|
|
}
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Series Header -->
|
|
<div class="w-full px-4 py-8">
|
|
<div class="flex items-center gap-4 mb-6">
|
|
<span class="px-3 py-1 rounded-full text-sm font-semibold" style="background-color: var(--accent); color: white;">
|
|
📚 Series
|
|
</span>
|
|
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">{ seriesName }</h1>
|
|
<span class="text-sm" style="color: var(--text-secondary)">{ fmt.Sprintf("%d", len(books)) } books</span>
|
|
</div>
|
|
<!-- Books Grid -->
|
|
if len(books) > 0 {
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4">
|
|
for _, book := range books {
|
|
@BookCard(book)
|
|
}
|
|
</div>
|
|
} else {
|
|
<div class="text-center py-16" style="color: var(--text-secondary)">
|
|
<div class="text-6xl mb-4">📚</div>
|
|
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Books Found</h3>
|
|
<p>This series doesn't have any books in this library yet</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
@ErrorToast(errorMessage)
|
|
</body>
|
|
</html>
|
|
}
|