Changeset 50f2fb4 for src/components
- Timestamp:
- 01/30/26 01:40:27 (8 months ago)
- Branches:
- master
- Children:
- ba52069
- Parents:
- 298f9b3
- Location:
- src/components
- Files:
-
- 1 added
- 4 edited
-
LanguageSwitcher.tsx (added)
-
exams.tsx (modified) (11 diffs)
-
header.tsx (modified) (3 diffs)
-
navbar.tsx (modified) (2 diffs)
-
professor-navbar.tsx (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/components/exams.tsx
r298f9b3 r50f2fb4 1 1 "use client" 2 2 import { useEffect, useMemo, useState } from 'react'; 3 import { useTranslation } from 'react-i18next'; 3 4 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 5 import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'; … … 33 34 34 35 const Exams = () => { 36 const { t } = useTranslation(); 35 37 const [sortField, setSortField] = useState<'grade' | 'date' | null>(null); 36 38 const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null); … … 185 187 return ( 186 188 <div className="bg-white rounded-lg shadow-sm p-6"> 187 Loading...189 {t('loading', 'Loading...')} 188 190 </div> 189 191 ); … … 194 196 <div className="bg-white rounded-lg shadow-sm p-6"> 195 197 <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)} 197 199 </div> 198 200 </div> … … 208 210 <div className="text-center"> 209 211 <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> 211 213 </div> 212 214 … … 217 219 <span className="text-lg text-gray-400">/{stats.credits.total}</span> 218 220 </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> 220 222 </div> 221 223 … … 223 225 <div className="text-center"> 224 226 <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> 226 228 </div> 227 229 … … 229 231 <div className="text-center"> 230 232 <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> 232 234 </div> 233 235 </div> 234 236 235 237 {/* Progress Bar spanning entire statistics section */} 236 238 <div className="w-full bg-gray-200 rounded-full h-2"> 237 <div 239 <div 238 240 className="bg-blue-500 h-2 rounded-full transition-all duration-300" 239 241 style={{ width: `${creditsPercentage}%` }} … … 244 246 {/* Exams Table */} 245 247 <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 248 250 <div className="overflow-x-auto"> 249 251 <table className="min-w-full"> … … 251 253 <tr className="border-b border-gray-200"> 252 254 <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> 256 258 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500"> 257 <button 259 <button 258 260 onClick={handleSortByDate} 259 261 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" 260 262 > 261 Датум262 <FontAwesomeIcon 263 icon={getSortIcon('date')} 263 {t('date', 'Датум')} 264 <FontAwesomeIcon 265 icon={getSortIcon('date')} 264 266 className={`text-xs transition-colors duration-200 ${ 265 267 sortField === 'date' ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600' … … 269 271 </th> 270 272 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500"> 271 <button 273 <button 272 274 onClick={handleSortByGrade} 273 275 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" 274 276 > 275 Оценка276 <FontAwesomeIcon 277 icon={getSortIcon('grade')} 277 {t('grade', 'Оценка')} 278 <FontAwesomeIcon 279 icon={getSortIcon('grade')} 278 280 className={`text-xs transition-colors duration-200 ${ 279 281 sortField === 'grade' ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600' -
src/components/header.tsx
r298f9b3 r50f2fb4 1 1 "use client" 2 2 3 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 4 import { faUser } from '@fortawesome/free-solid-svg-icons'; 4 5 import Image from 'next/image'; 5 6 7 import { useEffect, useState } from 'react'; 8 import { getAccessToken, clearAuthTokens } from '@/lib/auth'; 9 import { useTranslation } from 'react-i18next'; 10 import LanguageSwitcher from './LanguageSwitcher'; 11 6 12 const 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 7 46 return ( 8 47 <header className="bg-white shadow-sm border-b border-gray-200"> 9 48 <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"> 11 50 {/* 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"> 13 52 {/* UKIM Logo */} 14 53 <div className="flex-shrink-0"> … … 21 60 /> 22 61 </div> 23 24 62 {/* FINKI Logo */} 25 63 <div className="flex-shrink-0"> … … 32 70 /> 33 71 </div> 34 35 72 {/* University Text */} 36 73 <div className="hidden sm:block"> 37 74 <div className="text-gray-900 font-medium text-sm"> 38 Универзитет „Св. КИРИЛ И МЕТОДИЈ“ - Скопје75 {t('university')} 39 76 </div> 40 77 <div className="text-gray-700 text-sm"> 41 Факултет за информатички науки и компјутерско инженерство78 {t('faculty')} 42 79 </div> 43 80 </div> 44 81 </div> 45 46 82 {/* 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"> 49 89 {/* 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"> 51 91 <FontAwesomeIcon icon={faUser} className="text-sm" /> 52 92 </div> 53 54 93 {/* User Name */} 55 94 <div className="hidden sm:block text-gray-900 font-medium"> 56 Стефан Савески95 {userName || '...'} 57 96 </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')} 62 104 </button> 63 105 </div> -
src/components/navbar.tsx
r298f9b3 r50f2fb4 10 10 import { useState } from 'react'; 11 11 import Link from 'next/link'; 12 import { useTranslation } from 'react-i18next'; 12 13 13 14 const Navbar = () => { … … 18 19 }; 19 20 21 const { t } = useTranslation(); 20 22 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' } 27 29 ]; 28 30 -
src/components/professor-navbar.tsx
r298f9b3 r50f2fb4 5 5 import Link from "next/link"; 6 6 import { useState } from "react"; 7 import { useTranslation } from 'react-i18next'; 7 8 8 9 const ProfessorNavbar = () => { … … 13 14 }; 14 15 16 const { t } = useTranslation(); 15 17 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" }, 18 20 ]; 19 21
Note:
See TracChangeset
for help on using the changeset viewer.
