Files
bookhoard/web/src/queue.ts
T
john-okeefe 48eaa2d286 fix(alpine): wrap all Alpine.data() callbacks in arrow functions for proper component initialization
- Wrap all Alpine.data() object literals in arrow functions (() => ({}))
- This fixes "n.bind is not a function" errors when Alpine initializes components
- Alpine.data() requires a factory function, not a plain object
- Ensures each component instance gets its own closure and proper this binding

Fixed 24 TypeScript files:
- admin.ts, analytics.ts, api.ts, api-explorer-docs.ts
- bookshelf.ts, collection-rules.ts, collections.ts, conflicts.ts
- device-management.ts, docs.ts, header.ts, index.ts
- library.ts, linking.ts, login.ts, password_validation.ts
- profile-modal.ts, profile.ts, queue.ts, register.ts
- search.ts, theme.ts, toast-error.ts, toast.ts, unlinked_books.ts

Before: Alpine.data("name", { method1, method2 })
After:  Alpine.data("name", () => ({ method1, method2 }))

This is a critical fix for Alpine.js v3+ where components must be
registered as factory functions to ensure proper reactivity and
prevent binding errors during initialization.
2026-03-12 15:43:56 -04:00

205 lines
5.7 KiB
TypeScript

import { Alpine } from "./alpine";
import { showToast } from "./toast";
async function refreshQueue(): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch("/api/queue/all", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
const data = await response.json();
renderQueueItems(data.items || []);
}
} catch (error) {
console.error("Failed to refresh queue:", error);
}
}
async function processPendingItems(): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch("/api/queue/process", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Processing queue items", "success");
refreshQueue();
}
} catch (error) {
console.error("Failed to process queue:", error);
showToast("Failed to process queue", "error");
}
}
async function clearFailedItems(): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
if (!confirm("Are you sure you want to clear all failed items?")) return;
try {
const response = await fetch("/api/queue/failed", {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Failed items cleared", "success");
refreshQueue();
}
} catch (error) {
console.error("Failed to clear failed items:", error);
showToast("Failed to clear items", "error");
}
}
async function clearAllItems(): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
if (!confirm("Are you sure you want to clear all queue items?")) return;
try {
const response = await fetch("/api/queue/all", {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Queue cleared", "success");
refreshQueue();
}
} catch (error) {
console.error("Failed to clear queue:", error);
showToast("Failed to clear queue", "error");
}
}
async function retryQueueItem(itemId: string): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch(`/api/queue/items/${itemId}/retry`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Item queued for retry", "success");
refreshQueue();
}
} catch (error) {
console.error("Failed to retry item:", error);
showToast("Failed to retry item", "error");
}
}
async function deleteQueueItem(itemId: string): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch(`/api/queue/items/${itemId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Item deleted", "success");
refreshQueue();
}
} catch (error) {
console.error("Failed to delete item:", error);
showToast("Failed to delete item", "error");
}
}
function renderQueueItems(items: QueueItemResponse[]): void {
const container = document.getElementById("queue-items");
if (!container) return;
if (items.length === 0) {
container.innerHTML =
'<p class="text-center p-4" style="color: var(--text-secondary)">Queue is empty</p>';
return;
}
container.innerHTML = items
.map(
(item) => `
<div class="p-3 rounded-lg border mb-2" style="background-color: var(--bg-secondary); border-color: var(--border)">
<div class="flex justify-between items-center">
<div>
<p class="font-medium" style="color: var(--text-primary)">${item.media_title || "Unknown"}</p>
<p class="text-sm" style="color: var(--text-secondary)">${item.status} - ${item.sync_type}</p>
<p class="text-xs" style="color: var(--text-secondary)">Attempts: ${item.attempts}/${item.max_attempts}</p>
</div>
<div class="flex space-x-2">
${item.status === "failed" ? `<button onclick="window.retryQueueItem('${item.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Retry</button>` : ""}
<button onclick="window.deleteQueueItem('${item.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Delete</button>
</div>
</div>
${item.error_message ? `<p class="text-xs mt-2" style="color: var(--accent)">${item.error_message}</p>` : ""}
</div>
`,
)
.join("");
}
function showQueueItemModal(itemId: string): void {
const modal = document.getElementById("queue-item-modal");
const detailsContainer = document.getElementById("queue-item-details");
if (modal && detailsContainer) {
// TODO: Fetch and display item details
detailsContainer.innerHTML = "<p>Loading item details...</p>";
modal.classList.remove("hidden");
}
}
function hideQueueItemModal(): void {
const modal = document.getElementById("queue-item-modal");
if (modal) {
modal.classList.add("hidden");
}
}
function filterQueue(status: string): void {
const filterValue = status || "all";
// TODO: Implement filtering logic
console.log("Filter queue by:", filterValue);
}
export {
clearAllItems,
clearFailedItems,
deleteQueueItem,
processPendingItems,
refreshQueue,
retryQueueItem,
showQueueItemModal,
hideQueueItemModal,
filterQueue,
};
Alpine.data("queue", () => ({
clearAllItems,
clearFailedItems,
deleteQueueItem,
processPendingItems,
refreshQueue,
retryQueueItem,
showQueueItemModal,
hideQueueItemModal,
filterQueue,
}));