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