import { Alpine } from "./alpine"; import { showToast } from "./toast"; async function refreshConflicts(): Promise { // Rendering ownership: the list and stats are server-rendered markup. // Swap in the templ partial (stats arrive as an out-of-band swap) // instead of re-rendering cards from JSON. try { await window.htmx.ajax("GET", "/conflicts/list", { target: "#conflicts-list", swap: "outerHTML", }); const list = document.getElementById("conflicts-list"); if (list) Alpine.initTree(list); filterConflicts(); updateBulkActions(); } catch (error) { console.error("Failed to refresh conflicts:", error); } } // --- Client-side filter and bulk selection ------------------------------- // Pure functions over the server-rendered conflict cards (rendering // ownership: JS decides what shows; templ rendered what they look like). function filterConflicts(): void { const select = document.getElementById( "status-filter", ) as HTMLSelectElement | null; const filter = select?.value ?? "unresolved"; let total = 0; let visible = 0; document.querySelectorAll(".conflict-item").forEach((item) => { total++; const status = item.dataset.status ?? "unresolved"; const show = filter === "all" || status === filter; item.style.display = show ? "" : "none"; if (show) visible++; }); const empty = document.getElementById("filter-empty"); if (empty) empty.classList.toggle("hidden", visible > 0 || total === 0); // Cards hidden by the filter can't stay selected. if (visible !== total) updateBulkActions(); } function toggleAllConflicts(): void { const selectAll = document.getElementById( "select-all-conflicts", ) as HTMLInputElement | null; if (!selectAll) return; document.querySelectorAll(".conflict-item").forEach((item) => { if (item.style.display === "none") return; const box = item.querySelector(".conflict-checkbox"); if (box) box.checked = selectAll.checked; }); updateBulkActions(); } function getSelectedConflictIds(): string[] { return Array.from( document.querySelectorAll(".conflict-checkbox:checked"), ) .map((box) => box.dataset.conflictId || "") .filter((id) => id !== ""); } function updateBulkActions(): void { const selected = getSelectedConflictIds(); const bar = document.getElementById("bulk-actions"); if (bar) bar.classList.toggle("hidden", selected.length === 0); const count = document.getElementById("selected-count"); if (count) count.textContent = String(selected.length); } function resolveSelected(): void { const ids = getSelectedConflictIds(); if (ids.length === 0) return; const strategyEl = document.getElementById( "bulk-strategy", ) as HTMLSelectElement | null; const strategy = (strategyEl?.value ?? "most_recent") as | "most_recent" | "highest_progress"; void bulkResolve(strategy, ids); } function dismissSelected(): void { const ids = getSelectedConflictIds(); if (ids.length === 0) return; void bulkDismiss(ids); } async function resolveConflict( conflictId: string, winner: string, manualData?: Record, ): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch(`/api/conflicts/${conflictId}/resolve`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ winner, manual_data: manualData }), }); if (response.ok) { showToast("Conflict resolved", "success"); refreshConflicts(); } else { const error = await response.json(); showToast(error.error || "Failed to resolve conflict", "error"); } } catch (error) { console.error("Failed to resolve conflict:", error); showToast("Failed to resolve conflict", "error"); } } async function bulkResolve( strategy: "most_recent" | "highest_progress", conflictIds: string[], ): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/conflicts/bulk-resolve", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ conflict_ids: conflictIds, strategy }), }); if (response.ok) { const data: BulkResolveResponse = await response.json(); showToast(`Resolved ${data.success} conflicts`, "success"); refreshConflicts(); } } catch (error) { console.error("Failed to bulk resolve:", error); showToast("Failed to bulk resolve conflicts", "error"); } } async function bulkDismiss(conflictIds: string[]): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/conflicts/bulk-dismiss", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ conflict_ids: conflictIds }), }); if (response.ok) { showToast("Conflicts dismissed", "success"); refreshConflicts(); } } catch (error) { console.error("Failed to dismiss conflicts:", error); showToast("Failed to dismiss conflicts", "error"); } } async function dismissAllResolved(): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/conflicts/dismiss-resolved", { method: "POST", headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { showToast("Resolved conflicts dismissed", "success"); refreshConflicts(); } } catch (error) { console.error("Failed to dismiss resolved:", error); showToast("Failed to dismiss resolved conflicts", "error"); } } function showResolveModal(conflictId: string): void { const modal = document.getElementById("resolve-modal"); const conflictIdInput = document.getElementById( "resolve-conflict-id", ) as HTMLInputElement; if (modal && conflictIdInput) { conflictIdInput.value = conflictId; modal.classList.remove("hidden"); } } function hideResolveModal(): void { const modal = document.getElementById("resolve-modal"); if (modal) { modal.classList.add("hidden"); } } function handleResolveSubmit(event: Event): void { event.preventDefault(); const form = event.target as HTMLFormElement; const conflictId = ( form.querySelector("#resolve-conflict-id") as HTMLInputElement )?.value; const winner = ( form.querySelector('input[name="winner"]:checked') as HTMLInputElement )?.value; if (!conflictId || !winner) { showToast("Please select a winner", "error"); return; } resolveConflict(conflictId, winner); hideResolveModal(); } export { bulkDismiss, bulkResolve, dismissAllResolved, handleResolveSubmit, hideResolveModal, refreshConflicts, resolveConflict, showResolveModal, filterConflicts, toggleAllConflicts, updateBulkActions, resolveSelected, dismissSelected, }; Alpine.data("conflicts", () => ({ init() { filterConflicts(); }, bulkDismiss, bulkResolve, dismissAllResolved, handleResolveSubmit, hideResolveModal, refreshConflicts, resolveConflict, showResolveModal, filterConflicts, toggleAllConflicts, updateBulkActions, resolveSelected, dismissSelected, }));