// ===== Admin Dashboard JS ===== // ── Tab switching ── document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); btn.classList.add('active'); document.getElementById('tab-' + btn.dataset.tab).classList.add('active'); }); }); // ── Logout ── document.getElementById('logoutBtn').addEventListener('click', async () => { await fetch('/api/logout', { method: 'POST' }); window.location.href = '/login'; }); // ── User info ── fetch('/api/me').then(r => r.json()).then(u => { document.getElementById('userBadge').textContent = u.username; }); // ── State ── let allUsers = []; let allFiles = []; // ── Files ── async function loadFiles() { const res = await fetch('/api/files'); allFiles = await res.json(); const grid = document.getElementById('filesList'); if (allFiles.length === 0) { grid.innerHTML = '

No CSV files found in the data folder.

'; return; } grid.innerHTML = allFiles.map(f => { return `
${f.filename}
${f.total_samples} samples ${f.total_assignees} assignee(s)
${f.total_reviews} total reviews
`; }).join(''); } // ── Users ── async function loadUsers() { const res = await fetch('/api/users'); allUsers = await res.json(); const tbody = document.getElementById('usersBody'); tbody.innerHTML = allUsers.map(u => { const roleClass = u.role === 'admin' ? 'role-admin' : 'role-annotator'; const created = u.created_at ? new Date(u.created_at).toLocaleDateString() : '—'; return ` ${u.id} ${u.username} ${u.role} ${created} ${u.role !== 'admin' ? `` : '—'} `; }).join(''); } // ── Create User ── document.getElementById('addUserBtn').addEventListener('click', () => { document.getElementById('createUserForm').style.display = 'block'; document.getElementById('newUsername').focus(); }); document.getElementById('cancelUserBtn').addEventListener('click', () => { document.getElementById('createUserForm').style.display = 'none'; document.getElementById('userFormError').textContent = ''; }); document.getElementById('submitUserBtn').addEventListener('click', async () => { const username = document.getElementById('newUsername').value.trim(); const password = document.getElementById('newPassword').value; const role = document.getElementById('newRole').value; const errEl = document.getElementById('userFormError'); errEl.textContent = ''; const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, role }) }); const data = await res.json(); if (!res.ok) { errEl.textContent = data.error; return; } document.getElementById('createUserForm').style.display = 'none'; document.getElementById('newUsername').value = ''; document.getElementById('newPassword').value = ''; loadUsers(); loadAssignDropdowns(); }); async function deleteUser(id) { if (!confirm('Delete this user?')) return; await fetch(`/api/users/${id}`, { method: 'DELETE' }); loadUsers(); loadAssignments(); } // ── Assignments ── async function loadAssignments() { const res = await fetch('/api/assignments'); const data = await res.json(); const tbody = document.getElementById('assignBody'); if (data.length === 0) { tbody.innerHTML = 'No assignments yet'; return; } tbody.innerHTML = data.map(a => { const pct = a.progress; const barColor = pct >= 100 ? '#34d399' : pct > 50 ? '#fbbf24' : '#60a5fa'; const date = a.assigned_at ? new Date(a.assigned_at).toLocaleDateString() : '—'; return ` ${a.username} ${a.filename}
${a.reviewed_count}/${a.total_samples} (${pct}%)
${date} `; }).join(''); } async function loadAssignDropdowns() { // Populate user dropdown const userSel = document.getElementById('assignUser'); const fileSel = document.getElementById('assignFile'); const usersRes = await fetch('/api/users'); const users = await usersRes.json(); const filesRes = await fetch('/api/files'); const files = await filesRes.json(); userSel.innerHTML = '' + users.filter(u => u.role === 'annotator').map(u => ``).join(''); fileSel.innerHTML = '' + files.map(f => ``).join(''); } document.getElementById('addAssignBtn').addEventListener('click', () => { document.getElementById('createAssignForm').style.display = 'block'; loadAssignDropdowns(); }); document.getElementById('cancelAssignBtn').addEventListener('click', () => { document.getElementById('createAssignForm').style.display = 'none'; document.getElementById('assignFormError').textContent = ''; }); document.getElementById('submitAssignBtn').addEventListener('click', async () => { const user_id = document.getElementById('assignUser').value; const file_id = document.getElementById('assignFile').value; const errEl = document.getElementById('assignFormError'); errEl.textContent = ''; if (!user_id || !file_id) { errEl.textContent = 'Select both user and file'; return; } const res = await fetch('/api/assignments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: parseInt(user_id), file_id: parseInt(file_id) }) }); const data = await res.json(); if (!res.ok) { errEl.textContent = data.error; return; } document.getElementById('createAssignForm').style.display = 'none'; loadAssignments(); loadFiles(); }); async function deleteAssignment(id) { if (!confirm('Remove this assignment?')) return; await fetch(`/api/assignments/${id}`, { method: 'DELETE' }); loadAssignments(); loadFiles(); } // ── Init ── loadFiles(); loadUsers(); loadAssignments();