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:
+57
-22
@@ -75,18 +75,16 @@ 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"
|
||||
@@ -95,20 +93,14 @@ 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="author-datalist"
|
||||
@focus="fetchAuthorValues($el)"
|
||||
@input.debounce.300ms="if($el.value === '') authorValues = []"
|
||||
@input.debounce.300ms="if($el.value.length >= 2) fetchAuthorValues($el)"
|
||||
/>
|
||||
<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>
|
||||
<datalist id="author-datalist"></datalist>
|
||||
</div>
|
||||
</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
Reference in New Issue
Block a user