fix(ui): wire up collection detail page interactions

The /collections/:id page had several broken features because three
referenced functions (removeBook, toggleBookForRemoval,
filterCollectionBooks) were never defined, and every book card was
wrapped in <a href="/media/..."> so clicking the checkbox or remove
button navigated to the book detail page instead.

Card restructure:
- Remove the <a> wrapper; title and cover are now individual links.
- Checkbox sits in a <label> with expanded click area (p-2 -m-2).
- Checkbox uses Alpine :checked/@change bound to a reactive
  selectedBooks array on the collections component.

Remove (single + bulk):
- Add removeBook(id) and bulkRemove() methods with confirm() dialogs.
- Wire the "Remove Selected" button with :disabled binding and @click.
- Selected-count badge is now Alpine-reactive (x-show/x-text).

Search within collection:
- Add filterCollectionBooks() that filters cards client-side by
  title/author via data-* attributes and @input.

Book picker ("Add Books"):
- Point the HTMX search inputs at the existing /api/media-items/search
  endpoint instead of the non-existent /api/media-items/filtered.
- Add hx-trigger="loadBooks" + hx-get to the grid so loadBooks()
  actually fires an initial request when the picker opens.
- Merge the hidden limit/offset inputs into the #book-picker-filters
  div so hx-include picks them up (was a separate <form id=filter-form>
  that nobody referenced).
- Add show_checkbox mode to handleSearchHTML: when present, render a
  new BookPickerGrid template with clickable, selectable cards instead
  of the reader BookCard.
- Fix bookPicker submit() to location.reload() instead of a non-existent
  reloadCollection HTMX event, and clearFilters() to target text inputs.
This commit is contained in:
2026-08-06 10:40:18 -04:00
parent 73a2852ee3
commit 816ee0ec80
5 changed files with 445 additions and 116 deletions
+7 -10
View File
@@ -48,18 +48,16 @@ Alpine.store("bookPicker", {
},
clearFilters() {
const filterForm = document.getElementById(
"book-picker-filters",
) as HTMLFormElement;
if (!filterForm) return;
const filterDiv = document.getElementById("book-picker-filters");
if (!filterDiv) return;
// Reset all form fields except checkbox state (that's in Alpine.store)
const inputs = filterForm.querySelectorAll("input:not([type='checkbox'])");
const inputs = filterDiv.querySelectorAll(
'input[type="text"]',
) as NodeListOf<HTMLInputElement>;
inputs.forEach((input) => {
(input as HTMLInputElement).value = "";
input.value = "";
});
// Reload books - selection state preserved in Alpine.store
this.loadBooks();
},
@@ -97,8 +95,7 @@ Alpine.store("bookPicker", {
"success",
);
this.close();
// Reload collection detail page
window.htmx.trigger(document.body, "reloadCollection");
location.reload();
} else {
showToast("Failed to add books", "error");
}