Index: frontend/src/app/[locale]/layout.tsx
===================================================================
--- frontend/src/app/[locale]/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/[locale]/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export default function LocaleLayout({ children }: { children: React.ReactNode }) {
+	return <>{children}</>;
+}
Index: frontend/src/app/admin/layout.tsx
===================================================================
--- frontend/src/app/admin/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/admin/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,27 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "../globals.css";
+import "@fortawesome/fontawesome-svg-core/styles.css";
+import { config } from "@fortawesome/fontawesome-svg-core";
+import Header from "@/components/header";
+import AdminNavbar from "@/components/admin-navbar";
+
+config.autoAddCss = false;
+
+const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
+const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
+
+export const metadata: Metadata = {
+  title: "IKnow - Admin Portal",
+  description: "Administration of subjects, semesters and teaching assignments",
+};
+
+export default function AdminLayout({ children }: Readonly<{ children: React.ReactNode }>) {
+  return (
+    <div className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
+      <Header />
+      <AdminNavbar />
+      {children}
+    </div>
+  );
+}
Index: frontend/src/app/admin/page.tsx
===================================================================
--- frontend/src/app/admin/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/admin/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,5 @@
+import { redirect } from "next/navigation";
+
+export default function AdminIndex() {
+  redirect("/admin/subjects");
+}
Index: frontend/src/app/admin/schedule/page.tsx
===================================================================
--- frontend/src/app/admin/schedule/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/admin/schedule/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,224 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { adminGet, adminSend, type AdminSemester, type Schedule } from "@/lib/admin-api";
+
+export default function AdminSchedulePage() {
+  const { t } = useTranslation();
+
+  const [semesters, setSemesters] = useState<AdminSemester[]>([]);
+  const [semesterId, setSemesterId] = useState<number | null>(null);
+  const [schedule, setSchedule] = useState<Schedule | null>(null);
+
+  const [loading, setLoading] = useState(true);
+  const [busy, setBusy] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [notice, setNotice] = useState<string | null>(null);
+
+  const [subjectId, setSubjectId] = useState<number | "">("");
+  const [professorId, setProfessorId] = useState<number | "">("");
+
+  useEffect(() => {
+    (async () => {
+      try {
+        const list = await adminGet<AdminSemester[]>("/api/admin/semesters");
+        setSemesters(list);
+        setSemesterId(list[0]?.id ?? null);
+      } catch (e) {
+        setError(e instanceof Error ? e.message : String(e));
+      } finally {
+        setLoading(false);
+      }
+    })();
+  }, []);
+
+  const loadSchedule = useCallback(async (id: number) => {
+    setLoading(true);
+    setError(null);
+    try {
+      setSchedule(await adminGet<Schedule>(`/api/admin/schedule/${id}`));
+    } catch (e) {
+      setError(e instanceof Error ? e.message : String(e));
+    } finally {
+      setLoading(false);
+    }
+  }, []);
+
+  useEffect(() => {
+    if (semesterId !== null) void loadSchedule(semesterId);
+  }, [semesterId, loadSchedule]);
+
+  async function run(action: () => Promise<{ message?: string }>) {
+    setBusy(true);
+    setError(null);
+    setNotice(null);
+    try {
+      const result = await action();
+      setNotice(result.message ?? null);
+      if (semesterId !== null) await loadSchedule(semesterId);
+    } catch (e) {
+      setError(e instanceof Error ? e.message : String(e));
+    } finally {
+      setBusy(false);
+    }
+  }
+
+  return (
+    <div className="min-h-screen pb-8">
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <h1 className="text-3xl font-bold mb-2">{t("admin_schedule")}</h1>
+        <p className="text-lg opacity-90">{t("admin_schedule_intro")}</p>
+      </div>
+
+      {error && (
+        <div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{error}</div>
+      )}
+      {notice && (
+        <div className="mb-4 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800">{notice}</div>
+      )}
+
+      <div className="bg-card rounded-xl shadow-sm border border-border p-6 mb-6">
+        <div className="flex flex-wrap items-end gap-4">
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("semester")}</span>
+            <select
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={semesterId ?? ""}
+              onChange={(e) => setSemesterId(Number(e.target.value))}
+            >
+              {semesters.map((s) => (
+                <option key={s.id} value={s.id}>{s.name}</option>
+              ))}
+            </select>
+          </label>
+
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("subject")}</span>
+            <select
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={subjectId}
+              onChange={(e) => setSubjectId(e.target.value === "" ? "" : Number(e.target.value))}
+            >
+              <option value="">{t("admin_pick_subject")}</option>
+              {schedule?.allSubjects.map((s) => (
+                <option key={s.id} value={s.id}>{s.code} — {s.name}</option>
+              ))}
+            </select>
+          </label>
+
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_professor")}</span>
+            <select
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={professorId}
+              onChange={(e) => setProfessorId(e.target.value === "" ? "" : Number(e.target.value))}
+            >
+              <option value="">{t("admin_pick_professor")}</option>
+              {schedule?.professors.map((p) => (
+                <option key={p.id} value={p.id}>{p.name}</option>
+              ))}
+            </select>
+          </label>
+
+          <button
+            className="rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
+            disabled={busy || subjectId === "" || professorId === "" || semesterId === null}
+            onClick={() =>
+              void run(() =>
+                adminSend("/api/admin/schedule", "POST", {
+                  ProfessorId: professorId,
+                  SemesterId: semesterId,
+                  SubjectId: subjectId,
+                }),
+              )
+            }
+          >
+            {t("admin_assign")}
+          </button>
+        </div>
+      </div>
+
+      {schedule && schedule.uncoveredSubjects.length > 0 && (
+        <div className="mb-6 rounded-lg border border-yellow-200 bg-yellow-50 px-4 py-3 text-sm text-yellow-900">
+          <strong>{t("admin_uncovered_count", { count: schedule.uncoveredSubjects.length })}</strong>
+          <span className="ml-2">{t("admin_uncovered_hint")}</span>
+          <div className="mt-2 font-mono text-xs">
+            {schedule.uncoveredSubjects.map((u) => u.code).join(", ")}
+          </div>
+        </div>
+      )}
+
+      <div className="grid gap-6 lg:grid-cols-3">
+        <div className="lg:col-span-2 bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+          <div className="bg-primary text-white px-6 py-4">
+            <h2 className="text-xl font-bold">
+              {t("admin_assignments")} {schedule ? `— ${schedule.semesterName}` : ""}
+            </h2>
+          </div>
+          <div className="overflow-x-auto">
+            <table className="w-full">
+              <thead className="bg-accent">
+                <tr>
+                  <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_subject_code")}</th>
+                  <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("subject")}</th>
+                  <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_professor")}</th>
+                  <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("actions")}</th>
+                </tr>
+              </thead>
+              <tbody>
+                {loading ? (
+                  <tr><td className="px-4 py-4 text-muted-foreground" colSpan={4}>{t("loading")}</td></tr>
+                ) : !schedule || schedule.assignments.length === 0 ? (
+                  <tr><td className="px-4 py-4 text-muted-foreground" colSpan={4}>{t("admin_no_assignments")}</td></tr>
+                ) : (
+                  schedule.assignments.map((a) => (
+                    <tr key={`${a.subjectId}-${a.professorId}`} className="hover:bg-accent">
+                      <td className="px-4 py-3 border-b font-mono text-card-foreground">{a.subjectCode}</td>
+                      <td className="px-4 py-3 border-b text-card-foreground">{a.subjectName}</td>
+                      <td className="px-4 py-3 border-b text-card-foreground">{a.professorName}</td>
+                      <td className="px-4 py-3 border-b">
+                        <button
+                          className="rounded-lg bg-gray-800 px-3 py-1.5 text-sm text-white disabled:opacity-50"
+                          disabled={busy}
+                          onClick={() =>
+                            void run(() =>
+                              adminSend("/api/admin/schedule", "DELETE", {
+                                ProfessorId: a.professorId,
+                                SemesterId: schedule.semesterId,
+                                SubjectId: a.subjectId,
+                              }),
+                            )
+                          }
+                        >
+                          {t("admin_remove")}
+                        </button>
+                      </td>
+                    </tr>
+                  ))
+                )}
+              </tbody>
+            </table>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+          <div className="bg-primary text-white px-6 py-4">
+            <h2 className="text-xl font-bold">{t("admin_load")}</h2>
+          </div>
+          <ul className="divide-y divide-border">
+            {schedule?.load.map((l) => (
+              <li key={l.professorId} className="flex items-center justify-between px-4 py-3">
+                <span className="text-card-foreground">{l.professorName}</span>
+                <span className="rounded-full bg-accent px-2 py-0.5 text-sm text-muted-foreground">{l.subjects}</span>
+              </li>
+            ))}
+            {!schedule?.load.length && (
+              <li className="px-4 py-3 text-muted-foreground">{t("admin_no_professors")}</li>
+            )}
+          </ul>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/admin/semesters/page.tsx
===================================================================
--- frontend/src/app/admin/semesters/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/admin/semesters/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,173 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { adminGet, adminSend, type AdminSemester } from "@/lib/admin-api";
+
+export default function AdminSemestersPage() {
+  const { t } = useTranslation();
+
+  const [semesters, setSemesters] = useState<AdminSemester[]>([]);
+  const [loading, setLoading] = useState(true);
+  const [busy, setBusy] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [notice, setNotice] = useState<string | null>(null);
+
+  const [year, setYear] = useState(new Date().getFullYear());
+  const [type, setType] = useState<"winter" | "summer">("winter");
+  const [expandedId, setExpandedId] = useState<number | null>(null);
+
+  const refresh = useCallback(async () => {
+    setLoading(true);
+    setError(null);
+    try {
+      setSemesters(await adminGet<AdminSemester[]>("/api/admin/semesters"));
+    } catch (e) {
+      setError(e instanceof Error ? e.message : String(e));
+    } finally {
+      setLoading(false);
+    }
+  }, []);
+
+  useEffect(() => {
+    void refresh();
+  }, [refresh]);
+
+  async function create() {
+    setBusy(true);
+    setError(null);
+    setNotice(null);
+    try {
+      const result = await adminSend("/api/admin/semesters", "POST", { Year: year, Type: type });
+      setNotice(result.message ?? null);
+      await refresh();
+    } catch (e) {
+      setError(e instanceof Error ? e.message : String(e));
+    } finally {
+      setBusy(false);
+    }
+  }
+
+  return (
+    <div className="min-h-screen pb-8">
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <h1 className="text-3xl font-bold mb-2">{t("admin_semesters")}</h1>
+        <p className="text-lg opacity-90">{t("admin_semesters_intro")}</p>
+      </div>
+
+      {error && (
+        <div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{error}</div>
+      )}
+      {notice && (
+        <div className="mb-4 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800">{notice}</div>
+      )}
+
+      <div className="bg-card rounded-xl shadow-sm border border-border p-6 mb-6">
+        <h2 className="text-xl font-semibold mb-4 text-card-foreground">{t("admin_new_semester")}</h2>
+        <div className="flex flex-wrap items-end gap-4">
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_year")}</span>
+            <input
+              type="number"
+              min={2000}
+              max={2100}
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={year}
+              onChange={(e) => setYear(Number(e.target.value))}
+            />
+          </label>
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_type")}</span>
+            <select
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={type}
+              onChange={(e) => setType(e.target.value as "winter" | "summer")}
+            >
+              <option value="winter">{t("admin_winter")}</option>
+              <option value="summer">{t("admin_summer")}</option>
+            </select>
+          </label>
+          <button
+            disabled={busy}
+            className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
+            onClick={() => void create()}
+          >
+            {t("admin_open_semester")}
+          </button>
+        </div>
+      </div>
+
+      <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+        <div className="bg-primary text-white px-6 py-4">
+          <h2 className="text-xl font-bold">{t("admin_open_semesters")}</h2>
+        </div>
+        <div className="overflow-x-auto">
+          <table className="w-full">
+            <thead className="bg-accent">
+              <tr>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("semester")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_enrolments")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_coverage")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("actions")}</th>
+              </tr>
+            </thead>
+            <tbody>
+              {loading ? (
+                <tr><td className="px-4 py-4 text-muted-foreground" colSpan={4}>{t("loading")}</td></tr>
+              ) : semesters.length === 0 ? (
+                <tr><td className="px-4 py-4 text-muted-foreground" colSpan={4}>{t("admin_no_semesters")}</td></tr>
+              ) : (
+                semesters.map((s) => {
+                  const uncovered = s.uncoveredSubjects.length;
+                  return (
+                    <>
+                      <tr key={s.id} className="hover:bg-accent">
+                        <td className="px-4 py-3 border-b text-card-foreground">{s.name}</td>
+                        <td className="px-4 py-3 border-b text-card-foreground">{s.enrolmentCount}</td>
+                        <td className="px-4 py-3 border-b">
+                          {uncovered === 0 ? (
+                            <span className="rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-800">
+                              {t("admin_fully_covered")}
+                            </span>
+                          ) : (
+                            <span className="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-900">
+                              {t("admin_uncovered_count", { count: uncovered })}
+                            </span>
+                          )}
+                        </td>
+                        <td className="px-4 py-3 border-b">
+                          {uncovered > 0 && (
+                            <button
+                              className="rounded-lg border border-border px-3 py-1.5 text-sm"
+                              onClick={() => setExpandedId(expandedId === s.id ? null : s.id)}
+                            >
+                              {expandedId === s.id ? t("admin_close") : t("admin_show_uncovered")}
+                            </button>
+                          )}
+                        </td>
+                      </tr>
+                      {expandedId === s.id && uncovered > 0 && (
+                        <tr key={`${s.id}-detail`}>
+                          <td colSpan={4} className="border-b bg-accent/40 px-4 py-4 text-sm">
+                            <p className="mb-2 text-muted-foreground">{t("admin_uncovered_hint")}</p>
+                            <ul className="grid gap-1 sm:grid-cols-2 lg:grid-cols-3">
+                              {s.uncoveredSubjects.map((u) => (
+                                <li key={u.id} className="text-card-foreground">
+                                  <span className="font-mono">{u.code}</span> — {u.name}
+                                </li>
+                              ))}
+                            </ul>
+                          </td>
+                        </tr>
+                      )}
+                    </>
+                  );
+                })
+              )}
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/admin/subjects/page.tsx
===================================================================
--- frontend/src/app/admin/subjects/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/admin/subjects/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,409 @@
+"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<AdminSubject[]>([]);
+  const [majors, setMajors] = useState<SubjectRef[]>([]);
+  const [loading, setLoading] = useState(true);
+  const [busy, setBusy] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [notice, setNotice] = useState<string | null>(null);
+
+  const [form, setForm] = useState(EMPTY_FORM);
+  const [editingId, setEditingId] = useState<number | null>(null);
+  const [expandedId, setExpandedId] = useState<number | null>(null);
+  const [query, setQuery] = useState("");
+
+  const refresh = useCallback(async () => {
+    setLoading(true);
+    setError(null);
+    try {
+      const [s, m] = await Promise.all([
+        adminGet<AdminSubject[]>("/api/admin/subjects"),
+        adminGet<SubjectRef[]>("/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 (
+    <div className="min-h-screen pb-8">
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <h1 className="text-3xl font-bold mb-2">{t("admin_subjects")}</h1>
+        <p className="text-lg opacity-90">{t("admin_subjects_intro")}</p>
+      </div>
+
+      {error && (
+        <div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
+          {error}
+        </div>
+      )}
+      {notice && (
+        <div className="mb-4 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800">
+          {notice}
+        </div>
+      )}
+
+      {/* create / edit */}
+      <div className="bg-card rounded-xl shadow-sm border border-border p-6 mb-6">
+        <h2 className="text-xl font-semibold mb-4 text-card-foreground">
+          {editingId === null ? t("admin_new_subject") : t("admin_edit_subject")}
+        </h2>
+        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_subject_name")}</span>
+            <input
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={form.name}
+              onChange={(e) => setForm({ ...form, name: e.target.value })}
+            />
+          </label>
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_subject_code")}</span>
+            <input
+              className="rounded-lg border border-border bg-background px-3 py-2 font-mono"
+              value={form.code}
+              onChange={(e) => setForm({ ...form, code: e.target.value })}
+            />
+          </label>
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_credits")}</span>
+            <input
+              type="number"
+              min={1}
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={form.awardedCredits}
+              onChange={(e) => setForm({ ...form, awardedCredits: Number(e.target.value) })}
+            />
+          </label>
+          <label className="flex flex-col">
+            <span className="text-sm text-muted-foreground">{t("admin_dependency_credit")}</span>
+            <input
+              type="number"
+              min={0}
+              className="rounded-lg border border-border bg-background px-3 py-2"
+              value={form.dependencyCredit}
+              onChange={(e) => setForm({ ...form, dependencyCredit: Number(e.target.value) })}
+            />
+          </label>
+        </div>
+        <div className="mt-4 flex gap-3">
+          <button
+            disabled={busy}
+            className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
+            onClick={() =>
+              void run(async () => {
+                const body = {
+                  Name: form.name,
+                  Code: form.code,
+                  AwardedCredits: form.awardedCredits,
+                  DependencyCredit: form.dependencyCredit,
+                };
+                const result =
+                  editingId === null
+                    ? await adminSend("/api/admin/subjects", "POST", body)
+                    : await adminSend(`/api/admin/subjects/${editingId}`, "PUT", body);
+                cancelEdit();
+                return result;
+              })
+            }
+          >
+            {editingId === null ? t("admin_create") : t("admin_save")}
+          </button>
+          {editingId !== null && (
+            <button
+              className="rounded-lg border border-border px-4 py-2 text-sm font-medium"
+              onClick={cancelEdit}
+              disabled={busy}
+            >
+              {t("cancel")}
+            </button>
+          )}
+        </div>
+      </div>
+
+      {/* list */}
+      <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+        <div className="flex items-center justify-between gap-4 bg-primary text-white px-6 py-4">
+          <h2 className="text-xl font-bold">
+            {t("admin_subjects")} ({filtered.length})
+          </h2>
+          <input
+            className="rounded-lg px-3 py-2 text-card-foreground bg-background"
+            placeholder={t("admin_search_subject")}
+            value={query}
+            onChange={(e) => setQuery(e.target.value)}
+          />
+        </div>
+
+        <div className="overflow-x-auto">
+          <table className="w-full">
+            <thead className="bg-accent">
+              <tr>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_subject_code")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_subject_name")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_credits")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_majors")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("admin_prerequisites")}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium uppercase text-muted-foreground">{t("actions")}</th>
+              </tr>
+            </thead>
+            <tbody>
+              {loading ? (
+                <tr><td className="px-4 py-4 text-muted-foreground" colSpan={6}>{t("loading")}</td></tr>
+              ) : filtered.length === 0 ? (
+                <tr><td className="px-4 py-4 text-muted-foreground" colSpan={6}>{t("admin_no_subjects")}</td></tr>
+              ) : (
+                filtered.map((s) => (
+                  <ExpandableRow
+                    key={s.id}
+                    subject={s}
+                    majors={majors}
+                    subjects={subjects}
+                    busy={busy}
+                    expanded={expandedId === s.id}
+                    onToggle={() => setExpandedId(expandedId === s.id ? null : s.id)}
+                    onEdit={() => startEdit(s)}
+                    run={run}
+                  />
+                ))
+              )}
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+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<void>;
+}) {
+  const { t } = useTranslation();
+  const [majorId, setMajorId] = useState<number | "">("");
+  const [semester, setSemester] = useState(1);
+  const [prereqId, setPrereqId] = useState<number | "">("");
+
+  // A subject already taken by students cannot be deleted; the FK forbids it.
+  const deletable = subject.enrolledCount === 0;
+
+  return (
+    <>
+      <tr className="hover:bg-accent">
+        <td className="px-4 py-3 border-b font-mono text-card-foreground">{subject.code}</td>
+        <td className="px-4 py-3 border-b text-card-foreground">{subject.name}</td>
+        <td className="px-4 py-3 border-b text-card-foreground">{subject.awardedCredits}</td>
+        <td className="px-4 py-3 border-b text-sm text-muted-foreground">
+          {subject.majors.length === 0 ? "—" : subject.majors.map((m) => `${m.majorName} (${m.mandatorySemester})`).join(", ")}
+        </td>
+        <td className="px-4 py-3 border-b text-sm text-muted-foreground">
+          {subject.prerequisites.length === 0 ? "—" : subject.prerequisites.map((p) => p.code).join(", ")}
+        </td>
+        <td className="px-4 py-3 border-b">
+          <div className="flex flex-wrap gap-2">
+            <button className="rounded-lg border border-border px-3 py-1.5 text-sm" onClick={onToggle}>
+              {expanded ? t("admin_close") : t("admin_manage")}
+            </button>
+            <button className="rounded-lg bg-blue-600 px-3 py-1.5 text-sm text-white disabled:opacity-50" onClick={onEdit} disabled={busy}>
+              {t("edit_grade") === "Измени оценка" ? "Измени" : "Edit"}
+            </button>
+            <button
+              className="rounded-lg bg-gray-800 px-3 py-1.5 text-sm text-white disabled:opacity-40"
+              disabled={busy || !deletable}
+              title={deletable ? undefined : t("admin_delete_blocked", { count: subject.enrolledCount })}
+              onClick={() => void run(() => adminSend(`/api/admin/subjects/${subject.id}`, "DELETE"))}
+            >
+              {t("admin_delete")}
+            </button>
+          </div>
+        </td>
+      </tr>
+
+      {expanded && (
+        <tr>
+          <td colSpan={6} className="border-b bg-accent/40 px-4 py-4">
+            <div className="grid gap-6 lg:grid-cols-2">
+              {/* majors */}
+              <div>
+                <h3 className="font-semibold text-card-foreground mb-2">{t("admin_majors")}</h3>
+                <ul className="mb-3 space-y-1 text-sm">
+                  {subject.majors.map((m) => (
+                    <li key={m.majorId} className="flex items-center justify-between gap-2">
+                      <span>{m.majorName} — {t("enroll_semester_short")} {m.mandatorySemester}</span>
+                      <button
+                        className="text-red-700 hover:underline"
+                        disabled={busy}
+                        onClick={() => void run(() => adminSend(`/api/admin/subjects/${subject.id}/majors/${m.majorId}`, "DELETE"))}
+                      >
+                        {t("admin_remove")}
+                      </button>
+                    </li>
+                  ))}
+                  {subject.majors.length === 0 && <li className="text-muted-foreground">—</li>}
+                </ul>
+                <div className="flex flex-wrap gap-2">
+                  <select
+                    className="rounded-lg border border-border bg-background px-3 py-2 text-sm"
+                    value={majorId}
+                    onChange={(e) => setMajorId(e.target.value === "" ? "" : Number(e.target.value))}
+                  >
+                    <option value="">{t("admin_pick_major")}</option>
+                    {majors.map((m) => (
+                      <option key={m.id} value={m.id}>{m.name}</option>
+                    ))}
+                  </select>
+                  <input
+                    type="number"
+                    min={1}
+                    className="w-24 rounded-lg border border-border bg-background px-3 py-2 text-sm"
+                    value={semester}
+                    onChange={(e) => setSemester(Number(e.target.value))}
+                  />
+                  <button
+                    className="rounded-lg bg-green-600 px-3 py-2 text-sm text-white disabled:opacity-50"
+                    disabled={busy || majorId === ""}
+                    onClick={() =>
+                      void run(() =>
+                        adminSend(`/api/admin/subjects/${subject.id}/majors`, "POST", {
+                          MajorId: majorId,
+                          MandatorySemester: semester,
+                        }),
+                      )
+                    }
+                  >
+                    {t("admin_add")}
+                  </button>
+                </div>
+              </div>
+
+              {/* prerequisites */}
+              <div>
+                <h3 className="font-semibold text-card-foreground mb-2">{t("admin_prerequisites")}</h3>
+                <ul className="mb-3 space-y-1 text-sm">
+                  {subject.prerequisites.map((p) => (
+                    <li key={p.id} className="flex items-center justify-between gap-2">
+                      <span>{p.code} — {p.name}</span>
+                      <button
+                        className="text-red-700 hover:underline"
+                        disabled={busy}
+                        onClick={() => void run(() => adminSend(`/api/admin/subjects/${subject.id}/prerequisites/${p.id}`, "DELETE"))}
+                      >
+                        {t("admin_remove")}
+                      </button>
+                    </li>
+                  ))}
+                  {subject.prerequisites.length === 0 && <li className="text-muted-foreground">—</li>}
+                </ul>
+                <div className="flex flex-wrap gap-2">
+                  <select
+                    className="rounded-lg border border-border bg-background px-3 py-2 text-sm"
+                    value={prereqId}
+                    onChange={(e) => setPrereqId(e.target.value === "" ? "" : Number(e.target.value))}
+                  >
+                    <option value="">{t("admin_pick_prerequisite")}</option>
+                    {subjects
+                      .filter((other) => other.id !== subject.id)
+                      .map((other) => (
+                        <option key={other.id} value={other.id}>{other.code} — {other.name}</option>
+                      ))}
+                  </select>
+                  <button
+                    className="rounded-lg bg-green-600 px-3 py-2 text-sm text-white disabled:opacity-50"
+                    disabled={busy || prereqId === ""}
+                    onClick={() =>
+                      void run(() =>
+                        adminSend(`/api/admin/subjects/${subject.id}/prerequisites`, "POST", {
+                          DependencyId: prereqId,
+                        }),
+                      )
+                    }
+                  >
+                    {t("admin_add")}
+                  </button>
+                </div>
+              </div>
+            </div>
+          </td>
+        </tr>
+      )}
+    </>
+  );
+}
Index: frontend/src/app/globals.css
===================================================================
--- frontend/src/app/globals.css	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/globals.css	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,122 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+  --color-background: var(--background);
+  --color-foreground: var(--foreground);
+  --font-sans: var(--font-geist-sans);
+  --font-mono: var(--font-geist-mono);
+  --color-sidebar-ring: var(--sidebar-ring);
+  --color-sidebar-border: var(--sidebar-border);
+  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+  --color-sidebar-accent: var(--sidebar-accent);
+  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+  --color-sidebar-primary: var(--sidebar-primary);
+  --color-sidebar-foreground: var(--sidebar-foreground);
+  --color-sidebar: var(--sidebar);
+  --color-chart-5: var(--chart-5);
+  --color-chart-4: var(--chart-4);
+  --color-chart-3: var(--chart-3);
+  --color-chart-2: var(--chart-2);
+  --color-chart-1: var(--chart-1);
+  --color-ring: var(--ring);
+  --color-input: var(--input);
+  --color-border: var(--border);
+  --color-destructive: var(--destructive);
+  --color-accent-foreground: var(--accent-foreground);
+  --color-accent: var(--accent);
+  --color-muted-foreground: var(--muted-foreground);
+  --color-muted: var(--muted);
+  --color-secondary-foreground: var(--secondary-foreground);
+  --color-secondary: var(--secondary);
+  --color-primary-foreground: var(--primary-foreground);
+  --color-primary: var(--primary);
+  --color-popover-foreground: var(--popover-foreground);
+  --color-popover: var(--popover);
+  --color-card-foreground: var(--card-foreground);
+  --color-card: var(--card);
+  --radius-sm: calc(var(--radius) - 4px);
+  --radius-md: calc(var(--radius) - 2px);
+  --radius-lg: var(--radius);
+  --radius-xl: calc(var(--radius) + 4px);
+}
+
+:root {
+  --radius: 0.625rem;
+  --background: #F4FAFF;
+  --foreground: oklch(0.145 0 0);
+  --card: oklch(1 0 0);
+  --card-foreground: oklch(0.145 0 0);
+  --popover: oklch(1 0 0);
+  --popover-foreground: oklch(0.145 0 0);
+  --primary: #0272D1;
+  --primary-foreground: oklch(0.985 0 0);
+  --secondary: oklch(0.97 0 0);
+  --secondary-foreground: oklch(0.205 0 0);
+  --muted: oklch(0.97 0 0);
+  --muted-foreground: oklch(0.556 0 0);
+  --accent: oklch(0.97 0 0);
+  --accent-foreground: oklch(0.205 0 0);
+  --destructive: oklch(0.577 0.245 27.325);
+  --border: oklch(0.922 0 0);
+  --input: oklch(0.922 0 0);
+  --ring: oklch(0.708 0 0);
+  --chart-1: oklch(0.646 0.222 41.116);
+  --chart-2: oklch(0.6 0.118 184.704);
+  --chart-3: oklch(0.398 0.07 227.392);
+  --chart-4: oklch(0.828 0.189 84.429);
+  --chart-5: oklch(0.769 0.188 70.08);
+  --sidebar: oklch(0.985 0 0);
+  --sidebar-foreground: oklch(0.145 0 0);
+  --sidebar-primary: oklch(0.205 0 0);
+  --sidebar-primary-foreground: oklch(0.985 0 0);
+  --sidebar-accent: oklch(0.97 0 0);
+  --sidebar-accent-foreground: oklch(0.205 0 0);
+  --sidebar-border: oklch(0.922 0 0);
+  --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+  --background: oklch(0.15 0.01 240);
+  --foreground: oklch(0.95 0.01 240);
+  --card: oklch(0.18 0.01 240);
+  --card-foreground: oklch(0.95 0.01 240);
+  --popover: oklch(0.18 0.01 240);
+  --popover-foreground: oklch(0.95 0.01 240);
+  --primary: #3b82f6;
+  --primary-foreground: oklch(0.98 0 0);
+  --secondary: oklch(0.25 0.01 240);
+  --secondary-foreground: oklch(0.95 0.01 240);
+  --muted: oklch(0.25 0.01 240);
+  --muted-foreground: oklch(0.65 0.01 240);
+  --accent: oklch(0.25 0.01 240);
+  --accent-foreground: oklch(0.95 0.01 240);
+  --destructive: oklch(0.6 0.22 25);
+  --border: oklch(0.3 0.01 240);
+  --input: oklch(0.25 0.01 240);
+  --ring: oklch(0.5 0.15 240);
+  --chart-1: oklch(0.6 0.2 270);
+  --chart-2: oklch(0.65 0.18 180);
+  --chart-3: oklch(0.7 0.19 90);
+  --chart-4: oklch(0.6 0.24 310);
+  --chart-5: oklch(0.65 0.22 30);
+  --sidebar: oklch(0.18 0.01 240);
+  --sidebar-foreground: oklch(0.95 0.01 240);
+  --sidebar-primary: oklch(0.5 0.2 260);
+  --sidebar-primary-foreground: oklch(0.98 0 0);
+  --sidebar-accent: oklch(0.25 0.01 240);
+  --sidebar-accent-foreground: oklch(0.95 0.01 240);
+  --sidebar-border: oklch(0.3 0.01 240);
+  --sidebar-ring: oklch(0.5 0.15 240);
+}
+
+@layer base {
+  * {
+    @apply border-border outline-ring/50;
+  }
+  body {
+    @apply text-foreground bg-background;
+  }
+}
Index: frontend/src/app/layout.tsx
===================================================================
--- frontend/src/app/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,44 @@
+"use client";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.css";
+import '@fortawesome/fontawesome-svg-core/styles.css';
+import { config } from '@fortawesome/fontawesome-svg-core';
+import { I18nextProvider } from 'react-i18next';
+import i18n from '../i18n';
+import { ThemeProvider } from '../components/theme-provider';
+import { ThemeToggle } from '../components/theme-toggle';
+import ContactBubble from '../components/contact-bubble';
+
+config.autoAddCss = false;
+
+const geistSans = Geist({
+  variable: "--font-geist-sans",
+  subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+  variable: "--font-geist-mono",
+  subsets: ["latin"],
+});
+
+export default function RootLayout({
+  children,
+}: Readonly<{
+  children: React.ReactNode;
+}>) {
+  return (
+    <html lang="en" suppressHydrationWarning>
+      <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
+        <ThemeProvider attribute="class" defaultTheme="light" enableSystem>
+          <I18nextProvider i18n={i18n}>
+            <div className="fixed top-4 right-4 z-50">
+              <ThemeToggle />
+            </div>
+            <div className="mx-auto max-w-6xl px-4">{children}</div>
+            <ContactBubble />
+          </I18nextProvider>
+        </ThemeProvider>
+      </body>
+    </html>
+  );
+}
Index: frontend/src/app/page.tsx
===================================================================
--- frontend/src/app/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,242 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { 
+  faUser, 
+  faLock, 
+  faEye, 
+  faEyeSlash,
+  faSignInAlt,
+  faGraduationCap
+} from '@fortawesome/free-solid-svg-icons';
+import { useState } from 'react';
+import { login } from '@/lib/auth';
+import { useTranslation } from 'react-i18next';
+import LanguageSwitcher from '@/components/LanguageSwitcher';
+
+export default function LoginPage() {
+  const { t } = useTranslation();
+  const [showPassword, setShowPassword] = useState(false);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const [formData, setFormData] = useState({
+    email: '',
+    password: ''
+  });
+
+  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const { name, value } = e.target;
+    setFormData(prev => ({
+      ...prev,
+      [name]: value
+    }));
+  };
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    setErrorMessage(null);
+    setIsSubmitting(true);
+    try {
+      const session = await login({ email: formData.email, password: formData.password });
+      // Admins previously fell through to /students, where they have no
+      // enrolments and every page renders empty.
+      const home =
+        session.role === 'Professor' ? '/professor'
+        : session.role === 'Admin'   ? '/admin'
+        : '/students';
+      window.location.href = home;
+    } catch (err) {
+      setErrorMessage(err instanceof Error ? err.message : 'Login failed.');
+    } finally {
+      setIsSubmitting(false);
+    }
+  };
+
+  return (
+    <div className="container mx-auto px-4">
+      <div className="min-h-screen ">
+        {/* Login Form */}
+        <div className="flex items-center justify-center min-h-[calc(100vh-160px)] p-4">
+          <div className="w-full max-w-5xl">
+            <div className="grid grid-cols-1 gap-6 lg:grid-cols-[24rem_28rem_24rem] lg:items-start lg:justify-center">
+              {/* Sticky Notes */}
+              <div className="w-full flex flex-col gap-4">
+                <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
+                  <div className="text-sm font-bold text-card-foreground mb-2">{t('login_student_title')}</div>
+                  <div className="text-xs text-card-foreground whitespace-pre-wrap">
+                    {t('login_student_example')}
+                  </div>
+                  <div className="mt-3 text-xs text-blue-600 font-medium">
+                    {t('login_demo_note')}
+                  </div>
+                </div>
+
+                <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
+                  <div className="text-sm font-bold text-card-foreground mb-2">{t('login_professor_title')}</div>
+                  <div className="text-xs text-card-foreground whitespace-pre-wrap">
+                    {t('login_professor_example')}
+                  </div>
+                  <div className="mt-3 text-xs text-blue-600 font-medium">
+                    {t('login_demo_note')}
+                  </div>
+                </div>
+
+                <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
+                  <div className="text-sm font-bold text-card-foreground mb-2">{t('login_admin_title')}</div>
+                  <div className="text-xs text-card-foreground whitespace-pre-wrap">
+                    {t('login_admin_example')}
+                  </div>
+                  <div className="mt-3 text-xs text-blue-600 font-medium">
+                    {t('login_demo_note')}
+                  </div>
+                </div>
+              </div>
+
+              {/* Login Card Column */}
+              <div className="w-full max-w-md lg:max-w-none lg:w-[28rem] mx-auto">
+                {/* Login Card */}
+                <div className="bg-card rounded-2xl shadow-xl border border-border overflow-hidden">
+                {/* Header */}
+                <div className="bg-primary text-white px-8 py-6 text-center">
+              <div className="mb-4">
+                <div className="inline-flex items-center justify-center w-16 h-16 bg-white rounded-full shadow-lg">
+                  <FontAwesomeIcon icon={faUser} className="text-2xl text-[#0272D1]" />
+                </div>
+              </div>
+              <h1 className="text-2xl font-bold mb-2">{t('login_welcome')}</h1>
+              <p className="text-blue-100 text-sm">
+                {t('login_subtitle')}
+              </p>
+            </div>
+
+            {/* Form */}
+            <div className="p-8">
+              {/* Language Switcher above email field, centered */}
+              <div className="flex justify-center mb-6">
+                <LanguageSwitcher />
+              </div>
+              <form onSubmit={handleSubmit} className="space-y-6">
+                {/* Email Field */}
+                <div>
+                  <label htmlFor="email" className="block text-sm font-medium text-muted-foreground mb-2">
+                    {t('login_email_label')}
+                  </label>
+                  <div className="relative">
+                    <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                      <FontAwesomeIcon icon={faUser} className="h-4 w-4 text-muted-foreground" />
+                    </div>
+                    <input
+                      id="email"
+                      name="email"
+                      type="email"
+                      required
+                      value={formData.email}
+                      onChange={handleInputChange}
+                      className="w-full pl-10 pr-4 py-3 border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
+                      placeholder={t('login_email_placeholder')}
+                    />
+                  </div>
+                </div>
+
+                {/* Password Field */}
+                <div>
+                  <label htmlFor="password" className="block text-sm font-medium text-muted-foreground mb-2">
+                    {t('login_password_label')}
+                  </label>
+                  <div className="relative">
+                    <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
+                      <FontAwesomeIcon icon={faLock} className="h-4 w-4 text-muted-foreground" />
+                    </div>
+                    <input
+                      id="password"
+                      name="password"
+                      type={showPassword ? "text" : "password"}
+                      required
+                      value={formData.password}
+                      onChange={handleInputChange}
+                      className="w-full pl-10 pr-12 py-3 border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
+                      placeholder={t('login_password_placeholder')}
+                    />
+                    <button
+                      type="button"
+                      onClick={() => setShowPassword(!showPassword)}
+                      className="absolute inset-y-0 right-0 pr-3 flex items-center text-muted-foreground hover:text-foreground transition-colors"
+                    >
+                      <FontAwesomeIcon 
+                        icon={showPassword ? faEyeSlash : faEye} 
+                        className="h-4 w-4" 
+                      />
+                    </button>
+                  </div>
+                </div>
+
+                {/* Remember Me & Forgot Password */}
+                <div className="flex items-center justify-between">
+                  <div className="flex items-center">
+                    <input
+                      id="remember"
+                      name="remember"
+                      type="checkbox"
+                      className="h-4 w-4 text-primary focus:ring-primary border-input rounded"
+                    />
+                    <label htmlFor="remember" className="ml-2 block text-sm text-muted-foreground">
+                      {t('login_remember_me')}
+                    </label>
+                  </div>
+                  <button
+                    type="button"
+                    className="text-sm text-primary hover:text-blue-700 font-medium transition-colors"
+                  >
+                    {t('login_forgot_password')}
+                  </button>
+                </div>
+
+                {errorMessage && (
+                  <div className="rounded-lg border border-destructive/20 bg-destructive/10 px-4 py-3 text-sm text-destructive">
+                    {t(errorMessage) || errorMessage}
+                  </div>
+                )}
+
+                {/* Submit Button */}
+                <button
+                  type="submit"
+                  disabled={isSubmitting}
+                  className="w-full bg-primary hover:bg-blue-700 disabled:opacity-60 disabled:hover:bg-primary text-white font-medium py-3 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2 shadow-lg hover:shadow-xl"
+                >
+                  <FontAwesomeIcon icon={faSignInAlt} className="h-4 w-4" />
+                  {isSubmitting ? t('login_button_loading') : t('login_button')}
+                </button>
+              </form>
+            </div>
+
+            {/* Footer */}
+            <div className="bg-accent px-8 py-4 border-t border-border">
+              <p className="text-center text-sm text-muted-foreground">
+                {t('login_no_account')}{' '}
+                <button className="text-primary hover:text-blue-700 font-medium transition-colors">
+                  {t('login_contact_admin')}
+                </button>
+              </p>
+            </div>
+                </div>
+
+                {/* Additional Info */}
+                <div className="mt-6 text-center">
+                  <div className="inline-flex items-center gap-2 px-4 py-2 bg-card bg-opacity-80 rounded-lg shadow-sm">
+                    <FontAwesomeIcon icon={faGraduationCap} className="text-primary" />
+                    <span className="text-sm text-muted-foreground">
+                      {t('login_footer')}
+                    </span>
+                  </div>
+                </div>
+              </div>
+
+              {/* Right spacer column (keeps login centered) */}
+              <div className="hidden lg:block" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/professor/layout.tsx
===================================================================
--- frontend/src/app/professor/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/professor/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,38 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "../globals.css";
+import "@fortawesome/fontawesome-svg-core/styles.css";
+import { config } from "@fortawesome/fontawesome-svg-core";
+import Header from "@/components/header";
+import ProfessorNavbar from "@/components/professor-navbar";
+
+config.autoAddCss = false;
+
+const geistSans = Geist({
+  variable: "--font-geist-sans",
+  subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+  variable: "--font-geist-mono",
+  subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+  title: "IKnow - Professor Portal",
+  description: "Professor portal for managing students and grades",
+};
+
+export default function ProfessorLayout({
+  children,
+}: Readonly<{
+  children: React.ReactNode;
+}>) {
+  return (
+    <div className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
+      <Header />
+      <ProfessorNavbar />
+      {children}
+    </div>
+  );
+}
Index: frontend/src/app/professor/page.tsx
===================================================================
--- frontend/src/app/professor/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/professor/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,5 @@
+import { redirect } from "next/navigation";
+
+export default function ProfessorHome() {
+  redirect("/professor/students");
+}
Index: frontend/src/app/professor/profile/page.tsx
===================================================================
--- frontend/src/app/professor/profile/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/professor/profile/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,287 @@
+"use client";
+
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+  faUser,
+  faIdCard,
+  faAddressCard,
+  faCalendarAlt,
+  faFlag,
+  faVenus,
+  faMars,
+  faEnvelope,
+  faPhone,
+  faMapMarkerAlt,
+  faPassport,
+} from "@fortawesome/free-solid-svg-icons";
+import { useEffect, useState } from "react";
+import { getAccessToken } from "@/lib/auth";
+import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
+import { useTranslation } from 'react-i18next';
+import { apiUrl } from '@/lib/api';
+
+type PersonalInfo = {
+  firstName: string;
+  middleName: string;
+  lastName: string;
+  maidenName: string;
+  dateOfBirth: string;
+  gender: string;
+  nationality: string;
+  citizenship: string;
+  scholarship: string;
+  currentPlan: string;
+  registryNumber: string;
+  studyGroup: string;
+  notes?: string;
+  index: string;
+  embg: string;
+};
+
+type BirthInfo = {
+  placeOfBirth: string;
+  municipalityOfBirth: string;
+  country: string;
+};
+
+// Present in the API response, but intentionally not shown on professor profile.
+type PreviousEducation = {
+  type: string;
+  profession: string;
+  average: string | number;
+  language: string;
+  country: string;
+  previousUniversity: string;
+  previousFaculty: string;
+  previousStudyMode: string;
+};
+
+// Present in the API response, but intentionally not shown on professor profile.
+type EnrollmentInfo = {
+  enrollmentYear: string | number;
+  status: string;
+  cycle: string;
+  program: string;
+  quota: string;
+  secondaryEducationNumber: string;
+  previousEducationCredits: string | number;
+};
+
+type Contact = {
+  placeOfResidence: string;
+  municipalityOfResidence: string;
+  country: string;
+  address: string;
+  temporaryAddress: string;
+  phone: string;
+  mobilePhone: string;
+  passportNumber: string;
+  passportExpiryDate: string;
+  email: string;
+  microsoftEmail: string;
+};
+
+type StudentProfile = {
+  personalInfo: PersonalInfo;
+  birthInfo: BirthInfo;
+  previousEducation: PreviousEducation;
+  enrollmentInfo: EnrollmentInfo;
+  contact: Contact;
+};
+
+interface InfoRowProps {
+  label: string;
+  value: string | number;
+  icon?: IconDefinition;
+}
+
+const InfoRow = ({ label, value, icon }: InfoRowProps) => {
+  const { t } = useTranslation();
+  return (
+    <div className="flex justify-between items-center py-3 border-b border-border last:border-b-0">
+      <div className="flex items-center gap-2 text-muted-foreground font-medium">
+        {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
+        <span>{t(label)}:</span>
+      </div>
+      <div className="text-card-foreground font-semibold text-right max-w-xs break-words">
+        {value || t('n_a')}
+      </div>
+    </div>
+  );
+};
+
+interface SectionProps {
+  title: string;
+  icon: IconDefinition;
+  children: React.ReactNode;
+}
+
+const Section = ({ title, icon, children }: SectionProps) => {
+  const { t } = useTranslation();
+  return (
+    <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+      <div className="bg-primary text-white px-6 py-4">
+        <div className="flex items-center gap-3">
+          <FontAwesomeIcon icon={icon} className="text-xl" />
+          <h2 className="text-xl font-bold">{t(title)}</h2>
+        </div>
+      </div>
+      <div className="p-6">{children}</div>
+    </div>
+  );
+};
+
+export default function ProfessorProfilePage() {
+  const [profileData, setProfileData] = useState<StudentProfile | null>(null);
+  const [isLoading, setIsLoading] = useState(true);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const { t } = useTranslation();
+
+  useEffect(() => {
+    let cancelled = false;
+
+    async function load() {
+      setIsLoading(true);
+      setErrorMessage(null);
+
+      const token = getAccessToken();
+      if (!token) {
+        setErrorMessage("Not authenticated. Please login again.");
+        setIsLoading(false);
+        return;
+      }
+
+      try {
+        const response = await fetch(apiUrl("/api/user/getUser"), {
+          method: "GET",
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+
+        if (!response.ok) {
+          const text = await response.text().catch(() => "");
+          throw new Error(text || `Failed to load profile (${response.status})`);
+        }
+
+        const data = (await response.json()) as StudentProfile;
+        if (!cancelled) setProfileData(data);
+      } catch (err) {
+        if (!cancelled) {
+          setErrorMessage(err instanceof Error ? err.message : "Failed to load profile.");
+        }
+      } finally {
+        if (!cancelled) setIsLoading(false);
+      }
+    }
+
+    void load();
+
+    return () => {
+      cancelled = true;
+    };
+  }, []);
+
+  if (isLoading) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="bg-card rounded-xl shadow-sm border border-border p-6">{t('loading')}</div>
+      </div>
+    );
+  }
+
+  if (errorMessage || !profileData) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
+          {errorMessage ?? t('failed_to_load_profile')}
+        </div>
+      </div>
+    );
+  }
+
+  const { personalInfo, birthInfo, contact } = profileData;
+
+  return (
+    <div className="min-h-screen pb-8">
+      {/* Header */}
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <div className="flex items-center gap-6">
+          <div className="relative">
+            <div className="w-20 h-20 bg-white rounded-full flex items-center justify-center border-2 border-white shadow-lg">
+              <FontAwesomeIcon icon={faUser} className="text-3xl text-[#0272D1]" />
+            </div>
+            <div className="absolute -bottom-1 -right-1 w-6 h-6 bg-green-500 rounded-full border-2 border-white"></div>
+          </div>
+          <div>
+            <h1 className="text-3xl font-bold mb-2">
+              {personalInfo.firstName} {personalInfo.middleName} {personalInfo.lastName}
+            </h1>
+            <div className="text-lg opacity-90">
+              {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg}
+            </div>
+          </div>
+        </div>
+      </div>
+
+      {/* Profile Sections */}
+      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+        {/* Personal Information */}
+        <Section title="personal_info" icon={faIdCard}>
+          <InfoRow label="first_name" value={personalInfo.firstName} />
+          <InfoRow label="middle_name" value={personalInfo.middleName} />
+          <InfoRow label="last_name" value={personalInfo.lastName} />
+          <InfoRow label="maiden_name" value={personalInfo.maidenName} />
+          <InfoRow label="date_of_birth" value={personalInfo.dateOfBirth} icon={faCalendarAlt} />
+          <InfoRow
+            label="gender"
+            value={personalInfo.gender}
+            icon={personalInfo.gender === t('male') ? faMars : faVenus}
+          />
+          <InfoRow label="nationality" value={personalInfo.nationality} icon={faFlag} />
+          <InfoRow label="citizenship" value={personalInfo.citizenship} />
+          <InfoRow label="scholarship" value={personalInfo.scholarship} />
+          <InfoRow label="current_plan" value={personalInfo.currentPlan} />
+          <InfoRow label="registry_number" value={personalInfo.registryNumber} />
+          <InfoRow label="study_group" value={personalInfo.studyGroup} />
+          {personalInfo.notes && (
+            <div className="mt-4 p-4 bg-blue-50 rounded-lg">
+              <div className="text-sm font-medium text-blue-800 mb-1">{t('note')}:</div>
+              <div className="text-sm text-blue-700">{personalInfo.notes}</div>
+            </div>
+          )}
+        </Section>
+
+        {/* Birth Information */}
+        <Section title="birth_info" icon={faMapMarkerAlt}>
+          <InfoRow label="place_of_birth" value={birthInfo.placeOfBirth} />
+          <InfoRow label="municipality_of_birth" value={birthInfo.municipalityOfBirth} />
+          <InfoRow label="country" value={birthInfo.country} />
+        </Section>
+
+        {/* Contact Information */}
+        <div className="lg:col-span-2">
+          <Section title="contact" icon={faAddressCard}>
+            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+              <div>
+                <InfoRow label="place_of_residence" value={contact.placeOfResidence} icon={faMapMarkerAlt} />
+                <InfoRow label="municipality_of_residence" value={contact.municipalityOfResidence} />
+                <InfoRow label="country" value={contact.country} />
+                <InfoRow label="address" value={contact.address} />
+                <InfoRow label="temporary_address" value={contact.temporaryAddress} />
+              </div>
+              <div>
+                <InfoRow label="phone" value={contact.phone} icon={faPhone} />
+                <InfoRow label="mobile_phone" value={contact.mobilePhone} icon={faPhone} />
+                <InfoRow label="passport_number" value={contact.passportNumber} icon={faPassport} />
+                <InfoRow label="passport_expiry_date" value={contact.passportExpiryDate} />
+                <InfoRow label="email" value={contact.email} icon={faEnvelope} />
+                <InfoRow label="microsoft_email" value={contact.microsoftEmail} icon={faEnvelope} />
+              </div>
+            </div>
+          </Section>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/professor/students/page.tsx
===================================================================
--- frontend/src/app/professor/students/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/professor/students/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,315 @@
+"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<SubjectsAndUsers[]>([]);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const [subjectFilter, setSubjectFilter] = useState<string>("all");
+  const [studentIdQuery, setStudentIdQuery] = useState<string>("");
+
+  const [gradeSelection, setGradeSelection] = useState<Record<string, number>>({});
+  const [actionBusyKey, setActionBusyKey] = useState<string | null>(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<string, number> = {};
+      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<FlatRow[]>(() => {
+    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 (
+    <div className="bg-card rounded-xl shadow-sm border border-border p-6">
+      <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
+
+        <div>
+          <h1 className="text-2xl font-semibold text-card-foreground">{t('prof_students_title')}</h1>
+          <p className="text-muted-foreground mt-1">
+            {t('prof_students_data_note')} <span className="font-mono">/api/prof/students</span>.
+          </p>
+        </div>
+
+        <div className="flex flex-col sm:flex-row gap-3">
+          <div className="flex flex-col">
+            <label className="text-sm text-muted-foreground">{t('filter_by_subject')}</label>
+            <select
+              className="border border-border rounded-lg px-3 py-2"
+              value={subjectFilter}
+              onChange={(e) => setSubjectFilter(e.target.value)}
+            >
+              <option value="all">{t('all_subjects')}</option>
+              {subjects
+                .filter((s) => typeof s.id === "number")
+                .map((s) => (
+                  <option key={String(s.id)} value={String(s.id)}>
+                    {s.name ?? `${t('subject')} ${s.id}`}
+                  </option>
+                ))}
+            </select>
+          </div>
+
+          <div className="flex flex-col">
+            <label className="text-sm text-muted-foreground">{t('find_student_by_id')}</label>
+            <input
+              className="border border-border rounded-lg px-3 py-2"
+              placeholder={t('student_id_placeholder')}
+              value={studentIdQuery}
+              onChange={(e) => setStudentIdQuery(e.target.value)}
+            />
+          </div>
+        </div>
+      </div>
+
+      {error && (
+        <div className="mt-4 rounded-lg border border-red-200 bg-red-50 text-red-800 px-4 py-3">
+          {error}
+        </div>
+      )}
+
+      <div className="mt-6 overflow-x-auto">
+        <table className="min-w-full border border-border rounded-lg overflow-hidden">
+          <thead className="bg-accent">
+            <tr>
+              <th className="text-left text-sm font-semibold text-muted-foreground px-4 py-3 border-b">{t('student')}</th>
+              <th className="text-left text-sm font-semibold text-muted-foreground px-4 py-3 border-b">{t('id')}</th>
+              <th className="text-left text-sm font-semibold text-muted-foreground px-4 py-3 border-b">{t('subject')}</th>
+              <th className="text-left text-sm font-semibold text-muted-foreground px-4 py-3 border-b">{t('grade')}</th>
+              <th className="text-left text-sm font-semibold text-muted-foreground px-4 py-3 border-b">{t('actions')}</th>
+            </tr>
+          </thead>
+          <tbody>
+            {loading ? (
+              <tr>
+                <td className="px-4 py-4 text-muted-foreground" colSpan={5}>
+                  {t('loading')}
+                </td>
+              </tr>
+            ) : filteredRows.length === 0 ? (
+              <tr>
+                <td className="px-4 py-4 text-muted-foreground" colSpan={5}>
+                  {t('no_students_found')}
+                </td>
+              </tr>
+            ) : (
+              filteredRows.map((r) => {
+                const key = `${r.subjectId}:${r.studentIdNum}`;
+                const selected = gradeSelection[key] ?? 6;
+                const busy = actionBusyKey === key;
+
+                return (
+                  <tr key={key} className="hover:bg-accent">
+                    <td className="px-4 py-3 border-b text-card-foreground">{r.studentName}</td>
+                    <td className="px-4 py-3 border-b text-card-foreground">{r.studentIndex}</td>
+                    <td className="px-4 py-3 border-b text-card-foreground">
+                      {r.subjectName}
+                      {r.semester && (
+                        <span className="block text-xs text-muted-foreground">{r.semester}</span>
+                      )}
+                    </td>
+                    <td className="px-4 py-3 border-b">
+                      <div className="flex items-center gap-3">
+                        <select
+                          className="border border-border rounded-lg px-3 py-2"
+                          value={selected}
+                          onChange={(e) =>
+                            setGradeSelection((prev) => ({
+                              ...prev,
+                              [key]: Number.parseInt(e.target.value, 10),
+                            }))
+                          }
+                        >
+                          {[6, 7, 8, 9, 10].map((g) => (
+                            <option key={g} value={g}>
+                              {g}
+                            </option>
+                          ))}
+                        </select>
+                        <span className="text-sm text-muted-foreground">
+                          {t('current')}: {r.grade > 0 ? r.grade : t('none')}
+                        </span>
+                      </div>
+                    </td>
+                    <td className="px-4 py-3 border-b">
+                      <div className="flex flex-wrap gap-2">
+                        <button
+                          disabled={busy}
+                          className="px-3 py-2 rounded-lg bg-green-600 text-white text-sm font-medium disabled:opacity-50"
+                          onClick={() => {
+                            void postGrade(
+                              "/api/prof/grade/add",
+                              { StudentId: r.studentIdNum, SubjectId: r.subjectId, Grade: selected },
+                              key,
+                            );
+                          }}
+                        >
+                          {t('add_grade')}
+                        </button>
+
+                        <button
+                          disabled={busy}
+                          className="px-3 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium disabled:opacity-50"
+                          onClick={() => {
+                            void postGrade(
+                              "/api/prof/grade/edit",
+                              { StudentId: r.studentIdNum, SubjectId: r.subjectId, Grade: selected },
+                              key,
+                            );
+                          }}
+                        >
+                          {t('edit_grade')}
+                        </button>
+
+                        <button
+                          disabled={busy}
+                          className="px-3 py-2 rounded-lg bg-gray-800 text-white text-sm font-medium disabled:opacity-50"
+                          onClick={() => {
+                            void postGrade(
+                              "/api/prof/grade/remove",
+                              { StudentId: r.studentIdNum, SubjectId: r.subjectId, Grade: 0 },
+                              key,
+                            );
+                          }}
+                        >
+                          {t('remove_grade')}
+                        </button>
+                      </div>
+                    </td>
+                  </tr>
+                );
+              })
+            )}
+          </tbody>
+        </table>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/students/applications/page.tsx
===================================================================
--- frontend/src/app/students/applications/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/applications/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,254 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { 
+  faPenToSquare, 
+  faChevronDown,
+  faCalendarAlt,
+  faFileText,
+  faMoneyBillWave,
+  faUser,
+  faInfoCircle,
+  faCheckCircle,
+  faTimesCircle
+} from '@fortawesome/free-solid-svg-icons';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import applicationsData from '@/data/applications.json';
+
+interface TableCellProps {
+  children: React.ReactNode;
+  className?: string;
+}
+
+const TableCell = ({ children, className = "" }: TableCellProps) => (
+  <td className={`px-4 py-3 text-sm border-b border-border ${className}`}>
+    {children}
+  </td>
+);
+
+const CompletedBadge = ({ completed }: { completed: string }) => {
+  const { t } = useTranslation();
+  if (completed === 'Да') {
+    return (
+      <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full" title={t('completed')}> 
+        <FontAwesomeIcon icon={faCheckCircle} className="w-4 h-4" />
+      </span>
+    );
+  } else {
+    return (
+      <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full" title={t('not_completed')}>
+        <FontAwesomeIcon icon={faTimesCircle} className="w-4 h-4" />
+      </span>
+    );
+  }
+};
+
+const FeeBadge = ({ fee }: { fee: string }) => {
+  const feeValue = parseFloat(fee.replace(',', '.'));
+  
+  if (feeValue === 0) {
+    return (
+      <span className="inline-flex items-center gap-1 px-2 py-1 bg-green-100 text-green-800 rounded-md text-xs font-medium">
+        <FontAwesomeIcon icon={faMoneyBillWave} className="w-3 h-3" />
+        {fee}
+      </span>
+    );
+  } else {
+    return (
+      <span className="inline-flex items-center gap-1 px-2 py-1 bg-yellow-100 text-yellow-800 rounded-md text-xs font-medium">
+        <FontAwesomeIcon icon={faMoneyBillWave} className="w-3 h-3" />
+        {fee}
+      </span>
+    );
+  }
+};
+
+export default function ApplicationsPage() {
+  const { t } = useTranslation();
+  const [selectedSession, setSelectedSession] = useState(applicationsData.currentSession.id);
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+
+  const currentSessionData = applicationsData.examSessions.find(s => s.id === selectedSession) || applicationsData.currentSession;
+  const { applications } = applicationsData;
+
+  return (
+    <div className="min-h-screen pb-8">
+      {/* Header */}
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <div className="flex items-center gap-4">
+          <div className="bg-white rounded-full p-4">
+            <FontAwesomeIcon icon={faPenToSquare} className="text-3xl text-[#0272D1]" />
+          </div>
+          <div>
+            <h1 className="text-3xl font-bold mb-2">{t('applications_header', 'Пријави')}</h1>
+            <p className="text-lg opacity-90">
+              {t('applications_subheader', 'Електронски пријави за испити')}
+            </p>
+          </div>
+        </div>
+      </div>
+
+      {/* Session Selection */}
+      <div className="bg-card rounded-xl p-6 shadow-sm border border-border mb-8">
+        <div className="flex items-center justify-between">
+          <h2 className="text-lg font-semibold text-card-foreground flex items-center gap-2">
+            <FontAwesomeIcon icon={faCalendarAlt} className="text-primary" />
+            {t('select_exam_session', 'Избери испитна сесија:')}
+          </h2>
+
+          <div className="relative">
+            <button
+              onClick={() => setIsDropdownOpen(!isDropdownOpen)}
+              className="bg-card border border-border rounded-lg px-4 py-2 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary min-w-80"
+            >
+              <div className="flex items-center justify-between">
+                <span className="text-sm font-medium text-primary">
+                  {t(currentSessionData.id, currentSessionData.name)}
+                </span>
+                <FontAwesomeIcon
+                  icon={faChevronDown}
+                  className={`w-4 h-4 text-muted-foreground transition-transform ml-4 ${isDropdownOpen ? 'rotate-180' : ''}`}
+                />
+              </div>
+            </button>
+
+            {isDropdownOpen && (
+              <div className="absolute z-10 right-0 mt-1 w-80 bg-card border border-border rounded-lg shadow-lg">
+                {applicationsData.examSessions.map((session) => (
+                  <button
+                    key={session.id}
+                    onClick={() => {
+                      setSelectedSession(session.id);
+                      setIsDropdownOpen(false);
+                    }}
+                    className="w-full px-4 py-3 text-left text-sm hover:bg-accent focus:outline-none focus:bg-accent first:rounded-t-lg last:rounded-b-lg"
+                  >
+                    <div className="font-medium text-card-foreground">{t(session.id, session.name)}</div>
+                    <div className="text-xs text-muted-foreground">{session.year} - {t(session.semester, session.semester)} - {t(session.session, session.session)}</div>
+                  </button>
+                ))}
+              </div>
+            )}
+          </div>
+        </div>
+      </div>
+
+      {/* Applications Section */}
+      <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+        <div className="bg-primary text-white px-6 py-4">
+          <h2 className="text-xl font-bold">{t('registered_exams', 'Пријавени испити')}</h2>
+        </div>
+
+        <div className="overflow-x-auto">
+          <table className="w-full">
+            <thead className="bg-accent">
+              <tr>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">#</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('serial_number', 'Сериски број')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('code', 'Код')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('subject', 'Предмет')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('completed', 'Завршена')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('fee', 'Таксени')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('date', 'Датум')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('instructor', 'Наставник')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('decade', 'Декада')}</th>
+              </tr>
+            </thead>
+            <tbody className="divide-y divide-border">
+              {applications.map((application) => (
+                <tr key={application.id} className="hover:bg-accent transition-colors">
+                  <TableCell className="font-medium text-card-foreground">{application.id}</TableCell>
+                  <TableCell>
+                    <span className="font-mono text-sm text-primary font-medium hover:underline cursor-pointer">
+                      {application.serviceNumber}
+                    </span>
+                  </TableCell>
+                  <TableCell className="font-mono text-sm font-medium">{application.code}</TableCell>
+                  <TableCell className="font-medium text-card-foreground max-w-xs">
+                    <div className="flex items-center gap-2">
+                      <FontAwesomeIcon icon={faFileText} className="w-4 h-4 text-primary" />
+                      {t(application.subject, application.subject)}
+                    </div>
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <CompletedBadge completed={application.completed} />
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <FeeBadge fee={application.fee} />
+                  </TableCell>
+                  <TableCell className="text-muted-foreground font-medium">{application.date}</TableCell>
+                  <TableCell>
+                    <div className="flex items-center gap-2">
+                      <FontAwesomeIcon icon={faUser} className="w-4 h-4 text-muted-foreground" />
+                      <span className="font-medium text-card-foreground">{t(application.instructor, application.instructor)}</span>
+                    </div>
+                  </TableCell>
+                  <TableCell className="text-center font-medium text-primary">{application.decade}</TableCell>
+                </tr>
+              ))}
+            </tbody>
+          </table>
+        </div>
+
+        {/* Important Note */}
+        <div className="bg-blue-50 border-t border-blue-100 p-6">
+          <div className="flex items-start gap-3">
+            <FontAwesomeIcon icon={faInfoCircle} className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
+            <div>
+              <h3 className="font-medium text-blue-900 mb-1">{t('important_note', 'Важна забелешка')}</h3>
+              <p className="text-sm text-blue-800 leading-relaxed">
+                {t('applications_important_note_text')}
+              </p>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      {/* Statistics Cards */}
+      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-blue-100 text-blue-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faFileText} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('registered_exams', 'Пријавени испити')}</h3>
+              <p className="text-2xl font-bold text-blue-600">
+                {applications.length}
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-green-100 text-green-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faCheckCircle} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('completed', 'Завршени')}</h3>
+              <p className="text-2xl font-bold text-green-600">
+                {applications.filter(app => app.completed === 'Да').length}
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-yellow-100 text-yellow-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faMoneyBillWave} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('total_fee', 'Вкупна такса')}</h3>
+              <p className="text-2xl font-bold text-yellow-600">
+                {applications.reduce((sum, app) => sum + parseFloat(app.fee.replace(',', '.')), 0).toFixed(2)}
+              </p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/students/documents/page.tsx
===================================================================
--- frontend/src/app/students/documents/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/documents/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,447 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { 
+  faFilePdf, 
+  faChevronDown,
+  faCheckCircle,
+  faClock,
+  faMoneyBillWave,
+  faFileText,
+  faChevronLeft,
+  faChevronRight,
+  faAngleDoubleLeft,
+  faAngleDoubleRight,
+  faSpinner
+} from '@fortawesome/free-solid-svg-icons';
+import { useState } from 'react';
+import documentsData from '@/data/documents.json';
+import { useTranslation } from 'react-i18next';
+import { getAccessToken } from '@/lib/auth';
+import { downloadDocumentPDF } from '@/lib/pdf-generators';
+
+interface TableCellProps {
+  children: React.ReactNode;
+  className?: string;
+}
+
+const TableCell = ({ children, className = "" }: TableCellProps) => (
+  <td className={`px-4 py-3 text-sm border-b border-border ${className}`}>
+    {children}
+  </td>
+);
+
+const StatusBadge = ({ status }: { status: string }) => {
+  const { t } = useTranslation();
+  if (status === 'approved' || status === t('approved', 'Одобрено')) {
+    return (
+      <span className="inline-flex items-center justify-center w-8 h-8 bg-green-100 text-green-600 rounded-full">
+        <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5" />
+      </span>
+    );
+  }
+  if (status === 'pending') {
+    return (
+      <span className="inline-flex items-center gap-1 text-xs font-medium text-yellow-700 bg-yellow-100 px-2 py-1 rounded-full">
+        <FontAwesomeIcon icon={faClock} className="w-3 h-3" />
+        {t('pending', 'Во обработка')}
+      </span>
+    );
+  }
+  return (
+    <span className="inline-flex items-center justify-center w-8 h-8 bg-accent text-muted-foreground rounded-full">
+      <FontAwesomeIcon icon={faFileText} className="w-4 h-4" />
+    </span>
+  );
+};
+
+const PriceBadge = ({ price }: { price: number }) => {
+  const { t } = useTranslation();
+  if (price === 0) {
+    return <span className="font-medium text-green-600">{t('free', '0,00')}</span>;
+  }
+  return <span className="font-medium text-blue-600">{price.toFixed(2)}</span>;
+};
+
+interface DocumentRecord {
+  id: number;
+  archive: string;
+  date: string;
+  request: string;
+  price: number;
+  paid: string;
+  document: string;
+  payOnline: boolean;
+  status: string;
+  comment: string;
+}
+
+export default function DocumentsPage() {
+  const [selectedDocumentType, setSelectedDocumentType] = useState("select_document");
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+  const [comment, setComment] = useState("");
+  const [currentPage, setCurrentPage] = useState(1);
+  const [recordsPerPage, setRecordsPerPage] = useState(15);
+  const [downloadingId, setDownloadingId] = useState<number | null>(null);
+  const [documents, setDocuments] = useState<DocumentRecord[]>(documentsData.documents as DocumentRecord[]);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const { t } = useTranslation();
+
+  const handleDownload = async (docId: number, request: string, archive: string, date: string) => {
+    const token = getAccessToken();
+    if (!token) {
+      alert(t('not_authenticated', 'Не сте најавени. Ве молиме најавете се повторно.'));
+      return;
+    }
+    setDownloadingId(docId);
+    try {
+      await downloadDocumentPDF(request, archive, date, token);
+    } catch (err) {
+      console.error('PDF generation error:', err);
+      alert(t('pdf_error', 'Грешка при генерирање на документот. Обидете се повторно.'));
+    } finally {
+      setDownloadingId(null);
+    }
+  };
+
+  const handleSubmit = () => {
+    if (selectedDocumentType === "select_document") return;
+
+    const docType = documentsData.documentTypes.find(d => d.id === selectedDocumentType);
+    if (!docType) return;
+
+    setIsSubmitting(true);
+
+    // Generate archive number (random 5-digit)
+    const archiveNumber = String(90000 + Math.floor(Math.random() * 10000));
+
+    // Current date in DD.MM.YYYY format
+    const now = new Date();
+    const dateStr = `${String(now.getDate()).padStart(2, '0')}.${String(now.getMonth() + 1).padStart(2, '0')}.${now.getFullYear()}`;
+
+    const newDoc: DocumentRecord = {
+      id: documents.length > 0 ? Math.max(...documents.map(d => d.id)) + 1 : 1,
+      archive: archiveNumber,
+      date: dateStr,
+      request: docType.name,
+      price: docType.price,
+      paid: "Не",
+      document: "Преземи",
+      payOnline: docType.price > 0,
+      status: "pending",
+      comment: comment,
+    };
+
+    setDocuments(prev => [newDoc, ...prev]);
+    setSelectedDocumentType("select_document");
+    setComment("");
+    setIsSubmitting(false);
+    alert(t('document_submitted', 'Барањето за документ е успешно поднесено!'));
+  };
+
+  const selectedDocument = documentsData.documentTypes.find(d => d.id === selectedDocumentType);
+  const { paymentInfo } = documentsData;
+
+  const totalPages = Math.ceil(documents.length / recordsPerPage);
+  const startIndex = (currentPage - 1) * recordsPerPage;
+  const endIndex = startIndex + recordsPerPage;
+  const currentDocuments = documents.slice(startIndex, endIndex);
+
+  return (
+    <div className="min-h-screen pb-8">
+      {/* Header */}
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <div className="flex items-center gap-4">
+          <div className="bg-white rounded-full p-4">
+            <FontAwesomeIcon icon={faFilePdf} className="text-3xl text-[#0272D1]" />
+          </div>
+          <div>
+            <h1 className="text-3xl font-bold mb-2">{t('documents')}</h1>
+            <p className="text-lg opacity-90">
+              {t('documents_overview', 'Преглед на вашите документи и нивниот статус.')}
+            </p>
+          </div>
+        </div>
+      </div>
+
+      {/* Document Request Form */}
+      <div className="bg-card rounded-xl p-6 shadow-sm border border-border mb-8">
+        <h2 className="text-lg font-semibold text-card-foreground mb-6 flex items-center gap-2">
+          <FontAwesomeIcon icon={faFileText} className="text-primary" />
+          {t('new_document_request', 'Ново барање за документ')}
+        </h2>
+        
+        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+          {/* Document Type Selection */}
+          <div>
+            <label className="block text-sm font-medium text-muted-foreground mb-2">
+              {t('select_document', 'Избери документ')}:
+            </label>
+            <div className="relative">
+              <button
+                onClick={() => setIsDropdownOpen(!isDropdownOpen)}
+                className="w-full bg-card border border-border rounded-lg px-4 py-3 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
+              >
+                <div className="flex items-center justify-between">
+                  <span className="text-sm text-card-foreground truncate">
+                    {selectedDocument ? t(selectedDocument.id) : t('select_document', 'Избери документ')}
+                  </span>
+                  <FontAwesomeIcon 
+                    icon={faChevronDown} 
+                    className={`w-4 h-4 text-muted-foreground transition-transform ml-2 flex-shrink-0 ${isDropdownOpen ? 'rotate-180' : ''}`}
+                  />
+                </div>
+              </button>
+              
+              {isDropdownOpen && (
+                <div className="absolute z-10 w-full mt-1 bg-card border border-border rounded-lg shadow-lg max-h-80 overflow-y-auto">
+                  {documentsData.documentTypes.map((docType) => (
+                    <button
+                      key={docType.id}
+                      onClick={() => {
+                        setSelectedDocumentType(docType.id);
+                        setIsDropdownOpen(false);
+                      }}
+                      className="w-full px-4 py-3 text-left text-sm hover:bg-accent focus:outline-none focus:bg-accent border-b border-border last:border-b-0"
+                    >
+                      <div className="font-medium text-card-foreground">{t(docType.id)}</div>
+                      {docType.price > 0 && (
+                        <div className="text-xs text-blue-600 mt-1">{t('price', 'Цена')}: {docType.price} мкд</div>
+                      )}
+                    </button>
+                  ))}
+                </div>
+              )}
+            </div>
+          </div>
+
+          {/* Comment Section */}
+          <div>
+            <label className="block text-sm font-medium text-muted-foreground mb-2">
+              {t('comment', 'Коментар')}:
+            </label>
+            <textarea
+              value={comment}
+              onChange={(e) => setComment(e.target.value)}
+              rows={4}
+              className="w-full border border-border rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary resize-none"
+              placeholder={t('add_comment', 'Додај коментар')}
+            />
+          </div>
+        </div>
+
+        <div className="flex justify-end mt-6">
+          <button
+            onClick={handleSubmit}
+            className="bg-primary hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors duration-200 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
+            disabled={selectedDocumentType === "select_document" || isSubmitting}
+          >
+            <FontAwesomeIcon icon={isSubmitting ? faSpinner : faFileText} className={`w-4 h-4 ${isSubmitting ? 'animate-spin' : ''}`} />
+            {isSubmitting ? t('submitting', 'Се поднесува...') : t('submit', 'Поднеси')}
+          </button>
+        </div>
+      </div>
+
+      {/* Documents Table */}
+      <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+        <div className="bg-primary text-white px-6 py-4">
+          <h2 className="text-xl font-bold">{t('my_documents', 'Мои документи')}</h2>
+        </div>
+        
+        <div className="overflow-x-auto">
+          <table className="w-full">
+            <thead className="bg-accent">
+              <tr>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">#</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('archive', 'Архива')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('date', 'Датум')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('request', 'Барање')}</th>
+                <th className="px-4 py-4 text-right text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('price', 'Цена')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('paid', 'Платено')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('document', 'Документ')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('pay_online', 'Плати онлајн')}</th>
+                <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('status', 'Статус')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('comment', 'Коментар')}</th>
+              </tr>
+            </thead>
+            <tbody className="divide-y divide-border">
+              {currentDocuments.map((document) => (
+                <tr key={document.id} className="hover:bg-accent transition-colors">
+                  <TableCell className="font-medium text-card-foreground">{document.id}</TableCell>
+                  <TableCell className="font-mono text-sm text-primary font-medium">{document.archive}</TableCell>
+                  <TableCell className="text-muted-foreground">{document.date}</TableCell>
+                  <TableCell className="font-medium text-card-foreground max-w-xs">
+                    {t(document.request)}
+                  </TableCell>
+                  <TableCell className="text-right">
+                    <PriceBadge price={document.price} />
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <span className={`text-sm font-medium ${document.paid === 'ДА' ? 'text-green-600' : 'text-red-500'}`}>
+                      {document.paid}
+                    </span>
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <button 
+                      onClick={() => handleDownload(document.id, document.request, document.archive, document.date)}
+                      disabled={downloadingId === document.id}
+                      className="text-primary hover:text-blue-700 font-medium text-sm underline disabled:opacity-50 disabled:cursor-wait inline-flex items-center gap-1"
+                    >
+                      {downloadingId === document.id ? (
+                        <>
+                          <FontAwesomeIcon icon={faSpinner} className="w-3 h-3 animate-spin" />
+                          {t('generating', 'Генерира...')}
+                        </>
+                      ) : (
+                        t('download', document.document)
+                      )}
+                    </button>
+                  </TableCell>
+                  <TableCell className="text-center">
+                    {document.payOnline ? (
+                      <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5 text-green-600" />
+                    ) : (
+                      <span className="text-muted-foreground">{t('none', '—')}</span>
+                    )}
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <StatusBadge status={document.status} />
+                  </TableCell>
+                  <TableCell>
+                    {document.comment || (
+                      <span className="text-muted-foreground">{t('none', '—')}</span>
+                    )}
+                  </TableCell>
+                </tr>
+              ))}
+            </tbody>
+          </table>
+        </div>
+
+        {/* Pagination */}
+        <div className="bg-accent px-6 py-4 flex items-center justify-between border-t border-border">
+          <div className="flex items-center gap-4 text-sm text-muted-foreground">
+            <div className="flex items-center gap-2">
+              <span>{t('show_rows', 'Прикажи редови')}:</span>
+              <select
+                value={recordsPerPage}
+                onChange={(e) => setRecordsPerPage(Number(e.target.value))}
+                className="border border-border rounded px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
+              >
+                <option value={15}>15</option>
+                <option value={25}>25</option>
+                <option value={50}>50</option>
+              </select>
+            </div>
+            <div>
+              {t('page', 'Страница')} <input
+                type="number"
+                min="1"
+                max={totalPages}
+                value={currentPage}
+                onChange={(e) => setCurrentPage(Number(e.target.value))}
+                className="w-12 border border-border rounded px-2 py-1 text-sm text-center focus:outline-none focus:ring-1 focus:ring-primary"
+              /> {t('of', 'од')} {totalPages}
+            </div>
+          </div>
+
+          <div className="flex items-center gap-2">
+            <button
+              onClick={() => setCurrentPage(1)}
+              disabled={currentPage === 1}
+              className="p-2 text-muted-foreground hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
+            >
+              <FontAwesomeIcon icon={faAngleDoubleLeft} className="w-4 h-4" />
+            </button>
+            <button
+              onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
+              disabled={currentPage === 1}
+              className="p-2 text-muted-foreground hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
+            >
+              <FontAwesomeIcon icon={faChevronLeft} className="w-4 h-4" />
+            </button>
+            <span className="px-4 py-2 bg-primary text-white rounded text-sm font-medium">
+              {t('first', 'Прва')}
+            </span>
+            <button
+              onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
+              disabled={currentPage === totalPages}
+              className="p-2 text-muted-foreground hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
+            >
+              <FontAwesomeIcon icon={faChevronRight} className="w-4 h-4" />
+            </button>
+            <button
+              onClick={() => setCurrentPage(totalPages)}
+              disabled={currentPage === totalPages}
+              className="p-2 text-muted-foreground hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
+            >
+              <FontAwesomeIcon icon={faAngleDoubleRight} className="w-4 h-4" />
+            </button>
+            <span className="ml-4 text-sm text-muted-foreground">
+              {t('last', 'Последна')}
+            </span>
+          </div>
+
+          <div className="text-sm text-muted-foreground">
+            {t('total', 'Вкупно')}: {documents.length}
+          </div>
+        </div>
+
+        {/* Payment Info */}
+        <div className="bg-blue-50 border-t border-blue-100 p-4">
+          <div className="flex items-start gap-3">
+            <FontAwesomeIcon icon={faMoneyBillWave} className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
+            <p className="text-sm text-blue-800 leading-relaxed">
+              {t('documents_payment_info', paymentInfo)}
+            </p>
+          </div>
+        </div>
+      </div>
+
+      {/* Statistics Cards */}
+      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-blue-100 text-blue-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faFileText} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('total_documents', 'Вкупно документи')}</h3>
+              <p className="text-2xl font-bold text-blue-600">
+                {documents.length}
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-green-100 text-green-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faCheckCircle} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('approved', 'Одобрени')}</h3>
+              <p className="text-2xl font-bold text-green-600">
+                {documents.filter(doc => doc.status === "approved").length}
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-yellow-100 text-yellow-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faMoneyBillWave} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('total_price', 'Вкупна цена')}</h3>
+              <p className="text-2xl font-bold text-yellow-600">
+                {documents.reduce((sum, doc) => sum + doc.price, 0).toFixed(2)} {t('mkd', 'мкд')}
+              </p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/students/exams/page.tsx
===================================================================
--- frontend/src/app/students/exams/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/exams/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,7 @@
+import Exams from "@/components/exams";
+
+export default function ExamsPage() {
+  return (
+    <Exams></Exams>
+  );
+}
Index: frontend/src/app/students/layout.tsx
===================================================================
--- frontend/src/app/students/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/layout.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,40 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "../globals.css";
+import "@fortawesome/fontawesome-svg-core/styles.css";
+import { config } from "@fortawesome/fontawesome-svg-core";
+import Header from "@/components/header";
+import Navbar from "@/components/navbar";
+
+// Prevent FontAwesome from adding CSS automatically
+config.autoAddCss = false;
+
+const geistSans = Geist({
+  variable: "--font-geist-sans",
+  subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+  variable: "--font-geist-mono",
+  subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+  title: "IKnow - UKIM",
+  description:
+    "University Managment System used to provide students informations and manage their progress.",
+};
+
+export default function RootLayout({
+  children,
+}: Readonly<{
+  children: React.ReactNode;
+}>) {
+  return (
+    <div className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
+      <Header />
+      <Navbar />
+      {children}
+    </div>
+  );
+}
Index: frontend/src/app/students/page.tsx
===================================================================
--- frontend/src/app/students/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,5 @@
+import { redirect } from "next/navigation";
+
+export default function StudentsHome() {
+  redirect("/students/profile");
+}
Index: frontend/src/app/students/profile/page.tsx
===================================================================
--- frontend/src/app/students/profile/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/profile/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,338 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { 
+  faUser, 
+  faIdCard, 
+  faGraduationCap, 
+  faAddressCard, 
+  faSchool,
+  faCalendarAlt,
+  faFlag,
+  faVenus,
+  faMars,
+  faEnvelope,
+  faPhone,
+  faMapMarkerAlt,
+  faPassport
+} from '@fortawesome/free-solid-svg-icons';
+import { useEffect, useState } from 'react';
+import { getAccessToken } from '@/lib/auth';
+import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
+import { useTranslation } from 'react-i18next';
+import { apiUrl } from '@/lib/api';
+
+type PersonalInfo = {
+  firstName: string;
+  middleName: string;
+  lastName: string;
+  maidenName: string;
+  dateOfBirth: string;
+  gender: string;
+  nationality: string;
+  citizenship: string;
+  scholarship: string;
+  currentPlan: string;
+  registryNumber: string;
+  studyGroup: string;
+  notes?: string;
+  index: string;
+  embg: string;
+};
+
+type BirthInfo = {
+  placeOfBirth: string;
+  municipalityOfBirth: string;
+  country: string;
+};
+
+type PreviousEducation = {
+  type: string;
+  profession: string;
+  average: string | number;
+  language: string;
+  country: string;
+  previousUniversity: string;
+  previousFaculty: string;
+  previousStudyMode: string;
+};
+
+type EnrollmentInfo = {
+  enrollmentYear: string | number;
+  status: string;
+  cycle: string;
+  program: string;
+  quota: string;
+  secondaryEducationNumber: string;
+  previousEducationCredits: string | number;
+};
+
+type Contact = {
+  placeOfResidence: string;
+  municipalityOfResidence: string;
+  country: string;
+  address: string;
+  temporaryAddress: string;
+  phone: string;
+  mobilePhone: string;
+  passportNumber: string;
+  passportExpiryDate: string;
+  email: string;
+  microsoftEmail: string;
+};
+
+type StudentProfile = {
+  personalInfo: PersonalInfo;
+  birthInfo: BirthInfo;
+  previousEducation: PreviousEducation;
+  enrollmentInfo: EnrollmentInfo;
+  contact: Contact;
+};
+
+interface InfoRowProps {
+  label: string;
+  value: string | number;
+  icon?: IconDefinition;
+}
+
+const InfoRow = ({ label, value, icon }: InfoRowProps) => {
+  const { t } = useTranslation();
+  return (
+    <div className="flex justify-between items-center py-3 border-b border-border last:border-b-0">
+      <div className="flex items-center gap-2 text-muted-foreground font-medium">
+        {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
+        <span>{t(label)}:</span>
+      </div>
+      <div className="text-card-foreground font-semibold text-right max-w-xs break-words">
+        {value || t('n_a')}
+      </div>
+    </div>
+  );
+};
+
+interface SectionProps {
+  title: string;
+  icon: IconDefinition;
+  children: React.ReactNode;
+}
+
+const Section = ({ title, icon, children }: SectionProps) => {
+  const { t } = useTranslation();
+  return (
+    <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+      <div className="bg-primary text-white px-6 py-4">
+        <div className="flex items-center gap-3">
+          <FontAwesomeIcon icon={icon} className="text-xl" />
+          <h2 className="text-xl font-bold">{t(title)}</h2>
+        </div>
+      </div>
+      <div className="p-6">
+        {children}
+      </div>
+    </div>
+  );
+};
+
+export default function ProfilePage() {
+  const [studentData, setStudentData] = useState<StudentProfile | null>(null);
+  const [isLoading, setIsLoading] = useState(true);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const { t } = useTranslation();
+
+  useEffect(() => {
+    let cancelled = false;
+
+    async function load() {
+      setIsLoading(true);
+      setErrorMessage(null);
+
+      const token = getAccessToken();
+      if (!token) {
+        setErrorMessage('Not authenticated. Please login again.');
+        setIsLoading(false);
+        return;
+      }
+
+      try {
+        const response = await fetch(apiUrl('/api/user/getUser'), {
+          method: 'GET',
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+
+        if (!response.ok) {
+          const text = await response.text().catch(() => '');
+          throw new Error(text || `Failed to load profile (${response.status})`);
+        }
+
+        const data = (await response.json()) as StudentProfile;
+        if (!cancelled) setStudentData(data);
+      } catch (err) {
+        if (!cancelled) {
+          setErrorMessage(err instanceof Error ? err.message : 'Failed to load profile.');
+        }
+      } finally {
+        if (!cancelled) setIsLoading(false);
+      }
+    }
+
+    void load();
+
+    return () => {
+      cancelled = true;
+    };
+  }, []);
+
+  if (isLoading) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="bg-card rounded-xl shadow-sm border border-border p-6">
+          {t('loading')}
+        </div>
+      </div>
+    );
+  }
+
+  if (errorMessage || !studentData) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
+          {errorMessage ?? t('failed_to_load_profile')}
+        </div>
+      </div>
+    );
+  }
+
+  const { personalInfo, birthInfo, previousEducation, enrollmentInfo, contact } = studentData;
+
+  return (
+    <div className="min-h-screen pb-8">
+      {/* Header */}
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <div className="flex items-center gap-6">
+          <div className="relative">
+            <div className="w-20 h-20 bg-white rounded-full flex items-center justify-center border-2 border-white shadow-lg">
+              <FontAwesomeIcon icon={faUser} className="text-3xl text-[#0272D1]" />
+            </div>
+            <div className="absolute -bottom-1 -right-1 w-6 h-6 bg-green-500 rounded-full border-2 border-white"></div>
+          </div>
+          <div>
+            <h1 className="text-3xl font-bold mb-2">
+              {personalInfo.firstName} {personalInfo.middleName} {personalInfo.lastName}
+            </h1>
+            <div className="text-lg opacity-90">
+              {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg}
+            </div>
+            <div className="text-base opacity-80 mt-1">
+              {enrollmentInfo.program}
+            </div>
+          </div>
+        </div>
+      </div>
+
+      {/* Profile Sections */}
+      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+        
+        {/* Personal Information */}
+        <Section title="personal_info" icon={faIdCard}>
+          <InfoRow label="first_name" value={personalInfo.firstName} />
+          <InfoRow label="middle_name" value={personalInfo.middleName} />
+          <InfoRow label="last_name" value={personalInfo.lastName} />
+          <InfoRow label="maiden_name" value={personalInfo.maidenName} />
+          <InfoRow 
+            label="date_of_birth" 
+            value={personalInfo.dateOfBirth} 
+            icon={faCalendarAlt} 
+          />
+          <InfoRow 
+            label="gender" 
+            value={personalInfo.gender} 
+            icon={personalInfo.gender === t('male') ? faMars : faVenus} 
+          />
+          <InfoRow 
+            label="nationality" 
+            value={personalInfo.nationality} 
+            icon={faFlag} 
+          />
+          <InfoRow label="citizenship" value={personalInfo.citizenship} />
+          <InfoRow label="scholarship" value={personalInfo.scholarship} />
+          <InfoRow label="current_plan" value={personalInfo.currentPlan} />
+          <InfoRow label="registry_number" value={personalInfo.registryNumber} />
+          <InfoRow label="study_group" value={personalInfo.studyGroup} />
+          {personalInfo.notes && (
+            <div className="mt-4 p-4 bg-blue-50 rounded-lg">
+              <div className="text-sm font-medium text-blue-800 mb-1">{t('note')}:</div>
+              <div className="text-sm text-blue-700">{personalInfo.notes}</div>
+            </div>
+          )}
+        </Section>
+
+        {/* Birth Information */}
+        <Section title="birth_info" icon={faMapMarkerAlt}>
+          <InfoRow label="place_of_birth" value={birthInfo.placeOfBirth} />
+          <InfoRow label="municipality_of_birth" value={birthInfo.municipalityOfBirth} />
+          <InfoRow label="country" value={birthInfo.country} />
+        </Section>
+
+        {/* Previous Education */}
+        <Section title="previous_education" icon={faSchool}>
+          <InfoRow label="type" value={previousEducation.type} />
+          <InfoRow label="profession" value={previousEducation.profession} />
+          <InfoRow label="average" value={previousEducation.average} />
+          <InfoRow label="language" value={previousEducation.language} />
+          <InfoRow label="country" value={previousEducation.country} />
+          <InfoRow label="previous_university" value={previousEducation.previousUniversity} />
+          <InfoRow label="previous_faculty" value={previousEducation.previousFaculty} />
+          <InfoRow label="previous_study_mode" value={previousEducation.previousStudyMode} />
+        </Section>
+
+        {/* Enrollment Information */}
+        <Section title="enrollment_info" icon={faGraduationCap}>
+          <InfoRow label="enrollment_year" value={enrollmentInfo.enrollmentYear} />
+          <InfoRow label="status" value={enrollmentInfo.status} />
+          <InfoRow label="cycle" value={enrollmentInfo.cycle} />
+          <InfoRow label="program" value={enrollmentInfo.program} />
+          <InfoRow label="quota" value={enrollmentInfo.quota} />
+          <InfoRow label="secondary_education_number" value={enrollmentInfo.secondaryEducationNumber} />
+          <InfoRow label="previous_education_credits" value={enrollmentInfo.previousEducationCredits} />
+        </Section>
+
+        {/* Contact Information */}
+        <div className="lg:col-span-2">
+          <Section title="contact" icon={faAddressCard}>
+            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+              <div>
+                <InfoRow 
+                  label="place_of_residence" 
+                  value={contact.placeOfResidence} 
+                  icon={faMapMarkerAlt} 
+                />
+                <InfoRow label="municipality_of_residence" value={contact.municipalityOfResidence} />
+                <InfoRow label="country" value={contact.country} />
+                <InfoRow label="address" value={contact.address} />
+                <InfoRow label="temporary_address" value={contact.temporaryAddress} />
+              </div>
+              <div>
+                <InfoRow label="phone" value={contact.phone} icon={faPhone} />
+                <InfoRow label="mobile_phone" value={contact.mobilePhone} icon={faPhone} />
+                <InfoRow label="passport_number" value={contact.passportNumber} icon={faPassport} />
+                <InfoRow label="passport_expiry_date" value={contact.passportExpiryDate} />
+                <InfoRow 
+                  label="email" 
+                  value={contact.email} 
+                  icon={faEnvelope} 
+                />
+                <InfoRow 
+                  label="microsoft_email" 
+                  value={contact.microsoftEmail} 
+                  icon={faEnvelope} 
+                />
+              </div>
+            </div>
+          </Section>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/app/students/semesters/page.tsx
===================================================================
--- frontend/src/app/students/semesters/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/semesters/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,397 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { 
+  faCalendarAlt, 
+  faCheck, 
+  faTimes, 
+  faFileAlt, 
+  faMoneyBillWave,
+  faSignature,
+  faCheckCircle,
+  faTimesCircle
+} from '@fortawesome/free-solid-svg-icons';
+import { useEffect, useState } from 'react';
+import { getAccessToken } from '@/lib/auth';
+import { useTranslation } from 'react-i18next';
+import { apiUrl } from '@/lib/api';
+import EnrollSemesterDialog from '@/components/enroll-semester-dialog';
+
+type Semester = {
+  id: number | string;
+  semester: string;
+  direction: string;
+  quota: string;
+  note: string;
+  studentCom: string;
+  sum: string;
+  paid: string;
+  ukim: string;
+  createdOn: string;
+  dateChanged: string;
+  credits: string;
+  type: string;
+  doc: string;
+  doc1: string;
+  verified: string;
+  taxes: string;
+  signatures: string;
+  status: string;
+  completed: string;
+};
+
+type SemestersResponse = {
+  semesters: Semester[];
+};
+
+interface TableCellProps {
+  children: React.ReactNode;
+  className?: string;
+}
+
+const TableCell = ({ children, className = "" }: TableCellProps) => (
+  <td className={`px-4 py-3 text-sm border-b border-border ${className}`}>
+    {children}
+  </td>
+);
+
+const StatusBadge = ({ status }: { status: string }) => {
+  const { t } = useTranslation();
+  const baseClasses = "px-2 py-1 rounded-full text-xs font-medium";
+  if (status === 'Валидно') {
+    return (
+      <span className={`${baseClasses} bg-green-100 text-green-800 flex items-center gap-1`}>
+        <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" />
+        {t('Валидно', 'Валидно')}
+      </span>
+    );
+  }
+  return (
+    <span className={`${baseClasses} bg-accent text-gray-800`}>
+      {t(status, status)}
+    </span>
+  );
+};
+
+const YesNoBadge = ({ value }: { value: string }) => {
+  const { t } = useTranslation();
+  if (value === 'Да') {
+    return (
+      <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full">
+        <FontAwesomeIcon icon={faCheck} className="w-3 h-3" />
+      </span>
+    );
+  } else if (value === 'Не') {
+    return (
+      <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full">
+        <FontAwesomeIcon icon={faTimes} className="w-3 h-3" />
+      </span>
+    );
+  }
+  return <span className="text-muted-foreground">—</span>;
+};
+
+const SignatureBadge = ({ signatures }: { signatures: string }) => {
+  const [completed, total] = signatures.split('/').map(Number);
+  const percentage = total > 0 ? (completed / total) * 100 : 0;
+  
+  let colorClass = "text-red-600 bg-red-100";
+  if (percentage === 100) {
+    colorClass = "text-green-600 bg-green-100";
+  } else if (percentage >= 50) {
+    colorClass = "text-yellow-600 bg-yellow-100";
+  }
+  
+  return (
+    <span className={`px-2 py-1 rounded-full text-xs font-medium flex items-center gap-1 ${colorClass}`}>
+      <FontAwesomeIcon icon={faSignature} className="w-3 h-3" />
+      {signatures}
+    </span>
+  );
+};
+
+export default function SemestersPage() {
+  const [semesters, setSemesters] = useState<Semester[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const [enrollOpen, setEnrollOpen] = useState(false);
+  // Bumped after a successful enrolment so the table reloads.
+  const [reloadKey, setReloadKey] = useState(0);
+  const { t } = useTranslation();
+
+  useEffect(() => {
+    let cancelled = false;
+
+    async function load() {
+      setIsLoading(true);
+      setErrorMessage(null);
+
+      const token = getAccessToken();
+      if (!token) {
+        setErrorMessage('Not authenticated. Please login again.');
+        setIsLoading(false);
+        return;
+      }
+
+      try {
+        const response = await fetch(apiUrl('/api/user/getSemesters'), {
+          method: 'GET',
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+
+        if (!response.ok) {
+          const text = await response.text().catch(() => '');
+          throw new Error(text || `Failed to load semesters (${response.status})`);
+        }
+
+        const data = (await response.json()) as SemestersResponse;
+        if (!cancelled) setSemesters(Array.isArray(data?.semesters) ? data.semesters : []);
+      } catch (err) {
+        if (!cancelled) {
+          setErrorMessage(err instanceof Error ? err.message : 'Failed to load semesters.');
+        }
+      } finally {
+        if (!cancelled) setIsLoading(false);
+      }
+    }
+
+    void load();
+
+    return () => {
+      cancelled = true;
+    };
+  }, [reloadKey]);
+
+  if (isLoading) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="bg-card rounded-xl shadow-sm border border-border p-6">
+          {t('loading')}
+        </div>
+      </div>
+    );
+  }
+
+  if (errorMessage) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
+          {errorMessage}
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="min-h-screen pb-8">
+      {/* Header */}
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
+          <div className="flex items-center gap-4">
+            <div className="bg-white rounded-full p-4">
+              <FontAwesomeIcon icon={faCalendarAlt} className="text-3xl text-[#0272D1]" />
+            </div>
+            <div>
+              <h1 className="text-3xl font-bold mb-2">{t('semesters')}</h1>
+              <p className="text-lg opacity-90">
+                {t('semesters_overview')}
+              </p>
+            </div>
+          </div>
+
+          <button
+            className="self-start rounded-lg bg-white px-5 py-3 font-semibold text-[#0272D1] shadow-sm hover:bg-white/90 md:self-auto"
+            onClick={() => setEnrollOpen(true)}
+          >
+            + {t('enroll_semester')}
+          </button>
+        </div>
+      </div>
+
+      {/* Table Container */}
+      <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+        <div className="bg-primary text-white px-6 py-4">
+          <h2 className="text-xl font-bold">{t('semesters_list')}</h2>
+        </div>
+        
+        <div className="overflow-x-auto">
+          <table className="w-full">
+            <thead className="bg-accent">
+              <tr>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">#</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('semester')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('direction')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('quota')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('note')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('student_com')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('sum')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('paid')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('ukim')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('created_on')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('date_changed')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('credits')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('type')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('doc')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('doc1')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('verified')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('taxes')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('signatures')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('status')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('completed')}</th>
+              </tr>
+            </thead>
+            <tbody className="divide-y divide-border">
+              {semesters.map((semester) => (
+                <tr key={semester.id} className="hover:bg-accent transition-colors">
+                  <TableCell className="font-medium text-card-foreground">{semester.id}</TableCell>
+                  <TableCell className="font-medium text-primary">{semester.semester}</TableCell>
+                  <TableCell>{semester.direction}</TableCell>
+                  <TableCell className="max-w-xs">
+                    <div className="truncate" title={semester.quota}>
+                      {semester.quota}
+                    </div>
+                  </TableCell>
+                  <TableCell>
+                    {semester.note ? (
+                      <span className="text-yellow-600 font-medium" title={semester.note}>
+                        {semester.note.length > 10 ? `${semester.note.substring(0, 10)}...` : semester.note}
+                      </span>
+                    ) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell>
+                    {semester.studentCom ? (
+                      <span className="text-blue-600 font-medium" title={semester.studentCom}>
+                        {semester.studentCom.length > 10 ? `${semester.studentCom.substring(0, 10)}...` : semester.studentCom}
+                      </span>
+                    ) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell className="font-medium">
+                    {semester.sum ? (
+                      <span className="text-green-600 flex items-center gap-1">
+                        <FontAwesomeIcon icon={faMoneyBillWave} className="w-3 h-3" />
+                        {semester.sum}
+                      </span>
+                    ) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell className="font-medium">
+                    {semester.paid ? (
+                      <span className="text-green-600 flex items-center gap-1">
+                        <FontAwesomeIcon icon={faMoneyBillWave} className="w-3 h-3" />
+                        {semester.paid}
+                      </span>
+                    ) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell className="font-medium">
+                    {semester.ukim ? (
+                      <span className="text-blue-600">{semester.ukim}</span>
+                    ) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell className="text-muted-foreground">{semester.createdOn}</TableCell>
+                  <TableCell className="text-muted-foreground">{semester.dateChanged}</TableCell>
+                  <TableCell className="font-medium text-primary">{semester.credits}</TableCell>
+                  <TableCell>
+                    <span className="px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">
+                      {semester.type}
+                    </span>
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <YesNoBadge value={semester.doc} />
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <YesNoBadge value={semester.doc1} />
+                  </TableCell>
+                  <TableCell className="text-center">
+                    <YesNoBadge value={semester.verified} />
+                  </TableCell>
+                  <TableCell className="font-medium text-green-600">{semester.taxes}</TableCell>
+                  <TableCell>
+                    <SignatureBadge signatures={semester.signatures} />
+                  </TableCell>
+                  <TableCell>
+                    <StatusBadge status={semester.status} />
+                  </TableCell>
+                  <TableCell>
+                    {semester.completed !== "Не" ? (
+                      <span className="text-green-600 font-medium flex items-center gap-1">
+                        <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" />
+                        {t(semester.completed, semester.completed)}
+                      </span>
+                    ) : (
+                      <span className="text-red-600 font-medium flex items-center gap-1">
+                        <FontAwesomeIcon icon={faTimesCircle} className="w-3 h-3" />
+                        {t('Не', 'Не')}
+                      </span>
+                    )}
+                  </TableCell>
+                </tr>
+              ))}
+            </tbody>
+          </table>
+        </div>
+      </div>
+
+      {/* Summary Cards */}
+      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-green-100 text-green-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faCheckCircle} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('completed_semesters')}</h3>
+              <p className="text-2xl font-bold text-green-600">
+                {semesters.filter(s => s.completed !== "Не").length}
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-blue-100 text-blue-600 rounded-full p-3">
+              <FontAwesomeIcon icon={faFileAlt} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('verified_semesters')}</h3>
+              <p className="text-2xl font-bold text-blue-600">
+                {semesters.filter(s => s.verified === "Да").length}
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+          <div className="flex items-center gap-4">
+            <div className="bg-primary text-white rounded-full p-3">
+              <FontAwesomeIcon icon={faCalendarAlt} className="text-xl" />
+            </div>
+            <div>
+              <h3 className="text-lg font-bold text-card-foreground">{t('total_semesters')}</h3>
+              <p className="text-2xl font-bold text-primary">
+                {semesters.length}
+              </p>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <EnrollSemesterDialog
+        open={enrollOpen}
+        onClose={() => setEnrollOpen(false)}
+        onEnrolled={() => setReloadKey((k) => k + 1)}
+      />
+    </div>
+  );
+}
Index: frontend/src/app/students/subjects/page.tsx
===================================================================
--- frontend/src/app/students/subjects/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/app/students/subjects/page.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,529 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { 
+  faBook, 
+  faChevronDown,
+  faFileInvoice
+} from '@fortawesome/free-solid-svg-icons';
+import { useEffect, useState } from 'react';
+import { getAccessToken } from '@/lib/auth';
+import { useTranslation } from 'react-i18next';
+import { apiUrl } from '@/lib/api';
+
+interface Subject {
+  id: number;
+  code: string;
+  hours: string;
+  kojPat: number;
+  name: string;
+  semester: number;
+  status: string;
+  signature: string;
+  group: string;
+  professor: string;
+}
+
+type SemesterInfo = {
+  id: number;
+  name: string;
+  status: string;
+  serviceNumber: string | number;
+};
+
+type FinancialInfo = {
+  sum: string | number;
+  paid: string;
+  due: string;
+  materialCosts: string;
+  credits: string;
+  MKSA: string;
+  electronicRegistration: string;
+  eUKIM: string;
+  bankProvision: string;
+  total: string;
+};
+
+type CurrentSemester = {
+  id: string;
+  name: string;
+  status: string;
+  serviceNumber: string | number;
+  ticketNumber: string;
+  debt: string;
+  financialInfo: FinancialInfo;
+};
+
+type SubjectsResponse = {
+  currentSemester: CurrentSemester;
+  semesters: SemesterInfo[];
+  subjectsBySemester: Record<string, Subject[]>;
+  semesterKeyById: Record<number, string>;
+};
+
+type ApiCurrentSemester = {
+  id: string;
+  name: string;
+  status: string;
+  serviceNumber: string | number;
+  ticketNumber: string;
+  debt: string;
+  financialInfo: {
+    sum: string | number;
+    paid: string;
+    due: string;
+    materialCosts: string;
+    credits: string;
+    totalCredits?: string;
+    mksa?: string;
+    MKSA?: string;
+    electronicRegistration: string;
+    eUKIM: string;
+    bankProvision: string;
+    total: string;
+  };
+};
+
+type ApiSubjectsResponse = {
+  semesters: SemesterInfo[];
+  currentSemester?: ApiCurrentSemester;
+  currentSemestar?: ApiCurrentSemester;
+  subjectsBySemester: Record<string, Subject[]>;
+};
+
+function normalizeKey(input: string) {
+  return input.toLowerCase().replace(/\s|\(|\)|\.|,/g, '');
+}
+
+function detectSeasonFromName(name: string): 'summer' | 'winter' | null {
+  const n = name.toLowerCase();
+  if (n.includes('летен')) return 'summer';
+  if (n.includes('зимски')) return 'winter';
+  return null;
+}
+
+function buildSemesterKeyById(semesters: SemesterInfo[], keys: string[]) {
+  const keyBySeason: Partial<Record<'summer' | 'winter', string>> = {};
+  for (const key of keys) {
+    const k = key.toLowerCase();
+    if (k.includes('summer')) keyBySeason.summer = key;
+    if (k.includes('winter')) keyBySeason.winter = key;
+  }
+
+  const mapping: Record<number, string> = {};
+  for (const s of semesters) {
+    const season = detectSeasonFromName(s.name);
+    const mapped = season ? keyBySeason[season] : undefined;
+    if (mapped) mapping[s.id] = mapped;
+  }
+
+  // Fallback: if we couldn't infer seasons, try matching by normalized names.
+  if (Object.keys(mapping).length === 0) {
+    for (const s of semesters) {
+      const ns = normalizeKey(s.name);
+      const match = keys.find((k) => normalizeKey(k).includes(ns) || ns.includes(normalizeKey(k)));
+      if (match) mapping[s.id] = match;
+    }
+  }
+
+  // Last resort: map in order.
+  if (Object.keys(mapping).length === 0) {
+    semesters.forEach((s, idx) => {
+      if (keys[idx]) mapping[s.id] = keys[idx];
+    });
+  }
+
+  return mapping;
+}
+
+interface TableCellProps {
+  children: React.ReactNode;
+  className?: string;
+}
+
+const TableCell = ({ children, className = "" }: TableCellProps) => (
+  <td className={`px-4 py-3 text-sm border-b border-border ${className}`}>
+    {children}
+  </td>
+);
+
+const StatusBadge = ({ status }: { status: string }) => {
+  const { t } = useTranslation();
+  const baseClasses = "px-3 py-1 rounded-md text-xs font-medium";
+  if (status === t('mandatory_short')) {
+    return (
+      <span className={`${baseClasses} bg-blue-100 text-blue-800`}>
+        {t('mandatory_short')}
+      </span>
+    );
+  } else if (status === t('elective_short')) {
+    return (
+      <span className={`${baseClasses} bg-green-100 text-green-800`}>
+        {t('elective_short')}
+      </span>
+    );
+  }
+  return (
+    <span className={`${baseClasses} bg-accent text-gray-800`}>
+      {t(status) || status}
+    </span>
+  );
+};
+
+export default function SubjectsPage() {
+  const [subjectsData, setSubjectsData] = useState<SubjectsResponse | null>(null);
+  const [selectedSemester, setSelectedSemester] = useState<number | null>(null);
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+  const [isLoading, setIsLoading] = useState(true);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const { t } = useTranslation();
+
+  useEffect(() => {
+    let cancelled = false;
+
+    async function load() {
+      setIsLoading(true);
+      setErrorMessage(null);
+
+      const token = getAccessToken();
+      if (!token) {
+        setErrorMessage('Not authenticated. Please login again.');
+        setIsLoading(false);
+        return;
+      }
+
+      try {
+        const response = await fetch(apiUrl('/api/user/getSubjects'), {
+          method: 'GET',
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+
+        if (!response.ok) {
+          const text = await response.text().catch(() => '');
+          throw new Error(text || `Failed to load subjects (${response.status})`);
+        }
+
+        const apiData = (await response.json()) as ApiSubjectsResponse;
+        if (cancelled) return;
+
+        const current = apiData.currentSemester ?? apiData.currentSemestar;
+        if (!current) {
+          throw new Error('Response missing currentSemestar/currentSemester');
+        }
+
+        const keys = Object.keys(apiData.subjectsBySemester ?? {});
+        const semesterKeyById = buildSemesterKeyById(apiData.semesters ?? [], keys);
+
+        const normalized: SubjectsResponse = {
+          semesters: apiData.semesters ?? [],
+          subjectsBySemester: apiData.subjectsBySemester ?? {},
+          semesterKeyById,
+          currentSemester: {
+            id: current.id,
+            name: current.name,
+            status: current.status,
+            serviceNumber: current.serviceNumber,
+            ticketNumber: current.ticketNumber,
+            debt: current.debt,
+            financialInfo: {
+              sum: current.financialInfo.sum,
+              paid: current.financialInfo.paid,
+              due: current.financialInfo.due,
+              materialCosts: current.financialInfo.materialCosts,
+              credits: current.financialInfo.credits,
+              MKSA: current.financialInfo.MKSA ?? current.financialInfo.mksa ?? '',
+              electronicRegistration: current.financialInfo.electronicRegistration,
+              eUKIM: current.financialInfo.eUKIM,
+              bankProvision: current.financialInfo.bankProvision,
+              total: current.financialInfo.total,
+            },
+          },
+        };
+
+        setSubjectsData(normalized);
+
+        setSelectedSemester((prev) => {
+          if (prev !== null) return prev;
+          const season = detectSeasonFromName(current.name);
+          if (season) {
+            const key = keys.find((k) => k.toLowerCase().includes(season));
+            if (key) {
+              const matchId = normalized.semesters.find((s) => normalized.semesterKeyById[s.id] === key)?.id;
+              if (typeof matchId === 'number') return matchId;
+            }
+          }
+          return normalized.semesters[0]?.id ?? null;
+        });
+      } catch (err) {
+        if (!cancelled) {
+          setErrorMessage(err instanceof Error ? err.message : 'Failed to load subjects.');
+        }
+      } finally {
+        if (!cancelled) setIsLoading(false);
+      }
+    }
+
+    void load();
+
+    return () => {
+      cancelled = true;
+    };
+  }, []);
+
+  if (isLoading) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="bg-card rounded-xl shadow-sm border border-border p-6">
+          {t('loading')}
+        </div>
+      </div>
+    );
+  }
+
+  if (errorMessage || !subjectsData || selectedSemester === null) {
+    return (
+      <div className="min-h-screen pb-8">
+        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
+          {errorMessage ?? t('failed_to_load_subjects')}
+        </div>
+      </div>
+    );
+  }
+  
+  const currentSemesterData =
+    subjectsData.semesters.find((s) => s.id === selectedSemester) ?? subjectsData.semesters[0];
+  const semesterKey =
+    subjectsData.semesterKeyById[selectedSemester] ??
+    Object.keys(subjectsData.subjectsBySemester)[0];
+  const currentSubjects: Subject[] = semesterKey ? subjectsData.subjectsBySemester[semesterKey] || [] : [];
+  const { currentSemester } = subjectsData;
+
+  return (
+    <div className="min-h-screen pb-8">
+      {/* Header */}
+      <div className="bg-primary text-white rounded-xl p-8 mb-8">
+        <div className="flex items-center gap-4">
+          <div className="bg-white rounded-full p-4">
+            <FontAwesomeIcon icon={faBook} className="text-3xl text-[#0272D1]" />
+          </div>
+          <div>
+            <h1 className="text-3xl font-bold mb-2">{t('subjects')}</h1>
+            <p className="text-lg opacity-90">
+              {t('subjects_overview')}
+            </p>
+          </div>
+        </div>
+      </div>
+
+      {/* Status and Selection Section */}
+      <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
+        
+        {/* Left Column - Status and Dropdown */}
+        <div className="lg:col-span-1 space-y-6">
+          
+          {/* Status Card */}
+          <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+            <div className="text-sm text-muted-foreground mb-2">
+              {t('status')}: <span className="text-primary font-semibold">{t('enrolled_by_student')}</span>
+            </div>
+            <div className="text-sm text-muted-foreground">
+              {t('ticket_number')}: <span className="font-semibold">{currentSemester.ticketNumber}</span>
+            </div>
+          </div>
+
+          {/* Semester Selection */}
+          <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+            <div className="text-sm text-muted-foreground mb-2">
+              {t('debt_from_documents')}: <span className="font-semibold">{currentSemester.debt}</span>
+            </div>
+            
+            <div className="relative mt-4">
+              <label className="block text-sm font-medium text-muted-foreground mb-2">
+                {t('select_semester')}:
+              </label>
+              <div className="relative">
+                <button
+                  onClick={() => setIsDropdownOpen(!isDropdownOpen)}
+                  className="w-full bg-card border border-border rounded-lg px-4 py-3 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
+                >
+                  <div className="flex items-center justify-between">
+                    <span className="text-sm font-medium text-primary">
+                      {currentSemesterData.name}
+                    </span>
+                    <FontAwesomeIcon 
+                      icon={faChevronDown} 
+                      className={`w-4 h-4 text-muted-foreground transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`}
+                    />
+                  </div>
+                </button>
+                
+                {isDropdownOpen && (
+                  <div className="absolute z-10 w-full mt-1 bg-card border border-border rounded-lg shadow-lg">
+                    {subjectsData.semesters.map((semester) => (
+                      <button
+                        key={semester.id}
+                        onClick={() => {
+                          setSelectedSemester(semester.id);
+                          setIsDropdownOpen(false);
+                        }}
+                        className="w-full px-4 py-3 text-left text-sm hover:bg-accent focus:outline-none focus:bg-accent first:rounded-t-lg last:rounded-b-lg"
+                      >
+                        <div className="font-medium text-card-foreground">{semester.name}</div>
+                        <div className="text-xs text-muted-foreground">{t('status')}: {semester.status}</div>
+                      </button>
+                    ))}
+                  </div>
+                )}
+              </div>
+            </div>
+
+            <div className="mt-4 text-sm">
+              <div className="text-primary font-medium">
+                {t('serial_number')}: {currentSemesterData.serviceNumber}
+              </div>
+            </div>
+          </div>
+
+          {/* Enrolled Subjects */}
+          <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
+            <h3 className="font-semibold text-card-foreground mb-3 flex items-center gap-2">
+              <FontAwesomeIcon icon={faBook} className="w-4 h-4 text-primary" />
+              {t('enrolled_subjects')}
+            </h3>
+            <div className="text-3xl font-bold text-primary">
+              {currentSubjects.length}
+            </div>
+          </div>
+        </div>
+
+        {/* Right Column - Financial Information */}
+        <div className="lg:col-span-2">
+          <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+            <div className="bg-primary text-white px-6 py-4">
+              <h2 className="text-xl font-bold flex items-center gap-2">
+                <FontAwesomeIcon icon={faFileInvoice} />
+                {t('financial_info')}
+              </h2>
+            </div>
+            
+            <div className="p-6">
+              <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+                
+                {/* Left Financial Column */}
+                <div className="space-y-4">
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('sum')}:</span>
+                    <span className="font-semibold text-primary">{currentSemester.financialInfo.sum}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('paid')}:</span>
+                    <span className="font-semibold text-green-600">{currentSemester.financialInfo.paid}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('due')}:</span>
+                    <span className="font-semibold text-red-600">{currentSemester.financialInfo.due}</span>
+                  </div>
+                  <div className="mt-4 p-4 bg-blue-50 rounded-lg">
+                    <div className="text-sm font-medium text-blue-800 mb-1">{t('material_costs')}:</div>
+                    <div className="text-sm text-blue-700">{t('material_costs_info')}</div>
+                  </div>
+                </div>
+
+                {/* Right Financial Column */}
+                <div className="space-y-4">
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('credits')}:</span>
+                    <span className="font-semibold text-primary">{currentSemester.financialInfo.credits}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('mksa')}:</span>
+                    <span className="font-semibold">{currentSemester.financialInfo.MKSA}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('electronic_registration')}:</span>
+                    <span className="font-semibold">{currentSemester.financialInfo.electronicRegistration}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('eukim')}:</span>
+                    <span className="font-semibold">{currentSemester.financialInfo.eUKIM}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-2 border-b border-border">
+                    <span className="text-muted-foreground">{t('bank_provision')}:</span>
+                    <span className="font-semibold">{currentSemester.financialInfo.bankProvision}</span>
+                  </div>
+                  <div className="flex justify-between items-center py-3 border-t-2 border-primary bg-primary bg-opacity-5 rounded-lg px-4">
+                    <span className="font-bold text-white">{t('total')}:</span>
+                    <span className="font-bold text-xl text-white">{currentSemester.financialInfo.total}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      {/* Subjects Table */}
+      <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
+        <div className="bg-primary text-white px-6 py-4">
+          <h2 className="text-xl font-bold">{t('subjects_list')}</h2>
+        </div>
+        
+        <div className="overflow-x-auto">
+          <table className="w-full">
+            <thead className="bg-accent">
+              <tr>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">#</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('code')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('hours')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('which_time')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('subject')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('semester')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('status')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('signature')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('group')}</th>
+                <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('professor')}</th>
+              </tr>
+            </thead>
+            <tbody className="divide-y divide-border">
+              {currentSubjects.map((subject: Subject) => (
+                <tr key={subject.id} className="hover:bg-accent transition-colors">
+                  <TableCell className="font-medium text-card-foreground">{subject.id}</TableCell>
+                  <TableCell className="font-mono text-sm text-primary font-medium">{subject.code}</TableCell>
+                  <TableCell className="font-medium">{subject.hours}</TableCell>
+                  <TableCell className="text-center font-medium">{subject.kojPat}</TableCell>
+                  <TableCell className="font-medium text-card-foreground max-w-xs">
+                    <div className="flex items-center gap-2">
+                      <FontAwesomeIcon icon={faBook} className="w-4 h-4 text-primary" />
+                      {t(subject.name, subject.name)}
+                    </div>
+                  </TableCell>
+                  <TableCell className="text-center font-medium text-primary">{t(subject.semester.toString(), subject.semester.toString())}</TableCell>
+                  <TableCell>
+                    <StatusBadge status={subject.status} />
+                  </TableCell>
+                  <TableCell className="text-center">
+                    {subject.signature ? t(subject.signature, subject.signature) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell className="text-center">
+                    {subject.group ? t(subject.group, subject.group) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                  <TableCell>
+                    {subject.professor ? t(subject.professor, subject.professor) : (
+                      <span className="text-muted-foreground">—</span>
+                    )}
+                  </TableCell>
+                </tr>
+              ))}
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/components/LanguageSwitcher.tsx
===================================================================
--- frontend/src/components/LanguageSwitcher.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/LanguageSwitcher.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,29 @@
+"use client";
+import { useTranslation } from 'react-i18next';
+
+export default function LanguageSwitcher() {
+  const { i18n } = useTranslation();
+
+  const changeLanguage = (lng: string) => {
+    i18n.changeLanguage(lng);
+  };
+
+  return (
+    <div className="flex gap-2 items-center">
+      <button
+        onClick={() => changeLanguage('mk')}
+        className={`px-3 py-1 rounded border-2 text-sm font-semibold transition-all duration-150 ${i18n.language === 'mk' ? 'border-primary text-primary bg-accent' : 'border-border text-muted-foreground bg-card hover:border-primary hover:text-primary'}`}
+        aria-label="Macedonian"
+      >
+        MK
+      </button>
+      <button
+        onClick={() => changeLanguage('en')}
+        className={`px-3 py-1 rounded border-2 text-sm font-semibold transition-all duration-150 ${i18n.language === 'en' ? 'border-primary text-primary bg-accent' : 'border-border text-muted-foreground bg-card hover:border-primary hover:text-primary'}`}
+        aria-label="English"
+      >
+        EN
+      </button>
+    </div>
+  );
+}
Index: frontend/src/components/admin-navbar.tsx
===================================================================
--- frontend/src/components/admin-navbar.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/admin-navbar.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,67 @@
+"use client";
+
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faBook, faCalendarPlus, faChalkboardUser, faBars, faTimes, faUserGear } from "@fortawesome/free-solid-svg-icons";
+import Link from "next/link";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+
+const AdminNavbar = () => {
+  const [isMenuOpen, setIsMenuOpen] = useState(false);
+  const toggleMenu = () => setIsMenuOpen((v) => !v);
+
+  const { t } = useTranslation();
+  const menuItems = [
+    { icon: faBook, label: t("admin_subjects"), href: "/admin/subjects" },
+    { icon: faCalendarPlus, label: t("admin_semesters"), href: "/admin/semesters" },
+    { icon: faChalkboardUser, label: t("admin_schedule"), href: "/admin/schedule" },
+  ];
+
+  return (
+    <>
+      {/* Desktop Navbar */}
+      <nav className="hidden md:flex flex-row gap-2 justify-evenly bg-primary text-primary-foreground p-7 rounded-xl my-5 text-xl">
+        {menuItems.map((item) => (
+          <Link key={item.href} href={item.href}>
+            <div className="group flex flex-row items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-all duration-300 hover:bg-card hover:text-card-foreground hover:scale-105 hover:shadow-lg">
+              <FontAwesomeIcon icon={item.icon} className="transition-transform duration-300 group-hover:rotate-12" />
+              <span>{item.label}</span>
+            </div>
+          </Link>
+        ))}
+      </nav>
+
+      {/* Mobile Navbar */}
+      <nav className="md:hidden bg-primary text-primary-foreground rounded-xl my-5">
+        <div className="flex justify-between items-center p-4">
+          <div className="flex items-center gap-2 text-xl font-bold">
+            <FontAwesomeIcon icon={faUserGear} className="text-xl" />
+            {t("admin_portal")}
+          </div>
+          <button
+            onClick={toggleMenu}
+            className="p-2 rounded-lg hover:bg-card hover:text-card-foreground transition-colors duration-300"
+            aria-label="Toggle menu"
+          >
+            <FontAwesomeIcon icon={isMenuOpen ? faTimes : faBars} className="text-xl transition-transform duration-300" />
+          </button>
+        </div>
+
+        <div className={`overflow-hidden transition-all duration-300 ease-in-out ${isMenuOpen ? "max-h-64 opacity-100" : "max-h-0 opacity-0"}`}>
+          <div className="px-4 pb-4 space-y-2">
+            {menuItems.map((item) => (
+              <Link key={item.href} href={item.href}>
+                <div className="group flex flex-row items-center gap-3 px-3 py-3 rounded-lg cursor-pointer transition-all duration-300 hover:bg-card hover:text-card-foreground">
+                  <FontAwesomeIcon icon={item.icon} className="transition-transform duration-300 group-hover:rotate-12" />
+                  <span className="text-lg">{item.label}</span>
+                </div>
+              </Link>
+            ))}
+          </div>
+        </div>
+      </nav>
+    </>
+  );
+};
+
+export default AdminNavbar;
Index: frontend/src/components/contact-bubble.tsx
===================================================================
--- frontend/src/components/contact-bubble.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/contact-bubble.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,176 @@
+"use client"
+
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faEnvelope, faTimes, faPaperPlane, faSpinner } from '@fortawesome/free-solid-svg-icons';
+
+export default function ContactBubble() {
+  const { t } = useTranslation();
+  const [isOpen, setIsOpen] = useState(false);
+  const [isSending, setIsSending] = useState(false);
+  const [sent, setSent] = useState(false);
+  const [form, setForm] = useState({ name: '', email: '', subject: '', message: '' });
+
+  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+    setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }));
+  };
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    setIsSending(true);
+    // Simulate sending
+    await new Promise((r) => setTimeout(r, 1200));
+    setIsSending(false);
+    setSent(true);
+    setTimeout(() => {
+      setSent(false);
+      setIsOpen(false);
+      setForm({ name: '', email: '', subject: '', message: '' });
+    }, 2500);
+  };
+
+  return (
+    <>
+      {/* Floating bubble */}
+      <button
+        onClick={() => setIsOpen(true)}
+        className={`fixed bottom-6 right-6 z-50 flex items-center gap-2 bg-primary hover:bg-blue-700 text-white px-5 py-3 rounded-full shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl ${isOpen ? 'scale-0 opacity-0' : 'scale-100 opacity-100'}`}
+        aria-label={t('contact_us', 'Контактирај нè')}
+      >
+        <FontAwesomeIcon icon={faEnvelope} className="w-5 h-5" />
+        <span className="font-medium text-sm">{t('contact_us', 'Контактирај нè')}</span>
+      </button>
+
+      {/* Backdrop */}
+      {isOpen && (
+        <div
+          className="fixed inset-0 z-50 bg-black/40 backdrop-blur-sm transition-opacity duration-300"
+          onClick={() => setIsOpen(false)}
+        />
+      )}
+
+      {/* Modal */}
+      <div
+        className={`fixed z-50 bottom-6 right-6 w-[380px] max-w-[calc(100vw-2rem)] bg-card border border-border rounded-2xl shadow-2xl transition-all duration-300 origin-bottom-right ${isOpen ? 'scale-100 opacity-100' : 'scale-0 opacity-0 pointer-events-none'}`}
+      >
+        {/* Modal header */}
+        <div className="bg-primary text-white rounded-t-2xl px-6 py-4 flex items-center justify-between">
+          <div className="flex items-center gap-3">
+            <FontAwesomeIcon icon={faEnvelope} className="w-5 h-5" />
+            <h2 className="text-lg font-bold">{t('contact_us', 'Контактирај нè')}</h2>
+          </div>
+          <button
+            onClick={() => setIsOpen(false)}
+            className="text-white/80 hover:text-white transition-colors p-1"
+            aria-label={t('close', 'Затвори')}
+          >
+            <FontAwesomeIcon icon={faTimes} className="w-5 h-5" />
+          </button>
+        </div>
+
+        {/* Modal body */}
+        <div className="p-6">
+          {sent ? (
+            <div className="text-center py-8">
+              <div className="w-16 h-16 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
+                <FontAwesomeIcon icon={faPaperPlane} className="w-7 h-7" />
+              </div>
+              <h3 className="text-lg font-bold text-card-foreground mb-1">
+                {t('contact_sent_title', 'Пораката е испратена!')}
+              </h3>
+              <p className="text-sm text-muted-foreground">
+                {t('contact_sent_desc', 'Ќе ви одговориме наскоро.')}
+              </p>
+            </div>
+          ) : (
+            <form onSubmit={handleSubmit} className="space-y-4">
+              {/* Name */}
+              <div>
+                <label className="block text-sm font-medium text-muted-foreground mb-1">
+                  {t('contact_name', 'Име и презиме')}
+                </label>
+                <input
+                  type="text"
+                  name="name"
+                  required
+                  value={form.name}
+                  onChange={handleChange}
+                  placeholder={t('contact_name_placeholder', 'Вашето име')}
+                  className="w-full border border-border rounded-lg px-3 py-2 text-sm bg-card text-card-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
+                />
+              </div>
+
+              {/* Email */}
+              <div>
+                <label className="block text-sm font-medium text-muted-foreground mb-1">
+                  {t('contact_email', 'Е-пошта')}
+                </label>
+                <input
+                  type="email"
+                  name="email"
+                  required
+                  value={form.email}
+                  onChange={handleChange}
+                  placeholder={t('contact_email_placeholder', 'example@email.com')}
+                  className="w-full border border-border rounded-lg px-3 py-2 text-sm bg-card text-card-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
+                />
+              </div>
+
+              {/* Subject */}
+              <div>
+                <label className="block text-sm font-medium text-muted-foreground mb-1">
+                  {t('contact_subject', 'Наслов')}
+                </label>
+                <input
+                  type="text"
+                  name="subject"
+                  required
+                  value={form.subject}
+                  onChange={handleChange}
+                  placeholder={t('contact_subject_placeholder', 'Тема на пораката')}
+                  className="w-full border border-border rounded-lg px-3 py-2 text-sm bg-card text-card-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
+                />
+              </div>
+
+              {/* Message */}
+              <div>
+                <label className="block text-sm font-medium text-muted-foreground mb-1">
+                  {t('contact_message', 'Порака')}
+                </label>
+                <textarea
+                  name="message"
+                  required
+                  rows={3}
+                  value={form.message}
+                  onChange={handleChange}
+                  placeholder={t('contact_message_placeholder', 'Напишете ја вашата порака...')}
+                  className="w-full border border-border rounded-lg px-3 py-2 text-sm bg-card text-card-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary resize-none"
+                />
+              </div>
+
+              {/* Submit */}
+              <button
+                type="submit"
+                disabled={isSending}
+                className="w-full bg-primary hover:bg-blue-700 text-white font-medium py-2.5 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
+              >
+                {isSending ? (
+                  <>
+                    <FontAwesomeIcon icon={faSpinner} className="w-4 h-4 animate-spin" />
+                    {t('contact_sending', 'Испраќа...')}
+                  </>
+                ) : (
+                  <>
+                    <FontAwesomeIcon icon={faPaperPlane} className="w-4 h-4" />
+                    {t('contact_send', 'Испрати')}
+                  </>
+                )}
+              </button>
+            </form>
+          )}
+        </div>
+      </div>
+    </>
+  );
+}
Index: frontend/src/components/enroll-semester-dialog.tsx
===================================================================
--- frontend/src/components/enroll-semester-dialog.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/enroll-semester-dialog.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,287 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { apiUrl } from "@/lib/api";
+import { getAccessToken } from "@/lib/auth";
+
+type SubjectOption = {
+  id: number;
+  name?: string;
+  code?: string;
+  credits: number;
+  mandatorySemester: number;
+  alreadyPassed: boolean;
+};
+
+type MajorOption = {
+  id: number;
+  name?: string;
+  subjects: SubjectOption[];
+};
+
+type SemesterOption = {
+  id: number;
+  name?: string;
+  year: number;
+  type?: string;
+};
+
+type EnrollmentOptions = {
+  requiredSubjects: number;
+  semesters: SemesterOption[];
+  majors: MajorOption[];
+  defaultMajorId: number | null;
+};
+
+export default function EnrollSemesterDialog({
+  open,
+  onClose,
+  onEnrolled,
+}: {
+  open: boolean;
+  onClose: () => void;
+  onEnrolled: () => void;
+}) {
+  const { t } = useTranslation();
+
+  const [options, setOptions] = useState<EnrollmentOptions | null>(null);
+  const [loading, setLoading] = useState(false);
+  const [submitting, setSubmitting] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+
+  const [semesterId, setSemesterId] = useState<number | null>(null);
+  const [majorId, setMajorId] = useState<number | null>(null);
+  const [picked, setPicked] = useState<number[]>([]);
+
+  useEffect(() => {
+    if (!open) return;
+
+    let cancelled = false;
+
+    async function load() {
+      setLoading(true);
+      setError(null);
+      try {
+        const token = getAccessToken();
+        if (!token) throw new Error(t("enroll_not_signed_in"));
+
+        const res = await fetch(apiUrl("/api/enrollment/options"), {
+          cache: "no-store",
+          headers: { Authorization: `Bearer ${token}` },
+        });
+        if (!res.ok) throw new Error(`${t("enroll_options_failed")} (${res.status})`);
+
+        const data = (await res.json()) as EnrollmentOptions;
+        if (cancelled) return;
+
+        setOptions(data);
+        setSemesterId(data.semesters[0]?.id ?? null);
+        setMajorId(data.defaultMajorId ?? data.majors[0]?.id ?? null);
+        setPicked([]);
+      } catch (e) {
+        if (!cancelled) setError(e instanceof Error ? e.message : String(e));
+      } finally {
+        if (!cancelled) setLoading(false);
+      }
+    }
+
+    void load();
+    return () => {
+      cancelled = true;
+    };
+  }, [open, t]);
+
+  const required = options?.requiredSubjects ?? 5;
+
+  const subjects = useMemo(
+    () => options?.majors.find((m) => m.id === majorId)?.subjects ?? [],
+    [options, majorId],
+  );
+
+  function toggle(subjectId: number) {
+    setPicked((prev) => {
+      if (prev.includes(subjectId)) return prev.filter((id) => id !== subjectId);
+      if (prev.length >= required) return prev; // the cap is the point of the form
+      return [...prev, subjectId];
+    });
+  }
+
+  async function submit() {
+    setSubmitting(true);
+    setError(null);
+    try {
+      const token = getAccessToken();
+      if (!token) throw new Error(t("enroll_not_signed_in"));
+
+      const res = await fetch(apiUrl("/api/enrollment"), {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+          Authorization: `Bearer ${token}`,
+        },
+        body: JSON.stringify({
+          SemesterId: semesterId,
+          MajorId: majorId,
+          SubjectIds: picked,
+        }),
+      });
+
+      const body = (await res.json()) as { ok: boolean; message?: string };
+      if (!res.ok || !body.ok) {
+        throw new Error(body.message || `${t("enroll_failed")} (${res.status})`);
+      }
+
+      onEnrolled();
+      onClose();
+    } catch (e) {
+      setError(e instanceof Error ? e.message : String(e));
+    } finally {
+      setSubmitting(false);
+    }
+  }
+
+  if (!open) return null;
+
+  const noSemestersLeft = !loading && options !== null && options.semesters.length === 0;
+  const canSubmit =
+    !submitting && semesterId !== null && majorId !== null && picked.length === required;
+
+  return (
+    <div
+      className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/50 p-4 sm:items-center"
+      role="dialog"
+      aria-modal="true"
+      onClick={onClose}
+    >
+      <div
+        className="w-full max-w-3xl rounded-xl border border-border bg-card shadow-lg"
+        onClick={(e) => e.stopPropagation()}
+      >
+        <div className="flex items-center justify-between rounded-t-xl bg-primary px-6 py-4 text-white">
+          <h2 className="text-xl font-bold">{t("enroll_semester")}</h2>
+          <button
+            className="rounded-lg px-3 py-1 text-sm hover:bg-white/20"
+            onClick={onClose}
+            aria-label={t("close")}
+          >
+            ✕
+          </button>
+        </div>
+
+        <div className="p-6">
+          {loading ? (
+            <p className="text-muted-foreground">{t("loading")}</p>
+          ) : noSemestersLeft ? (
+            <p className="text-muted-foreground">{t("enroll_no_semesters")}</p>
+          ) : (
+            <>
+              <div className="grid gap-4 sm:grid-cols-2">
+                <div className="flex flex-col">
+                  <label className="text-sm text-muted-foreground">{t("semester")}</label>
+                  <select
+                    className="rounded-lg border border-border bg-background px-3 py-2"
+                    value={semesterId ?? ""}
+                    onChange={(e) => setSemesterId(Number.parseInt(e.target.value, 10))}
+                  >
+                    {options?.semesters.map((s) => (
+                      <option key={s.id} value={s.id}>
+                        {s.name}
+                      </option>
+                    ))}
+                  </select>
+                </div>
+
+                <div className="flex flex-col">
+                  <label className="text-sm text-muted-foreground">{t("direction")}</label>
+                  <select
+                    className="rounded-lg border border-border bg-background px-3 py-2"
+                    value={majorId ?? ""}
+                    onChange={(e) => {
+                      setMajorId(Number.parseInt(e.target.value, 10));
+                      setPicked([]); // subjects differ per programme
+                    }}
+                  >
+                    {options?.majors.map((m) => (
+                      <option key={m.id} value={m.id}>
+                        {m.name}
+                      </option>
+                    ))}
+                  </select>
+                </div>
+              </div>
+
+              <div className="mt-6 flex items-center justify-between">
+                <h3 className="font-semibold text-card-foreground">{t("subjects")}</h3>
+                <span
+                  className={`text-sm font-medium ${
+                    picked.length === required ? "text-green-700" : "text-muted-foreground"
+                  }`}
+                >
+                  {picked.length} / {required}
+                </span>
+              </div>
+
+              <div className="mt-2 max-h-80 overflow-y-auto rounded-lg border border-border">
+                {subjects.map((s) => {
+                  const checked = picked.includes(s.id);
+                  const atLimit = !checked && picked.length >= required;
+                  return (
+                    <label
+                      key={s.id}
+                      className={`flex items-center gap-3 border-b border-border px-4 py-3 last:border-b-0 ${
+                        atLimit ? "opacity-50" : "cursor-pointer hover:bg-accent"
+                      }`}
+                    >
+                      <input
+                        type="checkbox"
+                        checked={checked}
+                        disabled={atLimit}
+                        onChange={() => toggle(s.id)}
+                      />
+                      <span className="flex-1">
+                        <span className="text-card-foreground">{s.name}</span>
+                        <span className="ml-2 font-mono text-xs text-muted-foreground">{s.code}</span>
+                        {s.alreadyPassed && (
+                          <span className="ml-2 rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-800">
+                            {t("enroll_already_passed")}
+                          </span>
+                        )}
+                      </span>
+                      <span className="text-xs text-muted-foreground">
+                        {t("enroll_semester_short")} {s.mandatorySemester} · {s.credits} {t("enroll_credits")}
+                      </span>
+                    </label>
+                  );
+                })}
+              </div>
+            </>
+          )}
+
+          {error && (
+            <div className="mt-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
+              {error}
+            </div>
+          )}
+
+          <div className="mt-6 flex justify-end gap-3">
+            <button
+              className="rounded-lg border border-border px-4 py-2 text-sm font-medium"
+              onClick={onClose}
+              disabled={submitting}
+            >
+              {t("cancel")}
+            </button>
+            <button
+              className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
+              onClick={() => void submit()}
+              disabled={!canSubmit}
+            >
+              {submitting ? t("loading") : t("enroll_confirm")}
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
Index: frontend/src/components/exams.tsx
===================================================================
--- frontend/src/components/exams.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/exams.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,312 @@
+"use client"
+import { useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
+import { getAccessToken } from '@/lib/auth';
+import { apiUrl } from '@/lib/api';
+
+type PassedSubject = {
+  id: number;
+  subjectId: number;
+  code: string;
+  subject: string;
+  credits: number;
+  grade: number;
+  gradeText: string;
+  date: string; // DD.MM.YYYY
+  semester: string;
+  professor: string;
+};
+
+type PassedSubjectsResponse = {
+  passedSubjects: PassedSubject[];
+};
+
+type ExamRow = {
+  id: number;
+  code: string;
+  subject: string;
+  date: string;
+  semester: string;
+  credits: number;
+  grade: number;
+};
+
+const Exams = () => {
+  const { t } = useTranslation();
+  const [sortField, setSortField] = useState<'grade' | 'date' | null>(null);
+  const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
+  const [exams, setExams] = useState<ExamRow[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [errorMessage, setErrorMessage] = useState<string | null>(null);
+
+  useEffect(() => {
+    let cancelled = false;
+
+    async function load() {
+      setIsLoading(true);
+      setErrorMessage(null);
+
+      const token = getAccessToken();
+      if (!token) {
+        setErrorMessage('Not authenticated. Please login again.');
+        setIsLoading(false);
+        return;
+      }
+
+      try {
+        const response = await fetch(apiUrl('/api/user/getPassedSubjects'), {
+          method: 'GET',
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+
+        if (!response.ok) {
+          const text = await response.text().catch(() => '');
+          throw new Error(text || `Failed to load passed subjects (${response.status})`);
+        }
+
+        const data = (await response.json()) as PassedSubjectsResponse;
+        const rows: ExamRow[] = (data?.passedSubjects ?? []).map((s) => ({
+          id: s.id,
+          code: s.code,
+          subject: s.subject,
+          date: s.date,
+          semester: s.semester,
+          credits: s.credits,
+          grade: s.grade,
+        }));
+
+        if (!cancelled) setExams(rows);
+      } catch (err) {
+        if (!cancelled) {
+          setErrorMessage(err instanceof Error ? err.message : 'Failed to load passed subjects.');
+        }
+      } finally {
+        if (!cancelled) setIsLoading(false);
+      }
+    }
+
+    void load();
+
+    return () => {
+      cancelled = true;
+    };
+  }, []);
+
+  const stats = useMemo(() => {
+    const passed = exams.length;
+    const creditsCurrent = exams.reduce((sum, s) => sum + (typeof s.credits === 'number' ? s.credits : 0), 0);
+    const numericGrades = exams.map((e) => (typeof e.grade === 'number' ? e.grade : null)).filter((g): g is number => g !== null);
+    const average =
+      numericGrades.length > 0
+        ? numericGrades.reduce((sum, g) => sum + g, 0) / numericGrades.length
+        : 0;
+
+    const totalCredits = 240;
+    const totalExams = 40;
+    return {
+      average: Number.isFinite(average) ? Number(average.toFixed(2)) : 0,
+      credits: { current: creditsCurrent, total: totalCredits },
+      passed,
+      remaining: Math.max(0, totalExams - passed),
+    };
+  }, [exams]);
+
+  const getGradeColor = (grade: number) => {
+    if (grade >= 9) return "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20";
+    if (grade >= 8) return "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/20";
+    if (grade >= 6) return "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20";
+    return "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20";
+  };
+
+  const handleSortByGrade = () => {
+    if (sortField === 'grade') {
+      if (sortOrder === 'desc') {
+        setSortOrder('asc');
+      } else if (sortOrder === 'asc') {
+        setSortField(null);
+        setSortOrder(null);
+      } else {
+        setSortOrder('desc');
+      }
+    } else {
+      setSortField('grade');
+      setSortOrder('desc');
+    }
+  };
+
+  const handleSortByDate = () => {
+    if (sortField === 'date') {
+      if (sortOrder === 'desc') {
+        setSortOrder('asc');
+      } else if (sortOrder === 'asc') {
+        setSortField(null);
+        setSortOrder(null);
+      } else {
+        setSortOrder('desc');
+      }
+    } else {
+      setSortField('date');
+      setSortOrder('desc');
+    }
+  };
+
+  const getSortedExams = () => {
+    if (!sortField || !sortOrder) return exams;
+    
+    return [...exams].sort((a, b) => {
+      if (sortField === 'grade') {
+        return sortOrder === 'asc' ? a.grade - b.grade : b.grade - a.grade;
+      }
+      
+      if (sortField === 'date') {
+        // Convert date strings to Date objects for proper sorting
+        const dateA = new Date(a.date.split('.').reverse().join('-')); // Convert DD.MM.YYYY to YYYY-MM-DD
+        const dateB = new Date(b.date.split('.').reverse().join('-'));
+        
+        return sortOrder === 'asc' ? dateA.getTime() - dateB.getTime() : dateB.getTime() - dateA.getTime();
+      }
+      
+      return 0;
+    });
+  };
+
+  const getSortIcon = (field: 'grade' | 'date') => {
+    if (sortField === field) {
+      if (sortOrder === 'asc') return faSortUp;
+      if (sortOrder === 'desc') return faSortDown;
+    }
+    return faSort;
+  };
+
+  const creditsPercentage = stats.credits.total > 0 ? (stats.credits.current / stats.credits.total) * 100 : 0;
+
+  if (isLoading) {
+    return (
+      <div className="bg-card rounded-lg shadow-sm p-6">
+        {t('loading', 'Loading...')}
+      </div>
+    );
+  }
+
+  if (errorMessage) {
+    return (
+      <div className="bg-card rounded-lg shadow-sm p-6">
+        <div className="rounded-lg border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400">
+          {t('failed_to_load_exams', errorMessage)}
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="bg-card rounded-lg shadow-sm p-6 space-y-6">
+      {/* Statistics Section */}
+      <div className="space-y-4">
+        <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
+          {/* Average Grade */}
+          <div className="text-center">
+            <div className="text-3xl font-bold text-card-foreground">{stats.average}</div>
+            <div className="text-sm text-muted-foreground mt-1">{t('average', 'Просек')}</div>
+          </div>
+
+          {/* Credits */}
+          <div className="text-center">
+            <div className="text-3xl font-bold text-card-foreground">
+              {stats.credits.current}
+              <span className="text-lg text-muted-foreground">/{stats.credits.total}</span>
+            </div>
+            <div className="text-sm text-muted-foreground mt-1">{t('credits', 'Кредити')}</div>
+          </div>
+
+          {/* Passed Exams */}
+          <div className="text-center">
+            <div className="text-3xl font-bold text-card-foreground">{stats.passed}</div>
+            <div className="text-sm text-muted-foreground mt-1">{t('passed', 'Положени')}</div>
+          </div>
+
+          {/* Remaining Exams */}
+          <div className="text-center">
+            <div className="text-3xl font-bold text-card-foreground">{stats.remaining}</div>
+            <div className="text-sm text-muted-foreground mt-1">{t('remaining', 'Останато')}</div>
+          </div>
+        </div>
+
+        {/* Progress Bar spanning entire statistics section */}
+        <div className="w-full bg-secondary rounded-full h-2">
+          <div
+            className="bg-primary h-2 rounded-full transition-all duration-300"
+            style={{ width: `${creditsPercentage}%` }}
+          ></div>
+        </div>
+      </div>
+
+      {/* Exams Table */}
+      <div>
+        <h3 className="text-xl font-semibold text-card-foreground mb-4">{t('exams', 'Испити')}</h3>
+
+        <div className="overflow-x-auto">
+          <table className="min-w-full">
+            <thead>
+              <tr className="border-b border-border">
+                <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">#</th>
+                <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">{t('subject', 'Предмет')}</th>
+                <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">{t('semester', 'Семестар')}</th>
+                <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">{t('credits', 'Кредити')}</th>
+                <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">
+                  <button
+                    onClick={handleSortByDate}
+                    className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-accent hover:text-accent-foreground transition-all duration-200 transform hover:scale-105"
+                  >
+                    {t('date', 'Датум')}
+                    <FontAwesomeIcon
+                      icon={getSortIcon('date')}
+                      className={`text-xs transition-colors duration-200 ${
+                        sortField === 'date' ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'
+                      }`}
+                    />
+                  </button>
+                </th>
+                <th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">
+                  <button
+                    onClick={handleSortByGrade}
+                    className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-accent hover:text-accent-foreground transition-all duration-200 transform hover:scale-105"
+                  >
+                    {t('grade', 'Оценка')}
+                    <FontAwesomeIcon
+                      icon={getSortIcon('grade')}
+                      className={`text-xs transition-colors duration-200 ${
+                        sortField === 'grade' ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'
+                      }`}
+                    />
+                  </button>
+                </th>
+              </tr>
+            </thead>
+            <tbody className="divide-y divide-border">
+              {getSortedExams().map((exam) => (
+                <tr key={exam.id} className="hover:bg-accent/50 transition-colors duration-150">
+                  <td className="py-4 px-4 text-sm text-card-foreground">{exam.id}</td>
+                  <td className="py-4 px-4 text-sm text-card-foreground font-medium">{t(exam.subject, exam.subject)}</td>
+                  <td className="py-4 px-4 text-sm text-muted-foreground">{t(exam.semester, exam.semester)}</td>
+                  <td className="py-4 px-4 text-sm text-muted-foreground">{exam.credits}</td>
+                  <td className="py-4 px-4 text-sm text-muted-foreground">{exam.date}</td>
+                  <td className="py-4 px-4">
+                    <span className={`inline-flex items-center px-2.5 py-1 rounded-md text-sm font-medium border ${getGradeColor(exam.grade)}`}>
+                      {exam.grade}
+                    </span>
+                  </td>
+                </tr>
+              ))}
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Exams;
Index: frontend/src/components/header.tsx
===================================================================
--- frontend/src/components/header.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/header.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,114 @@
+"use client"
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faUser } from '@fortawesome/free-solid-svg-icons';
+import Image from 'next/image';
+
+import { useEffect, useState } from 'react';
+import { getAccessToken, clearAuthTokens } from '@/lib/auth';
+import { useTranslation } from 'react-i18next';
+import LanguageSwitcher from './LanguageSwitcher';
+import { apiUrl } from '@/lib/api';
+
+const Header = () => {
+
+  const [userName, setUserName] = useState<string>('');
+  const { t } = useTranslation();
+
+  useEffect(() => {
+    async function fetchUser() {
+      const token = getAccessToken();
+      if (!token) return;
+      try {
+        const response = await fetch(apiUrl('/api/user/getUser'), {
+          method: 'GET',
+          headers: { Authorization: `Bearer ${token}` },
+        });
+        if (!response.ok) return;
+        const data = await response.json();
+        // Try both student and professor shape
+        if (data?.personalInfo) {
+          const p = data.personalInfo;
+          setUserName([p.firstName, p.middleName, p.lastName].filter(Boolean).join(' '));
+        }
+      } catch {}
+    }
+    fetchUser();
+    return () => {};
+  }, []);
+
+  const handleLogout = () => {
+    clearAuthTokens();
+    if (typeof window !== 'undefined') {
+      window.location.href = '/';
+    }
+  };
+
+  return (
+    <header className="bg-card shadow-sm border-b border-border">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="flex flex-col sm:flex-row sm:justify-between sm:items-center py-4 gap-4 sm:gap-0">
+          {/* Left side - Logos and University Info */}
+          <div className="flex items-center gap-2 justify-center sm:justify-start">
+            {/* UKIM Logo */}
+            <div className="flex-shrink-0">
+              <Image
+                src="/ukim-logo.png"
+                alt="UKIM Logo"
+                width={40}
+                height={40}
+                className="h-10 w-auto"
+              />
+            </div>
+            {/* FINKI Logo */}
+            <div className="flex-shrink-0">
+              <Image
+                src="/finki-logo.png"
+                alt="FINKI Logo"
+                width={40}
+                height={40}
+                className="h-10 w-auto"
+              />
+            </div>
+            {/* University Text */}
+            <div className="hidden sm:block">
+              <div className="text-card-foreground font-medium text-sm">
+                {t('university')}
+              </div>
+              <div className="text-muted-foreground text-sm">
+                {t('faculty')}
+              </div>
+            </div>
+          </div>
+          {/* Right side - User Profile */}
+          <div className="flex flex-col sm:flex-row items-center gap-2 sm:gap-4 w-full sm:w-auto justify-center sm:justify-end">
+            {/* Language Switcher - now left of user info */}
+            <div className="sm:mr-2 w-full sm:w-auto flex justify-center sm:justify-end">
+              <LanguageSwitcher />
+            </div>
+            <div className="flex items-center gap-2 sm:gap-3 w-full sm:w-auto justify-center sm:justify-end">
+              {/* User Avatar */}
+              <div className="hidden sm:flex items-center justify-center w-10 h-10 bg-primary text-primary-foreground rounded-full">
+                <FontAwesomeIcon icon={faUser} className="text-sm" />
+              </div>
+              {/* User Name */}
+              <div className="hidden sm:block text-card-foreground font-medium">
+                {userName || '...'}
+              </div>
+              {/* Logout Button - more stylish and full width on mobile */}
+              <button
+                className="w-full sm:w-auto ml-0 sm:ml-2 px-4 py-2 border-2 border-primary text-primary bg-card rounded-lg font-semibold flex items-center justify-center gap-2 hover:bg-accent transition-all duration-200 shadow-sm"
+                onClick={handleLogout}
+              >
+                <svg xmlns='http://www.w3.org/2000/svg' className='h-4 w-4' fill='none' viewBox='0 0 24 24' stroke='currentColor'><path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a2 2 0 01-2 2H7a2 2 0 01-2-2V7a2 2 0 012-2h4a2 2 0 012 2v1' /></svg>
+                {t('logout')}
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </header>
+  );
+};
+
+export default Header;
Index: frontend/src/components/navbar.tsx
===================================================================
--- frontend/src/components/navbar.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/navbar.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,86 @@
+"use client"
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faUser } from '@fortawesome/free-solid-svg-icons';
+import { faBookmark } from '@fortawesome/free-solid-svg-icons';
+import { faBook } from '@fortawesome/free-solid-svg-icons';
+import { faPenToSquare } from '@fortawesome/free-solid-svg-icons';
+import { faListCheck } from '@fortawesome/free-solid-svg-icons';
+import { faFilePdf } from '@fortawesome/free-solid-svg-icons';
+import { faBars, faTimes, faGraduationCap } from '@fortawesome/free-solid-svg-icons';
+import { useState } from 'react';
+import Link from 'next/link';
+import { useTranslation } from 'react-i18next';
+
+const Navbar = () => {
+  const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+  const toggleMenu = () => {
+    setIsMenuOpen(!isMenuOpen);
+  };
+
+  const { t } = useTranslation();
+  const menuItems = [
+    { icon: faUser, label: t('profile'), href: '/students/profile' },
+    { icon: faBookmark, label: t('semesters'), href: '/students/semesters' },
+    { icon: faBook, label: t('subjects'), href: '/students/subjects' },
+    { icon: faPenToSquare, label: t('applications'), href: '/students/applications' },
+    { icon: faListCheck, label: t('exams'), href: '/students/exams' },
+    { icon: faFilePdf, label: t('documents'), href: '/students/documents' }
+  ];
+
+  return (
+    <>
+      {/* Desktop Navbar */}
+      <nav className="hidden md:flex flex-row gap-2 justify-evenly bg-primary text-primary-foreground p-7 rounded-xl my-5 text-xl">
+        {menuItems.map((item, index) => (
+          <Link key={index} href={item.href}>
+            <div className="group flex flex-row items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-all duration-300 hover:bg-card hover:text-card-foreground hover:scale-105 hover:shadow-lg">
+              <FontAwesomeIcon icon={item.icon} className="transition-transform duration-300 group-hover:rotate-12" />
+              <span>{item.label}</span>
+            </div>
+          </Link>
+        ))}
+      </nav>
+
+      {/* Mobile Navbar */}
+      <nav className="md:hidden bg-primary text-primary-foreground rounded-xl my-5">
+        <div className="flex justify-between items-center p-4">
+          {/* IKnow Logo/Text */}
+          <div className="flex items-center gap-2 text-xl font-bold">
+            <FontAwesomeIcon icon={faGraduationCap} className="text-xl" />
+            IKnow
+          </div>
+          
+          {/* Hamburger Menu Button */}
+          <button
+            onClick={toggleMenu}
+            className="p-2 rounded-lg hover:bg-card hover:text-card-foreground transition-colors duration-300"
+          >
+            <FontAwesomeIcon 
+              icon={isMenuOpen ? faTimes : faBars} 
+              className="text-xl transition-transform duration-300"
+            />
+          </button>
+        </div>
+
+        {/* Mobile Menu Items */}
+        <div className={`overflow-hidden transition-all duration-300 ease-in-out ${
+          isMenuOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
+        }`}>
+          <div className="px-4 pb-4 space-y-2">
+            {menuItems.map((item, index) => (
+              <Link key={index} href={item.href}>
+                <div className="group flex flex-row items-center gap-3 px-3 py-3 rounded-lg cursor-pointer transition-all duration-300 hover:bg-card hover:text-card-foreground">
+                  <FontAwesomeIcon icon={item.icon} className="transition-transform duration-300 group-hover:rotate-12" />
+                  <span className="text-lg">{item.label}</span>
+                </div>
+              </Link>
+            ))}
+          </div>
+        </div>
+      </nav>
+    </>
+  );
+};
+
+export default Navbar;
Index: frontend/src/components/professor-navbar.tsx
===================================================================
--- frontend/src/components/professor-navbar.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/professor-navbar.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,80 @@
+"use client";
+
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faUser, faUsers, faBars, faTimes, faChalkboardUser } from "@fortawesome/free-solid-svg-icons";
+import Link from "next/link";
+import { useState } from "react";
+import { useTranslation } from 'react-i18next';
+
+const ProfessorNavbar = () => {
+  const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+  const toggleMenu = () => {
+    setIsMenuOpen((v) => !v);
+  };
+
+  const { t } = useTranslation();
+  const menuItems = [
+    { icon: faUser, label: t('profile'), href: "/professor/profile" },
+    { icon: faUsers, label: t('students') || 'Студенти', href: "/professor/students" },
+  ];
+
+  return (
+    <>
+      {/* Desktop Navbar */}
+      <nav className="hidden md:flex flex-row gap-2 justify-evenly bg-primary text-primary-foreground p-7 rounded-xl my-5 text-xl">
+        {menuItems.map((item) => (
+          <Link key={item.href} href={item.href}>
+            <div className="group flex flex-row items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-all duration-300 hover:bg-card hover:text-card-foreground hover:scale-105 hover:shadow-lg">
+              <FontAwesomeIcon icon={item.icon} className="transition-transform duration-300 group-hover:rotate-12" />
+              <span>{item.label}</span>
+            </div>
+          </Link>
+        ))}
+      </nav>
+
+      {/* Mobile Navbar */}
+      <nav className="md:hidden bg-primary text-primary-foreground rounded-xl my-5">
+        <div className="flex justify-between items-center p-4">
+          <div className="flex items-center gap-2 text-xl font-bold">
+            <FontAwesomeIcon icon={faChalkboardUser} className="text-xl" />
+            Professor
+          </div>
+
+          <button
+            onClick={toggleMenu}
+            className="p-2 rounded-lg hover:bg-card hover:text-card-foreground transition-colors duration-300"
+            aria-label="Toggle menu"
+          >
+            <FontAwesomeIcon
+              icon={isMenuOpen ? faTimes : faBars}
+              className="text-xl transition-transform duration-300"
+            />
+          </button>
+        </div>
+
+        <div
+          className={`overflow-hidden transition-all duration-300 ease-in-out ${
+            isMenuOpen ? "max-h-64 opacity-100" : "max-h-0 opacity-0"
+          }`}
+        >
+          <div className="px-4 pb-4 space-y-2">
+            {menuItems.map((item) => (
+              <Link key={item.href} href={item.href}>
+                <div className="group flex flex-row items-center gap-3 px-3 py-3 rounded-lg cursor-pointer transition-all duration-300 hover:bg-card hover:text-card-foreground">
+                  <FontAwesomeIcon
+                    icon={item.icon}
+                    className="transition-transform duration-300 group-hover:rotate-12"
+                  />
+                  <span className="text-lg">{item.label}</span>
+                </div>
+              </Link>
+            ))}
+          </div>
+        </div>
+      </nav>
+    </>
+  );
+};
+
+export default ProfessorNavbar;
Index: frontend/src/components/theme-provider.tsx
===================================================================
--- frontend/src/components/theme-provider.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/theme-provider.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,8 @@
+"use client";
+
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import { type ThemeProviderProps } from "next-themes";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
+}
Index: frontend/src/components/theme-toggle.tsx
===================================================================
--- frontend/src/components/theme-toggle.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/components/theme-toggle.tsx	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,37 @@
+"use client";
+
+import { Moon, Sun } from "lucide-react";
+import { useTheme } from "next-themes";
+import { useEffect, useState } from "react";
+
+export function ThemeToggle() {
+  const [mounted, setMounted] = useState(false);
+  const { theme, setTheme } = useTheme();
+
+  // Avoid hydration mismatch
+  useEffect(() => {
+    setMounted(true);
+  }, []);
+
+  if (!mounted) {
+    return (
+      <button className="p-2 rounded-md hover:bg-accent">
+        <Sun className="h-5 w-5" />
+      </button>
+    );
+  }
+
+  return (
+    <button
+      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
+      className="p-2 rounded-md hover:bg-accent transition-colors"
+      aria-label="Toggle theme"
+    >
+      {theme === "dark" ? (
+        <Sun className="h-5 w-5" />
+      ) : (
+        <Moon className="h-5 w-5" />
+      )}
+    </button>
+  );
+}
Index: frontend/src/data/applications.json
===================================================================
--- frontend/src/data/applications.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/data/applications.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,144 @@
+{
+  "examSessions": [
+    {
+      "id": "2025_summer_session2",
+      "name": "2025 (Летна) Втора испитна сесија",
+      "year": "2025",
+      "semester": "Летна",
+      "session": "Втора испитна сесија"
+    },
+    {
+      "id": "2025_summer_session1",
+      "name": "2025 (Летна) Прва испитна сесија",
+      "year": "2025",
+      "semester": "Летна",
+      "session": "Прва испитна сесија"
+    },
+    {
+      "id": "2025_winter_session2",
+      "name": "2025 (Зимска) Втора испитна сесија",
+      "year": "2025",
+      "semester": "Зимска",
+      "session": "Втора испитна сесија"
+    },
+    {
+      "id": "2025_winter_session1",
+      "name": "2025 (Зимска) Прва испитна сесија",
+      "year": "2025",
+      "semester": "Зимска",
+      "session": "Прва испитна сесија"
+    },
+    {
+      "id": "2024_summer_session2",
+      "name": "2024 (Летна) Втора испитна сесија",
+      "year": "2024",
+      "semester": "Летна",
+      "session": "Втора испитна сесија"
+    },
+    {
+      "id": "2024_summer_session1",
+      "name": "2024 (Летна) Прва испитна сесија",
+      "year": "2024",
+      "semester": "Летна",
+      "session": "Прва испитна сесија"
+    },
+    {
+      "id": "2024_winter_session2",
+      "name": "2024 (Зимска) Втора испитна сесија",
+      "year": "2024",
+      "semester": "Зимска",
+      "session": "Втора испитна сесија"
+    },
+    {
+      "id": "2024_winter_session1",
+      "name": "2024 (Зимска) Прва испитна сесија",
+      "year": "2024",
+      "semester": "Зимска",
+      "session": "Прва испитна сесија"
+    }
+  ],
+  "currentSession": {
+    "id": "2025_summer_session2",
+    "name": "2025 (Летна) Втора испитна сесија"
+  },
+  "applications": [
+    {
+      "id": 1,
+      "serviceNumber": "4684532",
+      "code": "F23L2W014",
+      "subject": "Компјутерски мрежи и безбедност",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Сашо Граматиков",
+      "decade": 1
+    },
+    {
+      "id": 2,
+      "serviceNumber": "4684531",
+      "code": "F23L2W167",
+      "subject": "Шаблони за дизајн на кориснички интерфејси",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Сузана Лошковска",
+      "decade": 1
+    },
+    {
+      "id": 3,
+      "serviceNumber": "4684530",
+      "code": "F23L3S100",
+      "subject": "Деловна пракса",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Александар Стојменски",
+      "decade": 1
+    },
+    {
+      "id": 4,
+      "serviceNumber": "4684529",
+      "code": "F23L2S026",
+      "subject": "Маркетинг",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Билјана Стојкоска",
+      "decade": 1
+    },
+    {
+      "id": 5,
+      "serviceNumber": "4684528",
+      "code": "F23L2S017",
+      "subject": "Оперативни системи",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Невена Ацковска",
+      "decade": 1
+    },
+    {
+      "id": 6,
+      "serviceNumber": "4684527",
+      "code": "F23L2S029",
+      "subject": "Софтверско инженерство",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Ѓорѓи Маџаров",
+      "decade": 1
+    },
+    {
+      "id": 7,
+      "serviceNumber": "4684526",
+      "code": "F23L2S030",
+      "subject": "Вештачка интелигенција",
+      "completed": "Не",
+      "fee": "0,00",
+      "date": "21.05.2025",
+      "instructor": "Соња Гиевска",
+      "decade": 1
+    }
+  ],
+  "note": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички."
+}
Index: frontend/src/data/documents.json
===================================================================
--- frontend/src/data/documents.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/data/documents.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,185 @@
+{
+  "documentTypes": [
+    {
+      "id": "select_document",
+      "name": "Изберете документ",
+      "price": 0
+    },
+    {
+      "id": "administrative_regulation",
+      "name": "Барање за административно регулирање на ретроактивен семестар (од 2024г) ( 1000)",
+      "price": 1000
+    },
+    {
+      "id": "exam_application_satisfaction",
+      "name": "Барање за задочно пријавување на испит (од 2024г) ( 1000)",
+      "price": 1000
+    },
+    {
+      "id": "semester_enrollment_after_deadline",
+      "name": "Барање за запишување на семестар по истек на рок (од 2024г) ( 1500)",
+      "price": 1500
+    },
+    {
+      "id": "certificate_issuance_various",
+      "name": "Барање за издавање потврди по разни основи (од 2024г) ( 1500)",
+      "price": 1500
+    },
+    {
+      "id": "student_status_verification",
+      "name": "Барање за мирување на студиите (од 2024г) ( 2000)",
+      "price": 2000
+    },
+    {
+      "id": "diploma_thesis_postponement",
+      "name": "Барање за откажување на пријавена тема за дипломска работа (од 2024г) ( 1000)",
+      "price": 1000
+    },
+    {
+      "id": "diploma_package",
+      "name": "Барање за пакет за дипломирање (од 2024г) (6200)",
+      "price": 6200
+    },
+    {
+      "id": "exam_postponement",
+      "name": "Барање за поништување на испит (од 2024г) ( 2000)",
+      "price": 2000
+    },
+    {
+      "id": "regular_student_certificate",
+      "name": "Барање за потврда за редовен студент (хартиена) ( 100)",
+      "price": 100
+    },
+    {
+      "id": "study_continuation_verification",
+      "name": "Барање за продолжување на студии во мирување (од 2024г) ( 2000)",
+      "price": 2000
+    },
+    {
+      "id": "failed_subject_grade_change",
+      "name": "Барање за промена на изборен предмет (од 2024г) ( 1500)",
+      "price": 1500
+    },
+    {
+      "id": "passed_subject_grade_change",
+      "name": "Барање за промена на положен предмет (од 2024г) ( 0)",
+      "price": 0
+    },
+    {
+      "id": "study_program_change_same_accreditation",
+      "name": "Барање за промена на студиска програма од иста акредитација (од 2024г) ( 2000)",
+      "price": 2000
+    },
+    {
+      "id": "study_program_change_new_accreditation",
+      "name": "Барање за промена на студиска програма од понова акредитација (од 2024г) ( 3000)",
+      "price": 3000
+    },
+    {
+      "id": "diploma_supplement",
+      "name": "Додаток на диплома ( 0)",
+      "price": 0
+    },
+    {
+      "id": "diploma_supplement_second_cycle",
+      "name": "Додаток на диплома (втор циклус) ( 0)",
+      "price": 0
+    },
+    {
+      "id": "mksa",
+      "name": "МКСА ( 750)",
+      "price": 750
+    },
+    {
+      "id": "exam_recognition",
+      "name": "Признавање на положени испити ( 0)",
+      "price": 0
+    },
+    {
+      "id": "student_card_old",
+      "name": "Студентски картон (Старо) ( 0)",
+      "price": 0
+    }
+  ],
+  "documents": [
+    {
+      "id": 1,
+      "archive": "91008",
+      "date": "05.11.2024",
+      "request": "Уверение за положени испити ФИНКИ",
+      "price": 100.00,
+      "paid": "ДА",
+      "document": "Преземи",
+      "payOnline": true,
+      "status": "approved",
+      "comment": ""
+    },
+    {
+      "id": 2,
+      "archive": "91007",
+      "date": "05.11.2024",
+      "request": "УППИ образец",
+      "price": 0.00,
+      "paid": "ДА",
+      "document": "Преземи",
+      "payOnline": false,
+      "status": "approved",
+      "comment": ""
+    },
+    {
+      "id": 3,
+      "archive": "90494",
+      "date": "31.10.2024",
+      "request": "Уверение за редовен студент - ФИНКИ",
+      "price": 0.00,
+      "paid": "ДА",
+      "document": "Преземи",
+      "payOnline": false,
+      "status": "approved",
+      "comment": ""
+    },
+    {
+      "id": 4,
+      "archive": "84239",
+      "date": "10.03.2024",
+      "request": "Уверение за редовен студент - ФИНКИ",
+      "price": 0.00,
+      "paid": "ДА",
+      "document": "Преземи",
+      "payOnline": false,
+      "status": "approved",
+      "comment": ""
+    },
+    {
+      "id": 5,
+      "archive": "84238",
+      "date": "10.03.2024",
+      "request": "Уверение за положени испити ФИНКИ",
+      "price": 100.00,
+      "paid": "ДА",
+      "document": "Преземи",
+      "payOnline": true,
+      "status": "approved",
+      "comment": ""
+    },
+    {
+      "id": 6,
+      "archive": "81891",
+      "date": "12.01.2024",
+      "request": "Уверение за редовен студент - ФИНКИ",
+      "price": 0.00,
+      "paid": "ДА",
+      "document": "Преземи",
+      "payOnline": false,
+      "status": "approved",
+      "comment": ""
+    }
+  ],
+  "pagination": {
+    "currentPage": 1,
+    "totalPages": 1,
+    "totalRecords": 6,
+    "recordsPerPage": 15
+  },
+  "paymentInfo": "Онлајн плаќањата имаат банкарска провизија за безготовинско плаќање од 1,14% од оригиналната сума (минималната провизија 3,00, максималната 300,00 мкд)"
+}
Index: frontend/src/i18n.ts
===================================================================
--- frontend/src/i18n.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/i18n.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,23 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+
+import translationEN from './locales/en/translation.json';
+import translationMK from './locales/mk/translation.json';
+
+const resources = {
+  en: { translation: translationEN },
+  mk: { translation: translationMK },
+};
+
+i18n
+  .use(initReactI18next)
+  .init({
+    resources,
+    lng: 'mk',
+    fallbackLng: 'mk',
+    interpolation: {
+      escapeValue: false,
+    },
+  });
+
+export default i18n;
Index: frontend/src/lib/admin-api.ts
===================================================================
--- frontend/src/lib/admin-api.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/lib/admin-api.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,90 @@
+import { apiUrl } from "@/lib/api";
+import { getAccessToken } from "@/lib/auth";
+
+export type AdminResult = { ok: boolean; message?: string; id?: number | null };
+
+export type SubjectRef = { id: number; name?: string; code?: string | null };
+
+export type SubjectMajor = {
+  majorId: number;
+  majorName?: string;
+  mandatorySemester: number;
+};
+
+export type AdminSubject = {
+  id: number;
+  name?: string;
+  code?: string;
+  awardedCredits: number;
+  dependencyCredit?: number | null;
+  majors: SubjectMajor[];
+  prerequisites: SubjectRef[];
+  enrolledCount: number;
+};
+
+export type AdminSemester = {
+  id: number;
+  year: number;
+  type?: string;
+  name?: string;
+  enrolmentCount: number;
+  uncoveredSubjects: SubjectRef[];
+};
+
+export type ScheduleRow = {
+  subjectId: number;
+  subjectName?: string;
+  subjectCode?: string;
+  professorId: number;
+  professorName?: string;
+};
+
+export type ProfessorLoad = {
+  professorId: number;
+  professorName?: string;
+  subjects: number;
+};
+
+export type Schedule = {
+  semesterId: number;
+  semesterName?: string;
+  assignments: ScheduleRow[];
+  load: ProfessorLoad[];
+  uncoveredSubjects: SubjectRef[];
+  allSubjects: SubjectRef[];
+  professors: { id: number; name?: string }[];
+};
+
+function authHeaders(): Record<string, string> {
+  const token = getAccessToken();
+  if (!token) throw new Error("You are not signed in.");
+  return { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
+}
+
+/** GET that throws with the server message rather than a bare status code. */
+export async function adminGet<T>(path: string): Promise<T> {
+  const res = await fetch(apiUrl(path), { cache: "no-store", headers: authHeaders() });
+  if (!res.ok) {
+    const body = await res.json().catch(() => null);
+    throw new Error(body?.message || `Request failed (${res.status})`);
+  }
+  return (await res.json()) as T;
+}
+
+/** POST/PUT/DELETE returning the standard {ok, message} envelope. */
+export async function adminSend(
+  path: string,
+  method: "POST" | "PUT" | "DELETE",
+  body?: unknown,
+): Promise<AdminResult> {
+  const res = await fetch(apiUrl(path), {
+    method,
+    headers: authHeaders(),
+    body: body === undefined ? undefined : JSON.stringify(body),
+  });
+  const result = (await res.json().catch(() => null)) as AdminResult | null;
+  if (!res.ok || !result?.ok) {
+    throw new Error(result?.message || `Request failed (${res.status})`);
+  }
+  return result;
+}
Index: frontend/src/lib/api.ts
===================================================================
--- frontend/src/lib/api.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/lib/api.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,15 @@
+/**
+ * Base URL of the iknow-api backend.
+ *
+ * Set NEXT_PUBLIC_API_BASE_URL in .env.local to point at a local API run
+ * (see .env.example). The faculty database is only reachable through the SSH
+ * tunnel on the developer machine, so the deployed backend cannot read it -
+ * working against real data means running iknow-api locally.
+ */
+export const API_BASE_URL =
+  process.env.NEXT_PUBLIC_API_BASE_URL ?? 'https://iknow-api.onrender.com';
+
+/** Builds an absolute API URL from a path such as `/api/user/getUser`. */
+export function apiUrl(path: string): string {
+  return `${API_BASE_URL}${path}`;
+}
Index: frontend/src/lib/auth.ts
===================================================================
--- frontend/src/lib/auth.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/lib/auth.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,142 @@
+import { API_BASE_URL } from './api';
+export type AuthTokens = {
+  accessToken: string;
+  refreshToken: string;
+};
+
+export type UserRole = 'Professor' | 'Student' | 'Admin' | string;
+
+export type AuthSession = AuthTokens & {
+  role: UserRole;
+};
+
+type StoredToken = {
+  value: string;
+  expiresAt: number; // epoch ms
+};
+
+const STORAGE_KEYS = {
+  access: 'iknow.auth.access',
+  refresh: 'iknow.auth.refresh',
+  role: 'iknow.auth.role',
+} as const;
+
+const ACCESS_TOKEN_TTL_MS = 60 * 60 * 1000; // 1 hour
+const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000; // ~1 month (30 days)
+
+function isBrowser() {
+  return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
+}
+
+function readStoredToken(key: string): StoredToken | null {
+  if (!isBrowser()) return null;
+  const raw = window.localStorage.getItem(key);
+  if (!raw) return null;
+
+  try {
+    const parsed = JSON.parse(raw) as StoredToken;
+    if (!parsed?.value || typeof parsed.expiresAt !== 'number') {
+      window.localStorage.removeItem(key);
+      return null;
+    }
+
+    if (Date.now() >= parsed.expiresAt) {
+      window.localStorage.removeItem(key);
+      return null;
+    }
+
+    return parsed;
+  } catch {
+    window.localStorage.removeItem(key);
+    return null;
+  }
+}
+
+function writeStoredToken(key: string, value: string, ttlMs: number) {
+  if (!isBrowser()) return;
+  const payload: StoredToken = {
+    value,
+    expiresAt: Date.now() + ttlMs,
+  };
+  window.localStorage.setItem(key, JSON.stringify(payload));
+}
+
+export function setAuthTokens(tokens: AuthTokens) {
+  writeStoredToken(STORAGE_KEYS.access, tokens.accessToken, ACCESS_TOKEN_TTL_MS);
+  writeStoredToken(STORAGE_KEYS.refresh, tokens.refreshToken, REFRESH_TOKEN_TTL_MS);
+}
+
+export function setUserRole(role: UserRole) {
+  // Keep role TTL aligned with access token TTL.
+  writeStoredToken(STORAGE_KEYS.role, role, ACCESS_TOKEN_TTL_MS);
+}
+
+export function getAccessToken(): string | null {
+  return readStoredToken(STORAGE_KEYS.access)?.value ?? null;
+}
+
+export function getRefreshToken(): string | null {
+  return readStoredToken(STORAGE_KEYS.refresh)?.value ?? null;
+}
+
+export function getUserRole(): UserRole | null {
+  return readStoredToken(STORAGE_KEYS.role)?.value ?? null;
+}
+
+export function clearAuthTokens() {
+  if (!isBrowser()) return;
+  window.localStorage.removeItem(STORAGE_KEYS.access);
+  window.localStorage.removeItem(STORAGE_KEYS.refresh);
+  window.localStorage.removeItem(STORAGE_KEYS.role);
+}
+
+export async function login(params: { email: string; password: string }): Promise<AuthSession> {
+  const baseUrl = API_BASE_URL;
+
+  const response = await fetch(`${baseUrl}/api/auth/login`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify({
+      email: params.email,
+      password: params.password,
+      GenerateRefreshToken: true,
+    }),
+  });
+
+  if (!response.ok) {
+    let message = `Login failed (${response.status})`;
+    try {
+      const text = await response.text();
+      if (text) message = text;
+    } catch {
+      // ignore
+    }
+    throw new Error(message);
+  }
+
+  const data = (await response.json()) as {
+    token?: string;
+    refreshToken?: string;
+    role?: string;
+  };
+
+  const accessToken = data?.token;
+  const refreshToken = data?.refreshToken;
+  const role = data?.role;
+
+  if (!accessToken || !refreshToken) {
+    throw new Error('Login response did not contain tokens.');
+  }
+
+  const session: AuthSession = {
+    accessToken,
+    refreshToken,
+    role: role ?? 'Student',
+  };
+
+  setAuthTokens(session);
+  setUserRole(session.role);
+  return session;
+}
Index: frontend/src/lib/pdf-fonts.ts
===================================================================
--- frontend/src/lib/pdf-fonts.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/lib/pdf-fonts.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,32 @@
+import { jsPDF } from 'jspdf';
+
+let fontsLoaded = false;
+let regularFontBase64: string | null = null;
+let boldFontBase64: string | null = null;
+
+async function fetchFontAsBase64(url: string): Promise<string> {
+  const response = await fetch(url);
+  const buffer = await response.arrayBuffer();
+  const bytes = new Uint8Array(buffer);
+  let binary = '';
+  for (let i = 0; i < bytes.length; i++) {
+    binary += String.fromCharCode(bytes[i]);
+  }
+  return btoa(binary);
+}
+
+export async function loadCyrillicFonts(doc: jsPDF): Promise<void> {
+  if (!fontsLoaded) {
+    regularFontBase64 = await fetchFontAsBase64('/fonts/Roboto-Regular.ttf');
+    boldFontBase64 = await fetchFontAsBase64('/fonts/Roboto-Bold.ttf');
+    fontsLoaded = true;
+  }
+
+  doc.addFileToVFS('Roboto-Regular.ttf', regularFontBase64!);
+  doc.addFont('Roboto-Regular.ttf', 'Roboto', 'normal');
+
+  doc.addFileToVFS('Roboto-Bold.ttf', boldFontBase64!);
+  doc.addFont('Roboto-Bold.ttf', 'Roboto', 'bold');
+
+  doc.setFont('Roboto', 'normal');
+}
Index: frontend/src/lib/pdf-generators.ts
===================================================================
--- frontend/src/lib/pdf-generators.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/lib/pdf-generators.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,1207 @@
+import { jsPDF } from 'jspdf';
+import autoTable from 'jspdf-autotable';
+import { loadCyrillicFonts } from './pdf-fonts';
+import { apiUrl } from './api';
+
+/* ---------- shared types ---------- */
+
+export type StudentInfo = {
+  firstName: string;
+  middleName: string;
+  lastName: string;
+  index: string;
+  embg: string;
+  dateOfBirth: string;
+  gender: string;
+  nationality: string;
+  citizenship: string;
+  currentPlan: string;
+  registryNumber: string;
+  studyGroup: string;
+  scholarship: string;
+};
+
+export type EnrollmentInfo = {
+  enrollmentYear: string | number;
+  status: string;
+  cycle: string;
+  program: string;
+  quota: string;
+};
+
+export type BirthInfo = {
+  placeOfBirth: string;
+  municipalityOfBirth: string;
+  country: string;
+};
+
+export type ContactInfo = {
+  placeOfResidence: string;
+  municipalityOfResidence: string;
+  address: string;
+  phone: string;
+  mobilePhone: string;
+  email: string;
+};
+
+export type PassedExam = {
+  id: number;
+  code: string;
+  subject: string;
+  credits: number;
+  grade: number;
+  date: string;
+  semester: string;
+  professor: string;
+};
+
+export type PreviousEducation = {
+  type: string;
+  profession: string;
+  average: string | number;
+  language: string;
+  country: string;
+  previousUniversity: string;
+  previousFaculty: string;
+};
+
+/* ---------- helpers ---------- */
+
+const PAGE_WIDTH = 210; // A4 mm
+const MARGIN = 20;
+const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
+
+function drawUniversityHeader(doc: jsPDF, y: number): number {
+  doc.setFont('Roboto', 'bold');
+
+  doc.setFontSize(11);
+  doc.text('УНИВЕРЗИТЕТ „СВ. КИРИЛ И МЕТОДИЈ" - СКОПЈЕ', PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 6;
+
+  doc.setFontSize(10);
+  doc.text('ФАКУЛТЕТ ЗА ИНФОРМАТИЧКИ НАУКИ И', PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 5;
+  doc.text('КОМПЈУТЕРСКО ИНЖЕНЕРСТВО', PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 5;
+
+  // Horizontal line under header
+  doc.setDrawColor(0, 114, 209); // primary blue
+  doc.setLineWidth(0.5);
+  doc.line(MARGIN, y, PAGE_WIDTH - MARGIN, y);
+  y += 8;
+
+  return y;
+}
+
+function drawDocumentTitle(doc: jsPDF, title: string, y: number): number {
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(14);
+  doc.text(title, PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 10;
+  return y;
+}
+
+function drawInfoRow(doc: jsPDF, label: string, value: string, x: number, y: number, labelWidth = 50): number {
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.text(`${label}:`, x, y);
+  doc.setFont('Roboto', 'normal');
+  doc.text(String(value || '—'), x + labelWidth, y);
+  return y + 5.5;
+}
+
+function drawFooter(doc: jsPDF, archiveNumber: string, date: string): void {
+  const pageHeight = doc.internal.pageSize.getHeight();
+  let y = pageHeight - 40;
+
+  doc.setDrawColor(0, 114, 209);
+  doc.setLineWidth(0.3);
+  doc.line(MARGIN, y, PAGE_WIDTH - MARGIN, y);
+  y += 8;
+
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(9);
+
+  doc.text(`Бр.: ${archiveNumber}`, MARGIN, y);
+  doc.text(`Датум: ${date}`, PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 12;
+
+  // Signature placeholders
+  doc.text('Потпис на студентот:', MARGIN, y);
+  doc.text('М.П.', PAGE_WIDTH / 2, y, { align: 'center' });
+  doc.text('Одговорно лице:', PAGE_WIDTH - MARGIN - 35, y);
+  y += 8;
+
+  doc.setDrawColor(150, 150, 150);
+  doc.setLineWidth(0.2);
+  doc.line(MARGIN, y, MARGIN + 45, y);
+  doc.line(PAGE_WIDTH / 2 - 15, y, PAGE_WIDTH / 2 + 15, y);
+  doc.line(PAGE_WIDTH - MARGIN - 45, y, PAGE_WIDTH - MARGIN, y);
+}
+
+/* ==================================================================
+   PDF 1 — Уверение за положени испити (Certificate of Passed Exams)
+   ================================================================== */
+
+export async function generatePassedExamsCertificate(
+  student: StudentInfo,
+  enrollment: EnrollmentInfo,
+  passedExams: PassedExam[],
+  archiveNumber: string,
+  date: string,
+): Promise<void> {
+  const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
+  await loadCyrillicFonts(doc);
+
+  let y = 20;
+
+  // --- Header ---
+  y = drawUniversityHeader(doc, y);
+
+  // --- Title ---
+  y = drawDocumentTitle(doc, 'УВЕРЕНИЕ ЗА ПОЛОЖЕНИ ИСПИТИ', y);
+
+  // --- Student Info Block ---
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(9);
+
+  y = drawInfoRow(doc, 'Студент', `${student.firstName} ${student.middleName} ${student.lastName}`, MARGIN, y);
+  y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y);
+  y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y);
+  y = drawInfoRow(doc, 'Циклус на студии', enrollment.cycle, MARGIN, y);
+  y = drawInfoRow(doc, 'Статус', enrollment.status, MARGIN, y);
+  y += 4;
+
+  // --- Passed Exams Table ---
+  const tableBody = passedExams.map((exam, idx) => [
+    String(idx + 1),
+    exam.code,
+    exam.subject,
+    String(exam.credits),
+    String(exam.grade),
+    exam.date,
+    exam.semester,
+    exam.professor || '—',
+  ]);
+
+  autoTable(doc, {
+    startY: y,
+    head: [['Р.Б.', 'Шифра', 'Предмет', 'Кредити', 'Оцена', 'Датум', 'Семестар', 'Професор']],
+    body: tableBody,
+    theme: 'grid',
+    styles: {
+      font: 'Roboto',
+      fontSize: 7.5,
+      cellPadding: 2,
+      textColor: [30, 30, 30],
+      lineColor: [200, 200, 200],
+      lineWidth: 0.2,
+    },
+    headStyles: {
+      fillColor: [0, 114, 209],
+      textColor: [255, 255, 255],
+      fontStyle: 'bold',
+      fontSize: 7.5,
+      halign: 'center',
+    },
+    columnStyles: {
+      0: { halign: 'center', cellWidth: 10 },
+      1: { halign: 'center', cellWidth: 16 },
+      2: { cellWidth: 50 },
+      3: { halign: 'center', cellWidth: 14 },
+      4: { halign: 'center', cellWidth: 12 },
+      5: { halign: 'center', cellWidth: 20 },
+      6: { cellWidth: 22 },
+      7: { cellWidth: 26 },
+    },
+    margin: { left: MARGIN, right: MARGIN },
+    didParseCell: (data) => {
+      if (data.section === 'body' && data.row.index % 2 === 0) {
+        data.cell.styles.fillColor = [245, 247, 250];
+      }
+    },
+  });
+
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  y = (doc as any).lastAutoTable.finalY + 8;
+
+  // --- Summary ---
+  const totalCredits = passedExams.reduce((sum, e) => sum + e.credits, 0);
+  const grades = passedExams.map((e) => e.grade).filter((g) => g > 0);
+  const average = grades.length > 0 ? (grades.reduce((s, g) => s + g, 0) / grades.length).toFixed(2) : '0.00';
+
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(10);
+  doc.text(`Вкупно положени предмети: ${passedExams.length}`, MARGIN, y);
+  y += 6;
+  doc.text(`Вкупно кредити: ${totalCredits}`, MARGIN, y);
+  y += 6;
+  doc.text(`Просечна оцена: ${average}`, MARGIN, y);
+
+  // --- Footer ---
+  drawFooter(doc, archiveNumber, date);
+
+  doc.save(`Уверение_положени_испити_${student.index}.pdf`);
+}
+
+/* ==================================================================
+   PDF 2 — УППИ образец (UPPI Form / Student Record)
+   ================================================================== */
+
+export async function generateUPPIForm(
+  student: StudentInfo,
+  enrollment: EnrollmentInfo,
+  birth: BirthInfo,
+  contact: ContactInfo,
+  previousEdu: PreviousEducation,
+  passedExams: PassedExam[],
+  archiveNumber: string,
+  date: string,
+): Promise<void> {
+  const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
+  await loadCyrillicFonts(doc);
+
+  let y = 20;
+
+  // --- Header ---
+  y = drawUniversityHeader(doc, y);
+
+  // --- Title ---
+  y = drawDocumentTitle(doc, 'УППИ ОБРАЗЕЦ', y);
+
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.text('УНИВЕРЗАЛНА ПРИЈАВА ЗА ПРАКТИЧНА ИНДИВИДУАЛНА РАБОТА', PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 10;
+
+  // --- Section: Personal Information ---
+  doc.setFillColor(0, 114, 209);
+  doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.setTextColor(255, 255, 255);
+  doc.text('ЛИЧНИ ПОДАТОЦИ', MARGIN + 3, y + 0.5);
+  doc.setTextColor(30, 30, 30);
+  y += 10;
+
+  const col2X = MARGIN + CONTENT_WIDTH / 2;
+
+  y = drawInfoRow(doc, 'Име', student.firstName, MARGIN, y, 35);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'Презиме', student.lastName, col2X, y, 35);
+  y = drawInfoRow(doc, 'Татково име', student.middleName, MARGIN, y, 35);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'ЕМБГ', student.embg, col2X, y, 35);
+  y = drawInfoRow(doc, 'Датум на раѓање', student.dateOfBirth, MARGIN, y, 35);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'Пол', student.gender, col2X, y, 35);
+  y = drawInfoRow(doc, 'Националност', student.nationality, MARGIN, y, 35);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'Државјанство', student.citizenship, col2X, y, 35);
+  y += 3;
+
+  // --- Section: Birth Info ---
+  doc.setFillColor(0, 114, 209);
+  doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.setTextColor(255, 255, 255);
+  doc.text('ПОДАТОЦИ ЗА РАЃАЊЕ', MARGIN + 3, y + 0.5);
+  doc.setTextColor(30, 30, 30);
+  y += 10;
+
+  y = drawInfoRow(doc, 'Место на раѓање', birth.placeOfBirth, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Општина', birth.municipalityOfBirth, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Држава', birth.country, MARGIN, y, 40);
+  y += 3;
+
+  // --- Section: Contact ---
+  doc.setFillColor(0, 114, 209);
+  doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.setTextColor(255, 255, 255);
+  doc.text('КОНТАКТ ИНФОРМАЦИИ', MARGIN + 3, y + 0.5);
+  doc.setTextColor(30, 30, 30);
+  y += 10;
+
+  y = drawInfoRow(doc, 'Место на живеење', contact.placeOfResidence, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Адреса', contact.address, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Телефон', contact.phone || contact.mobilePhone, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Е-пошта', contact.email, MARGIN, y, 40);
+  y += 3;
+
+  // --- Section: Enrollment ---
+  doc.setFillColor(0, 114, 209);
+  doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.setTextColor(255, 255, 255);
+  doc.text('СТУДИСКИ ИНФОРМАЦИИ', MARGIN + 3, y + 0.5);
+  doc.setTextColor(30, 30, 30);
+  y += 10;
+
+  y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y, 40);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'Мат. број', student.registryNumber, col2X, y, 35);
+  y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Циклус', enrollment.cycle, MARGIN, y, 40);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'Статус', enrollment.status, col2X, y, 35);
+  y = drawInfoRow(doc, 'Год. на запишување', String(enrollment.enrollmentYear), MARGIN, y, 40);
+  y -= 5.5;
+  y = drawInfoRow(doc, 'Квота', enrollment.quota, col2X, y, 35);
+  y += 3;
+
+  // --- Section: Previous Education ---
+  doc.setFillColor(0, 114, 209);
+  doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.setTextColor(255, 255, 255);
+  doc.text('ПРЕТХОДНО ОБРАЗОВАНИЕ', MARGIN + 3, y + 0.5);
+  doc.setTextColor(30, 30, 30);
+  y += 10;
+
+  y = drawInfoRow(doc, 'Вид', previousEdu.type, MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Просек', String(previousEdu.average), MARGIN, y, 40);
+  y = drawInfoRow(doc, 'Јазик', previousEdu.language, MARGIN, y, 40);
+  y += 3;
+
+  // --- Section: Passed Exams mini-table ---
+  doc.setFillColor(0, 114, 209);
+  doc.rect(MARGIN, y - 4, CONTENT_WIDTH, 7, 'F');
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.setTextColor(255, 255, 255);
+  doc.text('ПОЛОЖЕНИ ИСПИТИ', MARGIN + 3, y + 0.5);
+  doc.setTextColor(30, 30, 30);
+  y += 8;
+
+  const examTableBody = passedExams.map((exam, idx) => [
+    String(idx + 1),
+    exam.code,
+    exam.subject,
+    String(exam.credits),
+    String(exam.grade),
+    exam.date,
+  ]);
+
+  autoTable(doc, {
+    startY: y,
+    head: [['#', 'Шифра', 'Предмет', 'Кредити', 'Оцена', 'Датум']],
+    body: examTableBody,
+    theme: 'grid',
+    styles: {
+      font: 'Roboto',
+      fontSize: 7,
+      cellPadding: 1.5,
+      textColor: [30, 30, 30],
+      lineColor: [200, 200, 200],
+      lineWidth: 0.2,
+    },
+    headStyles: {
+      fillColor: [0, 114, 209],
+      textColor: [255, 255, 255],
+      fontStyle: 'bold',
+      fontSize: 7,
+      halign: 'center',
+    },
+    columnStyles: {
+      0: { halign: 'center', cellWidth: 8 },
+      1: { halign: 'center', cellWidth: 18 },
+      2: { cellWidth: 70 },
+      3: { halign: 'center', cellWidth: 16 },
+      4: { halign: 'center', cellWidth: 14 },
+      5: { halign: 'center', cellWidth: 22 },
+    },
+    margin: { left: MARGIN, right: MARGIN },
+    didParseCell: (data) => {
+      if (data.section === 'body' && data.row.index % 2 === 0) {
+        data.cell.styles.fillColor = [245, 247, 250];
+      }
+    },
+  });
+
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  y = (doc as any).lastAutoTable.finalY + 8;
+
+  // Summary
+  const totalCredits = passedExams.reduce((sum, e) => sum + e.credits, 0);
+  const grades = passedExams.map((e) => e.grade).filter((g) => g > 0);
+  const average = grades.length > 0 ? (grades.reduce((s, g) => s + g, 0) / grades.length).toFixed(2) : '0.00';
+
+  doc.setFont('Roboto', 'bold');
+  doc.setFontSize(9);
+  doc.text(`Вкупно кредити: ${totalCredits}    |    Просечна оцена: ${average}`, MARGIN, y);
+
+  // Footer
+  drawFooter(doc, archiveNumber, date);
+
+  doc.save(`УППИ_образец_${student.index}.pdf`);
+}
+
+/* ==================================================================
+   PDF 3 — Уверение за редовен студент (Regular Student Certificate)
+   ================================================================== */
+
+export async function generateRegularStudentCertificate(
+  student: StudentInfo,
+  enrollment: EnrollmentInfo,
+  passedExams: PassedExam[],
+  archiveNumber: string,
+  date: string,
+): Promise<void> {
+  const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
+  await loadCyrillicFonts(doc);
+
+  let y = 20;
+
+  // --- Header ---
+  y = drawUniversityHeader(doc, y);
+
+  // --- Title ---
+  y = drawDocumentTitle(doc, 'УВЕРЕНИЕ', y);
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(10);
+  doc.text('за редовен студент', PAGE_WIDTH / 2, y, { align: 'center' });
+  y += 12;
+
+  // --- Body text ---
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(10);
+
+  const totalCredits = passedExams.reduce((sum, e) => sum + e.credits, 0);
+  const grades = passedExams.map((e) => e.grade).filter((g) => g > 0);
+  const average = grades.length > 0 ? (grades.reduce((s, g) => s + g, 0) / grades.length).toFixed(2) : '0.00';
+
+  const bodyText = [
+    `Со ова се потврдува дека студентот/студентката`,
+    ``,
+    `${student.firstName} ${student.middleName} ${student.lastName}`,
+    ``,
+    `со индекс број ${student.index}, ЕМБГ ${student.embg},`,
+    `е редовен студент на Факултетот за информатички науки и компјутерско`,
+    `инженерство при Универзитетот „Св. Кирил и Методиј" - Скопје.`,
+    ``,
+    `Студиска програма: ${enrollment.program}`,
+    `Циклус на студии: ${enrollment.cycle}`,
+    `Статус: ${enrollment.status}`,
+    `Година на запишување: ${enrollment.enrollmentYear}`,
+    ``,
+    `Студентот/студентката до сега има положено ${passedExams.length} испити,`,
+    `со вкупно ${totalCredits} кредити и просечна оцена ${average}.`,
+    ``,
+    `Ова уверение се издава за потребите на студентот/студентката`,
+    `и може да послужи за секоја законска употреба.`,
+  ];
+
+  for (const line of bodyText) {
+    if (line === '') {
+      y += 4;
+    } else {
+      if (line.startsWith(`${student.firstName}`)) {
+        doc.setFont('Roboto', 'bold');
+        doc.setFontSize(12);
+        doc.text(line, PAGE_WIDTH / 2, y, { align: 'center' });
+        doc.setFont('Roboto', 'normal');
+        doc.setFontSize(10);
+      } else {
+        doc.text(line, MARGIN, y);
+      }
+      y += 6;
+    }
+  }
+
+  // Footer
+  drawFooter(doc, archiveNumber, date);
+
+  doc.save(`Уверение_редовен_студент_${student.index}.pdf`);
+}
+
+/* ==================================================================
+   Generic document — fallback for other document types
+   ================================================================== */
+
+export async function generateGenericDocument(
+  student: StudentInfo,
+  enrollment: EnrollmentInfo,
+  documentTitle: string,
+  archiveNumber: string,
+  date: string,
+): Promise<void> {
+  const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
+  await loadCyrillicFonts(doc);
+
+  let y = 20;
+
+  y = drawUniversityHeader(doc, y);
+  y = drawDocumentTitle(doc, documentTitle.toUpperCase(), y);
+
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(10);
+
+  y = drawInfoRow(doc, 'Студент', `${student.firstName} ${student.middleName} ${student.lastName}`, MARGIN, y);
+  y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y);
+  y = drawInfoRow(doc, 'ЕМБГ', student.embg, MARGIN, y);
+  y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y);
+  y = drawInfoRow(doc, 'Циклус', enrollment.cycle, MARGIN, y);
+  y = drawInfoRow(doc, 'Статус', enrollment.status, MARGIN, y);
+  y += 8;
+
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(10);
+  doc.text('Документот е издаден по барање на студентот.', MARGIN, y);
+
+  drawFooter(doc, archiveNumber, date);
+
+  doc.save(`${documentTitle.replace(/\s+/g, '_')}_${student.index}.pdf`);
+}
+
+/* ==================================================================
+   Themed request generators — one for each document type
+   ================================================================== */
+
+async function generateRequestDocument(
+  student: StudentInfo,
+  enrollment: EnrollmentInfo,
+  title: string,
+  bodyLines: string[],
+  archiveNumber: string,
+  date: string,
+  filename: string,
+): Promise<void> {
+  const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
+  await loadCyrillicFonts(doc);
+
+  let y = 20;
+
+  y = drawUniversityHeader(doc, y);
+  y = drawDocumentTitle(doc, title, y);
+
+  // Student info block
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(9);
+  y = drawInfoRow(doc, 'Студент', `${student.firstName} ${student.middleName} ${student.lastName}`, MARGIN, y);
+  y = drawInfoRow(doc, 'Индекс', student.index, MARGIN, y);
+  y = drawInfoRow(doc, 'ЕМБГ', student.embg, MARGIN, y);
+  y = drawInfoRow(doc, 'Студиска програма', enrollment.program, MARGIN, y);
+  y = drawInfoRow(doc, 'Циклус', enrollment.cycle, MARGIN, y);
+  y = drawInfoRow(doc, 'Статус', enrollment.status, MARGIN, y);
+  y = drawInfoRow(doc, 'Година на упис', String(enrollment.enrollmentYear), MARGIN, y);
+  y += 8;
+
+  // Separator
+  doc.setDrawColor(200, 200, 200);
+  doc.setLineWidth(0.3);
+  doc.line(MARGIN, y, PAGE_WIDTH - MARGIN, y);
+  y += 8;
+
+  // Body text
+  doc.setFont('Roboto', 'normal');
+  doc.setFontSize(10);
+  for (const line of bodyLines) {
+    if (line === '') {
+      y += 5;
+    } else {
+      const wrapped = doc.splitTextToSize(line, CONTENT_WIDTH);
+      for (const wl of wrapped) {
+        doc.text(wl, MARGIN, y);
+        y += 5.5;
+      }
+    }
+  }
+
+  drawFooter(doc, archiveNumber, date);
+  doc.save(`${filename}_${student.index}.pdf`);
+}
+
+// --- Administrative Regulation ---
+export async function generateAdministrativeRegulation(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА АДМИНИСТРАТИВНО РЕГУЛИРАЊЕ НА РЕТРОАКТИВЕН СЕМЕСТАР',
+    [
+      `До: Деканат на Факултетот за информатички науки и компјутерско инженерство`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `запишан/а на студиската програма ${enrollment.program}, ${enrollment.cycle} циклус студии,`,
+      `со ова барање побарувам административно регулирање на ретроактивен семестар.`,
+      '',
+      `Молам да ми се одобри административна регулација на семестар кој не е запишан во предвидениот рок,`,
+      `согласно одлуката на Факултетот и важечката регулатива.`,
+      '',
+      `Прилози: Уплатница за административна такса од 1000 ден.`,
+    ],
+    archiveNumber, date, 'Административно_регулирање');
+}
+
+// --- Late Exam Registration ---
+export async function generateExamApplicationSatisfaction(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ЗАДОЧНО ПРИЈАВУВАЊЕ НА ИСПИТ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `запишан/а на студиската програма ${enrollment.program},`,
+      `побарувам задочно пријавување на испит кој не е пријавен во предвидениот рок.`,
+      '',
+      `Причина за задоцнување: ___________________________________`,
+      '',
+      `Молам за одобрување на ова барање.`,
+      '',
+      `Прилози: Уплатница за административна такса од 1000 ден.`,
+    ],
+    archiveNumber, date, 'Задочно_пријавување_испит');
+}
+
+// --- Semester Enrollment After Deadline ---
+export async function generateSemesterEnrollmentAfterDeadline(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ЗАПИШУВАЊЕ НА СЕМЕСТАР ПО ИСТЕК НА РОК',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам да ми се одобри запишување на нареден семестар по истекот на официјалниот рок.`,
+      '',
+      `Причина за задоцнето запишување: ___________________________________`,
+      '',
+      `Молам за одобрување.`,
+      '',
+      `Прилози: Уплатница за административна такса од 1500 ден.`,
+    ],
+    archiveNumber, date, 'Запишување_семестар_по_рок');
+}
+
+// --- Certificate Issuance ---
+export async function generateCertificateIssuanceVarious(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ИЗДАВАЊЕ НА ПОТВРДИ ПО РАЗНИ ОСНОВИ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам издавање на потврда за следната намена:`,
+      '',
+      `Намена: ___________________________________`,
+      '',
+      `Молам да ми биде издадена бараната потврда.`,
+      '',
+      `Прилози: Уплатница за административна такса од 1500 ден.`,
+    ],
+    archiveNumber, date, 'Издавање_потврди');
+}
+
+// --- Study Suspension ---
+export async function generateStudySuspension(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА МИРУВАЊЕ НА СТУДИИТЕ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `запишан/а на студиската програма ${enrollment.program},`,
+      `побарувам мирување на студиите за период од ______ семестри.`,
+      '',
+      `Причина за мирување: ___________________________________`,
+      '',
+      `Изјавувам дека сум запознаен/а со правилата за продолжување на студиите`,
+      `по истекот на периодот на мирување.`,
+      '',
+      `Прилози: Уплатница за административна такса од 2000 ден.`,
+    ],
+    archiveNumber, date, 'Мирување_студии');
+}
+
+// --- Diploma Thesis Cancellation ---
+export async function generateDiplomaThesisCancellation(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ОТКАЖУВАЊЕ НА ПРИЈАВЕНА ТЕМА ЗА ДИПЛОМСКА РАБОТА',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам откажување на претходно пријавената тема за дипломска работа.`,
+      '',
+      `Назив на темата: ___________________________________`,
+      `Ментор: ___________________________________`,
+      '',
+      `Причина за откажување: ___________________________________`,
+      '',
+      `Прилози: Уплатница за административна такса од 1000 ден.`,
+    ],
+    archiveNumber, date, 'Откажување_дипломска');
+}
+
+// --- Graduation Package ---
+export async function generateDiplomaPackage(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПАКЕТ ЗА ДИПЛОМИРАЊЕ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `запишан/а на студиската програма ${enrollment.program}, ${enrollment.cycle} циклус,`,
+      `побарувам издавање на пакет за дипломирање.`,
+      '',
+      `Со ова потврдувам дека ги имам исполнето сите услови за дипломирање`,
+      `согласно студиската програма и правилникот на Факултетот.`,
+      '',
+      `Пакетот за дипломирање ги вклучува: диплома, додаток на диплома и свечена промоција.`,
+      '',
+      `Прилози: Уплатница за административна такса од 6200 ден.`,
+    ],
+    archiveNumber, date, 'Пакет_дипломирање');
+}
+
+// --- Exam Cancellation ---
+export async function generateExamCancellation(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПОНИШТУВАЊЕ НА ИСПИТ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам поништување на испит по предметот:`,
+      '',
+      `Предмет: ___________________________________`,
+      `Датум на полагање: ___________________________________`,
+      '',
+      `Причина за поништување: ___________________________________`,
+      '',
+      `Прилози: Уплатница за административна такса од 2000 ден.`,
+    ],
+    archiveNumber, date, 'Поништување_испит');
+}
+
+// --- Study Continuation ---
+export async function generateStudyContinuation(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПРОДОЛЖУВАЊЕ НА СТУДИИ ВО МИРУВАЊЕ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам продолжување на студиите по периодот на мирување.`,
+      '',
+      `Период на мирување: од ____________ до ____________`,
+      '',
+      `Молам да ми се одобри продолжување на студиите согласно тековната`,
+      `студиска програма и важечкиот правилник.`,
+      '',
+      `Прилози: Уплатница за административна такса од 2000 ден.`,
+    ],
+    archiveNumber, date, 'Продолжување_студии');
+}
+
+// --- Elective Subject Change ---
+export async function generateElectiveSubjectChange(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПРОМЕНА НА ИЗБОРЕН ПРЕДМЕТ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам промена на изборен предмет.`,
+      '',
+      `Предмет што се менува: ___________________________________`,
+      `Нов изборен предмет: ___________________________________`,
+      '',
+      `Причина за промена: ___________________________________`,
+      '',
+      `Прилози: Уплатница за административна такса од 1500 ден.`,
+    ],
+    archiveNumber, date, 'Промена_изборен_предмет');
+}
+
+// --- Passed Subject Change ---
+export async function generatePassedSubjectChange(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПРОМЕНА НА ПОЛОЖЕН ПРЕДМЕТ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам промена / корекција на запис за положен предмет.`,
+      '',
+      `Предмет: ___________________________________`,
+      `Датум на полагање: ___________________________________`,
+      `Оцена: ___________________________________`,
+      '',
+      `Детали за промена: ___________________________________`,
+    ],
+    archiveNumber, date, 'Промена_положен_предмет');
+}
+
+// --- Study Program Change (Same Accreditation) ---
+export async function generateProgramChangeSameAccreditation(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПРОМЕНА НА СТУДИСКА ПРОГРАМА ОД ИСТА АКРЕДИТАЦИЈА',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `тековно запишан/а на програмата ${enrollment.program},`,
+      `побарувам промена на студиска програма во рамки на истата акредитација.`,
+      '',
+      `Нова студиска програма: ___________________________________`,
+      '',
+      `Причина за промена: ___________________________________`,
+      '',
+      `Прилози: Уплатница за административна такса од 2000 ден.`,
+    ],
+    archiveNumber, date, 'Промена_програма_иста_акредитација');
+}
+
+// --- Study Program Change (New Accreditation) ---
+export async function generateProgramChangeNewAccreditation(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'БАРАЊЕ ЗА ПРОМЕНА НА СТУДИСКА ПРОГРАМА ОД ПОНОВА АКРЕДИТАЦИЈА',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `тековно запишан/а на програмата ${enrollment.program},`,
+      `побарувам промена на студиска програма во рамки на нова акредитација.`,
+      '',
+      `Нова студиска програма: ___________________________________`,
+      `Нова акредитација: ___________________________________`,
+      '',
+      `Причина за промена: ___________________________________`,
+      '',
+      `Прилози: Уплатница за административна такса од 3000 ден.`,
+    ],
+    archiveNumber, date, 'Промена_програма_нова_акредитација');
+}
+
+// --- Diploma Supplement ---
+export async function generateDiplomaSupplement(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'ДОДАТОК НА ДИПЛОМА',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `запишан/а на студиската програма ${enrollment.program}, ${enrollment.cycle} циклус,`,
+      `побарувам издавање на додаток на диплома.`,
+      '',
+      `Додатокот на дипломата се издава како придружен документ`,
+      `кон дипломата за завршените студии.`,
+    ],
+    archiveNumber, date, 'Додаток_диплома');
+}
+
+// --- Diploma Supplement Second Cycle ---
+export async function generateDiplomaSupplementSecondCycle(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'ДОДАТОК НА ДИПЛОМА (ВТОР ЦИКЛУС)',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `запишан/а на студиската програма ${enrollment.program}, втор циклус на студии,`,
+      `побарувам издавање на додаток на диплома за вториот циклус студии.`,
+      '',
+      `Додатокот на дипломата се издава како придружен документ`,
+      `кон магистерската диплома.`,
+    ],
+    archiveNumber, date, 'Додаток_диплома_втор_циклус');
+}
+
+// --- MKSA ---
+export async function generateMKSA(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'МКСА - МАКЕДОНСКА КВАЛИФИКАЦИСКА РАМКА',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам издавање на МКСА (Македонска Квалификациска Рамка) документ.`,
+      '',
+      `Студиска програма: ${enrollment.program}`,
+      `Циклус: ${enrollment.cycle}`,
+      '',
+      `Документот е потребен за целите на: ___________________________________`,
+      '',
+      `Прилози: Уплатница за административна такса од 750 ден.`,
+    ],
+    archiveNumber, date, 'МКСА');
+}
+
+// --- Exam Recognition ---
+export async function generateExamRecognition(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'ПРИЗНАВАЊЕ НА ПОЛОЖЕНИ ИСПИТИ',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам признавање на положени испити од претходна институција / програма.`,
+      '',
+      `Претходна институција: ___________________________________`,
+      `Претходна програма: ___________________________________`,
+      '',
+      `Листа на предмети за признавање:`,
+      `1. ___________________________________`,
+      `2. ___________________________________`,
+      `3. ___________________________________`,
+      '',
+      `Прилози: Уверение за положени испити од претходна институција.`,
+    ],
+    archiveNumber, date, 'Признавање_испити');
+}
+
+// --- Student Card (Old) ---
+export async function generateStudentCardOld(
+  student: StudentInfo, enrollment: EnrollmentInfo, archiveNumber: string, date: string,
+): Promise<void> {
+  await generateRequestDocument(student, enrollment,
+    'СТУДЕНТСКИ КАРТОН (СТАРО)',
+    [
+      `До: Деканат на ФИНКИ`,
+      '',
+      `Јас, ${student.firstName} ${student.middleName} ${student.lastName}, студент/ка со индекс ${student.index},`,
+      `побарувам издавање на студентски картон (стар формат).`,
+      '',
+      `Студиска програма: ${enrollment.program}`,
+      `Година на упис: ${enrollment.enrollmentYear}`,
+      '',
+      `Молам да ми биде издаден студентски картон.`,
+    ],
+    archiveNumber, date, 'Студентски_картон');
+}
+
+/* ==================================================================
+   Main dispatcher — called from the Documents page
+   ================================================================== */
+
+// Map document type IDs → generator function names
+const DOCUMENT_TYPE_MAP: Record<string, string> = {
+  'административно регулирање': 'administrative_regulation',
+  'задочно пријавување': 'exam_application_satisfaction',
+  'запишување на семестар по истек': 'semester_enrollment_after_deadline',
+  'издавање потврди': 'certificate_issuance_various',
+  'мирување на студиите': 'student_status_verification',
+  'откажување на пријавена тема': 'diploma_thesis_postponement',
+  'пакет за дипломирање': 'diploma_package',
+  'поништување на испит': 'exam_postponement',
+  'потврда за редовен студент': 'regular_student_certificate',
+  'продолжување на студии во мирување': 'study_continuation_verification',
+  'промена на изборен предмет': 'failed_subject_grade_change',
+  'промена на положен предмет': 'passed_subject_grade_change',
+  'промена на студиска програма од иста': 'study_program_change_same_accreditation',
+  'промена на студиска програма од понова': 'study_program_change_new_accreditation',
+  'додаток на диплома (втор циклус)': 'diploma_supplement_second_cycle',
+  'додаток на диплома': 'diploma_supplement',
+  'мкса': 'mksa',
+  'признавање на положени испити': 'exam_recognition',
+  'студентски картон': 'student_card_old',
+};
+
+function identifyDocumentType(request: string): string | null {
+  const lower = request.toLowerCase();
+
+  // Exact-match patterns (check longer patterns first)
+  if (lower.includes('положени испити')) return 'passed_exams_certificate';
+  if (lower.includes('уппи')) return 'uppi_form';
+  if (lower.includes('редовен студент')) return 'regular_student_certificate';
+
+  // Match by document type keywords
+  for (const [keyword, docType] of Object.entries(DOCUMENT_TYPE_MAP)) {
+    if (lower.includes(keyword)) return docType;
+  }
+
+  return null;
+}
+
+export async function downloadDocumentPDF(
+  documentRequest: string,
+  archiveNumber: string,
+  documentDate: string,
+  accessToken: string,
+): Promise<void> {
+  // Fetch student profile
+  const profileRes = await fetch(apiUrl('/api/user/getUser'), {
+    headers: { Authorization: `Bearer ${accessToken}` },
+  });
+  if (!profileRes.ok) throw new Error('Не може да се вчита профилот на студентот');
+
+  const profile = await profileRes.json();
+  const { personalInfo, enrollmentInfo, birthInfo, contact, previousEducation } = profile;
+
+  const student: StudentInfo = {
+    firstName: personalInfo.firstName,
+    middleName: personalInfo.middleName,
+    lastName: personalInfo.lastName,
+    index: personalInfo.index,
+    embg: personalInfo.embg,
+    dateOfBirth: personalInfo.dateOfBirth,
+    gender: personalInfo.gender,
+    nationality: personalInfo.nationality,
+    citizenship: personalInfo.citizenship,
+    currentPlan: personalInfo.currentPlan,
+    registryNumber: personalInfo.registryNumber,
+    studyGroup: personalInfo.studyGroup,
+    scholarship: personalInfo.scholarship,
+  };
+
+  const enrollment: EnrollmentInfo = {
+    enrollmentYear: enrollmentInfo.enrollmentYear,
+    status: enrollmentInfo.status,
+    cycle: enrollmentInfo.cycle,
+    program: enrollmentInfo.program,
+    quota: enrollmentInfo.quota,
+  };
+
+  const docType = identifyDocumentType(documentRequest);
+
+  // Types that need passed exams data
+  const needsExams = docType === 'passed_exams_certificate' || docType === 'uppi_form' || docType === 'regular_student_certificate';
+
+  let passedExams: PassedExam[] = [];
+  if (needsExams) {
+    const examsRes = await fetch(apiUrl('/api/user/getPassedSubjects'), {
+      headers: { Authorization: `Bearer ${accessToken}` },
+    });
+    if (examsRes.ok) {
+      const examsData = await examsRes.json();
+      passedExams = (examsData?.passedSubjects ?? []).map((s: Record<string, unknown>) => ({
+        id: s.id as number,
+        code: s.code as string,
+        subject: s.subject as string,
+        credits: s.credits as number,
+        grade: s.grade as number,
+        date: s.date as string,
+        semester: s.semester as string,
+        professor: (s.professor as string) || '—',
+      }));
+    }
+  }
+
+  switch (docType) {
+    case 'passed_exams_certificate':
+      await generatePassedExamsCertificate(student, enrollment, passedExams, archiveNumber, documentDate);
+      break;
+    case 'uppi_form': {
+      const birth: BirthInfo = {
+        placeOfBirth: birthInfo.placeOfBirth,
+        municipalityOfBirth: birthInfo.municipalityOfBirth,
+        country: birthInfo.country,
+      };
+      const contactInfo: ContactInfo = {
+        placeOfResidence: contact.placeOfResidence,
+        municipalityOfResidence: contact.municipalityOfResidence,
+        address: contact.address,
+        phone: contact.phone,
+        mobilePhone: contact.mobilePhone,
+        email: contact.email,
+      };
+      const prevEdu: PreviousEducation = {
+        type: previousEducation.type,
+        profession: previousEducation.profession,
+        average: previousEducation.average,
+        language: previousEducation.language,
+        country: previousEducation.country,
+        previousUniversity: previousEducation.previousUniversity,
+        previousFaculty: previousEducation.previousFaculty,
+      };
+      await generateUPPIForm(student, enrollment, birth, contactInfo, prevEdu, passedExams, archiveNumber, documentDate);
+      break;
+    }
+    case 'regular_student_certificate':
+      await generateRegularStudentCertificate(student, enrollment, passedExams, archiveNumber, documentDate);
+      break;
+    case 'administrative_regulation':
+      await generateAdministrativeRegulation(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'exam_application_satisfaction':
+      await generateExamApplicationSatisfaction(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'semester_enrollment_after_deadline':
+      await generateSemesterEnrollmentAfterDeadline(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'certificate_issuance_various':
+      await generateCertificateIssuanceVarious(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'student_status_verification':
+      await generateStudySuspension(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'diploma_thesis_postponement':
+      await generateDiplomaThesisCancellation(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'diploma_package':
+      await generateDiplomaPackage(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'exam_postponement':
+      await generateExamCancellation(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'study_continuation_verification':
+      await generateStudyContinuation(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'failed_subject_grade_change':
+      await generateElectiveSubjectChange(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'passed_subject_grade_change':
+      await generatePassedSubjectChange(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'study_program_change_same_accreditation':
+      await generateProgramChangeSameAccreditation(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'study_program_change_new_accreditation':
+      await generateProgramChangeNewAccreditation(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'diploma_supplement':
+      await generateDiplomaSupplement(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'diploma_supplement_second_cycle':
+      await generateDiplomaSupplementSecondCycle(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'mksa':
+      await generateMKSA(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'exam_recognition':
+      await generateExamRecognition(student, enrollment, archiveNumber, documentDate);
+      break;
+    case 'student_card_old':
+      await generateStudentCardOld(student, enrollment, archiveNumber, documentDate);
+      break;
+    default:
+      await generateGenericDocument(student, enrollment, documentRequest, archiveNumber, documentDate);
+      break;
+  }
+}
Index: frontend/src/lib/utils.ts
===================================================================
--- frontend/src/lib/utils.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/lib/utils.ts	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs))
+}
Index: frontend/src/locales/en/translation.json
===================================================================
--- frontend/src/locales/en/translation.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/locales/en/translation.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,321 @@
+{
+  "prof_students_title": "Professor • Students",
+  "prof_students_data_note": "Data comes from",
+  "filter_by_subject": "Filter by subject",
+  "all_subjects": "All subjects",
+  "find_student_by_id": "Find student by index / ID",
+  "student_id_placeholder": "e.g. 20001",
+  "no_students_found": "No students found.",
+  "taxes": "Taxes",
+  "signatures": "Signatures",
+  "valid": "Valid",
+  "completed_semester": "Completed semesters",
+  "verified_semesters": "Verified semesters",
+  "total_semesters": "Total semesters",
+  "completed_semesters": "Completed semesters",
+  "documents_overview": "Overview of your documents and their status.",
+  "new_document_request": "New Document Request",
+  "select_document": "Select Document",
+  "price": "Price",
+  "comment": "Comment",
+  "add_comment": "Add comment",
+  "submit": "Submit",
+  "submitting": "Submitting...",
+  "document_submitted": "Document request submitted successfully!",
+  "document_submit_error": "Error submitting document request.",
+  "not_authenticated": "You are not logged in. Please log in again.",
+  "pdf_error": "Error generating document. Please try again.",
+  "generating": "Generating...",
+  "pending": "Pending",
+  "my_documents": "My Documents",
+  "archive": "Archive",
+  "request": "Request",
+  "document": "Document",
+  "pay_online": "Pay Online",
+  "none": "—",
+  "show_rows": "Show rows",
+  "page": "Page",
+  "of": "of",
+  "first": "First",
+  "last": "Last",
+  "total": "Total",
+  "documents_payment_info": "For more information about document payments, contact the administration.",
+  "total_documents": "Total Documents",
+  "approved": "Approved",
+  "total_price": "Total Price",
+  "mkd": "MKD",
+  "free": "0.00",
+  "download": "Download",
+  "university": "University ‘St. Cyril and Methodius’ - Skopje",
+  "faculty": "Faculty of Computer Science and Engineering",
+  "logout": "Logout",
+  "profile": "Profile",
+  "semesters": "Semesters",
+  "applications": "Applications",
+  "documents": "Documents",
+  "exams": "Exams",
+  "subjects": "Subjects",
+  "students": "Students",
+  "loading": "Loading...",
+  "failed_to_load_profile": "Failed to load profile.",
+  "n_a": "N/A",
+  "index": "Index",
+  "embg": "EMBG",
+  "personal_info": "Personal Information",
+  "first_name": "First Name",
+  "middle_name": "Middle Name",
+  "last_name": "Last Name",
+  "maiden_name": "Maiden Name",
+  "date_of_birth": "Date of Birth",
+  "gender": "Gender",
+  "male": "Male",
+  "female": "Female",
+  "nationality": "Nationality",
+  "citizenship": "Citizenship",
+  "scholarship": "Scholarship",
+  "current_plan": "Current Plan",
+  "registry_number": "Registry Number",
+  "study_group": "Study Group",
+  "note": "Note",
+  "birth_info": "Birth Information",
+  "place_of_birth": "Place of Birth",
+  "municipality_of_birth": "Municipality of Birth",
+  "country": "Country",
+  "previous_education": "Previous Education",
+  "type": "Type",
+  "profession": "Profession",
+  "average": "Average",
+  "language": "Language",
+  "previous_university": "Previous University",
+  "previous_faculty": "Previous Faculty",
+  "previous_study_mode": "Previous Study Mode",
+  "enrollment_info": "Enrollment Information",
+  "enrollment_year": "Enrollment Year",
+  "status": "Status",
+  "cycle": "Cycle",
+  "program": "Program",
+  "quota": "Quota",
+  "secondary_education_number": "Secondary Education Number",
+  "previous_education_credits": "Previous Education Credits",
+  "contact": "Contact",
+  "place_of_residence": "Place of Residence",
+  "municipality_of_residence": "Municipality of Residence",
+  "address": "Address",
+  "temporary_address": "Temporary Address",
+  "phone": "Phone",
+  "mobile_phone": "Mobile Phone",
+  "passport_number": "Passport Number",
+  "passport_expiry_date": "Passport Expiry Date",
+  "email": "Email",
+  "microsoft_email": "Microsoft Email",
+  "applications_header": "Applications",
+  "applications_subheader": "Electronic exam applications",
+  "select_exam_session": "Select exam session:",
+  "registered_exams": "Registered Exams",
+  "serial_number": "Serial Number",
+  "code": "Code",
+  "subject": "Subject",
+  "completed": "Completed",
+  "not_completed": "Not Completed",
+  "fee": "Fee",
+  "date": "Date",
+  "instructor": "Instructor",
+  "decade": "Decade",
+  "important_note": "Important Note",
+  "applications_important_note": "Important note regarding exam applications",
+  "yes": "Yes",
+  "no": "No",
+  "total_fee": "Total Fee",
+  "failed_to_load_exams": "Failed to load exams.",
+  "credits": "Credits",
+  "passed": "Passed",
+  "remaining": "Remaining",
+  "semester": "Semester",
+  "grade": "Grade",
+  "semesters_overview": "Overview of your enrolled semesters and their statuses.",
+  "semesters_list": "Semesters List",
+  "direction": "Direction",
+  "student_com": "Student Commission",
+  "sum": "Sum",
+  "paid": "Paid",
+  "ukim": "UKIM",
+  "created_on": "Created On",
+  "date_changed": "Date Changed",
+  "doc": "Document",
+  "doc1": "Document 1",
+  "verified": "Verified",
+  "debt": "Debt",
+  "debt_from_documents": "Debt from documents",
+  "ticket_number": "Ticket number",
+  "select_semester": "Select semester",
+  "enrolled_subjects": "Enrolled subjects",
+  "financial_info": "Financial information",
+  "material_costs": "Material costs",
+  "material_costs_info": "Material costs include all required fees for materials and resources for the subjects.",
+  "electronic_registration": "Electronic registration",
+  "eukim": "eUKIM",
+  "bank_provision": "Bank provision",
+  "which_time": "Which time",
+  "signature": "Signature",
+  "group": "Group",
+  "professor": "Professor",
+  "subjects_list": "Subjects list",
+  "subjects_overview": "Overview of your enrolled subjects and their status.",
+  "mandatory_short": "Mand.",
+  "elective_short": "Elect.",
+  "enrolled_by_student": "Enrolled by student",
+  "due": "Debt",
+  "failed_to_load_subjects": "Failed to load subjects.",
+  "hours": "Hours",
+  "administrative_regulation": "Request for administrative regulation of retroactive semester (from 2024) (1000)",
+  "exam_application_satisfaction": "Request for late exam registration (from 2024) (1000)",
+  "semester_enrollment_after_deadline": "Request for semester enrollment after deadline (from 2024) (1500)",
+  "certificate_issuance_various": "Request for issuance of certificates for various reasons (from 2024) (1500)",
+  "student_status_verification": "Request for suspension of studies (from 2024) (2000)",
+  "diploma_thesis_postponement": "Request for cancellation of registered diploma thesis topic (from 2024) (1000)",
+  "diploma_package": "Request for graduation package (from 2024) (6200)",
+  "exam_postponement": "Request for exam cancellation (from 2024) (2000)",
+  "regular_student_certificate": "Request for regular student certificate (paper) (100)",
+  "study_continuation_verification": "Request for continuation of studies in suspension (from 2024) (2000)",
+  "failed_subject_grade_change": "Request for elective subject change (from 2024) (1500)",
+  "passed_subject_grade_change": "Request for passed subject change (from 2024) (0)",
+  "study_program_change_same_accreditation": "Request for study program change from same accreditation (from 2024) (2000)",
+  "study_program_change_new_accreditation": "Request for study program change from newer accreditation (from 2024) (3000)",
+  "diploma_supplement": "Diploma supplement (0)",
+  "diploma_supplement_second_cycle": "Diploma supplement (second cycle) (0)",
+  "mksa": "MKSA (750)",
+  "exam_recognition": "Recognition of passed exams (0)",
+  "student_card_old": "Student card (Old) (0)",
+  "Уверение за положени испити ФИНКИ": "Certificate of passed exams FINKI",
+  "УППИ образец": "UPPI form",
+  "Уверение за редовен студент - ФИНКИ": "Regular student certificate - FINKI",
+  "2025_summer_session2": "2025 (Summer) Second exam session",
+  "2025_summer_session1": "2025 (Summer) First exam session",
+  "2025_winter_session2": "2025 (Winter) Second exam session",
+  "2025_winter_session1": "2025 (Winter) First exam session",
+  "2024_summer_session2": "2024 (Summer) Second exam session",
+  "2024_summer_session1": "2024 (Summer) First exam session",
+  "2024_winter_session2": "2024 (Winter) Second exam session",
+  "2024_winter_session1": "2024 (Winter) First exam session",
+  "applications_important_note_text": "The serial number of the electronic application must be entered on the paper application that the student will submit physically.",
+  "login_student_title": "Login for Student",
+  "login_student_example": "Email: stefan.saveski@students.finki.ukim.mk\nPassword: Test12345!",
+  "login_professor_title": "Login for Professor",
+  "login_professor_example": "Email: vangel.ajanovski@finki.ukim.mk\nPassword: Test12345!",
+  "login_admin_title": "Login for Administrator",
+  "login_admin_example": "Email: igor.adminovski@finki.ukim.mk\nPassword: Test12345!",
+  "login_welcome": "Welcome",
+  "login_subtitle": "Sign in to your IKnow account",
+  "login_email_label": "Enter email",
+  "login_email_placeholder": "Enter email",
+  "login_password_label": "Password",
+  "login_password_placeholder": "Enter password",
+  "login_remember_me": "Remember me",
+  "login_forgot_password": "Forgot password?",
+  "login_button": "Sign in",
+  "login_button_loading": "Signing in...",
+  "login_no_account": "Don't have an account?",
+  "login_contact_admin": "Contact administration",
+  "login_footer": "Student Information System",
+  "contact_us": "Contact Us",
+  "close": "Close",
+  "contact_name": "Full Name",
+  "contact_name_placeholder": "Your name",
+  "contact_email": "Email",
+  "contact_email_placeholder": "example@email.com",
+  "contact_subject": "Subject",
+  "contact_subject_placeholder": "Message topic",
+  "contact_message": "Message",
+  "contact_message_placeholder": "Write your message...",
+  "contact_send": "Send",
+  "contact_sending": "Sending...",
+  "contact_sent_title": "Message sent!",
+  "contact_sent_desc": "We will get back to you soon.",
+  "Компјутерски мрежи и безбедност": "Computer Networks and Security",
+  "Шаблони за дизајн на кориснички интерфејси": "User Interface Design Patterns",
+  "Деловна пракса": "Business Practice",
+  "Маркетинг": "Marketing",
+  "Оперативни системи": "Operating Systems",
+  "Софтверско инженерство": "Software Engineering",
+  "Вештачка интелигенција": "Artificial Intelligence",
+  "Сашо Граматиков": "Sasho Gramatikov",
+  "Сузана Лошковска": "Suzana Loshkovska",
+  "Александар Стојменски": "Aleksandar Stojmenski",
+  "Билјана Стојкоска": "Biljana Stojkoska",
+  "Невена Ацковска": "Nevena Ackovska",
+  "Ѓорѓи Маџаров": "Gjorgji Madjarov",
+  "Соња Гиевска": "Sonja Gievska",
+  "Летна": "Summer",
+  "Зимска": "Winter",
+  "Прва испитна сесија": "First exam session",
+  "Втора испитна сесија": "Second exam session",
+  "Валидно": "Valid",
+  "Да": "Yes",
+  "Не": "No",
+  "exams_overview": "Overview of your passed exams and grades.",
+  "failed_to_load_semesters": "Failed to load semesters.",
+  "add_grade": "Add Grade",
+  "edit_grade": "Edit Grade",
+  "remove_grade": "Remove Grade",
+  "current": "Current",
+  "login_demo_note": "Use these credentials to explore the system.",
+  "enroll_semester": "Enroll semester",
+  "enroll_confirm": "Enroll",
+  "enroll_no_semesters": "You are already enrolled in every available semester.",
+  "enroll_already_passed": "passed",
+  "enroll_credits": "credits",
+  "enroll_semester_short": "sem.",
+  "enroll_not_signed_in": "You are not signed in.",
+  "enroll_options_failed": "Could not load enrollment options",
+  "enroll_failed": "Enrollment failed",
+  "cancel": "Cancel",
+  "admin_portal": "Admin",
+  "admin_subjects": "Subjects",
+  "admin_semesters": "Semesters",
+  "admin_schedule": "Teaching schedule",
+  "admin_subjects_intro": "Add subjects, map them to study programmes and set prerequisites.",
+  "admin_semesters_intro": "Open an active semester so students can enroll in it.",
+  "admin_schedule_intro": "Decide which professor teaches which subject in which semester.",
+  "admin_new_subject": "New subject",
+  "admin_edit_subject": "Edit subject",
+  "admin_subject_name": "Name",
+  "admin_subject_code": "Code",
+  "admin_credits": "Credits",
+  "admin_dependency_credit": "Required credits",
+  "admin_create": "Create",
+  "admin_save": "Save",
+  "admin_delete": "Delete",
+  "admin_remove": "Remove",
+  "admin_add": "Add",
+  "admin_manage": "Manage",
+  "admin_close": "Close",
+  "admin_majors": "Study programmes",
+  "admin_prerequisites": "Prerequisites",
+  "admin_pick_major": "Pick a study programme",
+  "admin_pick_prerequisite": "Pick a prerequisite",
+  "admin_pick_subject": "Pick a subject",
+  "admin_pick_professor": "Pick a professor",
+  "admin_search_subject": "Search by name or code",
+  "admin_no_subjects": "No subjects found.",
+  "admin_delete_blocked": "Cannot delete: {{count}} enrolment(s) already contain this subject.",
+  "admin_new_semester": "Open a new semester",
+  "admin_year": "Year",
+  "admin_type": "Type",
+  "admin_winter": "Winter",
+  "admin_summer": "Summer",
+  "admin_open_semester": "Open semester",
+  "admin_open_semesters": "Open semesters",
+  "admin_no_semesters": "No semesters yet.",
+  "admin_enrolments": "Enrolments",
+  "admin_coverage": "Coverage",
+  "admin_fully_covered": "all subjects covered",
+  "admin_uncovered_count": "{{count}} subject(s) without a professor",
+  "admin_uncovered_hint": "Students cannot enroll in a subject nobody teaches that semester.",
+  "admin_show_uncovered": "Show",
+  "admin_professor": "Professor",
+  "admin_assign": "Assign",
+  "admin_assignments": "Assignments",
+  "admin_load": "Teaching load",
+  "admin_no_assignments": "Nothing assigned yet.",
+  "admin_no_professors": "No professors."
+}
Index: frontend/src/locales/mk/translation.json
===================================================================
--- frontend/src/locales/mk/translation.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
+++ frontend/src/locales/mk/translation.json	(revision b8093a091d45f33bab33deb8ff2fffc4734972c0)
@@ -0,0 +1,325 @@
+{
+  "prof_students_title": "Професор • Студенти",
+  "prof_students_data_note": "Податоците се од",
+  "filter_by_subject": "Филтрирај по предмет",
+  "all_subjects": "Сите предмети",
+  "find_student_by_id": "Најди студент по индекс / ID",
+  "student_id_placeholder": "пр. 20001",
+  "no_students_found": "Не се пронајдени студенти.",
+  "student": "Студент",
+  "id": "ID",
+  "actions": "Акции",
+  "current": "Тековна",
+  "add_grade": "Додади оценка",
+  "edit_grade": "Измени оценка",
+  "remove_grade": "Избриши оценка",
+  "taxes": "Такси",
+  "signatures": "Потписи",
+  "valid": "Валидно",
+  "completed_semester": "Завршени семестри",
+  "verified_semesters": "Потврдени семестри",
+  "total_semesters": "Вкупно семестри",
+  "completed_semesters": "Завршени семестри",
+  "documents_overview": "Преглед на вашите документи и нивниот статус.",
+  "new_document_request": "Ново барање за документ",
+  "select_document": "Изберете документ",
+  "price": "Цена",
+  "comment": "Коментар",
+  "add_comment": "Додај коментар",
+  "submit": "Поднеси",
+  "submitting": "Се поднесува...",
+  "document_submitted": "Барањето за документ е успешно поднесено!",
+  "document_submit_error": "Грешка при поднесување на барањето.",
+  "not_authenticated": "Не сте најавени. Ве молиме најавете се повторно.",
+  "pdf_error": "Грешка при генерирање на документот. Обидете се повторно.",
+  "generating": "Генерира...",
+  "pending": "Во обработка",
+  "my_documents": "Мои документи",
+  "archive": "Архива",
+  "request": "Барање",
+  "document": "Документ",
+  "pay_online": "Плати онлајн",
+  "none": "—",
+  "show_rows": "Прикажи редови",
+  "page": "Страница",
+  "of": "од",
+  "first": "Прва",
+  "last": "Последна",
+  "total": "Вкупно",
+  "documents_payment_info": "За повеќе информации за плаќање документи, обратете се во администрација.",
+  "total_documents": "Вкупно документи",
+  "approved": "Одобрено",
+  "total_price": "Вкупна цена",
+  "mkd": "мкд",
+  "free": "0,00",
+  "download": "Преземи",
+  "choose_document": "Избери документ",
+  "university": "Универзитет „Св. КИРИЛ И МЕТОДИЈ“ - Скопје",
+  "faculty": "Факултет за информатички науки и компјутерско инженерство",
+  "logout": "Одјави се",
+  "profile": "Профил",
+  "semesters": "Семестри",
+  "applications": "Апликации",
+  "documents": "Документи",
+  "exams": "Испити",
+  "subjects": "Предмети",
+  "students": "Студенти",
+  "loading": "Се вчитува...",
+  "failed_to_load_profile": "Не успеавме да го вчитаме профилот.",
+  "n_a": "Н/П",
+  "index": "Индекс",
+  "embg": "ЕМБГ",
+  "personal_info": "Лични податоци",
+  "first_name": "Име",
+  "middle_name": "Средно име",
+  "last_name": "Презиме",
+  "maiden_name": "Моминско презиме",
+  "date_of_birth": "Датум на раѓање",
+  "gender": "Пол",
+  "male": "машки",
+  "female": "женски",
+  "nationality": "Националност",
+  "citizenship": "Државјанство",
+  "scholarship": "Стипендија",
+  "current_plan": "Тековен план",
+  "registry_number": "Бр. во матична книга",
+  "study_group": "Група на студирање",
+  "note": "Забелешка",
+  "birth_info": "Податоци за раѓање",
+  "place_of_birth": "Место на раѓање",
+  "municipality_of_birth": "Општина на раѓање",
+  "country": "Земја",
+  "previous_education": "Претходно образование",
+  "type": "Тип",
+  "profession": "Професија",
+  "average": "Просек",
+  "language": "Јазик",
+  "previous_university": "Претходен универзитет",
+  "previous_faculty": "Претходен факултет",
+  "previous_study_mode": "Режим на претходни студии",
+  "enrollment_info": "Податоци за упис",
+  "enrollment_year": "Година на упис",
+  "status": "Статус",
+  "cycle": "Циклус",
+  "program": "Запишана програма",
+  "quota": "Квота на прв упис",
+  "secondary_education_number": "Бр. дипл. ср. образование",
+  "previous_education_credits": "Кред. пр. образование",
+  "contact": "Контакт",
+  "place_of_residence": "Место на живеење",
+  "municipality_of_residence": "Општина на живеење",
+  "address": "Адреса",
+  "temporary_address": "Адреса на престој",
+  "phone": "Телефон",
+  "mobile_phone": "Моб. телефон",
+  "passport_number": "Број на пасош",
+  "passport_expiry_date": "Датум на истекување на пасошот",
+  "email": "Е-пошта",
+  "microsoft_email": "Microsoft email",
+  "applications_header": "Пријави",
+  "applications_subheader": "Електронски пријави за испити",
+  "select_exam_session": "Избери испитна сесија:",
+  "registered_exams": "Пријавени испити",
+  "serial_number": "Сериски број",
+  "code": "Код",
+  "subject": "Предмет",
+  "completed": "Завршени",
+  "not_completed": "Незавршени",
+  "fee": "Таксени",
+  "date": "Датум",
+  "instructor": "Наставник",
+  "decade": "Декада",
+  "important_note": "Важна забелешка",
+  "applications_important_note": "Важна забелешка за испитните пријави",
+  "yes": "Да",
+  "no": "Не",
+  "total_fee": "Вкупна такса",
+  "failed_to_load_exams": "Не успеавме да ги вчитаме испитите.",
+  "credits": "Кредити",
+  "passed": "Положени",
+  "remaining": "Останато",
+  "semester": "Семестар",
+  "grade": "Оценка",
+  "semesters_overview": "Преглед на вашите запишани семестри и нивниот статус.",
+  "semesters_list": "Листа на семестри",
+  "direction": "Насока",
+  "student_com": "Студентска комисија",
+  "sum": "Сума",
+  "paid": "Платено",
+  "ukim": "УКИМ",
+  "created_on": "Креирано на",
+  "date_changed": "Датум на промена",
+  "doc": "Документ",
+  "doc1": "Документ 1",
+  "verified": "Потврдено",
+  "debt": "Долг",
+  "debt_from_documents": "Долг од документи",
+  "ticket_number": "Број на тикет",
+  "select_semester": "Избери семестар",
+  "enrolled_subjects": "Запишани предмети",
+  "financial_info": "Финансиски информации",
+  "material_costs": "Материјални трошоци",
+  "material_costs_info": "Материјалните трошоци ги вклучуваат сите задолжителни надоместоци за материјали и ресурси за предметите.",
+  "electronic_registration": "Електронска регистрација",
+  "eukim": "еУКИМ",
+  "bank_provision": "Банкарска провизија",
+  "which_time": "Кој пат",
+  "signature": "Потпис",
+  "group": "Група",
+  "professor": "Професор",
+  "subjects_list": "Листа на предмети",
+  "subjects_overview": "Преглед на вашите запишани предмети и нивниот статус.",
+  "mandatory_short": "Зад.",
+  "elective_short": "Изб.",
+  "enrolled_by_student": "Запишан од студентот",
+  "due": "Долг",
+  "failed_to_load_subjects": "Не успеавме да ги вчитаме предметите.",
+  "hours": "Часови",
+  "administrative_regulation": "Барање за административно регулирање на ретроактивен семестар (од 2024г) ( 1000)",
+  "exam_application_satisfaction": "Барање за задочно пријавување на испит (од 2024г) ( 1000)",
+  "semester_enrollment_after_deadline": "Барање за запишување на семестар по истек на рок (од 2024г) ( 1500)",
+  "certificate_issuance_various": "Барање за издавање потврди по разни основи (од 2024г) ( 1500)",
+  "student_status_verification": "Барање за мирување на студиите (од 2024г) ( 2000)",
+  "diploma_thesis_postponement": "Барање за откажување на пријавена тема за дипломска работа (од 2024г) ( 1000)",
+  "diploma_package": "Барање за пакет за дипломирање (од 2024г) (6200)",
+  "exam_postponement": "Барање за поништување на испит (од 2024г) ( 2000)",
+  "regular_student_certificate": "Барање за потврда за редовен студент (хартиена) ( 100)",
+  "study_continuation_verification": "Барање за продолжување на студии во мирување (од 2024г) ( 2000)",
+  "failed_subject_grade_change": "Барање за промена на изборен предмет (од 2024г) ( 1500)",
+  "passed_subject_grade_change": "Барање за промена на положен предмет (од 2024г) ( 0)",
+  "study_program_change_same_accreditation": "Барање за промена на студиска програма од иста акредитација (од 2024г) ( 2000)",
+  "study_program_change_new_accreditation": "Барање за промена на студиска програма од понова акредитација (од 2024г) ( 3000)",
+  "diploma_supplement": "Додаток на диплома ( 0)",
+  "diploma_supplement_second_cycle": "Додаток на диплома (втор циклус) ( 0)",
+  "mksa": "МКСА ( 750)",
+  "exam_recognition": "Признавање на положени испити ( 0)",
+  "student_card_old": "Студентски картон (Старо) ( 0)",
+  "Уверение за положени испити ФИНКИ": "Уверение за положени испити ФИНКИ",
+  "УППИ образец": "УППИ образец",
+  "Уверение за редовен студент - ФИНКИ": "Уверение за редовен студент - ФИНКИ",
+  "Компјутерски мрежи и безбедност": "Компјутерски мрежи и безбедност",
+  "Шаблони за дизајн на кориснички интерфејси": "Шаблони за дизајн на кориснички интерфејси",
+  "Деловна пракса": "Деловна пракса",
+  "Маркетинг": "Маркетинг",
+  "Оперативни системи": "Оперативни системи",
+  "Софтверско инженерство": "Софтверско инженерство",
+  "Вештачка интелигенција": "Вештачка интелигенција",
+  "Сашо Граматиков": "Сашо Граматиков",
+  "Сузана Лошковска": "Сузана Лошковска",
+  "Александар Стојменски": "Александар Стојменски",
+  "Билјана Стојкоска": "Билјана Стојкоска",
+  "Невена Ацковска": "Невена Ацковска",
+  "Ѓорѓи Маџаров": "Ѓорѓи Маџаров",
+  "Соња Гиевска": "Соња Гиевска",
+  "exams_overview": "Преглед на вашите положени испити и оценки.",
+  "failed_to_load_semesters": "Не успеавме да ги вчитаме семестрите.",
+  "Летна": "Летна",
+  "Зимска": "Зимска",
+  "Прва испитна сесија": "Прва испитна сесија",
+  "Втора испитна сесија": "Втора испитна сесија",
+  "Валидно": "Валидно",
+  "Да": "Да",
+  "Не": "Не",
+  "2025_summer_session2": "2025 (Летна) Втора испитна сесија",
+  "2025_summer_session1": "2025 (Летна) Прва испитна сесија",
+  "2025_winter_session2": "2025 (Зимска) Втора испитна сесија",
+  "2025_winter_session1": "2025 (Зимска) Прва испитна сесија",
+  "2024_summer_session2": "2024 (Летна) Втора испитна сесија",
+  "2024_summer_session1": "2024 (Летна) Прва испитна сесија",
+  "2024_winter_session2": "2024 (Зимска) Втора испитна сесија",
+  "2024_winter_session1": "2024 (Зимска) Прва испитна сесија",
+  "applications_important_note_text": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички.",
+  "login_student_title": "Најава за студент",
+  "login_student_example": "Е-пошта: stefan.saveski@students.finki.ukim.mk\nЛозинка: Test12345!",
+  "login_professor_title": "Најава за професор",
+  "login_professor_example": "Е-пошта: vangel.ajanovski@finki.ukim.mk\nЛозинка: Test12345!",
+  "login_admin_title": "Најава за администратор",
+  "login_admin_example": "Е-пошта: igor.adminovski@finki.ukim.mk\nЛозинка: Test12345!",
+  "login_welcome": "Добредојде",
+  "login_subtitle": "Најавете се во вашиот IKnow акаунт",
+  "login_email_label": "Внесете е-пошта",
+  "login_email_placeholder": "Внесете е-пошта",
+  "login_password_label": "Лозинка",
+  "login_password_placeholder": "Внесете лозинка",
+  "login_remember_me": "Запомни ме",
+  "login_forgot_password": "Заборавена лозинка?",
+  "login_button": "Најави се",
+  "login_button_loading": "Најавување...",
+  "login_no_account": "Немате акаунт?",
+  "login_contact_admin": "Контактирајте ја администрацијата",
+  "login_footer": "Студентски информационен систем",
+  "contact_us": "Контактирај нè",
+  "close": "Затвори",
+  "contact_name": "Име и презиме",
+  "contact_name_placeholder": "Вашето име",
+  "contact_email": "Е-пошта",
+  "contact_email_placeholder": "example@email.com",
+  "contact_subject": "Наслов",
+  "contact_subject_placeholder": "Тема на пораката",
+  "contact_message": "Порака",
+  "contact_message_placeholder": "Напишете ја вашата порака...",
+  "contact_send": "Испрати",
+  "contact_sending": "Испраќа...",
+  "contact_sent_title": "Пораката е испратена!",
+  "contact_sent_desc": "Ќе ви одговориме наскоро.",
+  "login_demo_note": "Користете ги овие податоци за да го истражите системот.",
+  "enroll_semester": "Запиши семестар",
+  "enroll_confirm": "Запиши",
+  "enroll_no_semesters": "Веќе сте запишани во сите достапни семестри.",
+  "enroll_already_passed": "положен",
+  "enroll_credits": "кредити",
+  "enroll_semester_short": "сем.",
+  "enroll_not_signed_in": "Не сте најавени.",
+  "enroll_options_failed": "Не може да се вчитаат опциите за запишување",
+  "enroll_failed": "Запишувањето не успеа",
+  "cancel": "Откажи",
+  "admin_portal": "Админ",
+  "admin_subjects": "Предмети",
+  "admin_semesters": "Семестри",
+  "admin_schedule": "Распоред",
+  "admin_subjects_intro": "Додавање предмети, поврзување со студиски програми и предуслови.",
+  "admin_semesters_intro": "Отворање активен семестар во кој студентите можат да се запишат.",
+  "admin_schedule_intro": "Определување кој професор кој предмет го предава.",
+  "admin_new_subject": "Нов предмет",
+  "admin_edit_subject": "Измена на предмет",
+  "admin_subject_name": "Назив",
+  "admin_subject_code": "Шифра",
+  "admin_credits": "Кредити",
+  "admin_dependency_credit": "Потребни кредити",
+  "admin_create": "Креирај",
+  "admin_save": "Зачувај",
+  "admin_delete": "Избриши",
+  "admin_remove": "Отстрани",
+  "admin_add": "Додади",
+  "admin_manage": "Уреди",
+  "admin_close": "Затвори",
+  "admin_majors": "Студиски програми",
+  "admin_prerequisites": "Предуслови",
+  "admin_pick_major": "Избери студиска програма",
+  "admin_pick_prerequisite": "Избери предуслов",
+  "admin_pick_subject": "Избери предмет",
+  "admin_pick_professor": "Избери професор",
+  "admin_search_subject": "Пребарај по назив или шифра",
+  "admin_no_subjects": "Нема предмети.",
+  "admin_delete_blocked": "Не може да се избрише: {{count}} запишувања го содржат предметот.",
+  "admin_new_semester": "Отвори нов семестар",
+  "admin_year": "Година",
+  "admin_type": "Тип",
+  "admin_winter": "Зимски",
+  "admin_summer": "Летен",
+  "admin_open_semester": "Отвори семестар",
+  "admin_open_semesters": "Отворени семестри",
+  "admin_no_semesters": "Нема семестри.",
+  "admin_enrolments": "Запишувања",
+  "admin_coverage": "Покриеност",
+  "admin_fully_covered": "сите предмети се покриени",
+  "admin_uncovered_count": "{{count}} предмети без професор",
+  "admin_uncovered_hint": "Студентите не можат да запишат предмет кој никој не го предава.",
+  "admin_show_uncovered": "Прикажи",
+  "admin_professor": "Професор",
+  "admin_assign": "Распореди",
+  "admin_assignments": "Распоред",
+  "admin_load": "Оптовареност",
+  "admin_no_assignments": "Секој нема распоред.",
+  "admin_no_professors": "Нема професори."
+}
