| <!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">
|
|
|
| <div class="text-center py-5">
|
| <div class="spinner"></div>
|
| <p class="mt-2">Loading applications...</p>
|
| </div>
|
| </div>
|
| </main>
|
|
|
|
|
| <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>
|
|
|