// 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 filterDiv = document.getElementById("book-picker-filters"); if (!filterDiv) return; const inputs = filterDiv.querySelectorAll( 'input[type="text"]', ) as NodeListOf; inputs.forEach((input) => { input.value = ""; }); 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(); location.reload(); } 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 {};