// Book Picker for Collections - multi-select book picker modal // CRITICAL: Selection state must persist across HTMX grid updates import { Alpine } from "./alpine"; import { showToast } from "./toast"; // Alpine.store for global book picker state // Using store ensures state persists across HTMX DOM swaps Alpine.store("bookPicker", { isOpen: false, selectedBooks: new Set(), open() { this.isOpen = true; this.selectedBooks.clear(); this.loadBooks(); }, close() { this.isOpen = false; this.selectedBooks.clear(); }, toggleBook(bookId: string) { if (this.selectedBooks.has(bookId)) { this.selectedBooks.delete(bookId); } else { this.selectedBooks.add(bookId); } // Trigger Alpine reactivity by creating new Set this.selectedBooks = new Set(this.selectedBooks); }, isSelected(bookId: string): boolean { return this.selectedBooks.has(bookId); }, get selectedCount(): number { return this.selectedBooks.size; }, async loadBooks() { const grid = document.getElementById("book-picker-grid"); if (!grid) return; // Trigger initial HTMX load window.htmx.trigger(grid, "loadBooks"); }, clearFilters() { const filterForm = document.getElementById( "book-picker-filters", ) as HTMLFormElement; if (!filterForm) return; // Reset all form fields except checkbox state (that's in Alpine.store) const inputs = filterForm.querySelectorAll("input:not([type='checkbox'])"); inputs.forEach((input) => { (input as HTMLInputElement).value = ""; }); // Reload books - selection state preserved in Alpine.store this.loadBooks(); }, async submit() { const token = localStorage.getItem("token"); if (!token) { showToast("Not authenticated", "error"); return; } if (this.selectedBooks.size === 0) { showToast("No books selected", "error"); return; } // Get collection ID from current page const pathParts = window.location.pathname.split("/"); const collectionId = pathParts[pathParts.length - 1]; try { const response = await fetch(`/api/collections/${collectionId}/books`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ book_ids: Array.from(this.selectedBooks), }), }); if (response.ok) { showToast( `Added ${this.selectedBooks.size} books to collection`, "success", ); this.close(); // Reload collection detail page window.htmx.trigger(document.body, "reloadCollection"); } else { showToast("Failed to add books", "error"); } } catch (error) { console.error("Failed to add books:", error); showToast("Error adding books", "error"); } }, }); // Alpine component for book picker // Provides methods for template to access store Alpine.data("bookPicker", () => ({ get isOpen() { return (window as any).Alpine?.$store.bookPicker.isOpen; }, get selectedCount() { return (window as any).Alpine?.$store.bookPicker.selectedCount; }, toggleBook(bookId: string) { (window as any).Alpine?.$store.bookPicker.toggleBook(bookId); }, isSelected(bookId: string): boolean { return (window as any).Alpine?.$store.bookPicker.isSelected(bookId); }, clearFilters() { (window as any).Alpine?.$store.bookPicker.clearFilters(); }, close() { (window as any).Alpine?.$store.bookPicker.close(); }, submit() { (window as any).Alpine?.$store.bookPicker.submit(); }, })); export {};