document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('login-form'); const submitBtn = document.getElementById('submit-btn'); const btnText = submitBtn.querySelector('.btn-text'); const loader = submitBtn.querySelector('.loader'); const loginView = document.getElementById('login-view-container'); const dashboardView = document.getElementById('dashboard-view'); const errorMessage = document.getElementById('error-message'); const errorText = document.getElementById('error-text'); const logoutBtn = document.getElementById('logout-btn'); // Pages const homePage = document.getElementById('home-page'); const searchPage = document.getElementById('search-page'); const tabHome = document.getElementById('tab-home'); const tabSearch = document.getElementById('tab-search'); // Session state let currentSessionId = null; let currentUsername = null; // ========== TAB SWITCHING ========== document.getElementById('nav-tabs').addEventListener('click', (e) => { const tab = e.target.closest('.nav-tab'); if (!tab) return; const targetTab = tab.dataset.tab; // Update active tab styles document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Show/hide pages if (targetTab === 'home') { homePage.classList.remove('hidden'); searchPage.classList.add('hidden'); } else if (targetTab === 'search') { homePage.classList.add('hidden'); searchPage.classList.remove('hidden'); } }); // ========== LOGIN ========== form.addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value; if (!username || !password) return; setLoading(true); errorMessage.classList.add('hidden'); try { const formData = new FormData(); formData.append('username', username); formData.append('password', password); const response = await fetch('/api/login', { method: 'POST', body: formData }); const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(data.detail || `Server error: ${response.status}`); } if (data.success && data['ASP.NET_SessionId']) { currentSessionId = data['ASP.NET_SessionId']; currentUsername = username; loginView.classList.add('hidden'); dashboardView.classList.remove('hidden'); // Start fetch marks process fetchMarks(username, currentSessionId); } else { throw new Error("Login succeeded but no session cookie was returned."); } } catch (error) { console.error("Login error:", error); errorText.textContent = error.message || "Failed to login. Please try again."; errorMessage.classList.remove('hidden'); const newError = errorMessage.cloneNode(true); errorMessage.parentNode.replaceChild(newError, errorMessage); } finally { setLoading(false); } }); // ========== LOGOUT ========== logoutBtn.addEventListener('click', () => { dashboardView.classList.add('hidden'); loginView.classList.remove('hidden'); form.reset(); currentSessionId = null; currentUsername = null; // Reset to home tab tabHome.click(); // Clear search results document.getElementById('search-cards-container').innerHTML = ''; document.getElementById('search-cards-container').classList.add('hidden'); document.getElementById('username').focus(); }); // ========== FETCH MARKS (HOME) ========== async function fetchMarks(username, sessionId) { const loading = document.getElementById('dashboard-loading'); const container = document.getElementById('cards-container'); const errorMsg = document.getElementById('dashboard-error'); const userDisplay = document.getElementById('user-display'); const circle = document.querySelector('.progress-ring__circle:not(.search-ring-circle)'); const percentageText = document.getElementById('progress-percentage'); const progressStatus = document.getElementById('progress-text'); const radius = circle.r.baseVal.value; const circumference = radius * 2 * Math.PI; circle.style.strokeDasharray = `${circumference} ${circumference}`; circle.style.strokeDashoffset = circumference; userDisplay.textContent = `${username}`; loading.classList.remove('hidden'); container.classList.add('hidden'); errorMsg.classList.add('hidden'); function setProgress(percent) { const offset = circumference - percent / 100 * circumference; circle.style.strokeDashoffset = offset; percentageText.textContent = Math.round(percent); } setProgress(0); progressStatus.textContent = 'Connecting to SIMATS...'; let progress = 0; const progressInterval = setInterval(() => { const remaining = 99 - progress; progress += (Math.random() * 0.1 + 0.05) * remaining; setProgress(progress); if (progress > 30 && progress < 60) { progressStatus.textContent = 'Fetching course enrollments...'; } else if (progress >= 60 && progress < 85) { progressStatus.textContent = 'Aggregating detailed mark splits...'; } else if (progress >= 85) { progressStatus.textContent = 'Finalizing data format...'; } }, 500); try { const formData = new FormData(); formData.append('username', username); formData.append('session_id', sessionId); const response = await fetch('/api/fetch-marks', { method: 'POST', body: formData }); const data = await response.json().catch(() => ({})); clearInterval(progressInterval); if (!response.ok) throw new Error(data.detail || 'Failed to fetch marks'); setProgress(100); progressStatus.textContent = 'Data loaded successfully!'; setTimeout(() => { try { loading.classList.add('hidden'); renderCards(data.data, container); container.classList.remove('hidden'); } catch (renderError) { console.error("Rendering error:", renderError); loading.classList.add('hidden'); errorMsg.textContent = "An error occurred while displaying your marks. " + renderError.message; errorMsg.classList.remove('hidden'); } }, 800); } catch (e) { clearInterval(progressInterval); loading.classList.add('hidden'); errorMsg.textContent = e.message; errorMsg.classList.remove('hidden'); container.innerHTML = ''; } } // ========== SEARCH STUDENT ========== const searchForm = document.getElementById('search-form'); const searchSubmitBtn = document.getElementById('search-submit-btn'); searchForm.addEventListener('submit', async (e) => { e.preventDefault(); const registerNo = document.getElementById('search-register').value.trim(); if (!registerNo) return; if (!currentSessionId) { alert('Session expired. Please log in again.'); return; } const searchBtnText = searchSubmitBtn.querySelector('.btn-text'); const searchBtnLoader = searchSubmitBtn.querySelector('.loader'); const searchError = document.getElementById('search-error'); const searchLoading = document.getElementById('search-loading'); const searchContainer = document.getElementById('search-cards-container'); // Setup search loading ring const searchCircle = document.querySelector('.search-ring-circle'); const searchPercentText = document.getElementById('search-progress-percentage'); const searchProgressStatus = document.getElementById('search-progress-text'); const radius = searchCircle.r.baseVal.value; const circumference = radius * 2 * Math.PI; searchCircle.style.strokeDasharray = `${circumference} ${circumference}`; searchCircle.style.strokeDashoffset = circumference; function setSearchProgress(percent) { const offset = circumference - percent / 100 * circumference; searchCircle.style.strokeDashoffset = offset; searchPercentText.textContent = Math.round(percent); } // UI state searchSubmitBtn.disabled = true; searchBtnText.classList.add('hidden'); searchBtnLoader.classList.remove('hidden'); searchError.classList.add('hidden'); searchContainer.classList.add('hidden'); searchContainer.innerHTML = ''; searchLoading.classList.remove('hidden'); setSearchProgress(0); searchProgressStatus.textContent = 'Searching student records...'; let progress = 0; const progressInterval = setInterval(() => { const remaining = 95 - progress; // Faster increment for a snappier UI progress += (Math.random() * 0.12 + 0.05) * remaining; setSearchProgress(progress); if (progress > 20 && progress < 50) { searchProgressStatus.textContent = 'Scanning course databases...'; } else if (progress >= 50 && progress < 80) { searchProgressStatus.textContent = 'Matching register number...'; } else if (progress >= 80) { searchProgressStatus.textContent = 'Compiling results...'; } }, 300); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 75000); // 75s timeout try { const formData = new FormData(); formData.append('register_no', registerNo); formData.append('session_id', currentSessionId); const response = await fetch('/api/search-student', { method: 'POST', body: formData, signal: controller.signal }); clearTimeout(timeoutId); const data = await response.json().catch(() => ({})); clearInterval(progressInterval); if (!response.ok) throw new Error(data.detail || 'Search failed'); setSearchProgress(100); searchProgressStatus.textContent = 'Search complete!'; setTimeout(() => { searchLoading.classList.add('hidden'); if (!data.data || data.data.length === 0) { searchError.innerHTML = ` No results found for register number: ${registerNo}`; searchError.classList.remove('hidden'); return; } // Add student badge before results const badge = document.createElement('div'); badge.style.textAlign = 'center'; badge.innerHTML = `