Changeset 8496f3c for src


Ignore:
Timestamp:
09/15/26 21:16:02 (8 days ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
b8093a0
Parents:
4fe4582
Message:
  • ready for presentation
Location:
src
Files:
9 added
5 deleted
12 edited

Legend:

Unmodified
Added
Removed
  • src/app/page.tsx

    r4fe4582 r8496f3c  
    3939    try {
    4040      const session = await login({ email: formData.email, password: formData.password });
    41       window.location.href = session.role === 'Professor' ? '/professor' : '/students';
     41      // Admins previously fell through to /students, where they have no
     42      // enrolments and every page renders empty.
     43      const home =
     44        session.role === 'Professor' ? '/professor'
     45        : session.role === 'Admin'   ? '/admin'
     46        : '/students';
     47      window.location.href = home;
    4248    } catch (err) {
    4349      setErrorMessage(err instanceof Error ? err.message : 'Login failed.');
     
    7076                  <div className="text-xs text-card-foreground whitespace-pre-wrap">
    7177                    {t('login_professor_example')}
     78                  </div>
     79                  <div className="mt-3 text-xs text-blue-600 font-medium">
     80                    {t('login_demo_note')}
     81                  </div>
     82                </div>
     83
     84                <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
     85                  <div className="text-sm font-bold text-card-foreground mb-2">{t('login_admin_title')}</div>
     86                  <div className="text-xs text-card-foreground whitespace-pre-wrap">
     87                    {t('login_admin_example')}
    7288                  </div>
    7389                  <div className="mt-3 text-xs text-blue-600 font-medium">
  • src/app/professor/profile/page.tsx

    r4fe4582 r8496f3c  
    1919import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
    2020import { useTranslation } from 'react-i18next';
     21import { apiUrl } from '@/lib/api';
    2122
    2223type PersonalInfo = {
     
    152153
    153154      try {
    154         const response = await fetch("https://iknow-api.onrender.com/api/user/getUser", {
     155        const response = await fetch(apiUrl("/api/user/getUser"), {
    155156          method: "GET",
    156157          headers: {
  • src/app/professor/students/page.tsx

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

    r4fe4582 r8496f3c  
    2121import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
    2222import { useTranslation } from 'react-i18next';
     23import { apiUrl } from '@/lib/api';
    2324
    2425type PersonalInfo = {
     
    154155
    155156      try {
    156         const response = await fetch('https://iknow-api.onrender.com/api/user/getUser', {
     157        const response = await fetch(apiUrl('/api/user/getUser'), {
    157158          method: 'GET',
    158159          headers: {
  • src/app/students/semesters/page.tsx

    r4fe4582 r8496f3c  
    1515import { getAccessToken } from '@/lib/auth';
    1616import { useTranslation } from 'react-i18next';
     17import { apiUrl } from '@/lib/api';
     18import EnrollSemesterDialog from '@/components/enroll-semester-dialog';
    1719
    1820type Semester = {
     
    113115  const [isLoading, setIsLoading] = useState(true);
    114116  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     117  const [enrollOpen, setEnrollOpen] = useState(false);
     118  // Bumped after a successful enrolment so the table reloads.
     119  const [reloadKey, setReloadKey] = useState(0);
    115120  const { t } = useTranslation();
    116121
     
    130135
    131136      try {
    132         const response = await fetch('https://iknow-api.onrender.com/api/user/getSemesters', {
     137        const response = await fetch(apiUrl('/api/user/getSemesters'), {
    133138          method: 'GET',
    134139          headers: {
     
    158163      cancelled = true;
    159164    };
    160   }, []);
     165  }, [reloadKey]);
    161166
    162167  if (isLoading) {
     
    184189      {/* Header */}
    185190      <div className="bg-primary text-white rounded-xl p-8 mb-8">
    186         <div className="flex items-center gap-4">
    187           <div className="bg-white rounded-full p-4">
    188             <FontAwesomeIcon icon={faCalendarAlt} className="text-3xl text-[#0272D1]" />
     191        <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
     192          <div className="flex items-center gap-4">
     193            <div className="bg-white rounded-full p-4">
     194              <FontAwesomeIcon icon={faCalendarAlt} className="text-3xl text-[#0272D1]" />
     195            </div>
     196            <div>
     197              <h1 className="text-3xl font-bold mb-2">{t('semesters')}</h1>
     198              <p className="text-lg opacity-90">
     199                {t('semesters_overview')}
     200              </p>
     201            </div>
    189202          </div>
    190           <div>
    191             <h1 className="text-3xl font-bold mb-2">{t('semesters')}</h1>
    192             <p className="text-lg opacity-90">
    193               {t('semesters_overview')}
    194             </p>
    195           </div>
     203
     204          <button
     205            className="self-start rounded-lg bg-white px-5 py-3 font-semibold text-[#0272D1] shadow-sm hover:bg-white/90 md:self-auto"
     206            onClick={() => setEnrollOpen(true)}
     207          >
     208            + {t('enroll_semester')}
     209          </button>
    196210        </div>
    197211      </div>
     
    373387        </div>
    374388      </div>
     389
     390      <EnrollSemesterDialog
     391        open={enrollOpen}
     392        onClose={() => setEnrollOpen(false)}
     393        onEnrolled={() => setReloadKey((k) => k + 1)}
     394      />
    375395    </div>
    376396  );
  • src/app/students/subjects/page.tsx

    r4fe4582 r8496f3c  
    1010import { getAccessToken } from '@/lib/auth';
    1111import { useTranslation } from 'react-i18next';
     12import { apiUrl } from '@/lib/api';
    1213
    1314interface Subject {
     
    193194
    194195      try {
    195         const response = await fetch('https://iknow-api.onrender.com/api/user/getSubjects', {
     196        const response = await fetch(apiUrl('/api/user/getSubjects'), {
    196197          method: 'GET',
    197198          headers: {
  • src/components/exams.tsx

    r4fe4582 r8496f3c  
    55import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
    66import { getAccessToken } from '@/lib/auth';
     7import { apiUrl } from '@/lib/api';
    78
    89type PassedSubject = {
     
    5657
    5758      try {
    58         const response = await fetch('https://iknow-api.onrender.com/api/user/getPassedSubjects', {
     59        const response = await fetch(apiUrl('/api/user/getPassedSubjects'), {
    5960          method: 'GET',
    6061          headers: {
  • src/components/header.tsx

    r4fe4582 r8496f3c  
    99import { useTranslation } from 'react-i18next';
    1010import LanguageSwitcher from './LanguageSwitcher';
     11import { apiUrl } from '@/lib/api';
    1112
    1213const Header = () => {
     
    2021      if (!token) return;
    2122      try {
    22         const response = await fetch('https://iknow-api.onrender.com/api/user/getUser', {
     23        const response = await fetch(apiUrl('/api/user/getUser'), {
    2324          method: 'GET',
    2425          headers: { Authorization: `Bearer ${token}` },
  • src/lib/auth.ts

    r4fe4582 r8496f3c  
     1import { API_BASE_URL } from './api';
    12export type AuthTokens = {
    23  accessToken: string;
     
    45};
    56
    6 export type UserRole = 'Professor' | 'Student' | string;
     7export type UserRole = 'Professor' | 'Student' | 'Admin' | string;
    78
    89export type AuthSession = AuthTokens & {
     
    9192
    9293export async function login(params: { email: string; password: string }): Promise<AuthSession> {
    93   const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? 'https://iknow-api.onrender.com';
     94  const baseUrl = API_BASE_URL;
    9495
    9596  const response = await fetch(`${baseUrl}/api/auth/login`, {
  • src/lib/pdf-generators.ts

    r4fe4582 r8496f3c  
    22import autoTable from 'jspdf-autotable';
    33import { loadCyrillicFonts } from './pdf-fonts';
     4import { apiUrl } from './api';
    45
    56/* ---------- shared types ---------- */
     
    10561057): Promise<void> {
    10571058  // Fetch student profile
    1058   const profileRes = await fetch('https://iknow-api.onrender.com/api/user/getUser', {
     1059  const profileRes = await fetch(apiUrl('/api/user/getUser'), {
    10591060    headers: { Authorization: `Bearer ${accessToken}` },
    10601061  });
     
    10951096  let passedExams: PassedExam[] = [];
    10961097  if (needsExams) {
    1097     const examsRes = await fetch('https://iknow-api.onrender.com/api/user/getPassedSubjects', {
     1098    const examsRes = await fetch(apiUrl('/api/user/getPassedSubjects'), {
    10981099      headers: { Authorization: `Bearer ${accessToken}` },
    10991100    });
  • src/locales/en/translation.json

    r4fe4582 r8496f3c  
    200200  "applications_important_note_text": "The serial number of the electronic application must be entered on the paper application that the student will submit physically.",
    201201  "login_student_title": "Login for Student",
    202   "login_student_example": "Email: boris@example.com\nPassword: your-password-here",
     202  "login_student_example": "Email: stefan.saveski@students.finki.ukim.mk\nPassword: Test12345!",
    203203  "login_professor_title": "Login for Professor",
    204   "login_professor_example": "Email: andonov@gmail.com\nPassword: 123lol456",
     204  "login_professor_example": "Email: vangel.ajanovski@finki.ukim.mk\nPassword: Test12345!",
     205  "login_admin_title": "Login for Administrator",
     206  "login_admin_example": "Email: igor.adminovski@finki.ukim.mk\nPassword: Test12345!",
    205207  "login_welcome": "Welcome",
    206208  "login_subtitle": "Sign in to your IKnow account",
     
    257259  "remove_grade": "Remove Grade",
    258260  "current": "Current",
    259   "login_demo_note": "Use these credentials to explore the system."
     261  "login_demo_note": "Use these credentials to explore the system.",
     262  "enroll_semester": "Enroll semester",
     263  "enroll_confirm": "Enroll",
     264  "enroll_no_semesters": "You are already enrolled in every available semester.",
     265  "enroll_already_passed": "passed",
     266  "enroll_credits": "credits",
     267  "enroll_semester_short": "sem.",
     268  "enroll_not_signed_in": "You are not signed in.",
     269  "enroll_options_failed": "Could not load enrollment options",
     270  "enroll_failed": "Enrollment failed",
     271  "cancel": "Cancel",
     272  "admin_portal": "Admin",
     273  "admin_subjects": "Subjects",
     274  "admin_semesters": "Semesters",
     275  "admin_schedule": "Teaching schedule",
     276  "admin_subjects_intro": "Add subjects, map them to study programmes and set prerequisites.",
     277  "admin_semesters_intro": "Open an active semester so students can enroll in it.",
     278  "admin_schedule_intro": "Decide which professor teaches which subject in which semester.",
     279  "admin_new_subject": "New subject",
     280  "admin_edit_subject": "Edit subject",
     281  "admin_subject_name": "Name",
     282  "admin_subject_code": "Code",
     283  "admin_credits": "Credits",
     284  "admin_dependency_credit": "Required credits",
     285  "admin_create": "Create",
     286  "admin_save": "Save",
     287  "admin_delete": "Delete",
     288  "admin_remove": "Remove",
     289  "admin_add": "Add",
     290  "admin_manage": "Manage",
     291  "admin_close": "Close",
     292  "admin_majors": "Study programmes",
     293  "admin_prerequisites": "Prerequisites",
     294  "admin_pick_major": "Pick a study programme",
     295  "admin_pick_prerequisite": "Pick a prerequisite",
     296  "admin_pick_subject": "Pick a subject",
     297  "admin_pick_professor": "Pick a professor",
     298  "admin_search_subject": "Search by name or code",
     299  "admin_no_subjects": "No subjects found.",
     300  "admin_delete_blocked": "Cannot delete: {{count}} enrolment(s) already contain this subject.",
     301  "admin_new_semester": "Open a new semester",
     302  "admin_year": "Year",
     303  "admin_type": "Type",
     304  "admin_winter": "Winter",
     305  "admin_summer": "Summer",
     306  "admin_open_semester": "Open semester",
     307  "admin_open_semesters": "Open semesters",
     308  "admin_no_semesters": "No semesters yet.",
     309  "admin_enrolments": "Enrolments",
     310  "admin_coverage": "Coverage",
     311  "admin_fully_covered": "all subjects covered",
     312  "admin_uncovered_count": "{{count}} subject(s) without a professor",
     313  "admin_uncovered_hint": "Students cannot enroll in a subject nobody teaches that semester.",
     314  "admin_show_uncovered": "Show",
     315  "admin_professor": "Professor",
     316  "admin_assign": "Assign",
     317  "admin_assignments": "Assignments",
     318  "admin_load": "Teaching load",
     319  "admin_no_assignments": "Nothing assigned yet.",
     320  "admin_no_professors": "No professors."
    260321}
  • src/locales/mk/translation.json

    r4fe4582 r8496f3c  
    231231  "applications_important_note_text": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички.",
    232232  "login_student_title": "Најава за студент",
    233   "login_student_example": "Е-пошта: boris@example.com\nЛозинка: your-password-here",
     233  "login_student_example": "Е-пошта: stefan.saveski@students.finki.ukim.mk\nЛозинка: Test12345!",
    234234  "login_professor_title": "Најава за професор",
    235   "login_professor_example": "Е-пошта: andonov@gmail.com\nЛозинка: 123lol456",
     235  "login_professor_example": "Е-пошта: vangel.ajanovski@finki.ukim.mk\nЛозинка: Test12345!",
     236  "login_admin_title": "Најава за администратор",
     237  "login_admin_example": "Е-пошта: igor.adminovski@finki.ukim.mk\nЛозинка: Test12345!",
    236238  "login_welcome": "Добредојде",
    237239  "login_subtitle": "Најавете се во вашиот IKnow акаунт",
     
    261263  "contact_sent_title": "Пораката е испратена!",
    262264  "contact_sent_desc": "Ќе ви одговориме наскоро.",
    263   "login_demo_note": "Користете ги овие податоци за да го истражите системот."
     265  "login_demo_note": "Користете ги овие податоци за да го истражите системот.",
     266  "enroll_semester": "Запиши семестар",
     267  "enroll_confirm": "Запиши",
     268  "enroll_no_semesters": "Веќе сте запишани во сите достапни семестри.",
     269  "enroll_already_passed": "положен",
     270  "enroll_credits": "кредити",
     271  "enroll_semester_short": "сем.",
     272  "enroll_not_signed_in": "Не сте најавени.",
     273  "enroll_options_failed": "Не може да се вчитаат опциите за запишување",
     274  "enroll_failed": "Запишувањето не успеа",
     275  "cancel": "Откажи",
     276  "admin_portal": "Админ",
     277  "admin_subjects": "Предмети",
     278  "admin_semesters": "Семестри",
     279  "admin_schedule": "Распоред",
     280  "admin_subjects_intro": "Додавање предмети, поврзување со студиски програми и предуслови.",
     281  "admin_semesters_intro": "Отворање активен семестар во кој студентите можат да се запишат.",
     282  "admin_schedule_intro": "Определување кој професор кој предмет го предава.",
     283  "admin_new_subject": "Нов предмет",
     284  "admin_edit_subject": "Измена на предмет",
     285  "admin_subject_name": "Назив",
     286  "admin_subject_code": "Шифра",
     287  "admin_credits": "Кредити",
     288  "admin_dependency_credit": "Потребни кредити",
     289  "admin_create": "Креирај",
     290  "admin_save": "Зачувај",
     291  "admin_delete": "Избриши",
     292  "admin_remove": "Отстрани",
     293  "admin_add": "Додади",
     294  "admin_manage": "Уреди",
     295  "admin_close": "Затвори",
     296  "admin_majors": "Студиски програми",
     297  "admin_prerequisites": "Предуслови",
     298  "admin_pick_major": "Избери студиска програма",
     299  "admin_pick_prerequisite": "Избери предуслов",
     300  "admin_pick_subject": "Избери предмет",
     301  "admin_pick_professor": "Избери професор",
     302  "admin_search_subject": "Пребарај по назив или шифра",
     303  "admin_no_subjects": "Нема предмети.",
     304  "admin_delete_blocked": "Не може да се избрише: {{count}} запишувања го содржат предметот.",
     305  "admin_new_semester": "Отвори нов семестар",
     306  "admin_year": "Година",
     307  "admin_type": "Тип",
     308  "admin_winter": "Зимски",
     309  "admin_summer": "Летен",
     310  "admin_open_semester": "Отвори семестар",
     311  "admin_open_semesters": "Отворени семестри",
     312  "admin_no_semesters": "Нема семестри.",
     313  "admin_enrolments": "Запишувања",
     314  "admin_coverage": "Покриеност",
     315  "admin_fully_covered": "сите предмети се покриени",
     316  "admin_uncovered_count": "{{count}} предмети без професор",
     317  "admin_uncovered_hint": "Студентите не можат да запишат предмет кој никој не го предава.",
     318  "admin_show_uncovered": "Прикажи",
     319  "admin_professor": "Професор",
     320  "admin_assign": "Распореди",
     321  "admin_assignments": "Распоред",
     322  "admin_load": "Оптовареност",
     323  "admin_no_assignments": "Секој нема распоред.",
     324  "admin_no_professors": "Нема професори."
    264325}
Note: See TracChangeset for help on using the changeset viewer.