let allLocations = []; // To store fetched locations globally - ADDED HERE document.addEventListener('DOMContentLoaded', async () => { await fetchLocations(); // Fetch locations on page load const bookListContainer = document.getElementById('book-list-container'); const modal = document.getElementById('edit-modal'); const closeModalButton = modal.querySelector('.close-button'); const modalBookTitle = document.getElementById('modal-book-title'); const modalBookIdInput = document.getElementById('modal-book-id'); const modalSearchQueryInput = document.getElementById('modal-search-query'); const modalSearchButton = document.getElementById('modal-search-button'); const modalSearchResultsContainer = document.getElementById('modal-search-results'); const modalEditLocationSelect = document.getElementById('modal-edit-location'); const modalEditFields = { title: document.getElementById('modal-edit-title'), authors: document.getElementById('modal-edit-authors'), publishedDate: document.getElementById('modal-edit-publishedDate'), isbn: document.getElementById('modal-edit-isbn'), description: document.getElementById('modal-edit-description'), pages: document.getElementById('modal-edit-pages'), publishers: document.getElementById('modal-edit-publishers'), subjects: document.getElementById('modal-edit-subjects'), cover_small: document.getElementById('modal-edit-cover-small'), cover_medium: document.getElementById('modal-edit-cover-medium') }; const modalSaveButton = document.getElementById('modal-save-button'); let currentBooks = []; // To store the fetched books globally // let allLocations = []; // To store fetched locations globally - REMOVED FROM HERE let currentlyEditingBookId = null; const addNewBookButton = document.getElementById('add-new-book-button'); addNewBookButton.addEventListener('click', () => { currentlyEditingBookId = null; // Signal "add" mode modalBookIdInput.value = ''; // Clear hidden book ID input modalBookTitle.textContent = 'Add New Book'; // Update modal title for adding // Clear all form fields Object.values(modalEditFields).forEach(field => field.value = ''); modalEditLocationSelect.value = ''; // Reset location dropdown modalSearchResultsContainer.innerHTML = ''; // Clear any previous search results document.getElementById('modal-search-query').value = ''; // Clear search query too openModal(); // Open the modal for new entry }); // Authentication has been disabled for book updates // --- Fetch and Display Books --- async function fetchAndDisplayBooks() { try { const response = await fetch('/api/books'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } currentBooks = await response.json(); renderBookList(currentBooks); } catch (error) { console.error('Error fetching books:', error); bookListContainer.innerHTML = '

Error loading books. See console for details.

'; } } function renderBookList(books) { bookListContainer.innerHTML = ''; // Clear existing list if (books.length === 0) { bookListContainer.innerHTML = '

No books found in the library.

'; return; } const ul = document.createElement('ul'); ul.className = 'book-list'; // Add a class for styling if needed books.forEach(book => { const li = document.createElement('li'); const locationName = book.Location ? `${book.Location.name}${book.Location.shelf ? ' (Shelf: ' + book.Location.shelf + ')' : ''}` : 'No Location'; // Reverted to book.Location (capital L) li.innerHTML = `
Cover for ${book.title || 'N/A'}

Title: ${book.title || 'N/A'}

Author(s): ${book.authors || 'N/A'}

Published: ${book.publishedDate || 'N/A'}

ISBN: ${book.isbn || 'N/A'}

Publisher(s): ${book.publishers || 'N/A'}

Location: ${locationName}

`; ul.appendChild(li); }); bookListContainer.appendChild(ul); // Add event listeners to new edit buttons document.querySelectorAll('.edit-metadata-button').forEach(button => { button.addEventListener('click', handleEditMetadataClick); }); } // --- Modal Logic --- function openModal() { modal.style.display = 'block'; } function closeModal() { modal.style.display = 'none'; modalSearchResultsContainer.innerHTML = ''; // Clear search results // Clear form fields Object.values(modalEditFields).forEach(input => input.value = ''); modalSearchQueryInput.value = ''; } closeModalButton.onclick = closeModal; window.onclick = (event) => { if (event.target == modal) { closeModal(); } }; async function handleEditMetadataClick(event) { event.preventDefault(); // Prevent default action const bookId = event.target.dataset.bookId; const bookToEdit = currentBooks.find(b => b.id.toString() === bookId); if (!bookToEdit) { alert('Book not found!'); return; } modalBookTitle.textContent = bookToEdit.title; modalBookIdInput.value = bookToEdit.id; currentlyEditingBookId = bookToEdit.id; // Ensure this is set for edit mode // Pre-fill search query and existing data modalSearchQueryInput.value = bookToEdit.title || ''; if (bookToEdit.isbn) { modalSearchQueryInput.value += ` ISBN: ${bookToEdit.isbn}`; } modalEditFields.title.value = bookToEdit.title || ''; modalEditFields.authors.value = bookToEdit.authors || ''; modalEditFields.publishedDate.value = bookToEdit.publishedDate || ''; modalEditFields.isbn.value = bookToEdit.isbn || ''; modalEditFields.description.value = bookToEdit.description || ''; modalEditFields.pages.value = bookToEdit.number_of_pages || ''; modalEditFields.publishers.value = bookToEdit.publishers || ''; modalEditFields.subjects.value = bookToEdit.subjects || ''; modalEditFields.cover_small.value = bookToEdit.cover_small || ''; modalEditFields.cover_medium.value = bookToEdit.cover_medium || ''; // Populate and set location dropdown modalEditLocationSelect.innerHTML = ''; // Clear existing options but keep default allLocations.forEach(loc => { const option = document.createElement('option'); option.value = loc.id; option.textContent = `${loc.name}` + (loc.shelf ? ` (Shelf: ${loc.shelf})` : ''); modalEditLocationSelect.appendChild(option); }); modalEditLocationSelect.value = bookToEdit.location_id || (bookToEdit.Location ? bookToEdit.Location.id : ''); openModal(); } // --- Google Books API Search in Modal --- modalSearchButton.addEventListener('click', handleModalSearch); async function handleModalSearch() { let originalQuery = modalSearchQueryInput.value.trim(); if (!originalQuery) { alert('Please enter a search query.'); return; } let searchQuery = originalQuery; const potentialIsbn = originalQuery.replace(/-/g, ''); let isIsbnQuery = false; if (/^(\d{9}[\dX]|\d{13})$/.test(potentialIsbn)) { searchQuery = potentialIsbn; // Use the cleaned ISBN for OpenLibrary isIsbnQuery = true; console.log(`Detected ISBN-like string "${originalQuery}", searching as ISBN "${searchQuery}"`); } else if (originalQuery.toLowerCase().startsWith('isbn:')) { searchQuery = originalQuery.substring(5).replace(/-/g, ''); // Remove 'isbn:' and hyphens isIsbnQuery = true; console.log(`Query "${originalQuery}" formatted for ISBN search, using "${searchQuery}"`); } else { console.log(`Performing general search for "${originalQuery}"`); } modalSearchResultsContainer.innerHTML = '

Searching...

'; const searchButton = document.getElementById('modal-search-button'); if (searchButton) searchButton.disabled = true; let combinedResults = []; try { const googleQuery = isIsbnQuery ? `isbn:${searchQuery}` : searchQuery; const googleUrl = `https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(googleQuery)}&maxResults=5`; const openLibFields = 'key,title,author_name,first_publish_year,isbn,cover_i,publisher,subject,number_of_pages_median,subtitle,first_sentence_value'; const openLibUrl = `https://openlibrary.org/search.json?q=${encodeURIComponent(searchQuery)}&limit=5&fields=${openLibFields}`; console.log("Searching Google Books API with URL:", googleUrl); console.log("Searching OpenLibrary API with URL:", openLibUrl); const [googleResult, openLibResult] = await Promise.allSettled([ fetch(googleUrl).then(res => res.ok ? res.json() : Promise.reject(new Error(`Google Books API request failed: ${res.status} ${res.statusText}`))), fetch(openLibUrl).then(res => res.ok ? res.json() : Promise.reject(new Error(`OpenLibrary API request failed: ${res.status} ${res.statusText}`))) ]); if (googleResult.status === 'fulfilled' && googleResult.value.items) { const googleBooks = googleResult.value.items.map(item => { const volumeInfo = item.volumeInfo; let isbn13 = '', isbn10 = ''; if (volumeInfo.industryIdentifiers) { volumeInfo.industryIdentifiers.forEach(id => { if (id.type === 'ISBN_13') isbn13 = id.identifier; if (id.type === 'ISBN_10') isbn10 = id.identifier; }); } return { title: volumeInfo.title, authors: volumeInfo.authors ? volumeInfo.authors.join(', ') : '', publishedDate: volumeInfo.publishedDate, isbn: isbn13 || isbn10, description: volumeInfo.description, pageCount: volumeInfo.pageCount, publishers: volumeInfo.publisher ? (Array.isArray(volumeInfo.publisher) ? volumeInfo.publisher.join(', ') : volumeInfo.publisher) : '', subjects: volumeInfo.categories ? volumeInfo.categories.join(', ') : '', cover_small: volumeInfo.imageLinks?.smallThumbnail, cover_medium: volumeInfo.imageLinks?.thumbnail, source: 'Google Books', rawData: volumeInfo }; }); combinedResults.push(...googleBooks); } else if (googleResult.status === 'rejected') { console.error('Error fetching from Google Books:', googleResult.reason); } if (openLibResult.status === 'fulfilled' && openLibResult.value.docs) { const openLibBooks = openLibResult.value.docs.map(doc => { return { title: doc.title, authors: doc.author_name ? doc.author_name.join(', ') : '', publishedDate: doc.first_publish_year ? String(doc.first_publish_year) : '', isbn: doc.isbn ? doc.isbn[0] : '', // Take first ISBN description: doc.first_sentence_value || doc.subtitle || '', pageCount: doc.number_of_pages_median, publishers: doc.publisher ? doc.publisher.join(', ') : '', subjects: doc.subject ? doc.subject.join(', ') : '', cover_small: doc.cover_i ? `https://covers.openlibrary.org/b/id/${doc.cover_i}-S.jpg` : '', cover_medium: doc.cover_i ? `https://covers.openlibrary.org/b/id/${doc.cover_i}-M.jpg` : '', source: 'OpenLibrary', rawData: doc }; }); combinedResults.push(...openLibBooks); } else if (openLibResult.status === 'rejected') { console.error('Error fetching from OpenLibrary:', openLibResult.reason); } displayModalSearchResults(combinedResults); } catch (error) { // Catch any unexpected errors during the setup or Promise.allSettled itself console.error('Error in handleModalSearch:', error); modalSearchResultsContainer.innerHTML = `

An unexpected error occurred during search: ${error.message}. Please check console.

`; } finally { if (searchButton) searchButton.disabled = false; } } function displayModalSearchResults(items) { modalSearchResultsContainer.innerHTML = ''; if (!items || items.length === 0) { modalSearchResultsContainer.innerHTML = '

No results found from any source.

'; return; } const ul = document.createElement('ul'); items.forEach(bookItem => { const li = document.createElement('li'); li.innerHTML = ` ${bookItem.title || 'N/A'} by ${bookItem.authors || 'N/A'} (${bookItem.source})
Published: ${bookItem.publishedDate || 'N/A'}, ISBN: ${bookItem.isbn || 'N/A'} `; li.querySelector('.select-book-result').addEventListener('click', () => { populateFormWithSelectedBook(bookItem); }); ul.appendChild(li); }); modalSearchResultsContainer.appendChild(ul); } function populateFormWithSelectedBook(bookItem) { modalEditFields.title.value = bookItem.title || ''; modalEditFields.authors.value = bookItem.authors || ''; modalEditFields.publishedDate.value = bookItem.publishedDate || ''; modalEditFields.isbn.value = bookItem.isbn || ''; modalEditFields.description.value = bookItem.description || ''; modalEditFields.pages.value = bookItem.pageCount || ''; modalEditFields.publishers.value = bookItem.publishers || ''; modalEditFields.subjects.value = bookItem.subjects || ''; modalEditFields.cover_small.value = bookItem.cover_small || ''; modalEditFields.cover_medium.value = bookItem.cover_medium || ''; } // --- Save Changes --- modalSaveButton.addEventListener('click', async () => { const bookData = { title: modalEditFields.title.value.trim(), authors: modalEditFields.authors.value.trim(), publishedDate: modalEditFields.publishedDate.value.trim(), isbn: modalEditFields.isbn.value.trim(), description: modalEditFields.description.value.trim(), number_of_pages: modalEditFields.pages.value ? parseInt(modalEditFields.pages.value, 10) : null, publishers: modalEditFields.publishers.value.trim(), subjects: modalEditFields.subjects.value.trim(), cover_small: modalEditFields.cover_small.value.trim(), cover_medium: modalEditFields.cover_medium.value.trim(), location_id: modalEditLocationSelect.value || null, }; if (!bookData.title) { alert('Title is required.'); return; } let url; let method; const headers = { 'Content-Type': 'application/json' }; if (currentlyEditingBookId) { // Edit mode url = `/api/books/${currentlyEditingBookId}`; method = 'PUT'; // Basic Auth not currently implemented for PUT as per prior setup } else { // Add mode url = '/api/books'; method = 'POST'; // POST endpoint requires Basic Auth const adminPassword = prompt("Enter admin password to add a new book:"); if (adminPassword === null) { // User cancelled prompt return; } headers['Authorization'] = 'Basic ' + btoa('admin:' + adminPassword); } try { const response = await fetch(url, { method: method, headers: headers, body: JSON.stringify(bookData) }); const responseBody = await response.text(); // Read body as text first to handle potential non-JSON errors let result; try { result = JSON.parse(responseBody); } catch (e) { // If parsing fails, the response was not valid JSON console.error('Failed to parse server response as JSON:', responseBody); throw new Error(`Server returned non-JSON response (status: ${response.status}). Check console for details.`); } if (!response.ok) { const errorMessage = result && result.error ? result.error : `HTTP error! Status: ${response.status}`; throw new Error(errorMessage); } if (result.success) { alert(`Book ${method === 'POST' ? 'added' : 'updated'} successfully!`); closeModal(); fetchAndDisplayBooks(); // Refresh the list } else { alert(`Failed to ${method === 'POST' ? 'add' : 'update'} book: ${result.error || 'Unknown server error'}`); } } catch (error) { console.error(`Error ${method === 'POST' ? 'adding' : 'saving'} book data:`, error); alert(`Error ${method === 'POST' ? 'adding' : 'saving'}: ${error.message}`); } }); // Initial load fetchAndDisplayBooks(); }); async function fetchLocations() { try { const response = await fetch('/api/locations'); if (!response.ok) { throw new Error(`Failed to fetch locations: ${response.status}`); } allLocations = await response.json(); console.log('Locations fetched:', allLocations); } catch (error) { console.error('Error fetching locations:', error); // Optionally, inform the user that locations could not be loaded } }