Ignore:
Timestamp:
12/21/25 18:28:25 (9 months ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
90f7842
Parents:
82c9c53
Message:

Refactor Exams component to fetch passed subjects from API and remove hardcoded data; delete unused JSON data files; implement authentication token management in auth.ts

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/components/exams.tsx

    r82c9c53 rb67f274  
    11"use client"
    2 import { useState } from 'react';
     2import { useEffect, useMemo, useState } from 'react';
    33import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    44import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
     5import { getAccessToken } from '@/lib/auth';
     6
     7type PassedSubject = {
     8  id: number;
     9  subjectId: number;
     10  code: string;
     11  subject: string;
     12  credits: number;
     13  grade: number;
     14  gradeText: string;
     15  date: string; // DD.MM.YYYY
     16  semester: string;
     17  professor: string;
     18};
     19
     20type PassedSubjectsResponse = {
     21  passedSubjects: PassedSubject[];
     22};
     23
     24type ExamRow = {
     25  id: number;
     26  code: string;
     27  subject: string;
     28  date: string;
     29  semester: string;
     30  credits: number;
     31  grade: number;
     32};
    533
    634const Exams = () => {
    735  const [sortField, setSortField] = useState<'grade' | 'date' | null>(null);
    836  const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
    9   // Real exam data
    10   const stats = {
    11     average: 8.95,
    12     credits: { current: 108, total: 240 },
    13     passed: 19,
    14     remaining: 22
    15   };
    16 
    17   const exams = [
    18     {
    19       id: 1,
    20       code: "F23L1W005",
    21       subject: "Бизнис и менаџмент",
    22       session: "2024 (Зимска) Прва испитна сесија",
    23       date: "21.02.2024",
    24       semester: 1,
    25       credits: 6.00,
    26       type: "З",
    27       grade: 7
    28     },
    29     {
    30       id: 2,
    31       code: "F23L1W007",
    32       subject: "Вовед во компјутерските науки",
    33       session: "2024 (Зимска) Прва испитна сесија",
    34       date: "06.02.2024",
    35       semester: 1,
    36       credits: 6.00,
    37       type: "З",
    38       grade: 10
    39     },
    40     {
    41       id: 3,
    42       code: "F23L2W003",
    43       subject: "Избрани теми од математика",
    44       session: "2024 (Зимска) Прва испитна сесија",
    45       date: "12.02.2024",
    46       semester: 1,
    47       credits: 6.00,
    48       type: "З",
    49       grade: 9
    50     },
    51     {
    52       id: 4,
    53       code: "F23L1W018",
    54       subject: "Професионални вештини",
    55       session: "2024 (Зимска) Прва испитна сесија",
    56       date: "01.02.2024",
    57       semester: 1,
    58       credits: 4.00,
    59       type: "З",
    60       grade: 9
    61     },
    62     {
    63       id: 5,
    64       code: "F23L1W004",
    65       subject: "Спорт и здравје",
    66       session: "2024 (Летна) Втора испитна сесија",
    67       date: "15.07.2024",
    68       semester: 1,
    69       credits: 2.00,
    70       type: "З",
    71       grade: "Реализиран"
    72     },
    73     {
    74       id: 6,
    75       code: "F23L1W020",
    76       subject: "Структурно програмирање",
    77       session: "2024 (Зимска) Прва испитна сесија",
    78       date: "12.02.2024",
    79       semester: 1,
    80       credits: 6.00,
    81       type: "З",
    82       grade: 10
    83     },
    84     {
    85       id: 7,
    86       code: "F23L1S003",
    87       subject: "Архитектура и организација на компјутери",
    88       session: "2024 (Летна) Втора испитна сесија",
    89       date: "03.07.2024",
    90       semester: 2,
    91       credits: 6.00,
    92       type: "З",
    93       grade: 9
    94     },
    95     {
    96       id: 8,
    97       code: "F23L1S023",
    98       subject: "Бизнис статистика",
    99       session: "2024 (Летна) Втора испитна сесија",
    100       date: "02.07.2024",
    101       semester: 2,
    102       credits: 6.00,
    103       type: "З",
    104       grade: 8
    105     },
    106     {
    107       id: 9,
    108       code: "F23L1S120",
    109       subject: "Креативни вештини за решавање проблеми",
    110       session: "2024 (Летна) Втора испитна сесија",
    111       date: "25.06.2024",
    112       semester: 2,
    113       credits: 6.00,
    114       type: "И",
    115       grade: 10
    116     },
    117     {
    118       id: 10,
    119       code: "F23L1S016",
    120       subject: "Објектно-ориентирано програмирање",
    121       session: "2024 (Летна) Втора испитна сесија",
    122       date: "13.07.2024",
    123       semester: 2,
    124       credits: 6.00,
    125       type: "З",
    126       grade: 10
    127     },
    128     {
    129       id: 11,
    130       code: "F23L1S146",
    131       subject: "Основи на Веб дизајн",
    132       session: "2024 (Летна) Втора испитна сесија",
    133       date: "24.06.2024",
    134       semester: 2,
    135       credits: 6.00,
    136       type: "З",
    137       grade: 10
    138     },
    139     {
    140       id: 12,
    141       code: "F23L2W100",
    142       subject: "Економија за ИКТ инженери",
    143       session: "2025 (Зимска) Прва испитна сесија",
    144       date: "03.02.2025",
    145       semester: 3,
    146       credits: 6.00,
    147       type: "З",
    148       grade: 8
    149     },
    150     {
    151       id: 13,
    152       code: "F23L2W109",
    153       subject: "Интернет програмирање на клиентска страна",
    154       session: "2025 (Зимска) Прва испитна сесија",
    155       date: "30.01.2025",
    156       semester: 3,
    157       credits: 6.00,
    158       type: "И",
    159       grade: 10
    160     },
    161     {
    162       id: 14,
    163       code: "F23L2W014",
    164       subject: "Компјутерски мрежи и безбедност",
    165       session: "2025 (Зимска) Прва испитна сесија",
    166       date: "10.02.2025",
    167       semester: 3,
    168       credits: 6.00,
    169       type: "З",
    170       grade: 7
    171     },
    172     {
    173       id: 15,
    174       code: "F23L2W201",
    175       subject: "Примена на алгоритми и податочни структури",
    176       session: "2025 (Зимска) Прва испитна сесија",
    177       date: "13.02.2025",
    178       semester: 3,
    179       credits: 6.00,
    180       type: "З",
    181       grade: 10
    182     },
    183     {
    184       id: 16,
    185       code: "F23L2W167",
    186       subject: "Шаблони за дизајн на кориснички интерфејси",
    187       session: "2025 (Зимска) Прва испитна сесија",
    188       date: "06.02.2025",
    189       semester: 3,
    190       credits: 6.00,
    191       type: "И",
    192       grade: 10
    193     },
    194     {
    195       id: 17,
    196       code: "F23L2S026",
    197       subject: "Маркетинг",
    198       session: "2025 (Летна) Втора испитна сесија",
    199       date: "12.06.2025",
    200       semester: 4,
    201       credits: 6.00,
    202       type: "З",
    203       grade: 8
    204     },
    205     {
    206       id: 18,
    207       code: "F23L2S017",
    208       subject: "Оперативни системи",
    209       session: "2025 (Летна) Втора испитна сесија",
    210       date: "19.06.2025",
    211       semester: 4,
    212       credits: 6.00,
    213       type: "З",
    214       grade: 7
    215     },
    216     {
    217       id: 19,
    218       code: "F23L2S029",
    219       subject: "Софтверско инженерство",
    220       session: "2025 (Летна) Втора испитна сесија",
    221       date: "23.06.2025",
    222       semester: 4,
    223       credits: 6.00,
    224       type: "З",
    225       grade: 8
     37  const [exams, setExams] = useState<ExamRow[]>([]);
     38  const [isLoading, setIsLoading] = useState(true);
     39  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     40
     41  useEffect(() => {
     42    let cancelled = false;
     43
     44    async function load() {
     45      setIsLoading(true);
     46      setErrorMessage(null);
     47
     48      const token = getAccessToken();
     49      if (!token) {
     50        setErrorMessage('Not authenticated. Please login again.');
     51        setIsLoading(false);
     52        return;
     53      }
     54
     55      try {
     56        const response = await fetch('http://localhost:5147/api/user/getPassedSubjects', {
     57          method: 'GET',
     58          headers: {
     59            Authorization: `Bearer ${token}`,
     60          },
     61        });
     62
     63        if (!response.ok) {
     64          const text = await response.text().catch(() => '');
     65          throw new Error(text || `Failed to load passed subjects (${response.status})`);
     66        }
     67
     68        const data = (await response.json()) as PassedSubjectsResponse;
     69        const rows: ExamRow[] = (data?.passedSubjects ?? []).map((s) => ({
     70          id: s.id,
     71          code: s.code,
     72          subject: s.subject,
     73          date: s.date,
     74          semester: s.semester,
     75          credits: s.credits,
     76          grade: s.grade,
     77        }));
     78
     79        if (!cancelled) setExams(rows);
     80      } catch (err) {
     81        if (!cancelled) {
     82          setErrorMessage(err instanceof Error ? err.message : 'Failed to load passed subjects.');
     83        }
     84      } finally {
     85        if (!cancelled) setIsLoading(false);
     86      }
    22687    }
    227   ];
    228 
    229   const getGradeColor = (grade: number | string) => {
    230     if (grade === "Реализиран") return "bg-blue-100 text-blue-800 border-blue-200";
    231     if (typeof grade === "number") {
    232       if (grade >= 9) return "bg-green-100 text-green-800 border-green-200";
    233       if (grade >= 8) return "bg-yellow-100 text-yellow-800 border-yellow-200";
    234       if (grade >= 6) return "bg-orange-100 text-orange-800 border-orange-200";
    235       return "bg-red-100 text-red-800 border-red-200";
    236     }
    237     return "bg-gray-100 text-gray-800 border-gray-200";
     88
     89    void load();
     90
     91    return () => {
     92      cancelled = true;
     93    };
     94  }, []);
     95
     96  const stats = useMemo(() => {
     97    const passed = exams.length;
     98    const creditsCurrent = exams.reduce((sum, s) => sum + (typeof s.credits === 'number' ? s.credits : 0), 0);
     99    const numericGrades = exams.map((e) => (typeof e.grade === 'number' ? e.grade : null)).filter((g): g is number => g !== null);
     100    const average =
     101      numericGrades.length > 0
     102        ? numericGrades.reduce((sum, g) => sum + g, 0) / numericGrades.length
     103        : 0;
     104
     105    const totalCredits = 240;
     106    const totalExams = 40;
     107    return {
     108      average: Number.isFinite(average) ? Number(average.toFixed(2)) : 0,
     109      credits: { current: creditsCurrent, total: totalCredits },
     110      passed,
     111      remaining: Math.max(0, totalExams - passed),
     112    };
     113  }, [exams]);
     114
     115  const getGradeColor = (grade: number) => {
     116    if (grade >= 9) return "bg-green-100 text-green-800 border-green-200";
     117    if (grade >= 8) return "bg-yellow-100 text-yellow-800 border-yellow-200";
     118    if (grade >= 6) return "bg-orange-100 text-orange-800 border-orange-200";
     119    return "bg-red-100 text-red-800 border-red-200";
    238120  };
    239121
     
    275157    return [...exams].sort((a, b) => {
    276158      if (sortField === 'grade') {
    277         // Handle "Реализиран" grade separately
    278         if (a.grade === "Реализиран" && b.grade === "Реализиран") return 0;
    279         if (a.grade === "Реализиран") return sortOrder === 'asc' ? -1 : 1;
    280         if (b.grade === "Реализиран") return sortOrder === 'asc' ? 1 : -1;
    281        
    282         // Handle numeric grades
    283         const gradeA = typeof a.grade === 'number' ? a.grade : 0;
    284         const gradeB = typeof b.grade === 'number' ? b.grade : 0;
    285        
    286         return sortOrder === 'asc' ? gradeA - gradeB : gradeB - gradeA;
     159        return sortOrder === 'asc' ? a.grade - b.grade : b.grade - a.grade;
    287160      }
    288161     
     
    307180  };
    308181
    309   const creditsPercentage = (stats.credits.current / stats.credits.total) * 100;
     182  const creditsPercentage = stats.credits.total > 0 ? (stats.credits.current / stats.credits.total) * 100 : 0;
     183
     184  if (isLoading) {
     185    return (
     186      <div className="bg-white rounded-lg shadow-sm p-6">
     187        Loading...
     188      </div>
     189    );
     190  }
     191
     192  if (errorMessage) {
     193    return (
     194      <div className="bg-white rounded-lg shadow-sm p-6">
     195        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
     196          {errorMessage}
     197        </div>
     198      </div>
     199    );
     200  }
    310201
    311202  return (
Note: See TracChangeset for help on using the changeset viewer.