"use client" import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; 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 { t } = useTranslation(); 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-500/10 text-green-600 dark:text-green-400 border-green-500/20"; if (grade >= 8) return "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/20"; if (grade >= 6) return "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20"; return "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20"; }; 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 (
{t('loading', 'Loading...')}
); } if (errorMessage) { return (
{t('failed_to_load_exams', errorMessage)}
); } return (
{/* Statistics Section */}
{/* Average Grade */}
{stats.average}
{t('average', 'Просек')}
{/* Credits */}
{stats.credits.current} /{stats.credits.total}
{t('credits', 'Кредити')}
{/* Passed Exams */}
{stats.passed}
{t('passed', 'Положени')}
{/* Remaining Exams */}
{stats.remaining}
{t('remaining', 'Останато')}
{/* Progress Bar spanning entire statistics section */}
{/* Exams Table */}

{t('exams', 'Испити')}

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