"use client"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { apiUrl } from "@/lib/api"; import { getAccessToken } from "@/lib/auth"; type SubjectOption = { id: number; name?: string; code?: string; credits: number; mandatorySemester: number; alreadyPassed: boolean; }; type MajorOption = { id: number; name?: string; subjects: SubjectOption[]; }; type SemesterOption = { id: number; name?: string; year: number; type?: string; }; type EnrollmentOptions = { requiredSubjects: number; semesters: SemesterOption[]; majors: MajorOption[]; defaultMajorId: number | null; }; export default function EnrollSemesterDialog({ open, onClose, onEnrolled, }: { open: boolean; onClose: () => void; onEnrolled: () => void; }) { const { t } = useTranslation(); const [options, setOptions] = useState(null); const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [semesterId, setSemesterId] = useState(null); const [majorId, setMajorId] = useState(null); const [picked, setPicked] = useState([]); useEffect(() => { if (!open) return; let cancelled = false; async function load() { setLoading(true); setError(null); try { const token = getAccessToken(); if (!token) throw new Error(t("enroll_not_signed_in")); const res = await fetch(apiUrl("/api/enrollment/options"), { cache: "no-store", headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(`${t("enroll_options_failed")} (${res.status})`); const data = (await res.json()) as EnrollmentOptions; if (cancelled) return; setOptions(data); setSemesterId(data.semesters[0]?.id ?? null); setMajorId(data.defaultMajorId ?? data.majors[0]?.id ?? null); setPicked([]); } catch (e) { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); } finally { if (!cancelled) setLoading(false); } } void load(); return () => { cancelled = true; }; }, [open, t]); const required = options?.requiredSubjects ?? 5; const subjects = useMemo( () => options?.majors.find((m) => m.id === majorId)?.subjects ?? [], [options, majorId], ); function toggle(subjectId: number) { setPicked((prev) => { if (prev.includes(subjectId)) return prev.filter((id) => id !== subjectId); if (prev.length >= required) return prev; // the cap is the point of the form return [...prev, subjectId]; }); } async function submit() { setSubmitting(true); setError(null); try { const token = getAccessToken(); if (!token) throw new Error(t("enroll_not_signed_in")); const res = await fetch(apiUrl("/api/enrollment"), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ SemesterId: semesterId, MajorId: majorId, SubjectIds: picked, }), }); const body = (await res.json()) as { ok: boolean; message?: string }; if (!res.ok || !body.ok) { throw new Error(body.message || `${t("enroll_failed")} (${res.status})`); } onEnrolled(); onClose(); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setSubmitting(false); } } if (!open) return null; const noSemestersLeft = !loading && options !== null && options.semesters.length === 0; const canSubmit = !submitting && semesterId !== null && majorId !== null && picked.length === required; return (
e.stopPropagation()} >

{t("enroll_semester")}

{loading ? (

{t("loading")}

) : noSemestersLeft ? (

{t("enroll_no_semesters")}

) : ( <>

{t("subjects")}

{picked.length} / {required}
{subjects.map((s) => { const checked = picked.includes(s.id); const atLimit = !checked && picked.length >= required; return ( ); })}
)} {error && (
{error}
)}
); }