"use client"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from 'react-i18next'; import { apiUrl } from '@/lib/api'; import { getAccessToken } from '@/lib/auth'; type UsersBySubject = { /** users.id - what the grading endpoints expect as StudentId. */ id: number; name?: string; /** users.index - the number shown to the professor and searched on. */ index?: string; grade: number; semester?: string; }; type SubjectsAndUsers = { name?: string; id?: number; code?: string; users: UsersBySubject[]; }; type GradePayload = { StudentId: number; SubjectId: number; Grade: number; }; type FlatRow = { studentIdNum: number; studentIndex: string; studentName: string; subjectId: number; subjectName: string; semester: string; grade: number; }; export default function ProfessorStudentsPage() { const { t } = useTranslation(); 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 token = getAccessToken(); if (!token) { throw new Error("You are not signed in."); } const res = await fetch(apiUrl("/api/prof/students"), { cache: "no-store", headers: { Authorization: `Bearer ${token}` }, }); 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}`; // grade_type only declares 6..10, so an ungraded row starts at 6. nextSelections[key] = u.grade >= 6 && u.grade <= 10 ? u.grade : 6; } } 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({ studentIdNum: u.id, studentIndex: u.index ?? "", studentName: u.name ?? "", subjectId: subj.id, subjectName: subj.name ?? "", semester: u.semester ?? "", 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.studentIndex.includes(q)) return false; return true; }); }, [flatRows, subjectFilter, studentIdQuery]); async function postGrade(url: string, payload: GradePayload, busyKey: string) { setActionBusyKey(busyKey); setError(null); try { const token = getAccessToken(); if (!token) { throw new Error("You are not signed in."); } const res = await fetch(apiUrl(url), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, 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 (

{t('prof_students_title')}

{t('prof_students_data_note')} /api/prof/students.

setStudentIdQuery(e.target.value)} />
{error && (
{error}
)}
{loading ? ( ) : filteredRows.length === 0 ? ( ) : ( filteredRows.map((r) => { const key = `${r.subjectId}:${r.studentIdNum}`; const selected = gradeSelection[key] ?? 6; const busy = actionBusyKey === key; return ( ); }) )}
{t('student')} {t('id')} {t('subject')} {t('grade')} {t('actions')}
{t('loading')}
{t('no_students_found')}
{r.studentName} {r.studentIndex} {r.subjectName} {r.semester && ( {r.semester} )}
{t('current')}: {r.grade > 0 ? r.grade : t('none')}
); }