···
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin - Thistle</title>
<link rel="apple-touch-icon" sizes="180x180" href="../../public/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="../../public/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="../../public/favicon/favicon-16x16.png">
<link rel="manifest" href="../../public/favicon/site.webmanifest">
<link rel="stylesheet" href="../styles/main.css">
-
border-bottom: 2px solid var(--secondary);
-
padding: 0.75rem 1.5rem;
-
background: transparent;
-
border-bottom: 2px solid transparent;
-
border-bottom-color: var(--primary);
-
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
-
background: var(--background);
-
border: 2px solid var(--secondary);
-
margin-bottom: 0.25rem;
···
-
<div id="error-message" class="error" style="display: none;"></div>
<div id="loading" class="loading">Loading...</div>
-
<div id="content" style="display: none;">
<div class="stat-value" id="total-users">0</div>
···
<script type="module" src="../components/admin-classes.ts"></script>
<script type="module" src="../components/user-modal.ts"></script>
<script type="module" src="../components/transcript-view-modal.ts"></script>
-
const transcriptionsComponent = document.getElementById('transcriptions-component');
-
const usersComponent = document.getElementById('users-component');
-
const userModal = document.getElementById('user-modal');
-
const transcriptModal = document.getElementById('transcript-modal');
-
const errorMessage = document.getElementById('error-message');
-
const loading = document.getElementById('loading');
-
const content = document.getElementById('content');
-
function openUserModal(userId) {
-
userModal.setAttribute('open', '');
-
userModal.userId = userId;
-
function closeUserModal() {
-
userModal.removeAttribute('open');
-
userModal.userId = null;
-
function openTranscriptModal(transcriptId) {
-
transcriptModal.setAttribute('open', '');
-
transcriptModal.transcriptId = transcriptId;
-
function closeTranscriptModal() {
-
transcriptModal.removeAttribute('open');
-
transcriptModal.transcriptId = null;
-
// Listen for component events
-
transcriptionsComponent.addEventListener('open-transcription', (e) => {
-
openTranscriptModal(e.detail.id);
-
usersComponent.addEventListener('open-user', (e) => {
-
openUserModal(e.detail.id);
-
// Listen for modal close events
-
userModal.addEventListener('close', closeUserModal);
-
userModal.addEventListener('user-updated', async () => {
-
userModal.addEventListener('click', (e) => {
-
if (e.target === userModal) closeUserModal();
-
transcriptModal.addEventListener('close', closeTranscriptModal);
-
transcriptModal.addEventListener('transcript-deleted', async () => {
-
transcriptModal.addEventListener('click', (e) => {
-
if (e.target === transcriptModal) closeTranscriptModal();
-
async function loadStats() {
-
const [transcriptionsRes, usersRes] = await Promise.all([
-
fetch('/api/admin/transcriptions'),
-
fetch('/api/admin/users')
-
if (!transcriptionsRes.ok || !usersRes.ok) {
-
if (transcriptionsRes.status === 403 || usersRes.status === 403) {
-
window.location.href = '/';
-
throw new Error('Failed to load admin data');
-
const transcriptions = await transcriptionsRes.json();
-
const users = await usersRes.json();
-
document.getElementById('total-users').textContent = users.length;
-
document.getElementById('total-transcriptions').textContent = transcriptions.length;
-
const failed = transcriptions.filter(t => t.status === 'failed');
-
document.getElementById('failed-transcriptions').textContent = failed.length;
-
loading.style.display = 'none';
-
content.style.display = 'block';
-
errorMessage.textContent = error.message;
-
errorMessage.style.display = 'block';
-
loading.style.display = 'none';
-
function switchTab(tabName) {
-
document.querySelectorAll('.tab').forEach(t => {
-
t.classList.remove('active');
-
document.querySelectorAll('.tab-content').forEach(c => {
-
c.classList.remove('active');
-
const tabButton = document.querySelector(`[data-tab="${tabName}"]`);
-
const tabContent = document.getElementById(`${tabName}-tab`);
-
if (tabButton && tabContent) {
-
tabButton.classList.add('active');
-
tabContent.classList.add('active');
-
// Update URL without reloading
-
const url = new URL(window.location.href);
-
url.searchParams.set('tab', tabName);
-
// Remove subtab param when leaving classes tab
-
if (tabName !== 'classes') {
-
url.searchParams.delete('subtab');
-
window.history.pushState({}, '', url);
-
document.querySelectorAll('.tab').forEach(tab => {
-
tab.addEventListener('click', () => {
-
switchTab(tab.dataset.tab);
-
// Check for tab query parameter on load
-
const params = new URLSearchParams(window.location.search);
-
const initialTab = params.get('tab');
-
const validTabs = ['pending', 'transcriptions', 'users', 'classes'];
-
if (initialTab && validTabs.includes(initialTab)) {