Fixed template function call mismatches: - conflicts.templ: Renamed showConflictModal→showResolveModal, hideConflictModal→hideResolveModal, handleResolveConflict→handleResolveSubmit - collections.templ: Fixed case mismatch addSelectedBooks→addbooksToAdd - queue.templ: Removed dead /static/queue.js reference - progress.templ: Removed vestigial x-data="progress" attribute Added missing queue functions: - showQueueItemModal() - Shows queue item detail modal - hideQueueItemModal() - Hides queue item modal - filterQueue() - Filters queue by status (stubbed) Fixed build errors: - main.ts: Removed imports for deleted themeDropdown.ts and woodPaneling.ts files All fixes are minimal and conservative. No breaking changes to existing functionality. TypeScript builds successfully (168KB main.js). Templates regenerate successfully.
272 lines
10 KiB
Templ
272 lines
10 KiB
Templ
package templates
|
||
|
||
import "bookhoard/internal/handlers"
|
||
|
||
templ Collection(user User, collections []CollectionData, errorMessage string) {
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>Collections - Bookhoard</title>
|
||
<script src="/static/htmx.min.js"></script>
|
||
<script src="/static/toast.js"></script>
|
||
<link href="/static/style.css" rel="stylesheet"/>
|
||
</head>
|
||
<body x-data="collections" class="theme-{ user.Theme }">
|
||
@Header(user, "/collections")
|
||
<!-- Modal Container -->
|
||
<div id="modal-container"></div>
|
||
<!-- Actual container page -->
|
||
<div class="w-full px-4 sm:px-6 lg:px-8 py-8">
|
||
<div class="mb-8 flex justify-between items-center">
|
||
<div>
|
||
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">My Collections</h1>
|
||
<p style="color: var(--text-secondary)">Organize your books into custom collections</p>
|
||
</div>
|
||
<div class="flex gap-3">
|
||
<button
|
||
hx-get="/collections/restore-modal"
|
||
hx-target="#modal-container"
|
||
hx-swap="innerHTML"
|
||
class="btn-secondary px-4 py-2 rounded-lg"
|
||
>
|
||
🔄 Restore System
|
||
</button>
|
||
<button
|
||
hx-get="/collections/create-modal"
|
||
hx-target="#modal-container"
|
||
hx-swap="innerHTML"
|
||
class="btn-primary px-4 py-2 rounded-lg"
|
||
>
|
||
➕ New Collection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="collections-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
if len(collections) == 0 {
|
||
<div class="text-center py-16 col-span-full" style="color: var(--text-secondary)">
|
||
<div class="text-6xl mb-4">📚</div>
|
||
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Collections Yet</h3>
|
||
<p class="mb-4">Create collections to organize your books</p>
|
||
<button
|
||
hx-get="/collections/create-modal"
|
||
hx-target="#modal-container"
|
||
hx-swap="innerHTML"
|
||
class="btn-primary px-4 py-2 rounded-lg"
|
||
>
|
||
Create Your First Collection
|
||
</button>
|
||
</div>
|
||
}
|
||
for _, col := range collections {
|
||
<div @click="$store.collections.navigateToCollection($el)" data-href={ "/collections/" + col.ID } class="block">
|
||
<div
|
||
class="card p-6 rounded-lg border-l-4 cursor-pointer hover:shadow-lg transition-shadow"
|
||
style="background-color: var(--bg-secondary);"
|
||
data-color={ col.Color }
|
||
>
|
||
<div class="flex justify-between items-start mb-4">
|
||
<div class="text-3xl">{ col.Icon }</div>
|
||
<div class="flex space-x-2">
|
||
<button
|
||
hx-get={ "/collections/" + col.ID + "/edit-modal" }
|
||
hx-target="#modal-container"
|
||
hx-swap="innerHTML"
|
||
class="p-2 hover:opacity-80 rounded"
|
||
style="color: var(--text-secondary); background-color: var(--bg-primary);"
|
||
>
|
||
✏️
|
||
</button>
|
||
<button
|
||
hx-delete={ "/api/collections/" + col.ID + "" }
|
||
hx-redirect="/collections"
|
||
hx-confirm="Are you sure you want to delete this collection?"
|
||
class="p-2 hover:opacity-80 rounded"
|
||
style="color: var(--text-secondary); background-color: var(--bg-primary);"
|
||
>
|
||
🗑️
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<h3 class="text-lg font-semibold mb-2" style="color: var(--text-primary)">{ col.Name }</h3>
|
||
<p class="text-sm mb-4" style="color: var(--text-secondary)">{ col.Description }</p>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
@ErrorToast(errorMessage)
|
||
</body>
|
||
</html>
|
||
}
|
||
|
||
templ CollectionDetail(user User, collection CollectionData, books []handlers.BookInfo, libraryID string) {
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>{ collection.Name } - Bookhoard</title>
|
||
<script src="/static/htmx.min.js"></script>
|
||
<script src="/static/toast.js"></script>
|
||
<link href="/static/style.css" rel="stylesheet"/>
|
||
</head>
|
||
<body x-data="collections" class="theme-{ user.Theme }">
|
||
@Header(user, "/collections")
|
||
<div class="w-full px-4 sm:px-6 lg:px-8 py-8">
|
||
<div class="mb-6">
|
||
<button @click="$store.collections.backToCollections" class="btn-secondary px-4 py-2 rounded-lg mb-4">
|
||
← Back to Collections
|
||
</button>
|
||
<div class="flex items-center gap-4">
|
||
<div class="text-4xl" style="color: { collection.Color }">{ collection.Icon }</div>
|
||
<div>
|
||
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">{ collection.Name }</h1>
|
||
<p style="color: var(--text-secondary)">{ collection.Description }</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-6 flex justify-between items-center">
|
||
<div class="flex items-center gap-4">
|
||
<h2 class="text-xl font-semibold" style="color: var(--text-primary)">Books in this Collection</h2>
|
||
<span id="selected-count" class="hidden px-3 py-1 text-sm rounded" style="background-color: var(--accent); color: var(--bg-primary);">
|
||
0 selected
|
||
</span>
|
||
</div>
|
||
<div class="flex gap-3">
|
||
<div class="flex-1 max-w-md">
|
||
<input
|
||
type="text"
|
||
id="collection-search"
|
||
placeholder="Search within collection..."
|
||
onkeyup="filterCollectionBooks()"
|
||
class="w-full px-4 py-2 border rounded-lg"
|
||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||
/>
|
||
</div>
|
||
<button
|
||
id="bulk-remove-btn"
|
||
@click="$store.collections.removebooksToAdd"
|
||
disabled
|
||
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
🗑️ Remove Selected
|
||
</button>
|
||
<button @click="$store.collections.showAddBooksModal" class="btn-primary px-4 py-2 rounded-lg">
|
||
➕ Add Books
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="books-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
if len(books) == 0 {
|
||
<div id="empty-state" class="col-span-full text-center py-16" style="color: var(--text-secondary)">No books in this collection yet.</div>
|
||
}
|
||
for _, book := range books {
|
||
<div
|
||
class="card p-4 rounded-lg border hover:shadow-lg transition-shadow"
|
||
style="background-color: var(--bg-secondary); border-color: var(--border);"
|
||
>
|
||
<div class="flex gap-4">
|
||
<!-- Checkbox and Book Info -->
|
||
<div class="flex-shrink-0 pt-1">
|
||
<input
|
||
type="checkbox"
|
||
onchange="toggleBookForRemoval('{ book.MediaItemID }')"
|
||
class="w-5 h-5"
|
||
/>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<h3
|
||
class="font-semibold text-lg mb-1 line-clamp-2"
|
||
style="color: var(--text-primary)"
|
||
>
|
||
{ book.Title }
|
||
</h3>
|
||
if book.Author != "" {
|
||
<p
|
||
class="text-sm line-clamp-1"
|
||
style="color: var(--text-secondary)"
|
||
>
|
||
by { book.Author }
|
||
</p>
|
||
}
|
||
</div>
|
||
<!-- Book Cover -->
|
||
<div class="flex-shrink-0 w-16 sm:w-20">
|
||
if book.CoverImagePath != "" {
|
||
<img
|
||
src={ book.CoverImagePath }
|
||
alt="Cover"
|
||
class="w-full aspect-[3/4] object-cover rounded shadow-md"
|
||
onerror="this.src='/static/placeholder-book.svg'"
|
||
/>
|
||
} else {
|
||
<img
|
||
src="/static/placeholder-book.svg"
|
||
alt="Cover"
|
||
class="w-full aspect-[3/4] object-cover rounded shadow-md"
|
||
/>
|
||
}
|
||
</div>
|
||
</div>
|
||
<!-- Remove Button -->
|
||
<div class="mt-3 pt-3 border-t" style="border-color: var(--border);">
|
||
<button
|
||
@click="$store.collections.removeBook('{ book.MediaItemID }')"
|
||
class="px-3 py-1 text-sm border rounded hover:opacity-80"
|
||
style="border-color: var(--border); color: var(--text-secondary);"
|
||
>
|
||
🗑️ Remove from Collection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div id="add-books-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center" style="background-color: rgba(0, 0, 0, 0.7);">
|
||
<div class="card rounded-lg p-6 w-full max-w-2xl mx-4" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
||
<div class="flex justify-between items-center mb-6">
|
||
<h2 class="text-xl font-bold" style="color: var(--text-primary)">Add Books to Collection</h2>
|
||
<button @click="$store.collections.hideAddBooksModal" class="p-2 hover:opacity-80 rounded" style="color: var(--text-primary)">✕</button>
|
||
</div>
|
||
<p class="mb-4" style="color: var(--text-secondary)">Search and select books to add to this collection.</p>
|
||
<!-- Library filter toggle - hidden by default, shown by JS when library_id present -->
|
||
<div id="library-filter-container" class="mb-4 hidden">
|
||
<label class="flex items-center gap-2 text-sm" style="color: var(--text-secondary);">
|
||
<input type="checkbox" id="filter-by-library" class="w-4 h-4" onchange="searchBooksForCollection()"/>
|
||
<span>Only show books from this library</span>
|
||
</label>
|
||
</div>
|
||
<div class="mb-4">
|
||
<input
|
||
type="text"
|
||
id="book-search"
|
||
placeholder="Search books..."
|
||
class="w-full px-4 py-2 border rounded-lg"
|
||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||
/>
|
||
</div>
|
||
<div id="book-results" class="max-h-64 overflow-y-auto mb-4"></div>
|
||
<div class="flex justify-end space-x-3">
|
||
<button type="button" @click="$store.collections.hideAddBooksModal" class="btn-secondary px-4 py-2 rounded-lg">
|
||
Cancel
|
||
</button>
|
||
<button type="button" @click="$store.collections.addbooksToAdd" class="btn-primary px-4 py-2 rounded-lg">
|
||
Add Selected Books
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
<div
|
||
id="collection-data"
|
||
data-id="{collection.ID}"
|
||
data-library-id="{ libraryID }"
|
||
style="display: none;"
|
||
></div>
|
||
<!-- Include compiled TypeScript -->
|
||
<script src="/static/collections.js"></script>
|
||
</html>
|
||
}
|