馃 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();