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