Changeset 50f2fb4 for src


Ignore:
Timestamp:
01/30/26 01:40:27 (8 months ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
ba52069
Parents:
298f9b3
Message:

Added dual language feature.

Location:
src
Files:
5 added
13 edited

Legend:

Unmodified
Added
Removed
  • src/app/layout.tsx

    r298f9b3 r50f2fb4  
    1 import type { Metadata } from "next";
     1"use client";
    22import { Geist, Geist_Mono } from "next/font/google";
    33import "./globals.css";
    44import '@fortawesome/fontawesome-svg-core/styles.css';
    55import { config } from '@fortawesome/fontawesome-svg-core';
     6import { I18nextProvider } from 'react-i18next';
     7import i18n from '../i18n';
    68
    7 // Prevent FontAwesome from adding CSS automatically
    89config.autoAddCss = false;
    910
     
    1819});
    1920
    20 export const metadata: Metadata = {
    21   title: "IKnow - Student Portal",
    22   description: "Student portal for Ss. Cyril and Methodius University",
    23 };
    24 
    2521export default function RootLayout({
    2622  children,
     
    3026  return (
    3127    <html lang="en">
    32       <body
    33         className={`${geistSans.variable} ${geistMono.variable} antialiased`}
    34       >
    35         <div className="mx-auto max-w-6xl px-4">{children}</div>
     28      <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
     29        <I18nextProvider i18n={i18n}>
     30          <div className="mx-auto max-w-6xl px-4">{children}</div>
     31        </I18nextProvider>
    3632      </body>
    3733    </html>
  • src/app/page.tsx

    r298f9b3 r50f2fb4  
    1212import { useState } from 'react';
    1313import { login } from '@/lib/auth';
     14import { useTranslation } from 'react-i18next';
     15import LanguageSwitcher from '@/components/LanguageSwitcher';
    1416
    1517export default function LoginPage() {
     18  const { t } = useTranslation();
    1619  const [showPassword, setShowPassword] = useState(false);
    1720  const [isSubmitting, setIsSubmitting] = useState(false);
     
    5457              <div className="w-full flex flex-col gap-4">
    5558                <div className="bg-yellow-100 border border-yellow-200 rounded-xl shadow-sm p-5">
    56                   <div className="text-sm font-bold text-gray-900 mb-2">Login for Student</div>
     59                  <div className="text-sm font-bold text-gray-900 mb-2">{t('login_student_title')}</div>
    5760                  <div className="text-xs text-gray-800 whitespace-pre-wrap font-mono">
    58                     {`"email":"boris@example.com",
    59 "password":"your-password-here",`}
     61                    {t('login_student_example')}
    6062                  </div>
    6163                  <div className="mt-3 text-xs text-gray-700">
    62                     may take up to 30 seconds afte long inactivity
     64                    {t('login_note_slow')}
    6365                  </div>
    6466                </div>
    6567
    6668                <div className="bg-yellow-100 border border-yellow-200 rounded-xl shadow-sm p-5">
    67                   <div className="text-sm font-bold text-gray-900 mb-2">Login for Professor</div>
     69                  <div className="text-sm font-bold text-gray-900 mb-2">{t('login_professor_title')}</div>
    6870                  <div className="text-xs text-gray-800 whitespace-pre-wrap font-mono">
    69                     {`"email":"andonov@gmail.com",
    70 "password":"123lol456",`}
     71                    {t('login_professor_example')}
    7172                  </div>
    7273                </div>
     
    8485                </div>
    8586              </div>
    86               <h1 className="text-2xl font-bold mb-2">Добредојде</h1>
     87              <h1 className="text-2xl font-bold mb-2">{t('login_welcome')}</h1>
    8788              <p className="text-blue-100 text-sm">
    88                 Најавете се во вашиот IKnow акаунт
     89                {t('login_subtitle')}
    8990              </p>
    9091            </div>
     
    9293            {/* Form */}
    9394            <div className="p-8">
     95              {/* Language Switcher above email field, centered */}
     96              <div className="flex justify-center mb-6">
     97                <LanguageSwitcher />
     98              </div>
    9499              <form onSubmit={handleSubmit} className="space-y-6">
    95100                {/* Email Field */}
    96101                <div>
    97102                  <label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
    98                     Внесете е-пошта
     103                    {t('login_email_label')}
    99104                  </label>
    100105                  <div className="relative">
     
    110115                      onChange={handleInputChange}
    111116                      className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
    112                       placeholder="Внесете е-пошта"
     117                      placeholder={t('login_email_placeholder')}
    113118                    />
    114119                  </div>
     
    118123                <div>
    119124                  <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
    120                     Лозинка
     125                    {t('login_password_label')}
    121126                  </label>
    122127                  <div className="relative">
     
    132137                      onChange={handleInputChange}
    133138                      className="w-full pl-10 pr-12 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
    134                       placeholder="Внесете лозинка"
     139                      placeholder={t('login_password_placeholder')}
    135140                    />
    136141                    <button
     
    157162                    />
    158163                    <label htmlFor="remember" className="ml-2 block text-sm text-gray-700">
    159                       Запомни ме
     164                      {t('login_remember_me')}
    160165                    </label>
    161166                  </div>
     
    164169                    className="text-sm text-primary hover:text-blue-700 font-medium transition-colors"
    165170                  >
    166                     Заборавена лозинка?
     171                    {t('login_forgot_password')}
    167172                  </button>
    168173                </div>
     
    170175                {errorMessage && (
    171176                  <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
    172                     {errorMessage}
     177                    {t(errorMessage) || errorMessage}
    173178                  </div>
    174179                )}
     
    181186                >
    182187                  <FontAwesomeIcon icon={faSignInAlt} className="h-4 w-4" />
    183                   {isSubmitting ? 'Најави се...' : 'Најави се'}
     188                  {isSubmitting ? t('login_button_loading') : t('login_button')}
    184189                </button>
    185190              </form>
     
    189194            <div className="bg-gray-50 px-8 py-4 border-t border-gray-100">
    190195              <p className="text-center text-sm text-gray-600">
    191                 Немате акаунт?{' '}
     196                {t('login_no_account')}{' '}
    192197                <button className="text-primary hover:text-blue-700 font-medium transition-colors">
    193                   Контактирајте ја администрацијата
     198                  {t('login_contact_admin')}
    194199                </button>
    195200              </p>
     
    202207                    <FontAwesomeIcon icon={faGraduationCap} className="text-primary" />
    203208                    <span className="text-sm text-gray-600">
    204                       Студентски информационен систем
     209                      {t('login_footer')}
    205210                    </span>
    206211                  </div>
  • src/app/professor/profile/page.tsx

    r298f9b3 r50f2fb4  
    1818import { getAccessToken } from "@/lib/auth";
    1919import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
     20import { useTranslation } from 'react-i18next';
    2021
    2122type PersonalInfo = {
     
    9495}
    9596
    96 const InfoRow = ({ label, value, icon }: InfoRowProps) => (
    97   <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0">
    98     <div className="flex items-center gap-2 text-gray-600 font-medium">
    99       {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
    100       <span>{label}:</span>
     97const InfoRow = ({ label, value, icon }: InfoRowProps) => {
     98  const { t } = useTranslation();
     99  return (
     100    <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0">
     101      <div className="flex items-center gap-2 text-gray-600 font-medium">
     102        {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
     103        <span>{t(label)}:</span>
     104      </div>
     105      <div className="text-gray-900 font-semibold text-right max-w-xs break-words">
     106        {value || t('n_a')}
     107      </div>
    101108    </div>
    102     <div className="text-gray-900 font-semibold text-right max-w-xs break-words">
    103       {value || "N/A"}
    104     </div>
    105   </div>
    106 );
     109  );
     110};
    107111
    108112interface SectionProps {
     
    112116}
    113117
    114 const Section = ({ title, icon, children }: SectionProps) => (
    115   <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
    116     <div className="bg-primary text-white px-6 py-4">
    117       <div className="flex items-center gap-3">
    118         <FontAwesomeIcon icon={icon} className="text-xl" />
    119         <h2 className="text-xl font-bold">{title}</h2>
    120       </div>
     118const Section = ({ title, icon, children }: SectionProps) => {
     119  const { t } = useTranslation();
     120  return (
     121    <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
     122      <div className="bg-primary text-white px-6 py-4">
     123        <div className="flex items-center gap-3">
     124          <FontAwesomeIcon icon={icon} className="text-xl" />
     125          <h2 className="text-xl font-bold">{t(title)}</h2>
     126        </div>
     127      </div>
     128      <div className="p-6">{children}</div>
    121129    </div>
    122     <div className="p-6">{children}</div>
    123   </div>
    124 );
     130  );
     131};
    125132
    126133export default function ProfessorProfilePage() {
     
    128135  const [isLoading, setIsLoading] = useState(true);
    129136  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     137  const { t } = useTranslation();
    130138
    131139  useEffect(() => {
     
    177185    return (
    178186      <div className="min-h-screen pb-8">
    179         <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">Loading...</div>
     187        <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">{t('loading')}</div>
    180188      </div>
    181189    );
     
    186194      <div className="min-h-screen pb-8">
    187195        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
    188           {errorMessage ?? "Failed to load profile."}
     196          {errorMessage ?? t('failed_to_load_profile')}
    189197        </div>
    190198      </div>
     
    210218            </h1>
    211219            <div className="text-lg opacity-90">
    212               Индекс: {personalInfo.index} | ЕМБГ: {personalInfo.embg}
     220              {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg}
    213221            </div>
    214222          </div>
     
    219227      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
    220228        {/* Personal Information */}
    221         <Section title="Лични податоци" icon={faIdCard}>
    222           <InfoRow label="Име" value={personalInfo.firstName} />
    223           <InfoRow label="Средно име" value={personalInfo.middleName} />
    224           <InfoRow label="Презиме" value={personalInfo.lastName} />
    225           <InfoRow label="Моминско презиме" value={personalInfo.maidenName} />
    226           <InfoRow label="Датум на раѓање" value={personalInfo.dateOfBirth} icon={faCalendarAlt} />
     229        <Section title="personal_info" icon={faIdCard}>
     230          <InfoRow label="first_name" value={personalInfo.firstName} />
     231          <InfoRow label="middle_name" value={personalInfo.middleName} />
     232          <InfoRow label="last_name" value={personalInfo.lastName} />
     233          <InfoRow label="maiden_name" value={personalInfo.maidenName} />
     234          <InfoRow label="date_of_birth" value={personalInfo.dateOfBirth} icon={faCalendarAlt} />
    227235          <InfoRow
    228             label="Пол"
     236            label="gender"
    229237            value={personalInfo.gender}
    230             icon={personalInfo.gender === "машки" ? faMars : faVenus}
     238            icon={personalInfo.gender === t('male') ? faMars : faVenus}
    231239          />
    232           <InfoRow label="Националност" value={personalInfo.nationality} icon={faFlag} />
    233           <InfoRow label="Државјанство" value={personalInfo.citizenship} />
    234           <InfoRow label="Стипендија" value={personalInfo.scholarship} />
    235           <InfoRow label="Тековен план" value={personalInfo.currentPlan} />
    236           <InfoRow label="Бр. во матична книга" value={personalInfo.registryNumber} />
    237           <InfoRow label="Група на студирање" value={personalInfo.studyGroup} />
     240          <InfoRow label="nationality" value={personalInfo.nationality} icon={faFlag} />
     241          <InfoRow label="citizenship" value={personalInfo.citizenship} />
     242          <InfoRow label="scholarship" value={personalInfo.scholarship} />
     243          <InfoRow label="current_plan" value={personalInfo.currentPlan} />
     244          <InfoRow label="registry_number" value={personalInfo.registryNumber} />
     245          <InfoRow label="study_group" value={personalInfo.studyGroup} />
    238246          {personalInfo.notes && (
    239247            <div className="mt-4 p-4 bg-blue-50 rounded-lg">
    240               <div className="text-sm font-medium text-blue-800 mb-1">Забелешка:</div>
     248              <div className="text-sm font-medium text-blue-800 mb-1">{t('note')}:</div>
    241249              <div className="text-sm text-blue-700">{personalInfo.notes}</div>
    242250            </div>
     
    245253
    246254        {/* Birth Information */}
    247         <Section title="Податоци за раѓање" icon={faMapMarkerAlt}>
    248           <InfoRow label="Место на раѓање" value={birthInfo.placeOfBirth} />
    249           <InfoRow label="Општина на раѓање" value={birthInfo.municipalityOfBirth} />
    250           <InfoRow label="Земја" value={birthInfo.country} />
     255        <Section title="birth_info" icon={faMapMarkerAlt}>
     256          <InfoRow label="place_of_birth" value={birthInfo.placeOfBirth} />
     257          <InfoRow label="municipality_of_birth" value={birthInfo.municipalityOfBirth} />
     258          <InfoRow label="country" value={birthInfo.country} />
    251259        </Section>
    252260
    253261        {/* Contact Information */}
    254262        <div className="lg:col-span-2">
    255           <Section title="Контакт" icon={faAddressCard}>
     263          <Section title="contact" icon={faAddressCard}>
    256264            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
    257265              <div>
    258                 <InfoRow label="Место на живеење" value={contact.placeOfResidence} icon={faMapMarkerAlt} />
    259                 <InfoRow label="Општина на живеење" value={contact.municipalityOfResidence} />
    260                 <InfoRow label="Држава" value={contact.country} />
    261                 <InfoRow label="Адреса" value={contact.address} />
    262                 <InfoRow label="Адреса на престој" value={contact.temporaryAddress} />
     266                <InfoRow label="place_of_residence" value={contact.placeOfResidence} icon={faMapMarkerAlt} />
     267                <InfoRow label="municipality_of_residence" value={contact.municipalityOfResidence} />
     268                <InfoRow label="country" value={contact.country} />
     269                <InfoRow label="address" value={contact.address} />
     270                <InfoRow label="temporary_address" value={contact.temporaryAddress} />
    263271              </div>
    264272              <div>
    265                 <InfoRow label="Телефон" value={contact.phone} icon={faPhone} />
    266                 <InfoRow label="Моб. телефон" value={contact.mobilePhone} icon={faPhone} />
    267                 <InfoRow label="Број на пасош" value={contact.passportNumber} icon={faPassport} />
    268                 <InfoRow label="Датум на истекување на пасошот" value={contact.passportExpiryDate} />
    269                 <InfoRow label="Е-пошта" value={contact.email} icon={faEnvelope} />
    270                 <InfoRow label="Microsoft email" value={contact.microsoftEmail} icon={faEnvelope} />
     273                <InfoRow label="phone" value={contact.phone} icon={faPhone} />
     274                <InfoRow label="mobile_phone" value={contact.mobilePhone} icon={faPhone} />
     275                <InfoRow label="passport_number" value={contact.passportNumber} icon={faPassport} />
     276                <InfoRow label="passport_expiry_date" value={contact.passportExpiryDate} />
     277                <InfoRow label="email" value={contact.email} icon={faEnvelope} />
     278                <InfoRow label="microsoft_email" value={contact.microsoftEmail} icon={faEnvelope} />
    271279              </div>
    272280            </div>
  • src/app/professor/students/page.tsx

    r298f9b3 r50f2fb4  
    22
    33import { useEffect, useMemo, useState } from "react";
     4import { useTranslation } from 'react-i18next';
    45
    56type UsersBySubject = {
     
    3637
    3738export default function ProfessorStudentsPage() {
     39  const { t } = useTranslation();
    3840  const [subjects, setSubjects] = useState<SubjectsAndUsers[]>([]);
    3941  const [loading, setLoading] = useState(true);
     
    129131    <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
    130132      <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
     133
    131134        <div>
    132           <h1 className="text-2xl font-semibold text-gray-900">Professor • Students</h1>
     135          <h1 className="text-2xl font-semibold text-gray-900">{t('prof_students_title')}</h1>
    133136          <p className="text-gray-700 mt-1">
    134             Data comes from <span className="font-mono">/api/prof/students</span> (demo store).
     137            {t('prof_students_data_note')} <span className="font-mono">/api/prof/students</span> (demo store).
    135138          </p>
    136139        </div>
     
    138141        <div className="flex flex-col sm:flex-row gap-3">
    139142          <div className="flex flex-col">
    140             <label className="text-sm text-gray-700">Filter by subject</label>
     143            <label className="text-sm text-gray-700">{t('filter_by_subject')}</label>
    141144            <select
    142145              className="border border-gray-300 rounded-lg px-3 py-2"
     
    144147              onChange={(e) => setSubjectFilter(e.target.value)}
    145148            >
    146               <option value="all">All subjects</option>
     149              <option value="all">{t('all_subjects')}</option>
    147150              {subjects
    148151                .filter((s) => typeof s.Id === "number")
    149152                .map((s) => (
    150153                  <option key={String(s.Id)} value={String(s.Id)}>
    151                     {s.Name ?? `Subject ${s.Id}`}
     154                    {s.Name ?? `${t('subject')} ${s.Id}`}
    152155                  </option>
    153156                ))}
     
    156159
    157160          <div className="flex flex-col">
    158             <label className="text-sm text-gray-700">Find student by index / ID</label>
     161            <label className="text-sm text-gray-700">{t('find_student_by_id')}</label>
    159162            <input
    160163              className="border border-gray-300 rounded-lg px-3 py-2"
    161               placeholder="e.g. 20001"
     164              placeholder={t('student_id_placeholder')}
    162165              value={studentIdQuery}
    163166              onChange={(e) => setStudentIdQuery(e.target.value)}
     
    177180          <thead className="bg-gray-50">
    178181            <tr>
    179               <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">Student</th>
    180               <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">ID</th>
    181               <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">Subject</th>
    182               <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">Grade</th>
    183               <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">Actions</th>
     182              <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('student')}</th>
     183              <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('id')}</th>
     184              <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('subject')}</th>
     185              <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('grade')}</th>
     186              <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('actions')}</th>
    184187            </tr>
    185188          </thead>
     
    188191              <tr>
    189192                <td className="px-4 py-4 text-gray-700" colSpan={5}>
    190                   Loading...
     193                  {t('loading')}
    191194                </td>
    192195              </tr>
     
    194197              <tr>
    195198                <td className="px-4 py-4 text-gray-700" colSpan={5}>
    196                   No students found.
     199                  {t('no_students_found')}
    197200                </td>
    198201              </tr>
     
    227230                        </select>
    228231                        <span className="text-sm text-gray-600">
    229                           Current: {r.grade > 0 ? r.grade : "—"}
     232                          {t('current')}: {r.grade > 0 ? r.grade : t('none')}
    230233                        </span>
    231234                      </div>
     
    245248                          }}
    246249                        >
    247                           Add grade
     250                          {t('add_grade')}
    248251                        </button>
    249252
     
    260263                          }}
    261264                        >
    262                           Edit grade
     265                          {t('edit_grade')}
    263266                        </button>
    264267
     
    275278                          }}
    276279                        >
    277                           Remove grade
     280                          {t('remove_grade')}
    278281                        </button>
    279282                      </div>
  • src/app/students/applications/page.tsx

    r298f9b3 r50f2fb4  
    1414} from '@fortawesome/free-solid-svg-icons';
    1515import { useState } from 'react';
     16import { useTranslation } from 'react-i18next';
    1617import applicationsData from '@/data/applications.json';
    1718
     
    2829
    2930const CompletedBadge = ({ completed }: { completed: string }) => {
    30   if (completed === "Да") {
    31     return (
    32       <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full">
     31  const { t } = useTranslation();
     32  if (completed === t('yes', 'Да')) {
     33    return (
     34      <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full" title={t('completed')}>
    3335        <FontAwesomeIcon icon={faCheckCircle} className="w-4 h-4" />
    3436      </span>
     
    3638  } else {
    3739    return (
    38       <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full">
     40      <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full" title={t('not_completed')}>
    3941        <FontAwesomeIcon icon={faTimesCircle} className="w-4 h-4" />
    4042      </span>
     
    6466
    6567export default function ApplicationsPage() {
     68  const { t } = useTranslation();
    6669  const [selectedSession, setSelectedSession] = useState(applicationsData.currentSession.id);
    6770  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
    68  
     71
    6972  const currentSessionData = applicationsData.examSessions.find(s => s.id === selectedSession) || applicationsData.currentSession;
    7073  const { applications } = applicationsData;
     
    7982          </div>
    8083          <div>
    81             <h1 className="text-3xl font-bold mb-2">Пријави</h1>
     84            <h1 className="text-3xl font-bold mb-2">{t('applications_header', 'Пријави')}</h1>
    8285            <p className="text-lg opacity-90">
    83               Електронски пријави за испити
     86              {t('applications_subheader', 'Електронски пријави за испити')}
    8487            </p>
    8588          </div>
     
    9295          <h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
    9396            <FontAwesomeIcon icon={faCalendarAlt} className="text-primary" />
    94             Избери испитна сесија:
     97            {t('select_exam_session', 'Избери испитна сесија:')}
    9598          </h2>
    96          
     99
    97100          <div className="relative">
    98101            <button
     
    102105              <div className="flex items-center justify-between">
    103106                <span className="text-sm font-medium text-primary">
    104                   {currentSessionData.name}
     107                  {t(currentSessionData.id, currentSessionData.name)}
    105108                </span>
    106                 <FontAwesomeIcon 
    107                   icon={faChevronDown} 
     109                <FontAwesomeIcon
     110                  icon={faChevronDown}
    108111                  className={`w-4 h-4 text-gray-400 transition-transform ml-4 ${isDropdownOpen ? 'rotate-180' : ''}`}
    109112                />
    110113              </div>
    111114            </button>
    112            
     115
    113116            {isDropdownOpen && (
    114117              <div className="absolute z-10 right-0 mt-1 w-80 bg-white border border-gray-200 rounded-lg shadow-lg">
     
    122125                    className="w-full px-4 py-3 text-left text-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 first:rounded-t-lg last:rounded-b-lg"
    123126                  >
    124                     <div className="font-medium text-gray-900">{session.name}</div>
    125                     <div className="text-xs text-gray-500">{session.year} - {session.semester} - {session.session}</div>
     127                    <div className="font-medium text-gray-900">{t(session.id, session.name)}</div>
     128                    <div className="text-xs text-gray-500">{session.year} - {t(session.semester, session.semester)} - {t(session.session, session.session)}</div>
    126129                  </button>
    127130                ))}
     
    135138      <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
    136139        <div className="bg-primary text-white px-6 py-4">
    137           <h2 className="text-xl font-bold">Пријавени испити</h2>
    138         </div>
    139        
     140          <h2 className="text-xl font-bold">{t('registered_exams', 'Пријавени испити')}</h2>
     141        </div>
     142
    140143        <div className="overflow-x-auto">
    141144          <table className="w-full">
     
    143146              <tr>
    144147                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
    145                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Сериски број</th>
    146                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Код</th>
    147                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Предмет</th>
    148                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Завршена</th>
    149                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Таксени</th>
    150                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Датум</th>
    151                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Наставник</th>
    152                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Декада</th>
     148                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('serial_number', 'Сериски број')}</th>
     149                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('code', 'Код')}</th>
     150                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('subject', 'Предмет')}</th>
     151                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('completed', 'Завршена')}</th>
     152                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('fee', 'Таксени')}</th>
     153                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('date', 'Датум')}</th>
     154                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('instructor', 'Наставник')}</th>
     155                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('decade', 'Декада')}</th>
    153156              </tr>
    154157            </thead>
     
    166169                    <div className="flex items-center gap-2">
    167170                      <FontAwesomeIcon icon={faFileText} className="w-4 h-4 text-primary" />
    168                       {application.subject}
     171                      {t(application.subject, application.subject)}
    169172                    </div>
    170173                  </TableCell>
     
    179182                    <div className="flex items-center gap-2">
    180183                      <FontAwesomeIcon icon={faUser} className="w-4 h-4 text-gray-400" />
    181                       <span className="font-medium text-gray-900">{application.instructor}</span>
     184                      <span className="font-medium text-gray-900">{t(application.instructor, application.instructor)}</span>
    182185                    </div>
    183186                  </TableCell>
     
    194197            <FontAwesomeIcon icon={faInfoCircle} className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
    195198            <div>
    196               <h3 className="font-medium text-blue-900 mb-1">Важна забелешка</h3>
     199              <h3 className="font-medium text-blue-900 mb-1">{t('important_note', 'Важна забелешка')}</h3>
    197200              <p className="text-sm text-blue-800 leading-relaxed">
    198                 {applicationsData.note}
     201                {t('applications_important_note_text')}
    199202              </p>
    200203            </div>
     
    211214            </div>
    212215            <div>
    213               <h3 className="text-lg font-bold text-gray-900">Пријавени испити</h3>
     216              <h3 className="text-lg font-bold text-gray-900">{t('registered_exams', 'Пријавени испити')}</h3>
    214217              <p className="text-2xl font-bold text-blue-600">
    215218                {applications.length}
     
    225228            </div>
    226229            <div>
    227               <h3 className="text-lg font-bold text-gray-900">Завршени</h3>
     230              <h3 className="text-lg font-bold text-gray-900">{t('completed', 'Завршени')}</h3>
    228231              <p className="text-2xl font-bold text-green-600">
    229                 {applications.filter(app => app.completed === "Да").length}
     232                {applications.filter(app => app.completed === t('yes', 'Да')).length}
    230233              </p>
    231234            </div>
     
    239242            </div>
    240243            <div>
    241               <h3 className="text-lg font-bold text-gray-900">Вкупна такса</h3>
     244              <h3 className="text-lg font-bold text-gray-900">{t('total_fee', 'Вкупна такса')}</h3>
    242245              <p className="text-2xl font-bold text-yellow-600">
    243246                {applications.reduce((sum, app) => sum + parseFloat(app.fee.replace(',', '.')), 0).toFixed(2)}
  • src/app/students/documents/page.tsx

    r298f9b3 r50f2fb4  
    1515import { useState } from 'react';
    1616import documentsData from '@/data/documents.json';
     17import { useTranslation } from 'react-i18next';
    1718
    1819interface TableCellProps {
     
    2829
    2930const StatusBadge = ({ status }: { status: string }) => {
    30   if (status === "approved") {
     31  const { t } = useTranslation();
     32  if (status === t('approved', 'Одобрено')) {
    3133    return (
    3234      <span className="inline-flex items-center justify-center w-8 h-8 bg-green-100 text-green-600 rounded-full">
     
    4345
    4446const PriceBadge = ({ price }: { price: number }) => {
     47  const { t } = useTranslation();
    4548  if (price === 0) {
    46     return <span className="font-medium text-green-600">0,00</span>;
     49    return <span className="font-medium text-green-600">{t('free', '0,00')}</span>;
    4750  }
    4851  return <span className="font-medium text-blue-600">{price.toFixed(2)}</span>;
     
    5558  const [currentPage, setCurrentPage] = useState(1);
    5659  const [recordsPerPage, setRecordsPerPage] = useState(15);
     60  const { t } = useTranslation();
    5761
    5862  const selectedDocument = documentsData.documentTypes.find(d => d.id === selectedDocumentType);
     
    7377          </div>
    7478          <div>
    75             <h1 className="text-3xl font-bold mb-2">Документи</h1>
     79            <h1 className="text-3xl font-bold mb-2">{t('documents')}</h1>
    7680            <p className="text-lg opacity-90">
    77               Барање и преглед на документи
     81              {t('documents_overview', 'Преглед на вашите документи и нивниот статус.')}
    7882            </p>
    7983          </div>
     
    8589        <h2 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
    8690          <FontAwesomeIcon icon={faFileText} className="text-primary" />
    87           Ново барање за документ
     91          {t('new_document_request', 'Ново барање за документ')}
    8892        </h2>
    8993       
     
    9296          <div>
    9397            <label className="block text-sm font-medium text-gray-700 mb-2">
    94               Изберете документ:
     98              {t('select_document', 'Избери документ')}:
    9599            </label>
    96100            <div className="relative">
     
    101105                <div className="flex items-center justify-between">
    102106                  <span className="text-sm text-gray-900 truncate">
    103                     {selectedDocument?.name || "Изберете документ"}
     107                    {selectedDocument ? t(selectedDocument.id) : t('select_document', 'Избери документ')}
    104108                  </span>
    105109                  <FontAwesomeIcon
     
    121125                      className="w-full px-4 py-3 text-left text-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 border-b border-gray-100 last:border-b-0"
    122126                    >
    123                       <div className="font-medium text-gray-900">{docType.name}</div>
     127                      <div className="font-medium text-gray-900">{t(docType.id)}</div>
    124128                      {docType.price > 0 && (
    125                         <div className="text-xs text-blue-600 mt-1">Цена: {docType.price} мкд</div>
     129                        <div className="text-xs text-blue-600 mt-1">{t('price', 'Цена')}: {docType.price} мкд</div>
    126130                      )}
    127131                    </button>
     
    135139          <div>
    136140            <label className="block text-sm font-medium text-gray-700 mb-2">
    137               Коментар:
     141              {t('comment', 'Коментар')}:
    138142            </label>
    139143            <textarea
     
    142146              rows={4}
    143147              className="w-full border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary resize-none"
    144               placeholder="Додајте коментар..."
     148              placeholder={t('add_comment', 'Додај коментар')}
    145149            />
    146150          </div>
     
    153157          >
    154158            <FontAwesomeIcon icon={faFileText} className="w-4 h-4" />
    155             Внеси
     159            {t('submit', 'Поднеси')}
    156160          </button>
    157161        </div>
     
    161165      <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
    162166        <div className="bg-primary text-white px-6 py-4">
    163           <h2 className="text-xl font-bold">Мои документи</h2>
     167          <h2 className="text-xl font-bold">{t('my_documents', 'Мои документи')}</h2>
    164168        </div>
    165169       
     
    169173              <tr>
    170174                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
    171                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Архива</th>
    172                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Датум</th>
    173                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Барање</th>
    174                 <th className="px-4 py-4 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Цена</th>
    175                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Платено</th>
    176                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Документ</th>
    177                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Плати онлајн</th>
    178                 <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Статус</th>
    179                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Коментар</th>
     175                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('archive', 'Архива')}</th>
     176                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('date', 'Датум')}</th>
     177                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('request', 'Барање')}</th>
     178                <th className="px-4 py-4 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{t('price', 'Цена')}</th>
     179                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('paid', 'Платено')}</th>
     180                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('document', 'Документ')}</th>
     181                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('pay_online', 'Плати онлајн')}</th>
     182                <th className="px-4 py-4 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">{t('status', 'Статус')}</th>
     183                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('comment', 'Коментар')}</th>
    180184              </tr>
    181185            </thead>
     
    187191                  <TableCell className="text-gray-600">{document.date}</TableCell>
    188192                  <TableCell className="font-medium text-gray-900 max-w-xs">
    189                     {document.request}
     193                    {t(document.request)}
    190194                  </TableCell>
    191195                  <TableCell className="text-right">
     
    206210                      <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5 text-green-600" />
    207211                    ) : (
    208                       <span className="text-gray-400"></span>
     212                      <span className="text-gray-400">{t('none', '—')}</span>
    209213                    )}
    210214                  </TableCell>
     
    214218                  <TableCell>
    215219                    {document.comment || (
    216                       <span className="text-gray-400"></span>
     220                      <span className="text-gray-400">{t('none', '—')}</span>
    217221                    )}
    218222                  </TableCell>
     
    227231          <div className="flex items-center gap-4 text-sm text-gray-700">
    228232            <div className="flex items-center gap-2">
    229               <span>Прикажи редови:</span>
     233              <span>{t('show_rows', 'Прикажи редови')}:</span>
    230234              <select
    231235                value={recordsPerPage}
     
    239243            </div>
    240244            <div>
    241               Страна <input
     245              {t('page', 'Страница')} <input
    242246                type="number"
    243247                min="1"
     
    246250                onChange={(e) => setCurrentPage(Number(e.target.value))}
    247251                className="w-12 border border-gray-300 rounded px-2 py-1 text-sm text-center focus:outline-none focus:ring-1 focus:ring-primary"
    248               /> од {totalPages}
     252              /> {t('of', 'од')} {totalPages}
    249253            </div>
    250254          </div>
     
    266270            </button>
    267271            <span className="px-4 py-2 bg-primary text-white rounded text-sm font-medium">
    268               Прва
     272              {t('first', 'Прва')}
    269273            </span>
    270274            <button
     
    283287            </button>
    284288            <span className="ml-4 text-sm text-gray-700">
    285               Последна
     289              {t('last', 'Последна')}
    286290            </span>
    287291          </div>
    288292
    289293          <div className="text-sm text-gray-700">
    290             Вкупно: {documents.length}
     294            {t('total', 'Вкупно')}: {documents.length}
    291295          </div>
    292296        </div>
     
    297301            <FontAwesomeIcon icon={faMoneyBillWave} className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
    298302            <p className="text-sm text-blue-800 leading-relaxed">
    299               {paymentInfo}
     303              {t('documents_payment_info', paymentInfo)}
    300304            </p>
    301305          </div>
     
    311315            </div>
    312316            <div>
    313               <h3 className="text-lg font-bold text-gray-900">Вкупно документи</h3>
     317              <h3 className="text-lg font-bold text-gray-900">{t('total_documents', 'Вкупно документи')}</h3>
    314318              <p className="text-2xl font-bold text-blue-600">
    315319                {documents.length}
     
    325329            </div>
    326330            <div>
    327               <h3 className="text-lg font-bold text-gray-900">Одобрени</h3>
     331              <h3 className="text-lg font-bold text-gray-900">{t('approved', 'Одобрени')}</h3>
    328332              <p className="text-2xl font-bold text-green-600">
    329333                {documents.filter(doc => doc.status === "approved").length}
     
    339343            </div>
    340344            <div>
    341               <h3 className="text-lg font-bold text-gray-900">Вкупна цена</h3>
     345              <h3 className="text-lg font-bold text-gray-900">{t('total_price', 'Вкупна цена')}</h3>
    342346              <p className="text-2xl font-bold text-yellow-600">
    343                 {documents.reduce((sum, doc) => sum + doc.price, 0).toFixed(2)} мкд
     347                {documents.reduce((sum, doc) => sum + doc.price, 0).toFixed(2)} {t('mkd', 'мкд')}
    344348              </p>
    345349            </div>
  • src/app/students/profile/page.tsx

    r298f9b3 r50f2fb4  
    2020import { getAccessToken } from '@/lib/auth';
    2121import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
     22import { useTranslation } from 'react-i18next';
    2223
    2324type PersonalInfo = {
     
    9495}
    9596
    96 const InfoRow = ({ label, value, icon }: InfoRowProps) => (
    97   <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0">
    98     <div className="flex items-center gap-2 text-gray-600 font-medium">
    99       {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
    100       <span>{label}:</span>
     97const InfoRow = ({ label, value, icon }: InfoRowProps) => {
     98  const { t } = useTranslation();
     99  return (
     100    <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0">
     101      <div className="flex items-center gap-2 text-gray-600 font-medium">
     102        {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />}
     103        <span>{t(label)}:</span>
     104      </div>
     105      <div className="text-gray-900 font-semibold text-right max-w-xs break-words">
     106        {value || t('n_a')}
     107      </div>
    101108    </div>
    102     <div className="text-gray-900 font-semibold text-right max-w-xs break-words">
    103       {value || "N/A"}
    104     </div>
    105   </div>
    106 );
     109  );
     110};
    107111
    108112interface SectionProps {
     
    112116}
    113117
    114 const Section = ({ title, icon, children }: SectionProps) => (
    115   <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
    116     <div className="bg-primary text-white px-6 py-4">
    117       <div className="flex items-center gap-3">
    118         <FontAwesomeIcon icon={icon} className="text-xl" />
    119         <h2 className="text-xl font-bold">{title}</h2>
     118const Section = ({ title, icon, children }: SectionProps) => {
     119  const { t } = useTranslation();
     120  return (
     121    <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
     122      <div className="bg-primary text-white px-6 py-4">
     123        <div className="flex items-center gap-3">
     124          <FontAwesomeIcon icon={icon} className="text-xl" />
     125          <h2 className="text-xl font-bold">{t(title)}</h2>
     126        </div>
     127      </div>
     128      <div className="p-6">
     129        {children}
    120130      </div>
    121131    </div>
    122     <div className="p-6">
    123       {children}
    124     </div>
    125   </div>
    126 );
     132  );
     133};
    127134
    128135export default function ProfilePage() {
     
    130137  const [isLoading, setIsLoading] = useState(true);
    131138  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     139  const { t } = useTranslation();
    132140
    133141  useEffect(() => {
     
    180188      <div className="min-h-screen pb-8">
    181189        <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
    182           Loading...
     190          {t('loading')}
    183191        </div>
    184192      </div>
     
    190198      <div className="min-h-screen pb-8">
    191199        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
    192           {errorMessage ?? 'Failed to load profile.'}
     200          {errorMessage ?? t('failed_to_load_profile')}
    193201        </div>
    194202      </div>
     
    214222            </h1>
    215223            <div className="text-lg opacity-90">
    216               Индекс: {personalInfo.index} | ЕМБГ: {personalInfo.embg}
     224              {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg}
    217225            </div>
    218226            <div className="text-base opacity-80 mt-1">
     
    227235       
    228236        {/* Personal Information */}
    229         <Section title="Лични податоци" icon={faIdCard}>
    230           <InfoRow label="Име" value={personalInfo.firstName} />
    231           <InfoRow label="Средно име" value={personalInfo.middleName} />
    232           <InfoRow label="Презиме" value={personalInfo.lastName} />
    233           <InfoRow label="Моминско презиме" value={personalInfo.maidenName} />
     237        <Section title="personal_info" icon={faIdCard}>
     238          <InfoRow label="first_name" value={personalInfo.firstName} />
     239          <InfoRow label="middle_name" value={personalInfo.middleName} />
     240          <InfoRow label="last_name" value={personalInfo.lastName} />
     241          <InfoRow label="maiden_name" value={personalInfo.maidenName} />
    234242          <InfoRow
    235             label="Датум на раѓање"
     243            label="date_of_birth"
    236244            value={personalInfo.dateOfBirth}
    237245            icon={faCalendarAlt}
    238246          />
    239247          <InfoRow
    240             label="Пол"
     248            label="gender"
    241249            value={personalInfo.gender}
    242             icon={personalInfo.gender === 'машки' ? faMars : faVenus}
     250            icon={personalInfo.gender === t('male') ? faMars : faVenus}
    243251          />
    244252          <InfoRow
    245             label="Националност"
     253            label="nationality"
    246254            value={personalInfo.nationality}
    247255            icon={faFlag}
    248256          />
    249           <InfoRow label="Државјанство" value={personalInfo.citizenship} />
    250           <InfoRow label="Стипендија" value={personalInfo.scholarship} />
    251           <InfoRow label="Тековен план" value={personalInfo.currentPlan} />
    252           <InfoRow label="Бр. во матична книга" value={personalInfo.registryNumber} />
    253           <InfoRow label="Група на студирање" value={personalInfo.studyGroup} />
     257          <InfoRow label="citizenship" value={personalInfo.citizenship} />
     258          <InfoRow label="scholarship" value={personalInfo.scholarship} />
     259          <InfoRow label="current_plan" value={personalInfo.currentPlan} />
     260          <InfoRow label="registry_number" value={personalInfo.registryNumber} />
     261          <InfoRow label="study_group" value={personalInfo.studyGroup} />
    254262          {personalInfo.notes && (
    255263            <div className="mt-4 p-4 bg-blue-50 rounded-lg">
    256               <div className="text-sm font-medium text-blue-800 mb-1">Забелешка:</div>
     264              <div className="text-sm font-medium text-blue-800 mb-1">{t('note')}:</div>
    257265              <div className="text-sm text-blue-700">{personalInfo.notes}</div>
    258266            </div>
     
    261269
    262270        {/* Birth Information */}
    263         <Section title="Податоци за раѓање" icon={faMapMarkerAlt}>
    264           <InfoRow label="Место на раѓање" value={birthInfo.placeOfBirth} />
    265           <InfoRow label="Општина на раѓање" value={birthInfo.municipalityOfBirth} />
    266           <InfoRow label="Земја" value={birthInfo.country} />
     271        <Section title="birth_info" icon={faMapMarkerAlt}>
     272          <InfoRow label="place_of_birth" value={birthInfo.placeOfBirth} />
     273          <InfoRow label="municipality_of_birth" value={birthInfo.municipalityOfBirth} />
     274          <InfoRow label="country" value={birthInfo.country} />
    267275        </Section>
    268276
    269277        {/* Previous Education */}
    270         <Section title="Претходно образование" icon={faSchool}>
    271           <InfoRow label="Тип" value={previousEducation.type} />
    272           <InfoRow label="Професија" value={previousEducation.profession} />
    273           <InfoRow label="Просек" value={previousEducation.average} />
    274           <InfoRow label="Јазик" value={previousEducation.language} />
    275           <InfoRow label="Земја" value={previousEducation.country} />
    276           <InfoRow label="Претходен универзитет" value={previousEducation.previousUniversity} />
    277           <InfoRow label="Претходен факултет" value={previousEducation.previousFaculty} />
    278           <InfoRow label="Режим на претходни студии" value={previousEducation.previousStudyMode} />
     278        <Section title="previous_education" icon={faSchool}>
     279          <InfoRow label="type" value={previousEducation.type} />
     280          <InfoRow label="profession" value={previousEducation.profession} />
     281          <InfoRow label="average" value={previousEducation.average} />
     282          <InfoRow label="language" value={previousEducation.language} />
     283          <InfoRow label="country" value={previousEducation.country} />
     284          <InfoRow label="previous_university" value={previousEducation.previousUniversity} />
     285          <InfoRow label="previous_faculty" value={previousEducation.previousFaculty} />
     286          <InfoRow label="previous_study_mode" value={previousEducation.previousStudyMode} />
    279287        </Section>
    280288
    281289        {/* Enrollment Information */}
    282         <Section title="Податоци за упис" icon={faGraduationCap}>
    283           <InfoRow label="Година на упис" value={enrollmentInfo.enrollmentYear} />
    284           <InfoRow label="Статус" value={enrollmentInfo.status} />
    285           <InfoRow label="Циклус" value={enrollmentInfo.cycle} />
    286           <InfoRow label="Запишана програма" value={enrollmentInfo.program} />
    287           <InfoRow label="Квота на прв упис" value={enrollmentInfo.quota} />
    288           <InfoRow label="Бр. дипл. ср. образование" value={enrollmentInfo.secondaryEducationNumber} />
    289           <InfoRow label="Кред. пр. образование" value={enrollmentInfo.previousEducationCredits} />
     290        <Section title="enrollment_info" icon={faGraduationCap}>
     291          <InfoRow label="enrollment_year" value={enrollmentInfo.enrollmentYear} />
     292          <InfoRow label="status" value={enrollmentInfo.status} />
     293          <InfoRow label="cycle" value={enrollmentInfo.cycle} />
     294          <InfoRow label="program" value={enrollmentInfo.program} />
     295          <InfoRow label="quota" value={enrollmentInfo.quota} />
     296          <InfoRow label="secondary_education_number" value={enrollmentInfo.secondaryEducationNumber} />
     297          <InfoRow label="previous_education_credits" value={enrollmentInfo.previousEducationCredits} />
    290298        </Section>
    291299
    292300        {/* Contact Information */}
    293301        <div className="lg:col-span-2">
    294           <Section title="Контакт" icon={faAddressCard}>
     302          <Section title="contact" icon={faAddressCard}>
    295303            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
    296304              <div>
    297305                <InfoRow
    298                   label="Место на живеење"
     306                  label="place_of_residence"
    299307                  value={contact.placeOfResidence}
    300308                  icon={faMapMarkerAlt}
    301309                />
    302                 <InfoRow label="Општина на живеење" value={contact.municipalityOfResidence} />
    303                 <InfoRow label="Држава" value={contact.country} />
    304                 <InfoRow label="Адреса" value={contact.address} />
    305                 <InfoRow label="Адреса на престој" value={contact.temporaryAddress} />
     310                <InfoRow label="municipality_of_residence" value={contact.municipalityOfResidence} />
     311                <InfoRow label="country" value={contact.country} />
     312                <InfoRow label="address" value={contact.address} />
     313                <InfoRow label="temporary_address" value={contact.temporaryAddress} />
    306314              </div>
    307315              <div>
    308                 <InfoRow label="Телефон" value={contact.phone} icon={faPhone} />
    309                 <InfoRow label="Моб. телефон" value={contact.mobilePhone} icon={faPhone} />
    310                 <InfoRow label="Број на пасош" value={contact.passportNumber} icon={faPassport} />
    311                 <InfoRow label="Датум на истекување на пасошот" value={contact.passportExpiryDate} />
     316                <InfoRow label="phone" value={contact.phone} icon={faPhone} />
     317                <InfoRow label="mobile_phone" value={contact.mobilePhone} icon={faPhone} />
     318                <InfoRow label="passport_number" value={contact.passportNumber} icon={faPassport} />
     319                <InfoRow label="passport_expiry_date" value={contact.passportExpiryDate} />
    312320                <InfoRow
    313                   label="Е-пошта"
     321                  label="email"
    314322                  value={contact.email}
    315323                  icon={faEnvelope}
    316324                />
    317325                <InfoRow
    318                   label="Microsoft email"
     326                  label="microsoft_email"
    319327                  value={contact.microsoftEmail}
    320328                  icon={faEnvelope}
  • src/app/students/semesters/page.tsx

    r298f9b3 r50f2fb4  
    1414import { useEffect, useState } from 'react';
    1515import { getAccessToken } from '@/lib/auth';
     16import { useTranslation } from 'react-i18next';
    1617
    1718type Semester = {
     
    5455
    5556const StatusBadge = ({ status }: { status: string }) => {
     57  const { t } = useTranslation();
    5658  const baseClasses = "px-2 py-1 rounded-full text-xs font-medium";
    57  
    58   if (status === "валиден") {
     59  if (status === t('valid')) {
    5960    return (
    6061      <span className={`${baseClasses} bg-green-100 text-green-800 flex items-center gap-1`}>
    6162        <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" />
    62         {status}
     63        {t('valid')}
    6364      </span>
    6465    );
    6566  }
    66  
    6767  return (
    6868    <span className={`${baseClasses} bg-gray-100 text-gray-800`}>
    69       {status}
     69      {t(status) || status}
    7070    </span>
    7171  );
     
    7373
    7474const YesNoBadge = ({ value }: { value: string }) => {
    75   if (value === "Да") {
     75  const { t } = useTranslation();
     76  if (value === t('yes')) {
    7677    return (
    7778      <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full">
     
    7980      </span>
    8081    );
    81   } else if (value === "Не") {
     82  } else if (value === t('no')) {
    8283    return (
    8384      <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full">
     
    112113  const [isLoading, setIsLoading] = useState(true);
    113114  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     115  const { t } = useTranslation();
    114116
    115117  useEffect(() => {
     
    162164      <div className="min-h-screen pb-8">
    163165        <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
    164           Loading...
     166          {t('loading')}
    165167        </div>
    166168      </div>
     
    187189          </div>
    188190          <div>
    189             <h1 className="text-3xl font-bold mb-2">Семестри</h1>
     191            <h1 className="text-3xl font-bold mb-2">{t('semesters')}</h1>
    190192            <p className="text-lg opacity-90">
    191               Преглед на сите запишани семестри и нивниот статус
     193              {t('semesters_overview')}
    192194            </p>
    193195          </div>
     
    198200      <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
    199201        <div className="bg-primary text-white px-6 py-4">
    200           <h2 className="text-xl font-bold">Листа на семестри</h2>
     202          <h2 className="text-xl font-bold">{t('semesters_list')}</h2>
    201203        </div>
    202204       
     
    206208              <tr>
    207209                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
    208                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Семестар</th>
    209                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Насока</th>
    210                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Квота</th>
    211                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Забелешка</th>
    212                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Студ.Ком.</th>
    213                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Сума</th>
    214                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Платено</th>
    215                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">УКИМ</th>
    216                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Креирано на</th>
    217                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Датум промена</th>
    218                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ц.Кр</th>
    219                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Тип</th>
    220                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Док.</th>
    221                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Док1.</th>
    222                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Вериф.</th>
    223                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Таксени</th>
    224                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Потписи</th>
    225                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Статус</th>
    226                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Зав.</th>
     210                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('semester')}</th>
     211                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('direction')}</th>
     212                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('quota')}</th>
     213                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('note')}</th>
     214                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('student_com')}</th>
     215                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('sum')}</th>
     216                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('paid')}</th>
     217                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('ukim')}</th>
     218                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('created_on')}</th>
     219                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('date_changed')}</th>
     220                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('credits')}</th>
     221                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('type')}</th>
     222                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('doc')}</th>
     223                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('doc1')}</th>
     224                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('verified')}</th>
     225                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('taxes')}</th>
     226                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('signatures')}</th>
     227                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('status')}</th>
     228                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('completed')}</th>
    227229              </tr>
    228230            </thead>
     
    335337            </div>
    336338            <div>
    337               <h3 className="text-lg font-bold text-gray-900">Завршени семестри</h3>
     339              <h3 className="text-lg font-bold text-gray-900">{t('completed_semesters')}</h3>
    338340              <p className="text-2xl font-bold text-green-600">
    339341                {semesters.filter(s => s.completed !== "Не").length}
     
    349351            </div>
    350352            <div>
    351               <h3 className="text-lg font-bold text-gray-900">Верифицирани</h3>
     353              <h3 className="text-lg font-bold text-gray-900">{t('verified_semesters')}</h3>
    352354              <p className="text-2xl font-bold text-blue-600">
    353355                {semesters.filter(s => s.verified === "Да").length}
     
    363365            </div>
    364366            <div>
    365               <h3 className="text-lg font-bold text-gray-900">Вкупно семестри</h3>
     367              <h3 className="text-lg font-bold text-gray-900">{t('total_semesters')}</h3>
    366368              <p className="text-2xl font-bold text-primary">
    367369                {semesters.length}
  • src/app/students/subjects/page.tsx

    r298f9b3 r50f2fb4  
    99import { useEffect, useState } from 'react';
    1010import { getAccessToken } from '@/lib/auth';
     11import { useTranslation } from 'react-i18next';
    1112
    1213interface Subject {
     
    147148
    148149const StatusBadge = ({ status }: { status: string }) => {
     150  const { t } = useTranslation();
    149151  const baseClasses = "px-3 py-1 rounded-md text-xs font-medium";
    150  
    151   if (status === "Зад.") {
     152  if (status === t('mandatory_short')) {
    152153    return (
    153154      <span className={`${baseClasses} bg-blue-100 text-blue-800`}>
    154         {status}
     155        {t('mandatory_short')}
    155156      </span>
    156157    );
    157   } else if (status === "Изб.") {
     158  } else if (status === t('elective_short')) {
    158159    return (
    159160      <span className={`${baseClasses} bg-green-100 text-green-800`}>
    160         {status}
     161        {t('elective_short')}
    161162      </span>
    162163    );
    163164  }
    164  
    165165  return (
    166166    <span className={`${baseClasses} bg-gray-100 text-gray-800`}>
    167       {status}
     167      {t(status) || status}
    168168    </span>
    169169  );
     
    176176  const [isLoading, setIsLoading] = useState(true);
    177177  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     178  const { t } = useTranslation();
    178179
    179180  useEffect(() => {
     
    275276      <div className="min-h-screen pb-8">
    276277        <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
    277           Loading...
     278          {t('loading')}
    278279        </div>
    279280      </div>
     
    285286      <div className="min-h-screen pb-8">
    286287        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
    287           {errorMessage ?? 'Failed to load subjects.'}
     288          {errorMessage ?? t('failed_to_load_subjects')}
    288289        </div>
    289290      </div>
     
    308309          </div>
    309310          <div>
    310             <h1 className="text-3xl font-bold mb-2">Предмети</h1>
     311            <h1 className="text-3xl font-bold mb-2">{t('subjects')}</h1>
    311312            <p className="text-lg opacity-90">
    312               Преглед на запишани предмети по семестри
     313              {t('subjects_overview')}
    313314            </p>
    314315          </div>
     
    325326          <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
    326327            <div className="text-sm text-gray-600 mb-2">
    327               Статус: <span className="text-primary font-semibold">Запишан од студент</span>
     328              {t('status')}: <span className="text-primary font-semibold">{t('enrolled_by_student')}</span>
    328329            </div>
    329330            <div className="text-sm text-gray-600">
    330               Број на тикет: <span className="font-semibold">{currentSemester.ticketNumber}</span>
     331              {t('ticket_number')}: <span className="font-semibold">{currentSemester.ticketNumber}</span>
    331332            </div>
    332333          </div>
     
    335336          <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
    336337            <div className="text-sm text-gray-600 mb-2">
    337               Долг од документи: <span className="font-semibold">{currentSemester.debt}</span>
     338              {t('debt_from_documents')}: <span className="font-semibold">{currentSemester.debt}</span>
    338339            </div>
    339340           
    340341            <div className="relative mt-4">
    341342              <label className="block text-sm font-medium text-gray-700 mb-2">
    342                 Избери семестар:
     343                {t('select_semester')}:
    343344              </label>
    344345              <div className="relative">
     
    370371                      >
    371372                        <div className="font-medium text-gray-900">{semester.name}</div>
    372                         <div className="text-xs text-gray-500">Статус: {semester.status}</div>
     373                        <div className="text-xs text-gray-500">{t('status')}: {semester.status}</div>
    373374                      </button>
    374375                    ))}
     
    380381            <div className="mt-4 text-sm">
    381382              <div className="text-primary font-medium">
    382                 Сериски број: {currentSemesterData.serviceNumber}
     383                {t('serial_number')}: {currentSemesterData.serviceNumber}
    383384              </div>
    384385            </div>
     
    389390            <h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
    390391              <FontAwesomeIcon icon={faBook} className="w-4 h-4 text-primary" />
    391               Запишани предмети
     392              {t('enrolled_subjects')}
    392393            </h3>
    393394            <div className="text-3xl font-bold text-primary">
     
    403404              <h2 className="text-xl font-bold flex items-center gap-2">
    404405                <FontAwesomeIcon icon={faFileInvoice} />
    405                 Финансиски информации
     406                {t('financial_info')}
    406407              </h2>
    407408            </div>
     
    413414                <div className="space-y-4">
    414415                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    415                     <span className="text-gray-600">Сума:</span>
     416                    <span className="text-gray-600">{t('sum')}:</span>
    416417                    <span className="font-semibold text-primary">{currentSemester.financialInfo.sum}</span>
    417418                  </div>
    418419                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    419                     <span className="text-gray-600">Платено:</span>
     420                    <span className="text-gray-600">{t('paid')}:</span>
    420421                    <span className="font-semibold text-green-600">{currentSemester.financialInfo.paid}</span>
    421422                  </div>
    422423                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    423                     <span className="text-gray-600">Должи:</span>
     424                    <span className="text-gray-600">{t('due')}:</span>
    424425                    <span className="font-semibold text-red-600">{currentSemester.financialInfo.due}</span>
    425426                  </div>
    426427                  <div className="mt-4 p-4 bg-blue-50 rounded-lg">
    427                     <div className="text-sm font-medium text-blue-800 mb-1">Материјални трошоци:</div>
    428                     <div className="text-sm text-blue-700">{currentSemester.financialInfo.materialCosts}</div>
     428                    <div className="text-sm font-medium text-blue-800 mb-1">{t('material_costs')}:</div>
     429                    <div className="text-sm text-blue-700">{t('material_costs_info')}</div>
    429430                  </div>
    430431                </div>
     
    433434                <div className="space-y-4">
    434435                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    435                     <span className="text-gray-600">Кредити:</span>
     436                    <span className="text-gray-600">{t('credits')}:</span>
    436437                    <span className="font-semibold text-primary">{currentSemester.financialInfo.credits}</span>
    437438                  </div>
    438439                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    439                     <span className="text-gray-600">МКСА:</span>
     440                    <span className="text-gray-600">{t('mksa')}:</span>
    440441                    <span className="font-semibold">{currentSemester.financialInfo.MKSA}</span>
    441442                  </div>
    442443                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    443                     <span className="text-gray-600">Електронско запишување:</span>
     444                    <span className="text-gray-600">{t('electronic_registration')}:</span>
    444445                    <span className="font-semibold">{currentSemester.financialInfo.electronicRegistration}</span>
    445446                  </div>
    446447                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    447                     <span className="text-gray-600">Е-УКИМ:</span>
     448                    <span className="text-gray-600">{t('eukim')}:</span>
    448449                    <span className="font-semibold">{currentSemester.financialInfo.eUKIM}</span>
    449450                  </div>
    450451                  <div className="flex justify-between items-center py-2 border-b border-gray-100">
    451                     <span className="text-gray-600">Банкарска провизија:</span>
     452                    <span className="text-gray-600">{t('bank_provision')}:</span>
    452453                    <span className="font-semibold">{currentSemester.financialInfo.bankProvision}</span>
    453454                  </div>
    454455                  <div className="flex justify-between items-center py-3 border-t-2 border-primary bg-primary bg-opacity-5 rounded-lg px-4">
    455                     <span className="font-bold text-white">Тотал:</span>
     456                    <span className="font-bold text-white">{t('total')}:</span>
    456457                    <span className="font-bold text-xl text-white">{currentSemester.financialInfo.total}</span>
    457458                  </div>
     
    466467      <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
    467468        <div className="bg-primary text-white px-6 py-4">
    468           <h2 className="text-xl font-bold">Листа на предмети</h2>
     469          <h2 className="text-xl font-bold">{t('subjects_list')}</h2>
    469470        </div>
    470471       
     
    474475              <tr>
    475476                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
    476                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Код</th>
    477                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Часови</th>
    478                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Кој пат</th>
    479                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Предмет</th>
    480                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Семестар</th>
    481                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Статус</th>
    482                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Потпис</th>
    483                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Група</th>
    484                 <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Професор</th>
     477                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('code')}</th>
     478                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('hours')}</th>
     479                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('which_time')}</th>
     480                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('subject')}</th>
     481                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('semester')}</th>
     482                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('status')}</th>
     483                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('signature')}</th>
     484                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('group')}</th>
     485                <th className="px-4 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('professor')}</th>
    485486              </tr>
    486487            </thead>
     
    495496                    <div className="flex items-center gap-2">
    496497                      <FontAwesomeIcon icon={faBook} className="w-4 h-4 text-primary" />
    497                       {subject.name}
     498                      {t(subject.name, subject.name)}
    498499                    </div>
    499500                  </TableCell>
    500                   <TableCell className="text-center font-medium text-primary">{subject.semester}</TableCell>
     501                  <TableCell className="text-center font-medium text-primary">{t(subject.semester.toString(), subject.semester.toString())}</TableCell>
    501502                  <TableCell>
    502503                    <StatusBadge status={subject.status} />
    503504                  </TableCell>
    504505                  <TableCell className="text-center">
    505                     {subject.signature || (
     506                    {subject.signature ? t(subject.signature, subject.signature) : (
    506507                      <span className="text-gray-400">—</span>
    507508                    )}
    508509                  </TableCell>
    509510                  <TableCell className="text-center">
    510                     {subject.group || (
     511                    {subject.group ? t(subject.group, subject.group) : (
    511512                      <span className="text-gray-400">—</span>
    512513                    )}
    513514                  </TableCell>
    514515                  <TableCell>
    515                     {subject.professor || (
     516                    {subject.professor ? t(subject.professor, subject.professor) : (
    516517                      <span className="text-gray-400">—</span>
    517518                    )}
  • src/components/exams.tsx

    r298f9b3 r50f2fb4  
    11"use client"
    22import { useEffect, useMemo, useState } from 'react';
     3import { useTranslation } from 'react-i18next';
    34import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    45import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
     
    3334
    3435const Exams = () => {
     36  const { t } = useTranslation();
    3537  const [sortField, setSortField] = useState<'grade' | 'date' | null>(null);
    3638  const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
     
    185187    return (
    186188      <div className="bg-white rounded-lg shadow-sm p-6">
    187         Loading...
     189        {t('loading', 'Loading...')}
    188190      </div>
    189191    );
     
    194196      <div className="bg-white rounded-lg shadow-sm p-6">
    195197        <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
    196           {errorMessage}
     198          {t('failed_to_load_exams', errorMessage)}
    197199        </div>
    198200      </div>
     
    208210          <div className="text-center">
    209211            <div className="text-3xl font-bold text-gray-900">{stats.average}</div>
    210             <div className="text-sm text-gray-500 mt-1">Просек</div>
     212            <div className="text-sm text-gray-500 mt-1">{t('average', 'Просек')}</div>
    211213          </div>
    212214
     
    217219              <span className="text-lg text-gray-400">/{stats.credits.total}</span>
    218220            </div>
    219             <div className="text-sm text-gray-500 mt-1">Кредити</div>
     221            <div className="text-sm text-gray-500 mt-1">{t('credits', 'Кредити')}</div>
    220222          </div>
    221223
     
    223225          <div className="text-center">
    224226            <div className="text-3xl font-bold text-gray-900">{stats.passed}</div>
    225             <div className="text-sm text-gray-500 mt-1">Положени</div>
     227            <div className="text-sm text-gray-500 mt-1">{t('passed', 'Положени')}</div>
    226228          </div>
    227229
     
    229231          <div className="text-center">
    230232            <div className="text-3xl font-bold text-gray-900">{stats.remaining}</div>
    231             <div className="text-sm text-gray-500 mt-1">Останато</div>
     233            <div className="text-sm text-gray-500 mt-1">{t('remaining', 'Останато')}</div>
    232234          </div>
    233235        </div>
    234        
     236
    235237        {/* Progress Bar spanning entire statistics section */}
    236238        <div className="w-full bg-gray-200 rounded-full h-2">
    237           <div 
     239          <div
    238240            className="bg-blue-500 h-2 rounded-full transition-all duration-300"
    239241            style={{ width: `${creditsPercentage}%` }}
     
    244246      {/* Exams Table */}
    245247      <div>
    246         <h3 className="text-xl font-semibold text-gray-900 mb-4">Испити</h3>
    247        
     248        <h3 className="text-xl font-semibold text-gray-900 mb-4">{t('exams', 'Испити')}</h3>
     249
    248250        <div className="overflow-x-auto">
    249251          <table className="min-w-full">
     
    251253              <tr className="border-b border-gray-200">
    252254                <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">#</th>
    253                 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Предмет</th>
    254                 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Семестар</th>
    255                 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Кредити</th>
     255                <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">{t('subject', 'Предмет')}</th>
     256                <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">{t('semester', 'Семестар')}</th>
     257                <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">{t('credits', 'Кредити')}</th>
    256258                <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">
    257                   <button 
     259                  <button
    258260                    onClick={handleSortByDate}
    259261                    className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-gray-100 hover:text-gray-900 transition-all duration-200 transform hover:scale-105"
    260262                  >
    261                     Датум
    262                     <FontAwesomeIcon 
    263                       icon={getSortIcon('date')} 
     263                    {t('date', 'Датум')}
     264                    <FontAwesomeIcon
     265                      icon={getSortIcon('date')}
    264266                      className={`text-xs transition-colors duration-200 ${
    265267                        sortField === 'date' ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600'
     
    269271                </th>
    270272                <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">
    271                   <button 
     273                  <button
    272274                    onClick={handleSortByGrade}
    273275                    className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-gray-100 hover:text-gray-900 transition-all duration-200 transform hover:scale-105"
    274276                  >
    275                     Оценка
    276                     <FontAwesomeIcon 
    277                       icon={getSortIcon('grade')} 
     277                    {t('grade', 'Оценка')}
     278                    <FontAwesomeIcon
     279                      icon={getSortIcon('grade')}
    278280                      className={`text-xs transition-colors duration-200 ${
    279281                        sortField === 'grade' ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600'
  • src/components/header.tsx

    r298f9b3 r50f2fb4  
    11"use client"
     2
    23import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    34import { faUser } from '@fortawesome/free-solid-svg-icons';
    45import Image from 'next/image';
    56
     7import { useEffect, useState } from 'react';
     8import { getAccessToken, clearAuthTokens } from '@/lib/auth';
     9import { useTranslation } from 'react-i18next';
     10import LanguageSwitcher from './LanguageSwitcher';
     11
    612const Header = () => {
     13
     14  const [userName, setUserName] = useState<string>('');
     15  const { t } = useTranslation();
     16
     17  useEffect(() => {
     18    async function fetchUser() {
     19      const token = getAccessToken();
     20      if (!token) return;
     21      try {
     22        const response = await fetch('https://iknow-api.onrender.com/api/user/getUser', {
     23          method: 'GET',
     24          headers: { Authorization: `Bearer ${token}` },
     25        });
     26        if (!response.ok) return;
     27        const data = await response.json();
     28        // Try both student and professor shape
     29        if (data?.personalInfo) {
     30          const p = data.personalInfo;
     31          setUserName([p.firstName, p.middleName, p.lastName].filter(Boolean).join(' '));
     32        }
     33      } catch {}
     34    }
     35    fetchUser();
     36    return () => {};
     37  }, []);
     38
     39  const handleLogout = () => {
     40    clearAuthTokens();
     41    if (typeof window !== 'undefined') {
     42      window.location.href = '/';
     43    }
     44  };
     45
    746  return (
    847    <header className="bg-white shadow-sm border-b border-gray-200">
    948      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
    10         <div className="flex justify-between items-center py-4">
     49        <div className="flex flex-col sm:flex-row sm:justify-between sm:items-center py-4 gap-4 sm:gap-0">
    1150          {/* Left side - Logos and University Info */}
    12           <div className="flex items-center gap-4">
     51          <div className="flex items-center gap-2 justify-center sm:justify-start">
    1352            {/* UKIM Logo */}
    1453            <div className="flex-shrink-0">
     
    2160              />
    2261            </div>
    23            
    2462            {/* FINKI Logo */}
    2563            <div className="flex-shrink-0">
     
    3270              />
    3371            </div>
    34            
    3572            {/* University Text */}
    3673            <div className="hidden sm:block">
    3774              <div className="text-gray-900 font-medium text-sm">
    38                 Универзитет „Св. КИРИЛ И МЕТОДИЈ“ - Скопје
     75                {t('university')}
    3976              </div>
    4077              <div className="text-gray-700 text-sm">
    41                 Факултет за информатички науки и компјутерско инженерство
     78                {t('faculty')}
    4279              </div>
    4380            </div>
    4481          </div>
    45 
    4682          {/* Right side - User Profile */}
    47           <div className="flex items-center gap-4">
    48             <div className="flex items-center gap-3">
     83          <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">
     84            {/* Language Switcher - now left of user info */}
     85            <div className="sm:mr-2 w-full sm:w-auto flex justify-center sm:justify-end">
     86              <LanguageSwitcher />
     87            </div>
     88            <div className="flex items-center gap-2 sm:gap-3 w-full sm:w-auto justify-center sm:justify-end">
    4989              {/* User Avatar */}
    50               <div className="flex items-center justify-center w-10 h-10 bg-gray-800 text-white rounded-full">
     90              <div className="hidden sm:flex items-center justify-center w-10 h-10 bg-gray-800 text-white rounded-full">
    5191                <FontAwesomeIcon icon={faUser} className="text-sm" />
    5292              </div>
    53              
    5493              {/* User Name */}
    5594              <div className="hidden sm:block text-gray-900 font-medium">
    56                 Стефан Савески
     95                {userName || '...'}
    5796              </div>
    58              
    59               {/* Logout Button */}
    60               <button className="text-blue-600 hover:text-blue-800 font-medium text-sm transition-colors duration-200">
    61                 Logout
     97              {/* Logout Button - more stylish and full width on mobile */}
     98              <button
     99                className="w-full sm:w-auto ml-0 sm:ml-2 px-4 py-2 border-2 border-primary text-primary bg-white rounded-lg font-semibold flex items-center justify-center gap-2 hover:bg-blue-50 transition-all duration-200 shadow-sm"
     100                onClick={handleLogout}
     101              >
     102                <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>
     103                {t('logout')}
    62104              </button>
    63105            </div>
  • src/components/navbar.tsx

    r298f9b3 r50f2fb4  
    1010import { useState } from 'react';
    1111import Link from 'next/link';
     12import { useTranslation } from 'react-i18next';
    1213
    1314const Navbar = () => {
     
    1819  };
    1920
     21  const { t } = useTranslation();
    2022  const menuItems = [
    21     { icon: faUser, label: 'Профил', href: '/students/profile' },
    22     { icon: faBookmark, label: 'Семестри', href: '/students/semesters' },
    23     { icon: faBook, label: 'Предмети', href: '/students/subjects' },
    24     { icon: faPenToSquare, label: 'Пријави', href: '/students/applications' },
    25     { icon: faListCheck, label: 'Положени', href: '/students/exams' },
    26     { icon: faFilePdf, label: 'Документи', href: '/students/documents' }
     23    { icon: faUser, label: t('profile'), href: '/students/profile' },
     24    { icon: faBookmark, label: t('semesters'), href: '/students/semesters' },
     25    { icon: faBook, label: t('subjects'), href: '/students/subjects' },
     26    { icon: faPenToSquare, label: t('applications'), href: '/students/applications' },
     27    { icon: faListCheck, label: t('exams'), href: '/students/exams' },
     28    { icon: faFilePdf, label: t('documents'), href: '/students/documents' }
    2729  ];
    2830
  • src/components/professor-navbar.tsx

    r298f9b3 r50f2fb4  
    55import Link from "next/link";
    66import { useState } from "react";
     7import { useTranslation } from 'react-i18next';
    78
    89const ProfessorNavbar = () => {
     
    1314  };
    1415
     16  const { t } = useTranslation();
    1517  const menuItems = [
    16     { icon: faUser, label: "Профил", href: "/professor/profile" },
    17     { icon: faUsers, label: "Студенти", href: "/professor/students" },
     18    { icon: faUser, label: t('profile'), href: "/professor/profile" },
     19    { icon: faUsers, label: t('students') || 'Студенти', href: "/professor/students" },
    1820  ];
    1921
Note: See TracChangeset for help on using the changeset viewer.