馃 distributed transcription service thistle.dunkirk.sh
1const transcriptionsComponent = document.getElementById( 2 "transcriptions-component", 3) as HTMLElement | null; 4const usersComponent = document.getElementById( 5 "users-component", 6) as HTMLElement | null; 7const userModal = document.getElementById("user-modal") as HTMLElement | null; 8const transcriptModal = document.getElementById( 9 "transcript-modal", 10) as HTMLElement | null; 11const errorMessage = document.getElementById("error-message") as HTMLElement; 12const loading = document.getElementById("loading") as HTMLElement; 13const content = document.getElementById("content") as HTMLElement; 14 15// Modal functions 16function openUserModal(userId: string) { 17 userModal.setAttribute("open", ""); 18 userModal.userId = userId; 19} 20 21function closeUserModal() { 22 userModal.removeAttribute("open"); 23 userModal.userId = null; 24} 25 26function openTranscriptModal(transcriptId: string) { 27 transcriptModal.setAttribute("open", ""); 28 transcriptModal.transcriptId = transcriptId; 29} 30 31function closeTranscriptModal() { 32 transcriptModal.removeAttribute("open"); 33 transcriptModal.transcriptId = null; 34} 35 36// Listen for component events 37transcriptionsComponent?.addEventListener( 38 "open-transcription", 39 (e: CustomEvent) => { 40 openTranscriptModal(e.detail.id); 41 }, 42); 43 44usersComponent?.addEventListener("open-user", (e: CustomEvent) => { 45 openUserModal(e.detail.id); 46}); 47 48// Listen for modal close events 49userModal?.addEventListener("close", closeUserModal); 50userModal?.addEventListener("user-updated", async () => { 51 await loadStats(); 52}); 53userModal?.addEventListener("click", (e: MouseEvent) => { 54 if (e.target === userModal) closeUserModal(); 55}); 56 57transcriptModal?.addEventListener("close", closeTranscriptModal); 58transcriptModal?.addEventListener("transcript-deleted", async () => { 59 await loadStats(); 60}); 61transcriptModal?.addEventListener("click", (e: MouseEvent) => { 62 if (e.target === transcriptModal) closeTranscriptModal(); 63}); 64 65async function loadStats() { 66 try { 67 const [transcriptionsRes, usersRes] = await Promise.all([ 68 fetch("/api/admin/transcriptions"), 69 fetch("/api/admin/users"), 70 ]); 71 72 if (!transcriptionsRes.ok || !usersRes.ok) { 73 if (transcriptionsRes.status === 403 || usersRes.status === 403) { 74 window.location.href = "/"; 75 return; 76 } 77 throw new Error("Failed to load admin data"); 78 } 79 80 const transcriptions = await transcriptionsRes.json(); 81 const users = await usersRes.json(); 82 83 const totalUsers = document.getElementById("total-users"); 84 const totalTranscriptions = document.getElementById("total-transcriptions"); 85 const failedTranscriptions = document.getElementById( 86 "failed-transcriptions", 87 ); 88 89 if (totalUsers) totalUsers.textContent = users.length.toString(); 90 if (totalTranscriptions) 91 totalTranscriptions.textContent = transcriptions.length.toString(); 92 93 const failed = transcriptions.filter( 94 (t: { status: string }) => t.status === "failed", 95 ); 96 if (failedTranscriptions) 97 failedTranscriptions.textContent = failed.length.toString(); 98 99 loading.classList.add("hidden"); 100 content.classList.remove("hidden"); 101 } catch (error) { 102 errorMessage.textContent = (error as Error).message; 103 errorMessage.classList.remove("hidden"); 104 loading.classList.add("hidden"); 105 } 106} 107 108// Tab switching 109function switchTab(tabName: string) { 110 document.querySelectorAll(".tab").forEach((t) => { 111 t.classList.remove("active"); 112 }); 113 document.querySelectorAll(".tab-content").forEach((c) => { 114 c.classList.remove("active"); 115 }); 116 117 const tabButton = document.querySelector(`[data-tab="${tabName}"]`); 118 const tabContent = document.getElementById(`${tabName}-tab`); 119 120 if (tabButton && tabContent) { 121 tabButton.classList.add("active"); 122 tabContent.classList.add("active"); 123 124 // Update URL without reloading 125 const url = new URL(window.location.href); 126 url.searchParams.set("tab", tabName); 127 // Remove subtab param when leaving classes tab 128 if (tabName !== "classes") { 129 url.searchParams.delete("subtab"); 130 } 131 window.history.pushState({}, "", url); 132 } 133} 134 135document.querySelectorAll(".tab").forEach((tab) => { 136 tab.addEventListener("click", () => { 137 switchTab((tab as HTMLElement).dataset.tab || ""); 138 }); 139}); 140 141// Check for tab query parameter on load 142const params = new URLSearchParams(window.location.search); 143const initialTab = params.get("tab"); 144const validTabs = ["pending", "transcriptions", "users", "classes"]; 145 146if (initialTab && validTabs.includes(initialTab)) { 147 switchTab(initialTab); 148} 149 150// Initialize 151loadStats();