Empathy-Label / static /admin.js
cuongpm-cs's picture
Initial deploy
a73045d
Raw
History Blame Contribute Delete
7.57 kB
// ===== 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 = '<div class="empty-state"><p>No CSV files found in the data folder.</p></div>';
return;
}
grid.innerHTML = allFiles.map(f => {
return `
<div class="file-card" style="cursor:default">
<div class="file-card-header">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--accent);flex-shrink:0">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</svg>
<span class="file-card-name">${f.filename}</span>
</div>
<div class="file-card-stats">
<span>${f.total_samples} samples</span>
<span>${f.total_assignees} assignee(s)</span>
</div>
<div class="file-card-stats" style="margin-top:4px">
<span>${f.total_reviews} total reviews</span>
</div>
</div>`;
}).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 `<tr>
<td>${u.id}</td>
<td style="font-weight:600;color:var(--text-primary)">${u.username}</td>
<td><span class="role-badge ${roleClass}">${u.role}</span></td>
<td>${created}</td>
<td>${u.role !== 'admin' ? `<button class="danger-btn" onclick="deleteUser(${u.id})">Delete</button>` : 'β€”'}</td>
</tr>`;
}).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 = '<tr><td colspan="5" style="text-align:center;color:var(--text-muted)">No assignments yet</td></tr>';
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 `<tr>
<td style="font-weight:600;color:var(--text-primary)">${a.username}</td>
<td>${a.filename}</td>
<td>
<div class="progress-cell">
<div class="progress-bar"><div class="progress-fill" style="width:${pct}%;background:${barColor}"></div></div>
<span class="progress-text" style="color:${barColor}">${a.reviewed_count}/${a.total_samples} (${pct}%)</span>
</div>
</td>
<td>${date}</td>
<td>
<button class="danger-btn" onclick="deleteAssignment(${a.id})">Remove</button>
</td>
</tr>`;
}).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 = '<option value="">Select User...</option>' +
users.filter(u => u.role === 'annotator').map(u => `<option value="${u.id}">${u.username}</option>`).join('');
fileSel.innerHTML = '<option value="">Select File...</option>' +
files.map(f => `<option value="${f.id}">${f.filename} (${f.total_samples} samples)</option>`).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();