feat(collections): add book picker modal and fix icon picker
- Add book picker modal with Alpine.js state management for selecting books - Add toggleBookPickerBook, isBookPickerBookSelected, getBookPickerSelectedCount methods - Add clearBookPickerFilters function to reset filter form - Fix icon picker: add showAllIcons function to reset icon search - Fix setupHTMXModalInit to properly initialize Alpine tree after HTMX swap - Update collections template with book picker modal structure
This commit is contained in:
+120
-3
@@ -1,6 +1,9 @@
|
||||
package templates
|
||||
|
||||
import "bookhoard/internal/handlers"
|
||||
import (
|
||||
"bookhoard/internal/handlers"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
templ Collection(user User, collections []CollectionData, errorMessage string) {
|
||||
<!DOCTYPE html>
|
||||
@@ -12,7 +15,7 @@ templ Collection(user User, collections []CollectionData, errorMessage string) {
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<link href="/static/style.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body x-data="collections" x-init="initializeCollectionWebSocket" class="theme-{ user.Theme }">
|
||||
<body x-data="collections" x-init="initializeCollectionWebSocket() setupHTMXModalInit()" class="theme-{ user.Theme }">
|
||||
@Header(user, "/collections")
|
||||
<!-- Modal Container -->
|
||||
<div id="modal-container"></div>
|
||||
@@ -150,7 +153,10 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
>
|
||||
🗑️ Remove Selected
|
||||
</button>
|
||||
<button type="button" class="btn-primary px-4 py-2 rounded-lg" style="opacity: 0.5; cursor: not-allowed;" disabled>
|
||||
<button
|
||||
@click="openBookPicker()"
|
||||
class="btn-primary px-4 py-2 rounded-lg"
|
||||
>
|
||||
➕ Add Books
|
||||
</button>
|
||||
</div>
|
||||
@@ -255,6 +261,117 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
x-show="bookPickerOpen"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click.self="closeBookPicker()"
|
||||
@keyup.escape.window="closeBookPicker()"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center"
|
||||
style="background-color: rgba(0, 0, 0, 0.7); display: none;"
|
||||
>
|
||||
<div
|
||||
@click.stop
|
||||
class="card rounded-lg w-full max-w-4xl mx-4 my-8"
|
||||
style="background-color: var(--bg-secondary); border-color: var(--border);"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex justify-between items-center p-4 border-b"
|
||||
style="border-color: var(--border);"
|
||||
>
|
||||
<h2 class="text-xl font-bold" style="color: var(--text-primary)">Add Books to Collection</h2>
|
||||
<button @click="closeBookPicker()" class="p-2 hover:opacity-80 rounded" style="color: var(--text-primary)">✕</button>
|
||||
</div>
|
||||
<!-- Filters -->
|
||||
<div class="p-4 border-b" style="border-color: var(--border);">
|
||||
<div class="flex flex-wrap gap-4 items-center">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder="Search books..."
|
||||
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={ fmt.Sprintf("/api/media-items/filtered?show_checkbox=true&collection_id=%s", collection.ID) }
|
||||
hx-target="#book-picker-grid"
|
||||
hx-trigger="keyup changed delay:300ms"
|
||||
hx-include="#book-picker-filters"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[150px]">
|
||||
<input
|
||||
type="text"
|
||||
name="author_filter"
|
||||
placeholder="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={ fmt.Sprintf("/api/media-items/filtered?show_checkbox=true&collection_id=%s", collection.ID) }
|
||||
hx-target="#book-picker-grid"
|
||||
hx-trigger="change"
|
||||
hx-include="#book-picker-filters"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@click="clearBookPickerFilters()"
|
||||
class="px-4 py-2 rounded-lg border"
|
||||
style="border-color: var(--border); color: var(--text-primary);"
|
||||
>
|
||||
✕ Clear
|
||||
</button>
|
||||
</div>
|
||||
<!-- Hidden form for HTMX -->
|
||||
<form id="book-picker-filters" class="hidden">
|
||||
<input type="hidden" name="limit" value="50"/>
|
||||
<input type="hidden" name="offset" value="0"/>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Books Grid with Checkboxes -->
|
||||
<div
|
||||
id="book-picker-grid"
|
||||
class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-4 p-4 max-h-96 overflow-y-auto"
|
||||
>
|
||||
<!-- Books rendered via HTMX -->
|
||||
</div>
|
||||
<!-- Footer with Selection Count and Submit -->
|
||||
<div
|
||||
class="p-4 border-t flex justify-between items-center"
|
||||
style="border-color: var(--border);"
|
||||
>
|
||||
<span class="text-sm" style="color: var(--text-secondary);">
|
||||
<span x-text="bookPickerSelected.length"></span> books selected
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="closeBookPicker()"
|
||||
class="px-4 py-2 rounded-lg border"
|
||||
style="border-color: var(--border); color: var(--text-primary);"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<!-- HTMX Form Submission -->
|
||||
<form
|
||||
hx-post={ fmt.Sprintf("/api/collections/%s/books", collection.ID) }
|
||||
hx-on::after-request="if(event.detail.successful) { closeBookPicker(); htmx.trigger(htmx.find('#books-container'), 'refresh'); }"
|
||||
@submit.prevent="if(bookPickerSelected.length === 0) return; $el.querySelector('input[name=book_ids]').value = bookPickerSelected.join(','); $el.submit()"
|
||||
>
|
||||
<input type="hidden" name="book_ids" value=""/>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 rounded-lg font-medium"
|
||||
style="background-color: var(--accent); color: var(--bg-primary);"
|
||||
>
|
||||
Add Selected Books
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<div
|
||||
id="collection-data"
|
||||
|
||||
Reference in New Issue
Block a user