| 1 | "use client"
|
|---|
| 2 |
|
|---|
| 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|---|
| 4 | import {
|
|---|
| 5 | faPenToSquare,
|
|---|
| 6 | faChevronDown,
|
|---|
| 7 | faCalendarAlt,
|
|---|
| 8 | faFileText,
|
|---|
| 9 | faMoneyBillWave,
|
|---|
| 10 | faUser,
|
|---|
| 11 | faInfoCircle,
|
|---|
| 12 | faCheckCircle,
|
|---|
| 13 | faTimesCircle
|
|---|
| 14 | } from '@fortawesome/free-solid-svg-icons';
|
|---|
| 15 | import { useState } from 'react';
|
|---|
| 16 | import { useTranslation } from 'react-i18next';
|
|---|
| 17 | import applicationsData from '@/data/applications.json';
|
|---|
| 18 |
|
|---|
| 19 | interface TableCellProps {
|
|---|
| 20 | children: React.ReactNode;
|
|---|
| 21 | className?: string;
|
|---|
| 22 | }
|
|---|
| 23 |
|
|---|
| 24 | const TableCell = ({ children, className = "" }: TableCellProps) => (
|
|---|
| 25 | <td className={`px-4 py-3 text-sm border-b border-border ${className}`}>
|
|---|
| 26 | {children}
|
|---|
| 27 | </td>
|
|---|
| 28 | );
|
|---|
| 29 |
|
|---|
| 30 | const CompletedBadge = ({ completed }: { completed: string }) => {
|
|---|
| 31 | const { t } = useTranslation();
|
|---|
| 32 | if (completed === 'Да') {
|
|---|
| 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')}>
|
|---|
| 35 | <FontAwesomeIcon icon={faCheckCircle} className="w-4 h-4" />
|
|---|
| 36 | </span>
|
|---|
| 37 | );
|
|---|
| 38 | } else {
|
|---|
| 39 | return (
|
|---|
| 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')}>
|
|---|
| 41 | <FontAwesomeIcon icon={faTimesCircle} className="w-4 h-4" />
|
|---|
| 42 | </span>
|
|---|
| 43 | );
|
|---|
| 44 | }
|
|---|
| 45 | };
|
|---|
| 46 |
|
|---|
| 47 | const FeeBadge = ({ fee }: { fee: string }) => {
|
|---|
| 48 | const feeValue = parseFloat(fee.replace(',', '.'));
|
|---|
| 49 |
|
|---|
| 50 | if (feeValue === 0) {
|
|---|
| 51 | return (
|
|---|
| 52 | <span className="inline-flex items-center gap-1 px-2 py-1 bg-green-100 text-green-800 rounded-md text-xs font-medium">
|
|---|
| 53 | <FontAwesomeIcon icon={faMoneyBillWave} className="w-3 h-3" />
|
|---|
| 54 | {fee}
|
|---|
| 55 | </span>
|
|---|
| 56 | );
|
|---|
| 57 | } else {
|
|---|
| 58 | return (
|
|---|
| 59 | <span className="inline-flex items-center gap-1 px-2 py-1 bg-yellow-100 text-yellow-800 rounded-md text-xs font-medium">
|
|---|
| 60 | <FontAwesomeIcon icon={faMoneyBillWave} className="w-3 h-3" />
|
|---|
| 61 | {fee}
|
|---|
| 62 | </span>
|
|---|
| 63 | );
|
|---|
| 64 | }
|
|---|
| 65 | };
|
|---|
| 66 |
|
|---|
| 67 | export default function ApplicationsPage() {
|
|---|
| 68 | const { t } = useTranslation();
|
|---|
| 69 | const [selectedSession, setSelectedSession] = useState(applicationsData.currentSession.id);
|
|---|
| 70 | const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|---|
| 71 |
|
|---|
| 72 | const currentSessionData = applicationsData.examSessions.find(s => s.id === selectedSession) || applicationsData.currentSession;
|
|---|
| 73 | const { applications } = applicationsData;
|
|---|
| 74 |
|
|---|
| 75 | return (
|
|---|
| 76 | <div className="min-h-screen pb-8">
|
|---|
| 77 | {/* Header */}
|
|---|
| 78 | <div className="bg-primary text-white rounded-xl p-8 mb-8">
|
|---|
| 79 | <div className="flex items-center gap-4">
|
|---|
| 80 | <div className="bg-white rounded-full p-4">
|
|---|
| 81 | <FontAwesomeIcon icon={faPenToSquare} className="text-3xl text-[#0272D1]" />
|
|---|
| 82 | </div>
|
|---|
| 83 | <div>
|
|---|
| 84 | <h1 className="text-3xl font-bold mb-2">{t('applications_header', 'Пријави')}</h1>
|
|---|
| 85 | <p className="text-lg opacity-90">
|
|---|
| 86 | {t('applications_subheader', 'Електронски пријави за испити')}
|
|---|
| 87 | </p>
|
|---|
| 88 | </div>
|
|---|
| 89 | </div>
|
|---|
| 90 | </div>
|
|---|
| 91 |
|
|---|
| 92 | {/* Session Selection */}
|
|---|
| 93 | <div className="bg-card rounded-xl p-6 shadow-sm border border-border mb-8">
|
|---|
| 94 | <div className="flex items-center justify-between">
|
|---|
| 95 | <h2 className="text-lg font-semibold text-card-foreground flex items-center gap-2">
|
|---|
| 96 | <FontAwesomeIcon icon={faCalendarAlt} className="text-primary" />
|
|---|
| 97 | {t('select_exam_session', 'Избери испитна сесија:')}
|
|---|
| 98 | </h2>
|
|---|
| 99 |
|
|---|
| 100 | <div className="relative">
|
|---|
| 101 | <button
|
|---|
| 102 | onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
|---|
| 103 | className="bg-card border border-border rounded-lg px-4 py-2 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary min-w-80"
|
|---|
| 104 | >
|
|---|
| 105 | <div className="flex items-center justify-between">
|
|---|
| 106 | <span className="text-sm font-medium text-primary">
|
|---|
| 107 | {t(currentSessionData.id, currentSessionData.name)}
|
|---|
| 108 | </span>
|
|---|
| 109 | <FontAwesomeIcon
|
|---|
| 110 | icon={faChevronDown}
|
|---|
| 111 | className={`w-4 h-4 text-muted-foreground transition-transform ml-4 ${isDropdownOpen ? 'rotate-180' : ''}`}
|
|---|
| 112 | />
|
|---|
| 113 | </div>
|
|---|
| 114 | </button>
|
|---|
| 115 |
|
|---|
| 116 | {isDropdownOpen && (
|
|---|
| 117 | <div className="absolute z-10 right-0 mt-1 w-80 bg-card border border-border rounded-lg shadow-lg">
|
|---|
| 118 | {applicationsData.examSessions.map((session) => (
|
|---|
| 119 | <button
|
|---|
| 120 | key={session.id}
|
|---|
| 121 | onClick={() => {
|
|---|
| 122 | setSelectedSession(session.id);
|
|---|
| 123 | setIsDropdownOpen(false);
|
|---|
| 124 | }}
|
|---|
| 125 | className="w-full px-4 py-3 text-left text-sm hover:bg-accent focus:outline-none focus:bg-accent first:rounded-t-lg last:rounded-b-lg"
|
|---|
| 126 | >
|
|---|
| 127 | <div className="font-medium text-card-foreground">{t(session.id, session.name)}</div>
|
|---|
| 128 | <div className="text-xs text-muted-foreground">{session.year} - {t(session.semester, session.semester)} - {t(session.session, session.session)}</div>
|
|---|
| 129 | </button>
|
|---|
| 130 | ))}
|
|---|
| 131 | </div>
|
|---|
| 132 | )}
|
|---|
| 133 | </div>
|
|---|
| 134 | </div>
|
|---|
| 135 | </div>
|
|---|
| 136 |
|
|---|
| 137 | {/* Applications Section */}
|
|---|
| 138 | <div className="bg-card rounded-xl shadow-sm border border-border overflow-hidden">
|
|---|
| 139 | <div className="bg-primary text-white px-6 py-4">
|
|---|
| 140 | <h2 className="text-xl font-bold">{t('registered_exams', 'Пријавени испити')}</h2>
|
|---|
| 141 | </div>
|
|---|
| 142 |
|
|---|
| 143 | <div className="overflow-x-auto">
|
|---|
| 144 | <table className="w-full">
|
|---|
| 145 | <thead className="bg-accent">
|
|---|
| 146 | <tr>
|
|---|
| 147 | <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">#</th>
|
|---|
| 148 | <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('serial_number', 'Сериски број')}</th>
|
|---|
| 149 | <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('code', 'Код')}</th>
|
|---|
| 150 | <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('subject', 'Предмет')}</th>
|
|---|
| 151 | <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('completed', 'Завршена')}</th>
|
|---|
| 152 | <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('fee', 'Таксени')}</th>
|
|---|
| 153 | <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('date', 'Датум')}</th>
|
|---|
| 154 | <th className="px-4 py-4 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('instructor', 'Наставник')}</th>
|
|---|
| 155 | <th className="px-4 py-4 text-center text-xs font-medium text-muted-foreground uppercase tracking-wider">{t('decade', 'Декада')}</th>
|
|---|
| 156 | </tr>
|
|---|
| 157 | </thead>
|
|---|
| 158 | <tbody className="divide-y divide-border">
|
|---|
| 159 | {applications.map((application) => (
|
|---|
| 160 | <tr key={application.id} className="hover:bg-accent transition-colors">
|
|---|
| 161 | <TableCell className="font-medium text-card-foreground">{application.id}</TableCell>
|
|---|
| 162 | <TableCell>
|
|---|
| 163 | <span className="font-mono text-sm text-primary font-medium hover:underline cursor-pointer">
|
|---|
| 164 | {application.serviceNumber}
|
|---|
| 165 | </span>
|
|---|
| 166 | </TableCell>
|
|---|
| 167 | <TableCell className="font-mono text-sm font-medium">{application.code}</TableCell>
|
|---|
| 168 | <TableCell className="font-medium text-card-foreground max-w-xs">
|
|---|
| 169 | <div className="flex items-center gap-2">
|
|---|
| 170 | <FontAwesomeIcon icon={faFileText} className="w-4 h-4 text-primary" />
|
|---|
| 171 | {t(application.subject, application.subject)}
|
|---|
| 172 | </div>
|
|---|
| 173 | </TableCell>
|
|---|
| 174 | <TableCell className="text-center">
|
|---|
| 175 | <CompletedBadge completed={application.completed} />
|
|---|
| 176 | </TableCell>
|
|---|
| 177 | <TableCell className="text-center">
|
|---|
| 178 | <FeeBadge fee={application.fee} />
|
|---|
| 179 | </TableCell>
|
|---|
| 180 | <TableCell className="text-muted-foreground font-medium">{application.date}</TableCell>
|
|---|
| 181 | <TableCell>
|
|---|
| 182 | <div className="flex items-center gap-2">
|
|---|
| 183 | <FontAwesomeIcon icon={faUser} className="w-4 h-4 text-muted-foreground" />
|
|---|
| 184 | <span className="font-medium text-card-foreground">{t(application.instructor, application.instructor)}</span>
|
|---|
| 185 | </div>
|
|---|
| 186 | </TableCell>
|
|---|
| 187 | <TableCell className="text-center font-medium text-primary">{application.decade}</TableCell>
|
|---|
| 188 | </tr>
|
|---|
| 189 | ))}
|
|---|
| 190 | </tbody>
|
|---|
| 191 | </table>
|
|---|
| 192 | </div>
|
|---|
| 193 |
|
|---|
| 194 | {/* Important Note */}
|
|---|
| 195 | <div className="bg-blue-50 border-t border-blue-100 p-6">
|
|---|
| 196 | <div className="flex items-start gap-3">
|
|---|
| 197 | <FontAwesomeIcon icon={faInfoCircle} className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
|---|
| 198 | <div>
|
|---|
| 199 | <h3 className="font-medium text-blue-900 mb-1">{t('important_note', 'Важна забелешка')}</h3>
|
|---|
| 200 | <p className="text-sm text-blue-800 leading-relaxed">
|
|---|
| 201 | {t('applications_important_note_text')}
|
|---|
| 202 | </p>
|
|---|
| 203 | </div>
|
|---|
| 204 | </div>
|
|---|
| 205 | </div>
|
|---|
| 206 | </div>
|
|---|
| 207 |
|
|---|
| 208 | {/* Statistics Cards */}
|
|---|
| 209 | <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
|
|---|
| 210 | <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
|
|---|
| 211 | <div className="flex items-center gap-4">
|
|---|
| 212 | <div className="bg-blue-100 text-blue-600 rounded-full p-3">
|
|---|
| 213 | <FontAwesomeIcon icon={faFileText} className="text-xl" />
|
|---|
| 214 | </div>
|
|---|
| 215 | <div>
|
|---|
| 216 | <h3 className="text-lg font-bold text-card-foreground">{t('registered_exams', 'Пријавени испити')}</h3>
|
|---|
| 217 | <p className="text-2xl font-bold text-blue-600">
|
|---|
| 218 | {applications.length}
|
|---|
| 219 | </p>
|
|---|
| 220 | </div>
|
|---|
| 221 | </div>
|
|---|
| 222 | </div>
|
|---|
| 223 |
|
|---|
| 224 | <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
|
|---|
| 225 | <div className="flex items-center gap-4">
|
|---|
| 226 | <div className="bg-green-100 text-green-600 rounded-full p-3">
|
|---|
| 227 | <FontAwesomeIcon icon={faCheckCircle} className="text-xl" />
|
|---|
| 228 | </div>
|
|---|
| 229 | <div>
|
|---|
| 230 | <h3 className="text-lg font-bold text-card-foreground">{t('completed', 'Завршени')}</h3>
|
|---|
| 231 | <p className="text-2xl font-bold text-green-600">
|
|---|
| 232 | {applications.filter(app => app.completed === 'Да').length}
|
|---|
| 233 | </p>
|
|---|
| 234 | </div>
|
|---|
| 235 | </div>
|
|---|
| 236 | </div>
|
|---|
| 237 |
|
|---|
| 238 | <div className="bg-card rounded-xl p-6 shadow-sm border border-border">
|
|---|
| 239 | <div className="flex items-center gap-4">
|
|---|
| 240 | <div className="bg-yellow-100 text-yellow-600 rounded-full p-3">
|
|---|
| 241 | <FontAwesomeIcon icon={faMoneyBillWave} className="text-xl" />
|
|---|
| 242 | </div>
|
|---|
| 243 | <div>
|
|---|
| 244 | <h3 className="text-lg font-bold text-card-foreground">{t('total_fee', 'Вкупна такса')}</h3>
|
|---|
| 245 | <p className="text-2xl font-bold text-yellow-600">
|
|---|
| 246 | {applications.reduce((sum, app) => sum + parseFloat(app.fee.replace(',', '.')), 0).toFixed(2)}
|
|---|
| 247 | </p>
|
|---|
| 248 | </div>
|
|---|
| 249 | </div>
|
|---|
| 250 | </div>
|
|---|
| 251 | </div>
|
|---|
| 252 | </div>
|
|---|
| 253 | );
|
|---|
| 254 | }
|
|---|