"use client"; import { useEffect, useMemo, useState } from "react"; type UsersBySubject = { Id?: string; Name?: string; Grade: number; }; type SubjectsAndUsers = { Name?: string; Id?: number; Users: UsersBySubject[]; }; type GradePayload = { StudentId: number; SubjectId: number; grade: number; }; type FlatRow = { studentIdStr: string; studentIdNum: number | null; studentName: string; subjectId: number; subjectName: string; grade: number; }; function toInt(value: string): number | null { const n = Number.parseInt(value, 10); return Number.isFinite(n) ? n : null; } export default function ProfessorStudentsPage() { const [subjects, setSubjects] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [subjectFilter, setSubjectFilter] = useState("all"); const [studentIdQuery, setStudentIdQuery] = useState(""); const [gradeSelection, setGradeSelection] = useState>({}); const [actionBusyKey, setActionBusyKey] = useState(null); async function refresh() { setLoading(true); setError(null); try { const res = await fetch("/api/prof/students", { cache: "no-store" }); if (!res.ok) { throw new Error(`Failed to fetch students (${res.status})`); } const data = (await res.json()) as SubjectsAndUsers[]; setSubjects(data); const nextSelections: Record = {}; for (const subj of data) { if (!subj.Id) continue; for (const u of subj.Users) { const key = `${subj.Id}:${u.Id ?? ""}`; const current = u.Grade; nextSelections[key] = current >= 5 && current <= 10 ? current : 5; } } setGradeSelection(nextSelections); } catch (e) { setError(e instanceof Error ? e.message : "Unknown error"); } finally { setLoading(false); } } useEffect(() => { void refresh(); }, []); const flatRows = useMemo(() => { const rows: FlatRow[] = []; for (const subj of subjects) { if (!subj.Id) continue; for (const u of subj.Users) { rows.push({ studentIdStr: u.Id ?? "", studentIdNum: toInt(u.Id ?? ""), studentName: u.Name ?? "", subjectId: subj.Id, subjectName: subj.Name ?? "", grade: u.Grade, }); } } return rows; }, [subjects]); const filteredRows = useMemo(() => { const q = studentIdQuery.trim(); return flatRows.filter((r) => { if (subjectFilter !== "all" && String(r.subjectId) !== subjectFilter) return false; if (q.length > 0 && !r.studentIdStr.includes(q)) return false; return true; }); }, [flatRows, subjectFilter, studentIdQuery]); async function postGrade(url: string, payload: GradePayload, busyKey: string) { setActionBusyKey(busyKey); setError(null); try { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const body = (await res.json()) as { ok: boolean; message?: string }; if (!res.ok || !body.ok) { throw new Error(body.message || `Request failed (${res.status})`); } await refresh(); } catch (e) { setError(e instanceof Error ? e.message : "Unknown error"); } finally { setActionBusyKey(null); } } return (

Professor • Students

Data comes from /api/prof/students (demo store).

setStudentIdQuery(e.target.value)} />
{error && (
{error}
)}
{loading ? ( ) : filteredRows.length === 0 ? ( ) : ( filteredRows.map((r) => { const key = `${r.subjectId}:${r.studentIdStr}`; const selected = gradeSelection[key] ?? 5; const busy = actionBusyKey === key; return ( ); }) )}
Student ID Subject Grade Actions
Loading...
No students found.
{r.studentName} {r.studentIdStr} {r.subjectName}
Current: {r.grade > 0 ? r.grade : "—"}
); }