source: src/components/exams.tsx@ 2d635ae

Last change on this file since 2d635ae was 875f7d6, checked in by stefansaveski <stefansaveski19@…>, 12 months ago

Initial commit with Next.js project setup

  • Property mode set to 100644
File size: 13.6 KB
Line 
1"use client"
2import { useState } from 'react';
3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
5
6const Exams = () => {
7 const [sortField, setSortField] = useState<'grade' | 'date' | null>(null);
8 const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
9 // Real exam data
10 const stats = {
11 average: 8.95,
12 credits: { current: 108, total: 240 },
13 passed: 19,
14 remaining: 22
15 };
16
17 const exams = [
18 {
19 id: 1,
20 code: "F23L1W005",
21 subject: "Бизнис и менаџмент",
22 session: "2024 (Зимска) Прва испитна сесија",
23 date: "21.02.2024",
24 semester: 1,
25 credits: 6.00,
26 type: "З",
27 grade: 7
28 },
29 {
30 id: 2,
31 code: "F23L1W007",
32 subject: "Вовед во компјутерските науки",
33 session: "2024 (Зимска) Прва испитна сесија",
34 date: "06.02.2024",
35 semester: 1,
36 credits: 6.00,
37 type: "З",
38 grade: 10
39 },
40 {
41 id: 3,
42 code: "F23L2W003",
43 subject: "Избрани теми од математика",
44 session: "2024 (Зимска) Прва испитна сесија",
45 date: "12.02.2024",
46 semester: 1,
47 credits: 6.00,
48 type: "З",
49 grade: 9
50 },
51 {
52 id: 4,
53 code: "F23L1W018",
54 subject: "Професионални вештини",
55 session: "2024 (Зимска) Прва испитна сесија",
56 date: "01.02.2024",
57 semester: 1,
58 credits: 4.00,
59 type: "З",
60 grade: 9
61 },
62 {
63 id: 5,
64 code: "F23L1W004",
65 subject: "Спорт и здравје",
66 session: "2024 (Летна) Втора испитна сесија",
67 date: "15.07.2024",
68 semester: 1,
69 credits: 2.00,
70 type: "З",
71 grade: "Реализиран"
72 },
73 {
74 id: 6,
75 code: "F23L1W020",
76 subject: "Структурно програмирање",
77 session: "2024 (Зимска) Прва испитна сесија",
78 date: "12.02.2024",
79 semester: 1,
80 credits: 6.00,
81 type: "З",
82 grade: 10
83 },
84 {
85 id: 7,
86 code: "F23L1S003",
87 subject: "Архитектура и организација на компјутери",
88 session: "2024 (Летна) Втора испитна сесија",
89 date: "03.07.2024",
90 semester: 2,
91 credits: 6.00,
92 type: "З",
93 grade: 9
94 },
95 {
96 id: 8,
97 code: "F23L1S023",
98 subject: "Бизнис статистика",
99 session: "2024 (Летна) Втора испитна сесија",
100 date: "02.07.2024",
101 semester: 2,
102 credits: 6.00,
103 type: "З",
104 grade: 8
105 },
106 {
107 id: 9,
108 code: "F23L1S120",
109 subject: "Креативни вештини за решавање проблеми",
110 session: "2024 (Летна) Втора испитна сесија",
111 date: "25.06.2024",
112 semester: 2,
113 credits: 6.00,
114 type: "И",
115 grade: 10
116 },
117 {
118 id: 10,
119 code: "F23L1S016",
120 subject: "Објектно-ориентирано програмирање",
121 session: "2024 (Летна) Втора испитна сесија",
122 date: "13.07.2024",
123 semester: 2,
124 credits: 6.00,
125 type: "З",
126 grade: 10
127 },
128 {
129 id: 11,
130 code: "F23L1S146",
131 subject: "Основи на Веб дизајн",
132 session: "2024 (Летна) Втора испитна сесија",
133 date: "24.06.2024",
134 semester: 2,
135 credits: 6.00,
136 type: "З",
137 grade: 10
138 },
139 {
140 id: 12,
141 code: "F23L2W100",
142 subject: "Економија за ИКТ инженери",
143 session: "2025 (Зимска) Прва испитна сесија",
144 date: "03.02.2025",
145 semester: 3,
146 credits: 6.00,
147 type: "З",
148 grade: 8
149 },
150 {
151 id: 13,
152 code: "F23L2W109",
153 subject: "Интернет програмирање на клиентска страна",
154 session: "2025 (Зимска) Прва испитна сесија",
155 date: "30.01.2025",
156 semester: 3,
157 credits: 6.00,
158 type: "И",
159 grade: 10
160 },
161 {
162 id: 14,
163 code: "F23L2W014",
164 subject: "Компјутерски мрежи и безбедност",
165 session: "2025 (Зимска) Прва испитна сесија",
166 date: "10.02.2025",
167 semester: 3,
168 credits: 6.00,
169 type: "З",
170 grade: 7
171 },
172 {
173 id: 15,
174 code: "F23L2W201",
175 subject: "Примена на алгоритми и податочни структури",
176 session: "2025 (Зимска) Прва испитна сесија",
177 date: "13.02.2025",
178 semester: 3,
179 credits: 6.00,
180 type: "З",
181 grade: 10
182 },
183 {
184 id: 16,
185 code: "F23L2W167",
186 subject: "Шаблони за дизајн на кориснички интерфејси",
187 session: "2025 (Зимска) Прва испитна сесија",
188 date: "06.02.2025",
189 semester: 3,
190 credits: 6.00,
191 type: "И",
192 grade: 10
193 },
194 {
195 id: 17,
196 code: "F23L2S026",
197 subject: "Маркетинг",
198 session: "2025 (Летна) Втора испитна сесија",
199 date: "12.06.2025",
200 semester: 4,
201 credits: 6.00,
202 type: "З",
203 grade: 8
204 },
205 {
206 id: 18,
207 code: "F23L2S017",
208 subject: "Оперативни системи",
209 session: "2025 (Летна) Втора испитна сесија",
210 date: "19.06.2025",
211 semester: 4,
212 credits: 6.00,
213 type: "З",
214 grade: 7
215 },
216 {
217 id: 19,
218 code: "F23L2S029",
219 subject: "Софтверско инженерство",
220 session: "2025 (Летна) Втора испитна сесија",
221 date: "23.06.2025",
222 semester: 4,
223 credits: 6.00,
224 type: "З",
225 grade: 8
226 }
227 ];
228
229 const getGradeColor = (grade: number | string) => {
230 if (grade === "Реализиран") return "bg-blue-100 text-blue-800 border-blue-200";
231 if (typeof grade === "number") {
232 if (grade >= 9) return "bg-green-100 text-green-800 border-green-200";
233 if (grade >= 8) return "bg-yellow-100 text-yellow-800 border-yellow-200";
234 if (grade >= 6) return "bg-orange-100 text-orange-800 border-orange-200";
235 return "bg-red-100 text-red-800 border-red-200";
236 }
237 return "bg-gray-100 text-gray-800 border-gray-200";
238 };
239
240 const handleSortByGrade = () => {
241 if (sortField === 'grade') {
242 if (sortOrder === 'desc') {
243 setSortOrder('asc');
244 } else if (sortOrder === 'asc') {
245 setSortField(null);
246 setSortOrder(null);
247 } else {
248 setSortOrder('desc');
249 }
250 } else {
251 setSortField('grade');
252 setSortOrder('desc');
253 }
254 };
255
256 const handleSortByDate = () => {
257 if (sortField === 'date') {
258 if (sortOrder === 'desc') {
259 setSortOrder('asc');
260 } else if (sortOrder === 'asc') {
261 setSortField(null);
262 setSortOrder(null);
263 } else {
264 setSortOrder('desc');
265 }
266 } else {
267 setSortField('date');
268 setSortOrder('desc');
269 }
270 };
271
272 const getSortedExams = () => {
273 if (!sortField || !sortOrder) return exams;
274
275 return [...exams].sort((a, b) => {
276 if (sortField === 'grade') {
277 // Handle "Реализиран" grade separately
278 if (a.grade === "Реализиран" && b.grade === "Реализиран") return 0;
279 if (a.grade === "Реализиран") return sortOrder === 'asc' ? -1 : 1;
280 if (b.grade === "Реализиран") return sortOrder === 'asc' ? 1 : -1;
281
282 // Handle numeric grades
283 const gradeA = typeof a.grade === 'number' ? a.grade : 0;
284 const gradeB = typeof b.grade === 'number' ? b.grade : 0;
285
286 return sortOrder === 'asc' ? gradeA - gradeB : gradeB - gradeA;
287 }
288
289 if (sortField === 'date') {
290 // Convert date strings to Date objects for proper sorting
291 const dateA = new Date(a.date.split('.').reverse().join('-')); // Convert DD.MM.YYYY to YYYY-MM-DD
292 const dateB = new Date(b.date.split('.').reverse().join('-'));
293
294 return sortOrder === 'asc' ? dateA.getTime() - dateB.getTime() : dateB.getTime() - dateA.getTime();
295 }
296
297 return 0;
298 });
299 };
300
301 const getSortIcon = (field: 'grade' | 'date') => {
302 if (sortField === field) {
303 if (sortOrder === 'asc') return faSortUp;
304 if (sortOrder === 'desc') return faSortDown;
305 }
306 return faSort;
307 };
308
309 const creditsPercentage = (stats.credits.current / stats.credits.total) * 100;
310
311 return (
312 <div className="bg-white rounded-lg shadow-sm p-6 space-y-6">
313 {/* Statistics Section */}
314 <div className="space-y-4">
315 <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
316 {/* Average Grade */}
317 <div className="text-center">
318 <div className="text-3xl font-bold text-gray-900">{stats.average}</div>
319 <div className="text-sm text-gray-500 mt-1">Просек</div>
320 </div>
321
322 {/* Credits */}
323 <div className="text-center">
324 <div className="text-3xl font-bold text-gray-900">
325 {stats.credits.current}
326 <span className="text-lg text-gray-400">/{stats.credits.total}</span>
327 </div>
328 <div className="text-sm text-gray-500 mt-1">Кредити</div>
329 </div>
330
331 {/* Passed Exams */}
332 <div className="text-center">
333 <div className="text-3xl font-bold text-gray-900">{stats.passed}</div>
334 <div className="text-sm text-gray-500 mt-1">Положени</div>
335 </div>
336
337 {/* Remaining Exams */}
338 <div className="text-center">
339 <div className="text-3xl font-bold text-gray-900">{stats.remaining}</div>
340 <div className="text-sm text-gray-500 mt-1">Останато</div>
341 </div>
342 </div>
343
344 {/* Progress Bar spanning entire statistics section */}
345 <div className="w-full bg-gray-200 rounded-full h-2">
346 <div
347 className="bg-blue-500 h-2 rounded-full transition-all duration-300"
348 style={{ width: `${creditsPercentage}%` }}
349 ></div>
350 </div>
351 </div>
352
353 {/* Exams Table */}
354 <div>
355 <h3 className="text-xl font-semibold text-gray-900 mb-4">Испити</h3>
356
357 <div className="overflow-x-auto">
358 <table className="min-w-full">
359 <thead>
360 <tr className="border-b border-gray-200">
361 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">#</th>
362 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Предмет</th>
363 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Семестар</th>
364 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">Кредити</th>
365 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">
366 <button
367 onClick={handleSortByDate}
368 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"
369 >
370 Датум
371 <FontAwesomeIcon
372 icon={getSortIcon('date')}
373 className={`text-xs transition-colors duration-200 ${
374 sortField === 'date' ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600'
375 }`}
376 />
377 </button>
378 </th>
379 <th className="text-left py-3 px-4 text-sm font-medium text-gray-500">
380 <button
381 onClick={handleSortByGrade}
382 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"
383 >
384 Оценка
385 <FontAwesomeIcon
386 icon={getSortIcon('grade')}
387 className={`text-xs transition-colors duration-200 ${
388 sortField === 'grade' ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600'
389 }`}
390 />
391 </button>
392 </th>
393 </tr>
394 </thead>
395 <tbody className="divide-y divide-gray-100">
396 {getSortedExams().map((exam) => (
397 <tr key={exam.id} className="hover:bg-gray-50 transition-colors duration-150">
398 <td className="py-4 px-4 text-sm text-gray-900">{exam.id}</td>
399 <td className="py-4 px-4 text-sm text-gray-900 font-medium">{exam.subject}</td>
400 <td className="py-4 px-4 text-sm text-gray-500">{exam.semester}</td>
401 <td className="py-4 px-4 text-sm text-gray-500">{exam.credits}</td>
402 <td className="py-4 px-4 text-sm text-gray-500">{exam.date}</td>
403 <td className="py-4 px-4">
404 <span className={`inline-flex items-center px-2.5 py-1 rounded-md text-sm font-medium border ${getGradeColor(exam.grade)}`}>
405 {exam.grade}
406 </span>
407 </td>
408 </tr>
409 ))}
410 </tbody>
411 </table>
412 </div>
413 </div>
414 </div>
415 );
416};
417
418export default Exams;
Note: See TracBrowser for help on using the repository browser.