// ===== 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.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();