Ignore:
Timestamp:
09/15/26 21:16:02 (8 days ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
b8093a0
Parents:
4fe4582
Message:
  • ready for presentation
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/app/professor/students/page.tsx

    r4fe4582 r8496f3c  
    33import { useEffect, useMemo, useState } from "react";
    44import { useTranslation } from 'react-i18next';
     5import { apiUrl } from '@/lib/api';
     6import { getAccessToken } from '@/lib/auth';
    57
    68type UsersBySubject = {
    7   Id?: string;
    8   Name?: string;
    9   Grade: number;
     9  /** users.id - what the grading endpoints expect as StudentId. */
     10  id: number;
     11  name?: string;
     12  /** users.index - the number shown to the professor and searched on. */
     13  index?: string;
     14  grade: number;
     15  semester?: string;
    1016};
    1117
    1218type SubjectsAndUsers = {
    13   Name?: string;
    14   Id?: number;
    15   Users: UsersBySubject[];
     19  name?: string;
     20  id?: number;
     21  code?: string;
     22  users: UsersBySubject[];
    1623};
    1724
     
    1926  StudentId: number;
    2027  SubjectId: number;
    21   grade: number;
     28  Grade: number;
    2229};
    2330
    2431type FlatRow = {
    25   studentIdStr: string;
    26   studentIdNum: number | null;
     32  studentIdNum: number;
     33  studentIndex: string;
    2734  studentName: string;
    2835  subjectId: number;
    2936  subjectName: string;
     37  semester: string;
    3038  grade: number;
    3139};
    32 
    33 function toInt(value: string): number | null {
    34   const n = Number.parseInt(value, 10);
    35   return Number.isFinite(n) ? n : null;
    36 }
    3740
    3841export default function ProfessorStudentsPage() {
     
    5255    setError(null);
    5356    try {
    54       const res = await fetch("/api/prof/students", { cache: "no-store" });
     57      const token = getAccessToken();
     58      if (!token) {
     59        throw new Error("You are not signed in.");
     60      }
     61
     62      const res = await fetch(apiUrl("/api/prof/students"), {
     63        cache: "no-store",
     64        headers: { Authorization: `Bearer ${token}` },
     65      });
    5566      if (!res.ok) {
    5667        throw new Error(`Failed to fetch students (${res.status})`);
     
    6172      const nextSelections: Record<string, number> = {};
    6273      for (const subj of data) {
    63         if (!subj.Id) continue;
    64         for (const u of subj.Users) {
    65           const key = `${subj.Id}:${u.Id ?? ""}`;
    66           const current = u.Grade;
    67           nextSelections[key] = current >= 5 && current <= 10 ? current : 5;
     74        if (!subj.id) continue;
     75        for (const u of subj.users) {
     76          const key = `${subj.id}:${u.id}`;
     77          // grade_type only declares 6..10, so an ungraded row starts at 6.
     78          nextSelections[key] = u.grade >= 6 && u.grade <= 10 ? u.grade : 6;
    6879        }
    6980      }
     
    8394    const rows: FlatRow[] = [];
    8495    for (const subj of subjects) {
    85       if (!subj.Id) continue;
    86       for (const u of subj.Users) {
     96      if (!subj.id) continue;
     97      for (const u of subj.users) {
    8798        rows.push({
    88           studentIdStr: u.Id ?? "",
    89           studentIdNum: toInt(u.Id ?? ""),
    90           studentName: u.Name ?? "",
    91           subjectId: subj.Id,
    92           subjectName: subj.Name ?? "",
    93           grade: u.Grade,
     99          studentIdNum: u.id,
     100          studentIndex: u.index ?? "",
     101          studentName: u.name ?? "",
     102          subjectId: subj.id,
     103          subjectName: subj.name ?? "",
     104          semester: u.semester ?? "",
     105          grade: u.grade,
    94106        });
    95107      }
     
    102114    return flatRows.filter((r) => {
    103115      if (subjectFilter !== "all" && String(r.subjectId) !== subjectFilter) return false;
    104       if (q.length > 0 && !r.studentIdStr.includes(q)) return false;
     116      if (q.length > 0 && !r.studentIndex.includes(q)) return false;
    105117      return true;
    106118    });
     
    111123    setError(null);
    112124    try {
    113       const res = await fetch(url, {
     125      const token = getAccessToken();
     126      if (!token) {
     127        throw new Error("You are not signed in.");
     128      }
     129
     130      const res = await fetch(apiUrl(url), {
    114131        method: "POST",
    115         headers: { "Content-Type": "application/json" },
     132        headers: {
     133          "Content-Type": "application/json",
     134          Authorization: `Bearer ${token}`,
     135        },
    116136        body: JSON.stringify(payload),
    117137      });
     
    135155          <h1 className="text-2xl font-semibold text-card-foreground">{t('prof_students_title')}</h1>
    136156          <p className="text-muted-foreground mt-1">
    137             {t('prof_students_data_note')} <span className="font-mono">/api/prof/students</span> (demo store).
     157            {t('prof_students_data_note')} <span className="font-mono">/api/prof/students</span>.
    138158          </p>
    139159        </div>
     
    149169              <option value="all">{t('all_subjects')}</option>
    150170              {subjects
    151                 .filter((s) => typeof s.Id === "number")
     171                .filter((s) => typeof s.id === "number")
    152172                .map((s) => (
    153                   <option key={String(s.Id)} value={String(s.Id)}>
    154                     {s.Name ?? `${t('subject')} ${s.Id}`}
     173                  <option key={String(s.id)} value={String(s.id)}>
     174                    {s.name ?? `${t('subject')} ${s.id}`}
    155175                  </option>
    156176                ))}
     
    202222            ) : (
    203223              filteredRows.map((r) => {
    204                 const key = `${r.subjectId}:${r.studentIdStr}`;
    205                 const selected = gradeSelection[key] ?? 5;
     224                const key = `${r.subjectId}:${r.studentIdNum}`;
     225                const selected = gradeSelection[key] ?? 6;
    206226                const busy = actionBusyKey === key;
    207227
     
    209229                  <tr key={key} className="hover:bg-accent">
    210230                    <td className="px-4 py-3 border-b text-card-foreground">{r.studentName}</td>
    211                     <td className="px-4 py-3 border-b text-card-foreground">{r.studentIdStr}</td>
    212                     <td className="px-4 py-3 border-b text-card-foreground">{r.subjectName}</td>
     231                    <td className="px-4 py-3 border-b text-card-foreground">{r.studentIndex}</td>
     232                    <td className="px-4 py-3 border-b text-card-foreground">
     233                      {r.subjectName}
     234                      {r.semester && (
     235                        <span className="block text-xs text-muted-foreground">{r.semester}</span>
     236                      )}
     237                    </td>
    213238                    <td className="px-4 py-3 border-b">
    214239                      <div className="flex items-center gap-3">
     
    223248                          }
    224249                        >
    225                           {[5, 6, 7, 8, 9, 10].map((g) => (
     250                          {[6, 7, 8, 9, 10].map((g) => (
    226251                            <option key={g} value={g}>
    227252                              {g}
     
    237262                      <div className="flex flex-wrap gap-2">
    238263                        <button
    239                           disabled={busy || r.studentIdNum === null}
     264                          disabled={busy}
    240265                          className="px-3 py-2 rounded-lg bg-green-600 text-white text-sm font-medium disabled:opacity-50"
    241266                          onClick={() => {
    242                             if (r.studentIdNum === null) return;
    243267                            void postGrade(
    244268                              "/api/prof/grade/add",
    245                               { StudentId: r.studentIdNum, SubjectId: r.subjectId, grade: selected },
     269                              { StudentId: r.studentIdNum, SubjectId: r.subjectId, Grade: selected },
    246270                              key,
    247271                            );
     
    252276
    253277                        <button
    254                           disabled={busy || r.studentIdNum === null}
     278                          disabled={busy}
    255279                          className="px-3 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium disabled:opacity-50"
    256280                          onClick={() => {
    257                             if (r.studentIdNum === null) return;
    258281                            void postGrade(
    259282                              "/api/prof/grade/edit",
    260                               { StudentId: r.studentIdNum, SubjectId: r.subjectId, grade: selected },
     283                              { StudentId: r.studentIdNum, SubjectId: r.subjectId, Grade: selected },
    261284                              key,
    262285                            );
     
    267290
    268291                        <button
    269                           disabled={busy || r.studentIdNum === null}
     292                          disabled={busy}
    270293                          className="px-3 py-2 rounded-lg bg-gray-800 text-white text-sm font-medium disabled:opacity-50"
    271294                          onClick={() => {
    272                             if (r.studentIdNum === null) return;
    273295                            void postGrade(
    274296                              "/api/prof/grade/remove",
    275                               { StudentId: r.studentIdNum, SubjectId: r.subjectId, grade: 0 },
     297                              { StudentId: r.studentIdNum, SubjectId: r.subjectId, Grade: 0 },
    276298                              key,
    277299                            );
Note: See TracChangeset for help on using the changeset viewer.