"use client" import { useEffect, useMemo, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'; import { getAccessToken } from '@/lib/auth'; type PassedSubject = { id: number; subjectId: number; code: string; subject: string; credits: number; grade: number; gradeText: string; date: string; // DD.MM.YYYY semester: string; professor: string; }; type PassedSubjectsResponse = { passedSubjects: PassedSubject[]; }; type ExamRow = { id: number; code: string; subject: string; date: string; semester: string; credits: number; grade: number; }; const Exams = () => { const [sortField, setSortField] = useState<'grade' | 'date' | null>(null); const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null); const [exams, setExams] = useState([]); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { let cancelled = false; async function load() { setIsLoading(true); setErrorMessage(null); const token = getAccessToken(); if (!token) { setErrorMessage('Not authenticated. Please login again.'); setIsLoading(false); return; } try { const response = await fetch('https://iknow-api.onrender.com/api/user/getPassedSubjects', { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, }); if (!response.ok) { const text = await response.text().catch(() => ''); throw new Error(text || `Failed to load passed subjects (${response.status})`); } const data = (await response.json()) as PassedSubjectsResponse; const rows: ExamRow[] = (data?.passedSubjects ?? []).map((s) => ({ id: s.id, code: s.code, subject: s.subject, date: s.date, semester: s.semester, credits: s.credits, grade: s.grade, })); if (!cancelled) setExams(rows); } catch (err) { if (!cancelled) { setErrorMessage(err instanceof Error ? err.message : 'Failed to load passed subjects.'); } } finally { if (!cancelled) setIsLoading(false); } } void load(); return () => { cancelled = true; }; }, []); const stats = useMemo(() => { const passed = exams.length; const creditsCurrent = exams.reduce((sum, s) => sum + (typeof s.credits === 'number' ? s.credits : 0), 0); const numericGrades = exams.map((e) => (typeof e.grade === 'number' ? e.grade : null)).filter((g): g is number => g !== null); const average = numericGrades.length > 0 ? numericGrades.reduce((sum, g) => sum + g, 0) / numericGrades.length : 0; const totalCredits = 240; const totalExams = 40; return { average: Number.isFinite(average) ? Number(average.toFixed(2)) : 0, credits: { current: creditsCurrent, total: totalCredits }, passed, remaining: Math.max(0, totalExams - passed), }; }, [exams]); const getGradeColor = (grade: number) => { if (grade >= 9) return "bg-green-100 text-green-800 border-green-200"; if (grade >= 8) return "bg-yellow-100 text-yellow-800 border-yellow-200"; if (grade >= 6) return "bg-orange-100 text-orange-800 border-orange-200"; return "bg-red-100 text-red-800 border-red-200"; }; const handleSortByGrade = () => { if (sortField === 'grade') { if (sortOrder === 'desc') { setSortOrder('asc'); } else if (sortOrder === 'asc') { setSortField(null); setSortOrder(null); } else { setSortOrder('desc'); } } else { setSortField('grade'); setSortOrder('desc'); } }; const handleSortByDate = () => { if (sortField === 'date') { if (sortOrder === 'desc') { setSortOrder('asc'); } else if (sortOrder === 'asc') { setSortField(null); setSortOrder(null); } else { setSortOrder('desc'); } } else { setSortField('date'); setSortOrder('desc'); } }; const getSortedExams = () => { if (!sortField || !sortOrder) return exams; return [...exams].sort((a, b) => { if (sortField === 'grade') { return sortOrder === 'asc' ? a.grade - b.grade : b.grade - a.grade; } if (sortField === 'date') { // Convert date strings to Date objects for proper sorting const dateA = new Date(a.date.split('.').reverse().join('-')); // Convert DD.MM.YYYY to YYYY-MM-DD const dateB = new Date(b.date.split('.').reverse().join('-')); return sortOrder === 'asc' ? dateA.getTime() - dateB.getTime() : dateB.getTime() - dateA.getTime(); } return 0; }); }; const getSortIcon = (field: 'grade' | 'date') => { if (sortField === field) { if (sortOrder === 'asc') return faSortUp; if (sortOrder === 'desc') return faSortDown; } return faSort; }; const creditsPercentage = stats.credits.total > 0 ? (stats.credits.current / stats.credits.total) * 100 : 0; if (isLoading) { return (
Loading...
); } if (errorMessage) { return (
{errorMessage}
); } return (
{/* Statistics Section */}
{/* Average Grade */}
{stats.average}
Просек
{/* Credits */}
{stats.credits.current} /{stats.credits.total}
Кредити
{/* Passed Exams */}
{stats.passed}
Положени
{/* Remaining Exams */}
{stats.remaining}
Останато
{/* Progress Bar spanning entire statistics section */}
{/* Exams Table */}

Испити

{getSortedExams().map((exam) => ( ))}
# Предмет Семестар Кредити
{exam.id} {exam.subject} {exam.semester} {exam.credits} {exam.date} {exam.grade}
); }; export default Exams;