Changeset 50f2fb4 for src/components


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/components
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • 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.