"use client" import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBook, faChevronDown, faFileInvoice } from '@fortawesome/free-solid-svg-icons'; import { useEffect, useState } from 'react'; import { getAccessToken } from '@/lib/auth'; import { useTranslation } from 'react-i18next'; interface Subject { id: number; code: string; hours: string; kojPat: number; name: string; semester: number; status: string; signature: string; group: string; professor: string; } type SemesterInfo = { id: number; name: string; status: string; serviceNumber: string | number; }; type FinancialInfo = { sum: string | number; paid: string; due: string; materialCosts: string; credits: string; MKSA: string; electronicRegistration: string; eUKIM: string; bankProvision: string; total: string; }; type CurrentSemester = { id: string; name: string; status: string; serviceNumber: string | number; ticketNumber: string; debt: string; financialInfo: FinancialInfo; }; type SubjectsResponse = { currentSemester: CurrentSemester; semesters: SemesterInfo[]; subjectsBySemester: Record; semesterKeyById: Record; }; type ApiCurrentSemester = { id: string; name: string; status: string; serviceNumber: string | number; ticketNumber: string; debt: string; financialInfo: { sum: string | number; paid: string; due: string; materialCosts: string; credits: string; totalCredits?: string; mksa?: string; MKSA?: string; electronicRegistration: string; eUKIM: string; bankProvision: string; total: string; }; }; type ApiSubjectsResponse = { semesters: SemesterInfo[]; currentSemester?: ApiCurrentSemester; currentSemestar?: ApiCurrentSemester; subjectsBySemester: Record; }; function normalizeKey(input: string) { return input.toLowerCase().replace(/\s|\(|\)|\.|,/g, ''); } function detectSeasonFromName(name: string): 'summer' | 'winter' | null { const n = name.toLowerCase(); if (n.includes('летен')) return 'summer'; if (n.includes('зимски')) return 'winter'; return null; } function buildSemesterKeyById(semesters: SemesterInfo[], keys: string[]) { const keyBySeason: Partial> = {}; for (const key of keys) { const k = key.toLowerCase(); if (k.includes('summer')) keyBySeason.summer = key; if (k.includes('winter')) keyBySeason.winter = key; } const mapping: Record = {}; for (const s of semesters) { const season = detectSeasonFromName(s.name); const mapped = season ? keyBySeason[season] : undefined; if (mapped) mapping[s.id] = mapped; } // Fallback: if we couldn't infer seasons, try matching by normalized names. if (Object.keys(mapping).length === 0) { for (const s of semesters) { const ns = normalizeKey(s.name); const match = keys.find((k) => normalizeKey(k).includes(ns) || ns.includes(normalizeKey(k))); if (match) mapping[s.id] = match; } } // Last resort: map in order. if (Object.keys(mapping).length === 0) { semesters.forEach((s, idx) => { if (keys[idx]) mapping[s.id] = keys[idx]; }); } return mapping; } interface TableCellProps { children: React.ReactNode; className?: string; } const TableCell = ({ children, className = "" }: TableCellProps) => ( {children} ); const StatusBadge = ({ status }: { status: string }) => { const { t } = useTranslation(); const baseClasses = "px-3 py-1 rounded-md text-xs font-medium"; if (status === t('mandatory_short')) { return ( {t('mandatory_short')} ); } else if (status === t('elective_short')) { return ( {t('elective_short')} ); } return ( {t(status) || status} ); }; export default function SubjectsPage() { const [subjectsData, setSubjectsData] = useState(null); const [selectedSemester, setSelectedSemester] = useState(null); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); const { t } = useTranslation(); 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/getSubjects', { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, }); if (!response.ok) { const text = await response.text().catch(() => ''); throw new Error(text || `Failed to load subjects (${response.status})`); } const apiData = (await response.json()) as ApiSubjectsResponse; if (cancelled) return; const current = apiData.currentSemester ?? apiData.currentSemestar; if (!current) { throw new Error('Response missing currentSemestar/currentSemester'); } const keys = Object.keys(apiData.subjectsBySemester ?? {}); const semesterKeyById = buildSemesterKeyById(apiData.semesters ?? [], keys); const normalized: SubjectsResponse = { semesters: apiData.semesters ?? [], subjectsBySemester: apiData.subjectsBySemester ?? {}, semesterKeyById, currentSemester: { id: current.id, name: current.name, status: current.status, serviceNumber: current.serviceNumber, ticketNumber: current.ticketNumber, debt: current.debt, financialInfo: { sum: current.financialInfo.sum, paid: current.financialInfo.paid, due: current.financialInfo.due, materialCosts: current.financialInfo.materialCosts, credits: current.financialInfo.credits, MKSA: current.financialInfo.MKSA ?? current.financialInfo.mksa ?? '', electronicRegistration: current.financialInfo.electronicRegistration, eUKIM: current.financialInfo.eUKIM, bankProvision: current.financialInfo.bankProvision, total: current.financialInfo.total, }, }, }; setSubjectsData(normalized); setSelectedSemester((prev) => { if (prev !== null) return prev; const season = detectSeasonFromName(current.name); if (season) { const key = keys.find((k) => k.toLowerCase().includes(season)); if (key) { const matchId = normalized.semesters.find((s) => normalized.semesterKeyById[s.id] === key)?.id; if (typeof matchId === 'number') return matchId; } } return normalized.semesters[0]?.id ?? null; }); } catch (err) { if (!cancelled) { setErrorMessage(err instanceof Error ? err.message : 'Failed to load subjects.'); } } finally { if (!cancelled) setIsLoading(false); } } void load(); return () => { cancelled = true; }; }, []); if (isLoading) { return (
{t('loading')}
); } if (errorMessage || !subjectsData || selectedSemester === null) { return (
{errorMessage ?? t('failed_to_load_subjects')}
); } const currentSemesterData = subjectsData.semesters.find((s) => s.id === selectedSemester) ?? subjectsData.semesters[0]; const semesterKey = subjectsData.semesterKeyById[selectedSemester] ?? Object.keys(subjectsData.subjectsBySemester)[0]; const currentSubjects: Subject[] = semesterKey ? subjectsData.subjectsBySemester[semesterKey] || [] : []; const { currentSemester } = subjectsData; return (
{/* Header */}

{t('subjects')}

{t('subjects_overview')}

{/* Status and Selection Section */}
{/* Left Column - Status and Dropdown */}
{/* Status Card */}
{t('status')}: {t('enrolled_by_student')}
{t('ticket_number')}: {currentSemester.ticketNumber}
{/* Semester Selection */}
{t('debt_from_documents')}: {currentSemester.debt}
{isDropdownOpen && (
{subjectsData.semesters.map((semester) => ( ))}
)}
{t('serial_number')}: {currentSemesterData.serviceNumber}
{/* Enrolled Subjects */}

{t('enrolled_subjects')}

{currentSubjects.length}
{/* Right Column - Financial Information */}

{t('financial_info')}

{/* Left Financial Column */}
{t('sum')}: {currentSemester.financialInfo.sum}
{t('paid')}: {currentSemester.financialInfo.paid}
{t('due')}: {currentSemester.financialInfo.due}
{t('material_costs')}:
{t('material_costs_info')}
{/* Right Financial Column */}
{t('credits')}: {currentSemester.financialInfo.credits}
{t('mksa')}: {currentSemester.financialInfo.MKSA}
{t('electronic_registration')}: {currentSemester.financialInfo.electronicRegistration}
{t('eukim')}: {currentSemester.financialInfo.eUKIM}
{t('bank_provision')}: {currentSemester.financialInfo.bankProvision}
{t('total')}: {currentSemester.financialInfo.total}
{/* Subjects Table */}

{t('subjects_list')}

{currentSubjects.map((subject: Subject) => ( {subject.id}{subject.code}{subject.hours}{subject.kojPat}
{t(subject.name, subject.name)}
{t(subject.semester.toString(), subject.semester.toString())} {subject.signature ? t(subject.signature, subject.signature) : ( — )} {subject.group ? t(subject.group, subject.group) : ( — )} {subject.professor ? t(subject.professor, subject.professor) : ( — )} ))}
# {t('code')} {t('hours')} {t('which_time')} {t('subject')} {t('semester')} {t('status')} {t('signature')} {t('group')} {t('professor')}
); }