"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'; 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 baseClasses = "px-3 py-1 rounded-md text-xs font-medium"; if (status === "Зад.") { return ( {status} ); } else if (status === "Изб.") { return ( {status} ); } return ( {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); 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 (
Loading...
); } if (errorMessage || !subjectsData || selectedSemester === null) { return (
{errorMessage ?? '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 */}

Предмети

Преглед на запишани предмети по семестри

{/* Status and Selection Section */}
{/* Left Column - Status and Dropdown */}
{/* Status Card */}
Статус: Запишан од студент
Број на тикет: {currentSemester.ticketNumber}
{/* Semester Selection */}
Долг од документи: {currentSemester.debt}
{isDropdownOpen && (
{subjectsData.semesters.map((semester) => ( ))}
)}
Сериски број: {currentSemesterData.serviceNumber}
{/* Enrolled Subjects */}

Запишани предмети

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

Финансиски информации

{/* Left Financial Column */}
Сума: {currentSemester.financialInfo.sum}
Платено: {currentSemester.financialInfo.paid}
Должи: {currentSemester.financialInfo.due}
Материјални трошоци:
{currentSemester.financialInfo.materialCosts}
{/* Right Financial Column */}
Кредити: {currentSemester.financialInfo.credits}
МКСА: {currentSemester.financialInfo.MKSA}
Електронско запишување: {currentSemester.financialInfo.electronicRegistration}
Е-УКИМ: {currentSemester.financialInfo.eUKIM}
Банкарска провизија: {currentSemester.financialInfo.bankProvision}
Тотал: {currentSemester.financialInfo.total}
{/* Subjects Table */}

Листа на предмети

{currentSubjects.map((subject: Subject) => ( {subject.id}{subject.code}{subject.hours}{subject.kojPat}
{subject.name}
{subject.semester} {subject.signature || ( — )} {subject.group || ( — )} {subject.professor || ( — )} ))}
# Код Часови Кој пат Предмет Семестар Статус Потпис Група Професор
); }