feat(collections): add collection-specific search/filter (Limitation #3)
Add search and filter within a collection: Frontend Implementation: - Search input box in collection detail toolbar - filterCollectionBooks() JavaScript function - Real-time filtering of books by title and author - Case-insensitive search - Hides non-matching book cards - Shows all books when search is cleared How It Works: - AllBooks array contains book data from server render - On keyup, filters books by title and author - Toggles display property on book cards - Empty state has ID and is not hidden - Pure client-side filtering (no server round-trips) User Experience: - Instant search results as user types - No page reload required - Works with existing multi-select for bulk operations - Search box always visible in toolbar Resolves Limitation #3: Collection-Specific Search
This commit is contained in:
@@ -247,6 +247,12 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
|
<div class="flex-1 max-w-md">
|
||||||
|
<input type="text" id="collection-search" placeholder="Search within collection..."
|
||||||
|
onkeyup="filterCollectionBooks()"
|
||||||
|
class="w-full px-4 py-2 border rounded-lg"
|
||||||
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);">
|
||||||
|
</div>
|
||||||
<button id="bulk-remove-btn" onclick="removeSelectedBooks()" disabled
|
<button id="bulk-remove-btn" onclick="removeSelectedBooks()" disabled
|
||||||
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed">
|
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
🗑️ Remove Selected
|
🗑️ Remove Selected
|
||||||
@@ -259,7 +265,7 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD
|
|||||||
|
|
||||||
<div id="books-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div id="books-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
if len(books) == 0 {
|
if len(books) == 0 {
|
||||||
<div class="col-span-full text-center py-16" style="color: var(--text-secondary)">No books in this collection yet.</div>
|
<div id="empty-state" class="col-span-full text-center py-16" style="color: var(--text-secondary)">No books in this collection yet.</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, book := range books {
|
for _, book := range books {
|
||||||
@@ -319,11 +325,45 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD
|
|||||||
let collectionId = '{ collection.ID }';
|
let collectionId = '{ collection.ID }';
|
||||||
let selectedBooks = new Set();
|
let selectedBooks = new Set();
|
||||||
let booksToRemove = new Set();
|
let booksToRemove = new Set();
|
||||||
|
let allBooks = [
|
||||||
|
{ range books }{
|
||||||
|
{
|
||||||
|
id: '{ book.MediaItemID }',
|
||||||
|
title: '{ book.Title }',
|
||||||
|
author: '{ book.Author }',
|
||||||
|
cover: '{ book.CoverImagePath }'
|
||||||
|
},
|
||||||
|
}{ end }
|
||||||
|
];
|
||||||
|
|
||||||
function backToCollections() {
|
function backToCollections() {
|
||||||
window.location.href = '/collections';
|
window.location.href = '/collections';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterCollectionBooks() {
|
||||||
|
const searchTerm = document.getElementById('collection-search').value.toLowerCase();
|
||||||
|
const booksContainer = document.getElementById('books-container');
|
||||||
|
const bookCards = booksContainer.children;
|
||||||
|
|
||||||
|
let visibleCount = 0;
|
||||||
|
for (let i = 0; i < bookCards.length; i++) {
|
||||||
|
const card = bookCards[i];
|
||||||
|
if (card.id === 'empty-state') continue;
|
||||||
|
|
||||||
|
const title = card.querySelector('.font-semibold')?.textContent.toLowerCase() || '';
|
||||||
|
const author = card.querySelector('.text-sm')?.textContent.toLowerCase() || '';
|
||||||
|
|
||||||
|
const matches = title.includes(searchTerm) || author.includes(searchTerm);
|
||||||
|
|
||||||
|
if (matches || searchTerm === '') {
|
||||||
|
card.style.display = '';
|
||||||
|
visibleCount++;
|
||||||
|
} else {
|
||||||
|
card.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showAddBooksModal() {
|
function showAddBooksModal() {
|
||||||
document.getElementById('add-books-modal').classList.remove('hidden');
|
document.getElementById('add-books-modal').classList.remove('hidden');
|
||||||
selectedBooks.clear();
|
selectedBooks.clear();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user