feat: update bookshelf template with unified search UI

- Change search box to use /api/media-items/search endpoint (was /filtered)
- Add autocomplete to all 4 text filters: author, genre, series, language
- Add series and language filters (were missing)
- Add datalist elements for autocomplete dropdowns
- Change filter triggers to Enter key instead of instant search
- Preserve existing sort dropdown (all 5 options: title ASC/DESC, author ASC/DESC, created_at ASC/DESC, page_count ASC/DESC)
- Preserve Save Filter button and modal
- Preserve Load Filter button and dropdown
- Preserve Clear Filters button
- Update pagination to use /search endpoint
- Add Alpine.js event handlers for dropdown population (@input.debounce.300ms)

All filter inputs include hidden filter-form via hx-include for combined searches.
This commit is contained in:
2026-03-23 22:37:58 -04:00
parent 172536f888
commit 2e24ce00cf
2 changed files with 77 additions and 42 deletions
+68 -33
View File
@@ -75,40 +75,32 @@ templ BookShelf(
type="text"
placeholder="Search all fields..."
hx-get="/api/media-items/search"
hx-trigger="keyup[key=='Enter'] from:#search-form, keyup changed delay:500ms"
hx-trigger="keyup[key=='Enter'] from:#filter-form, keyup changed delay:500ms"
hx-target="#books-grid"
hx-include="#filter-form, #library-select"
/>
</div>
<!-- Author Filter -->
<!-- Author Filter with Autocomplete -->
<div class="flex-1 min-w-[150px]">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
Author
</label>
<div class="relative" x-data="{ authorValues: [] }">
<input
type="text"
name="author_filter"
placeholder="Filter by author"
class="w-full px-3 py-2 border rounded-lg"
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="change"
hx-include="#filter-form"
list="author-datalist"
@focus="fetchAuthorValues($el)"
@input.debounce.300ms="if($el.value === '') authorValues = []"
/>
<datalist id="author-datalist">
<template x-for="item in authorValues" :key="item.value">
<option :value="item.value" x-text="`${item.value} (${item.count})`"></option>
</template>
</datalist>
</div>
<input
type="text"
name="author_filter"
placeholder="Filter by author"
class="w-full px-3 py-2 border rounded-lg"
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="keyup[key=='Enter'] from:#filter-form"
hx-include="#filter-form"
list="author-datalist"
@input.debounce.300ms="if($el.value.length >= 2) fetchAuthorValues($el)"
/>
<datalist id="author-datalist"></datalist>
</div>
<!-- Genre Filter -->
<!-- Genre Filter with Autocomplete -->
<div class="flex-1 min-w-[150px]">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
Genre
@@ -121,9 +113,52 @@ templ BookShelf(
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="change"
hx-trigger="keyup[key=='Enter'] from:#filter-form"
hx-include="#filter-form"
list="genre-datalist"
@input.debounce.300ms="if($el.value.length >= 2) fetchGenreValues($el)"
/>
<datalist id="genre-datalist"></datalist>
</div>
<!-- Series Filter with Autocomplete (NEW) -->
<div class="flex-1 min-w-[150px]">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
Series
</label>
<input
type="text"
name="series_filter"
placeholder="Filter by series"
class="w-full px-3 py-2 border rounded-lg"
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="keyup[key=='Enter'] from:#filter-form"
hx-include="#filter-form"
list="series-datalist"
@input.debounce.300ms="if($el.value.length >= 2) fetchSeriesValues($el)"
/>
<datalist id="series-datalist"></datalist>
</div>
<!-- Language Filter with Autocomplete (NEW) -->
<div class="flex-1 min-w-[150px]">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
Language
</label>
<input
type="text"
name="language_filter"
placeholder="Filter by language"
class="w-full px-3 py-2 border rounded-lg"
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="keyup[key=='Enter'] from:#filter-form"
hx-include="#filter-form"
list="language-datalist"
@input.debounce.300ms="if($el.value.length >= 2) fetchLanguageValues($el)"
/>
<datalist id="language-datalist"></datalist>
</div>
<!-- Year Range -->
<div class="flex-1 min-w-[200px]">
@@ -139,7 +174,7 @@ templ BookShelf(
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="change"
hx-trigger="keyup[key=='Enter'] from:#filter-form"
hx-include="#filter-form"
/>
<input
@@ -150,7 +185,7 @@ templ BookShelf(
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
hx-get="/api/media-items/search"
hx-target="#books-grid"
hx-trigger="change"
hx-trigger="keyup[key=='Enter'] from:#filter-form"
hx-include="#filter-form"
/>
</div>
@@ -171,7 +206,7 @@ templ BookShelf(
<span class="text-sm" style="color: var(--text-primary)">Has Cover</span>
</label>
</div>
<!-- Sort -->
<!-- Sort By Dropdown (PRESERVED) -->
<div class="flex-1 min-w-[150px]">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">
Sort By
@@ -192,7 +227,7 @@ templ BookShelf(
<option value="page_count DESC">Page Count</option>
</select>
</div>
<!-- Save Filter Button -->
<!-- Save Filter Button (PRESERVED) -->
<div class="flex items-end">
<button
@click="showSaveFilterModal()"
@@ -202,7 +237,7 @@ templ BookShelf(
💾 Save Filter
</button>
</div>
<!-- Load Filter Button -->
<!-- Load Filter Button (PRESERVED) -->
<div class="flex items-end relative">
<button
@click="toggleFiltersDropdown()"
@@ -211,7 +246,7 @@ templ BookShelf(
>
📂 Load Filter
</button>
<!-- Saved Filters Dropdown -->
<!-- Saved Filters Dropdown (PRESERVED) -->
<div
x-show="showFiltersDropdown"
@click.outside="showFiltersDropdown = false"
@@ -249,7 +284,7 @@ templ BookShelf(
</div>
</div>
</div>
<!-- Clear Filters -->
<!-- Clear Filters Button (PRESERVED) -->
<div class="flex items-end">
<button
@click="clearFilters()"
@@ -260,7 +295,7 @@ templ BookShelf(
</button>
</div>
</div>
<!-- Hidden form for HTMX include -->
<!-- Hidden form for HTMX include (PRESERVED) -->
<form id="filter-form" class="hidden">
<input type="hidden" name="limit" value="50"/>
<input type="hidden" name="offset" value="0"/>
File diff suppressed because one or more lines are too long