Attender / admin.html
chualinwei3's picture
Upload 2 files
5710e63 verified
Raw
History Blame Contribute Delete
7.96 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - Attendr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@700;800&display=swap"
rel="stylesheet">
<style>
.admin-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
.user-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
margin-bottom: 1rem;
transition: transform 0.2s;
}
.user-card:hover {
transform: translateY(-2px);
}
.user-info h3 {
margin: 0;
font-size: 1.25rem;
}
.user-meta {
font-size: 0.875rem;
color: var(--color-text-muted);
margin-top: 0.25rem;
}
.action-btns {
display: flex;
gap: 0.5rem;
}
.tab-container {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--color-border);
padding-bottom: 0.5rem;
}
.tab {
padding: 0.5rem 1rem;
cursor: pointer;
border-radius: 8px;
font-weight: 500;
}
.tab.active {
background: var(--color-primary);
color: white;
}
.badge {
padding: 0.25rem 0.6rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.badge-student { background: #e0f2fe; color: #0369a1; }
.badge-lecturer { background: #fef3c7; color: #92400e; }
.badge-pending { background: #f3f4f6; color: #374151; }
.badge-approved { background: #dcfce7; color: #166534; }
.badge-rejected { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
{% include '_navbar.html' %}
<main class="container section">
<div class="header-section mb-3">
<h1>Admin Dashboard</h1>
<p class="text-muted">Manage user registration applications and verify information.</p>
</div>
<div class="tab-container">
<div class="tab active" onclick="loadUsers('pending', this)">Pending</div>
<div class="tab" onclick="loadUsers('approved', this)">Approved</div>
<div class="tab" onclick="loadUsers('rejected', this)">Rejected</div>
<div class="tab" onclick="loadUsers('all', this)">All Users</div>
</div>
<div id="usersList" class="admin-grid">
<!-- Users will be loaded here -->
<div class="text-center py-5">
<div class="spinner"></div>
<p class="mt-2">Loading applications...</p>
</div>
</div>
</main>
<!-- Loading Overlay -->
<div id="loadingOverlay" class="loading-overlay hidden">
<div class="text-center">
<div class="spinner"></div>
<p class="mt-2" id="loadingText">Processing...</p>
</div>
</div>
<script>
let currentStatus = 'pending';
document.addEventListener('DOMContentLoaded', () => {
loadUsers('pending');
});
async function loadUsers(status, el) {
if (el) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
el.classList.add('active');
}
currentStatus = status;
const listDiv = document.getElementById('usersList');
listDiv.innerHTML = '<div class="text-center py-5"><div class="spinner"></div><p class="mt-2">Loading...</p></div>';
try {
const response = await fetch(`/api/admin/users?status=${status}`);
const data = await response.json();
if (data.success) {
renderUsers(data.data);
} else {
listDiv.innerHTML = `<div class="alert alert-error">${data.error}</div>`;
}
} catch (error) {
listDiv.innerHTML = '<div class="alert alert-error">Failed to load users.</div>';
}
}
function renderUsers(users) {
const listDiv = document.getElementById('usersList');
if (users.length === 0) {
listDiv.innerHTML = `<div class="glass-card text-center py-5"><p class="text-muted">No ${currentStatus} applications found.</p></div>`;
return;
}
listDiv.innerHTML = users.map(user => `
<div class="glass-card user-card">
<div class="user-info">
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;">
<h3>${user.name}</h3>
<span class="badge badge-${user.role}">${user.role}</span>
<span class="badge badge-${user.status}">${user.status}</span>
</div>
<div class="user-meta">
<strong>Matric/ID:</strong> ${user.matric_no} |
<strong>Email:</strong> ${user.email || 'N/A'}<br>
<strong>Applied on:</strong> ${new Date(user.created_at).toLocaleDateString()} ${new Date(user.created_at).toLocaleTimeString()}
</div>
</div>
<div class="action-btns">
${user.status === 'pending' || user.status === 'rejected' ? `
<button class="btn btn-primary btn-sm" onclick="handleAction(${user.id}, 'approve')">✅ Approve</button>
` : ''}
${user.status === 'pending' || user.status === 'approved' ? `
<button class="btn btn-error btn-sm" style="background: #ef4444; color: white;" onclick="handleAction(${user.id}, 'reject')">❌ Reject</button>
` : ''}
</div>
</div>
`).join('');
}
async function handleAction(userId, action) {
if (!confirm(`Are you sure you want to ${action} this user?`)) return;
showLoading(`${action === 'approve' ? 'Approving' : 'Rejecting'} user...`);
try {
const response = await fetch(`/api/admin/users/${userId}/${action}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
hideLoading();
if (data.success) {
alert(data.message);
loadUsers(currentStatus);
} else {
alert(data.error);
}
} catch (error) {
hideLoading();
alert('Operation failed.');
}
}
function showLoading(text) {
document.getElementById('loadingText').textContent = text;
document.getElementById('loadingOverlay').classList.remove('hidden');
}
function hideLoading() {
document.getElementById('loadingOverlay').classList.add('hidden');
}
</script>
</body>
</html>