"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { adminGet, adminSend, type AdminSubject, type SubjectRef, } from "@/lib/admin-api"; const EMPTY_FORM = { name: "", code: "", awardedCredits: 6, dependencyCredit: 0 }; export default function AdminSubjectsPage() { const { t } = useTranslation(); const [subjects, setSubjects] = useState([]); const [majors, setMajors] = useState([]); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [form, setForm] = useState(EMPTY_FORM); const [editingId, setEditingId] = useState(null); const [expandedId, setExpandedId] = useState(null); const [query, setQuery] = useState(""); const refresh = useCallback(async () => { setLoading(true); setError(null); try { const [s, m] = await Promise.all([ adminGet("/api/admin/subjects"), adminGet("/api/admin/majors"), ]); setSubjects(s); setMajors(m); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setLoading(false); } }, []); useEffect(() => { void refresh(); }, [refresh]); async function run(action: () => Promise<{ message?: string }>) { setBusy(true); setError(null); setNotice(null); try { const result = await action(); setNotice(result.message ?? null); await refresh(); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } } const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return subjects; return subjects.filter( (s) => (s.name ?? "").toLowerCase().includes(q) || (s.code ?? "").toLowerCase().includes(q), ); }, [subjects, query]); function startEdit(s: AdminSubject) { setEditingId(s.id); setForm({ name: s.name ?? "", code: s.code ?? "", awardedCredits: s.awardedCredits, dependencyCredit: s.dependencyCredit ?? 0, }); window.scrollTo({ top: 0, behavior: "smooth" }); } function cancelEdit() { setEditingId(null); setForm(EMPTY_FORM); } return (

{t("admin_subjects")}

{t("admin_subjects_intro")}

{error && (
{error}
)} {notice && (
{notice}
)} {/* create / edit */}

{editingId === null ? t("admin_new_subject") : t("admin_edit_subject")}

{editingId !== null && ( )}
{/* list */}

{t("admin_subjects")} ({filtered.length})

setQuery(e.target.value)} />
{loading ? ( ) : filtered.length === 0 ? ( ) : ( filtered.map((s) => ( setExpandedId(expandedId === s.id ? null : s.id)} onEdit={() => startEdit(s)} run={run} /> )) )}
{t("admin_subject_code")} {t("admin_subject_name")} {t("admin_credits")} {t("admin_majors")} {t("admin_prerequisites")} {t("actions")}
{t("loading")}
{t("admin_no_subjects")}
); } function ExpandableRow({ subject, majors, subjects, busy, expanded, onToggle, onEdit, run, }: { subject: AdminSubject; majors: SubjectRef[]; subjects: AdminSubject[]; busy: boolean; expanded: boolean; onToggle: () => void; onEdit: () => void; run: (action: () => Promise<{ message?: string }>) => Promise; }) { const { t } = useTranslation(); const [majorId, setMajorId] = useState(""); const [semester, setSemester] = useState(1); const [prereqId, setPrereqId] = useState(""); // A subject already taken by students cannot be deleted; the FK forbids it. const deletable = subject.enrolledCount === 0; return ( <> {subject.code} {subject.name} {subject.awardedCredits} {subject.majors.length === 0 ? "—" : subject.majors.map((m) => `${m.majorName} (${m.mandatorySemester})`).join(", ")} {subject.prerequisites.length === 0 ? "—" : subject.prerequisites.map((p) => p.code).join(", ")}
{expanded && (
{/* majors */}

{t("admin_majors")}

    {subject.majors.map((m) => (
  • {m.majorName} — {t("enroll_semester_short")} {m.mandatorySemester}
  • ))} {subject.majors.length === 0 &&
  • —
  • }
setSemester(Number(e.target.value))} />
{/* prerequisites */}

{t("admin_prerequisites")}

    {subject.prerequisites.map((p) => (
  • {p.code} — {p.name}
  • ))} {subject.prerequisites.length === 0 &&
  • —
  • }
)} ); }