Spaces:
Runtime error
Runtime error
Download static/script.js from sidhesh01/ARMS-Result: direct link, hf CLI and curl.
- Browser
- Download file 20 kB
-
https://huggingface.co/spaces/sidhesh01/ARMS-Result/resolve/main/static/script.js
- Command line
-
hf download hf://spaces/sidhesh01/ARMS-Result/static/script.js
-
curl -L -o script.js https://huggingface.co/spaces/sidhesh01/ARMS-Result/resolve/main/static/script.js
20 kB
| 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'); | |
| 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']) { | |
| // Hide login, show dashboard | |
| loginView.classList.add('hidden'); | |
| dashboardView.classList.remove('hidden'); | |
| // Start fetch marks process | |
| fetchMarks(username, data['ASP.NET_SessionId']); | |
| } 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'); | |
| // Trigger shake animation again by cloning and replacing | |
| const newError = errorMessage.cloneNode(true); | |
| errorMessage.parentNode.replaceChild(newError, errorMessage); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }); | |
| logoutBtn.addEventListener('click', () => { | |
| dashboardView.classList.add('hidden'); | |
| loginView.classList.remove('hidden'); | |
| form.reset(); | |
| document.getElementById('username').focus(); | |
| }); | |
| 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'); | |
| 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; // Add 5-15% of remaining distance | |
| 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(() => { | |
| loading.classList.add('hidden'); | |
| renderCards(data.data); | |
| container.classList.remove('hidden'); | |
| }, 800); | |
| } catch (e) { | |
| clearInterval(progressInterval); | |
| loading.classList.add('hidden'); | |
| errorMsg.textContent = e.message; | |
| errorMsg.classList.remove('hidden'); | |
| // Render basic fallback if UI needs resetting | |
| container.innerHTML = ''; | |
| } | |
| } | |
| function renderCards(data) { | |
| const container = document.getElementById('cards-container'); | |
| container.innerHTML = ''; | |
| if (!data || data.length === 0) { | |
| container.innerHTML = '<div style="color:var(--text-muted); text-align:center; width: 100%; grid-column: 1 / -1; padding: 40px;">No course enrollments found for this period.</div>'; | |
| return; | |
| } | |
| let totalGradePoints = 0; | |
| let totalCredits = 0; | |
| let validCourses = 0; | |
| const gradeValues = { 'S': 10, 'A': 9, 'B': 8, 'C': 7, 'D': 6, 'E': 5 }; | |
| let gradeCounts = { 'S': 0, 'A': 0, 'B': 0, 'C': 0, 'D': 0, 'E': 0 }; | |
| data.forEach((item, index) => { | |
| const card = document.createElement('div'); | |
| card.className = 'course-card'; | |
| card.style.opacity = '0'; | |
| card.animate([ | |
| { opacity: 0, transform: 'translateY(30px) scale(0.95)' }, | |
| { opacity: 1, transform: 'translateY(0) scale(1)' } | |
| ], { | |
| duration: 500, | |
| easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', | |
| fill: 'forwards', | |
| delay: index * 100 | |
| }); | |
| const c = item.course; | |
| const marks = item.marks; | |
| const error = item.error; | |
| const grade = c.FinalGrade ? c.FinalGrade.trim().toUpperCase() : ''; | |
| if (gradeValues[grade] !== undefined) { | |
| const credit = parseFloat(c.Credit || c.Credits || 1); | |
| totalGradePoints += gradeValues[grade] * credit; | |
| totalCredits += credit; | |
| validCourses++; | |
| if (gradeCounts[grade] !== undefined) { | |
| gradeCounts[grade]++; | |
| } | |
| } | |
| let marksHtml = ''; | |
| let totalObtained = 0; | |
| if (error) { | |
| marksHtml = `<div class="card-error">${error}</div>`; | |
| } else if (marks && marks.length > 0) { | |
| const targetCategories = ["Class Test (IA)", "Research", "Class Practical", "University Theory", "University Practical"]; | |
| marks.forEach(m => { | |
| const category = m.RubricCategory ? m.RubricCategory.trim() : ""; | |
| if (targetCategories.includes(category)) { | |
| const val = parseFloat(m.OrginalConvertedMark); | |
| if (!isNaN(val)) { | |
| totalObtained += val; | |
| } | |
| } | |
| }); | |
| totalObtained = Math.round(totalObtained * 100) / 100; | |
| const list = marks.map(m => { | |
| const isPass = m.IsPassed; | |
| const statusClass = isPass ? 'mark-pass' : 'mark-fail'; | |
| const statusText = isPass ? 'PASS' : 'FAIL'; | |
| return ` | |
| <li class="mark-item"> | |
| <div class="mark-info"> | |
| <span class="mark-name">${m.RubricCategory}</span> | |
| <span class="mark-type">${m.Type}</span> | |
| </div> | |
| <div class="mark-score-container"> | |
| <span class="mark-value">${m.OrginalConvertedMark} <span style="opacity:0.4;font-size:12px;">/ ${m.RubricsMaxMark}</span></span> | |
| <div><span class="mark-status ${statusClass}">${statusText}</span></div> | |
| </div> | |
| </li> | |
| `; | |
| }).join(''); | |
| marksHtml = `<ul class="mark-list">${list}</ul>`; | |
| } else { | |
| marksHtml = `<div style="padding:40px 20px; color:var(--text-muted); text-align:center;">No marks available for this course</div>`; | |
| } | |
| card.innerHTML = ` | |
| <div class="card-header"> | |
| <span class="course-code">${c.CourseCode || '-'}</span> | |
| <h3>${c.CourseName || 'Unknown Course'}</h3> | |
| <div class="card-meta"> | |
| <span class="month-badge">${c.MonthYearValue || '-'}</span> | |
| <div style="display: flex; gap: 8px; align-items: center;"> | |
| ${marks && !error ? `<span class="grade-badge" style="background: rgba(212,175,55,0.15); border-color: rgba(212,175,55,0.3); color: #fff;">Total: ${totalObtained}/500</span>` : ''} | |
| <span class="grade-badge">Grade: ${c.FinalGrade || 'N/A'}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-body"> | |
| ${marksHtml} | |
| </div> | |
| `; | |
| container.appendChild(card); | |
| }); | |
| if (validCourses > 0 && totalCredits > 0) { | |
| const cgpa = (totalGradePoints / totalCredits).toFixed(2); | |
| const cgpaCard = document.createElement('div'); | |
| cgpaCard.className = 'course-card'; | |
| cgpaCard.style.opacity = '0'; | |
| cgpaCard.style.gridColumn = '1 / -1'; | |
| cgpaCard.style.background = 'var(--card-bg)'; | |
| cgpaCard.style.border = '2px solid rgba(212, 175, 55, 0.4)'; | |
| cgpaCard.style.textAlign = 'center'; | |
| cgpaCard.style.marginTop = '10px'; | |
| cgpaCard.animate([ | |
| { opacity: 0, transform: 'translateY(30px) scale(0.95)' }, | |
| { opacity: 1, transform: 'translateY(0) scale(1)' } | |
| ], { | |
| duration: 500, | |
| easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', | |
| fill: 'forwards', | |
| delay: data.length * 100 | |
| }); | |
| cgpaCard.innerHTML = ` | |
| <div style="padding: 40px;"> | |
| <h2 style="font-size: 18px; font-weight: 500; letter-spacing: 0.15em; text-transform: uppercase; margin-bottom: 12px; color: var(--text-muted);">Overall CGPA</h2> | |
| <div style="font-size: 64px; font-weight: 700; line-height: 1; letter-spacing: -0.02em; background: var(--primary-grad); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0 10px 30px rgba(212, 175, 55, 0.2);"> | |
| ${cgpa} | |
| </div> | |
| <div style="margin-top: 30px; padding-top: 25px; border-top: 1px solid rgba(212,175,55,0.2);"> | |
| <h3 style="font-size: 15px; color: var(--text-main); margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.05em;">Target CGPA Planner</h3> | |
| <div style="display: flex; gap: 15px; justify-content: center; align-items: center; flex-wrap: wrap;"> | |
| <div style="display: flex; flex-direction: column; align-items: flex-start;"> | |
| <label style="color: var(--text-muted); font-size: 12px; margin-bottom: 4px;">Target CGPA</label> | |
| <input type="number" id="target-cgpa-input" value="9.0" step="0.1" min="0" max="10" style="background: rgba(0,0,0,0.4); border: 1px solid rgba(212,175,55,0.5); color: #fff; padding: 8px 12px; border-radius: 8px; width: 100px; font-family: inherit; outline: none;"> | |
| </div> | |
| <div style="display: flex; flex-direction: column; align-items: flex-start;"> | |
| <label style="color: var(--text-muted); font-size: 12px; margin-bottom: 4px;">Expected Grade</label> | |
| <select id="future-grade-select" style="background: rgba(0,0,0,0.4); border: 1px solid rgba(212,175,55,0.5); color: #fff; padding: 8px 12px; border-radius: 8px; font-family: inherit; outline: none; cursor: pointer;"> | |
| <option value="10">S (10)</option> | |
| <option value="9">A (9)</option> | |
| <option value="8">B (8)</option> | |
| <option value="7">C (7)</option> | |
| <option value="6">D (6)</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div id="calculator-result" style="margin-top: 20px; font-size: 15px; color: #d4af37; font-weight: 500; min-height: 24px;"></div> | |
| </div> | |
| </div> | |
| `; | |
| container.appendChild(cgpaCard); | |
| const updateCalc = () => { | |
| const target = parseFloat(document.getElementById('target-cgpa-input').value); | |
| const futureGp = parseFloat(document.getElementById('future-grade-select').value); | |
| const resultDiv = document.getElementById('calculator-result'); | |
| const curr = totalGradePoints / totalCredits; | |
| if (isNaN(target) || isNaN(futureGp)) return; | |
| if (curr >= target) { | |
| resultDiv.innerHTML = `<span style="color: #34d399;">You already have a ${target.toFixed(1)}+ CGPA!</span>`; | |
| return; | |
| } | |
| if (futureGp <= target) { | |
| resultDiv.innerHTML = `<span style="color: #f87171;">Impossible to reach a ${target.toFixed(1)} CGPA by scoring ${futureGp} points or less.</span>`; | |
| return; | |
| } | |
| const reqCredits = (target * totalCredits - totalGradePoints) / (futureGp - target); | |
| resultDiv.innerHTML = `You need <strong style="font-size: 20px; color: #fff;">${Math.ceil(reqCredits)}</strong> more credits of this grade to reach a ${target.toFixed(1)} CGPA.`; | |
| }; | |
| document.getElementById('target-cgpa-input').addEventListener('input', updateCalc); | |
| document.getElementById('future-grade-select').addEventListener('change', updateCalc); | |
| updateCalc(); | |
| const totalGradesCount = Object.values(gradeCounts).reduce((a, b) => a + b, 0); | |
| if (totalGradesCount > 0) { | |
| const graphCard = document.createElement('div'); | |
| graphCard.className = 'course-card'; | |
| graphCard.style.opacity = '0'; | |
| graphCard.style.gridColumn = '1 / -1'; | |
| graphCard.style.background = 'var(--card-bg)'; | |
| graphCard.style.border = '1px solid rgba(212, 175, 55, 0.2)'; | |
| graphCard.style.padding = '30px'; | |
| graphCard.style.marginTop = '20px'; | |
| graphCard.animate([ | |
| { opacity: 0, transform: 'translateY(30px) scale(0.95)' }, | |
| { opacity: 1, transform: 'translateY(0) scale(1)' } | |
| ], { | |
| duration: 500, | |
| easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', | |
| fill: 'forwards', | |
| delay: (data.length + 1) * 100 | |
| }); | |
| graphCard.innerHTML = ` | |
| <h2 style="font-size: 18px; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 20px; color: var(--text-main); text-align: center;">Grade Distribution</h2> | |
| <div style="position: relative; height: 300px; width: 100%;"> | |
| <canvas id="performanceChart"></canvas> | |
| </div> | |
| `; | |
| container.appendChild(graphCard); | |
| const pieLabels = []; | |
| const pieData = []; | |
| const pieColors = []; | |
| const colorPalette = { | |
| 'S': 'rgba(212, 175, 55, 0.9)', // Gold | |
| 'A': 'rgba(52, 211, 153, 0.9)', // Emerald | |
| 'B': 'rgba(96, 165, 250, 0.9)', // Blue | |
| 'C': 'rgba(167, 139, 250, 0.9)', // Purple | |
| 'D': 'rgba(251, 146, 60, 0.9)', // Orange | |
| 'E': 'rgba(248, 113, 113, 0.9)' // Red | |
| }; | |
| for (const g in gradeCounts) { | |
| if (gradeCounts[g] > 0) { | |
| pieLabels.push(g); | |
| pieData.push(gradeCounts[g]); | |
| pieColors.push(colorPalette[g] || '#888'); | |
| } | |
| } | |
| const ctx = document.getElementById('performanceChart').getContext('2d'); | |
| new Chart(ctx, { | |
| type: 'pie', | |
| data: { | |
| labels: pieLabels, | |
| datasets: [{ | |
| data: pieData, | |
| backgroundColor: pieColors, | |
| borderColor: '#0a0a0a', | |
| borderWidth: 2 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'right', | |
| labels: { color: '#a1a1aa', font: { size: 14 } } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| function setLoading(isLoading) { | |
| submitBtn.disabled = isLoading; | |
| if (isLoading) { | |
| btnText.classList.add('hidden'); | |
| loader.classList.remove('hidden'); | |
| } else { | |
| btnText.classList.remove('hidden'); | |
| loader.classList.add('hidden'); | |
| } | |
| } | |
| }); | |