source: src/app/students/documents/page.tsx@ 50f2fb4

Last change on this file since 50f2fb4 was 50f2fb4, checked in by Stefan-Saveski <stefansaveski19@…>, 8 months ago

Added dual language feature.

  • Property mode set to 100644
File size: 16.1 KB
Line 
1"use client"
2
3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4import {
5 faFilePdf,
6 faChevronDown,
7 faCheckCircle,
8 faMoneyBillWave,
9 faFileText,
10 faChevronLeft,
11 faChevronRight,
12 faAngleDoubleLeft,
13 faAngleDoubleRight
14} from '@fortawesome/free-solid-svg-icons';
15import { useState } from 'react';
16import documentsData from '@/data/documents.json';
17import { useTranslation } from 'react-i18next';
18
19interface TableCellProps {
20 children: React.ReactNode;
21 className?: string;
22}
23
24const TableCell = ({ children, className = "" }: TableCellProps) => (
25 <td className={`px-4 py-3 text-sm border-b border-gray-200 ${className}`}>
26 {children}
27 </td>
28);
29
30const StatusBadge = ({ status }: { status: string }) => {
31 const { t } = useTranslation();
32 if (status === t('approved', 'Одобрено')) {
33 return (
34 <span className="inline-flex items-center justify-center w-8 h-8 bg-green-100 text-green-600 rounded-full">
35 <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5" />
36 </span>
37 );
38 }
39 return (
40 <span className="inline-flex items-center justify-center w-8 h-8 bg-gray-100 text-gray-600 rounded-full">
41 <FontAwesomeIcon icon={faFileText} className="w-4 h-4" />
42 </span>
43 );
44};
45
46const PriceBadge = ({ price }: { price: number }) => {
47 const { t } = useTranslation();
48 if (price === 0) {
49 return <span className="font-medium text-green-600">{t('free', '0,00')}</span>;
50 }
51 return <span className="font-medium text-blue-600">{price.toFixed(2)}</span>;
52};
53
54export default function DocumentsPage() {
55 const [selectedDocumentType, setSelectedDocumentType] = useState("select_document");
56 const [isDropdownOpen, setIsDropdownOpen] = useState(false);
57 const [comment, setComment] = useState("");
58 const [currentPage, setCurrentPage] = useState(1);
59 const [recordsPerPage, setRecordsPerPage] = useState(15);
60 const { t } = useTranslation();
61
62 const selectedDocument = documentsData.documentTypes.find(d => d.id === selectedDocumentType);
63 const { documents, paymentInfo } = documentsData;
64
65 const totalPages = Math.ceil(documents.length / recordsPerPage);
66 const startIndex = (currentPage - 1) * recordsPerPage;
67 const endIndex = startIndex + recordsPerPage;
68 const currentDocuments = documents.slice(startIndex, endIndex);
69
70 return (
71 <div className="min-h-screen pb-8">
72 {/* Header */}
73 <div className="bg-primary text-white rounded-xl p-8 mb-8">
74 <div className="flex items-center gap-4">
75 <div className="bg-white bg-opacity-20 rounded-full p-4">
76 <FontAwesomeIcon icon={faFilePdf} className="text-3xl text-primary" />
77 </div>
78 <div>
79 <h1 className="text-3xl font-bold mb-2">{t('documents')}</h1>
80 <p className="text-lg opacity-90">
81 {t('documents_overview', 'Преглед на вашите документи и нивниот статус.')}
82 </p>
83 </div>
84 </div>
85 </div>
86
87 {/* Document Request Form */}
88 <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 mb-8">
89 <h2 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
90 <FontAwesomeIcon icon={faFileText} className="text-primary" />
91 {t('new_document_request', 'Ново барање за документ')}
92 </h2>
93
94 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
95 {/* Document Type Selection */}
96 <div>
97 <label className="block text-sm font-medium text-gray-700 mb-2">
98 {t('select_document', 'Избери документ')}:
99 </label>
100 <div className="relative">
101 <button
102 onClick={() => setIsDropdownOpen(!isDropdownOpen)}
103 className="w-full bg-white border border-gray-300 rounded-lg px-4 py-3 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
104 >
105 <div className="flex items-center justify-between">
106 <span className="text-sm text-gray-900 truncate">
107 {selectedDocument ? t(selectedDocument.id) : t('select_document', 'Избери документ')}
108 </span>
109 <FontAwesomeIcon
110 icon={faChevronDown}
111 className={`w-4 h-4 text-gray-400 transition-transform ml-2 flex-shrink-0 ${isDropdownOpen ? 'rotate-180' : ''}`}
112 />
113 </div>
114 </button>
115
116 {isDropdownOpen && (
117 <div className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-80 overflow-y-auto">
118 {documentsData.documentTypes.map((docType) => (
119 <button
120 key={docType.id}
121 onClick={() => {
122 setSelectedDocumentType(docType.id);
123 setIsDropdownOpen(false);
124 }}
125 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"
126 >
127 <div className="font-medium text-gray-900">{t(docType.id)}</div>
128 {docType.price > 0 && (
129 <div className="text-xs text-blue-600 mt-1">{t('price', 'Цена')}: {docType.price} мкд</div>
130 )}
131 </button>
132 ))}
133 </div>
134 )}
135 </div>
136 </div>
137
138 {/* Comment Section */}
139 <div>
140 <label className="block text-sm font-medium text-gray-700 mb-2">
141 {t('comment', 'Коментар')}:
142 </label>
143 <textarea
144 value={comment}
145 onChange={(e) => setComment(e.target.value)}
146 rows={4}
147 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"
148 placeholder={t('add_comment', 'Додај коментар')}
149 />
150 </div>
151 </div>
152
153 <div className="flex justify-end mt-6">
154 <button
155 className="bg-primary hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors duration-200 flex items-center gap-2"
156 disabled={selectedDocumentType === "select_document"}
157 >
158 <FontAwesomeIcon icon={faFileText} className="w-4 h-4" />
159 {t('submit', 'Поднеси')}
160 </button>
161 </div>
162 </div>
163
164 {/* Documents Table */}
165 <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
166 <div className="bg-primary text-white px-6 py-4">
167 <h2 className="text-xl font-bold">{t('my_documents', 'Мои документи')}</h2>
168 </div>
169
170 <div className="overflow-x-auto">
171 <table className="w-full">
172 <thead className="bg-gray-50">
173 <tr>
174 <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>
184 </tr>
185 </thead>
186 <tbody className="divide-y divide-gray-100">
187 {currentDocuments.map((document) => (
188 <tr key={document.id} className="hover:bg-gray-50 transition-colors">
189 <TableCell className="font-medium text-gray-900">{document.id}</TableCell>
190 <TableCell className="font-mono text-sm text-primary font-medium">{document.archive}</TableCell>
191 <TableCell className="text-gray-600">{document.date}</TableCell>
192 <TableCell className="font-medium text-gray-900 max-w-xs">
193 {t(document.request)}
194 </TableCell>
195 <TableCell className="text-right">
196 <PriceBadge price={document.price} />
197 </TableCell>
198 <TableCell className="text-center">
199 <span className="text-sm font-medium text-green-600">
200 {document.paid}
201 </span>
202 </TableCell>
203 <TableCell className="text-center">
204 <button className="text-primary hover:text-blue-700 font-medium text-sm underline">
205 {document.document}
206 </button>
207 </TableCell>
208 <TableCell className="text-center">
209 {document.payOnline ? (
210 <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5 text-green-600" />
211 ) : (
212 <span className="text-gray-400">{t('none', '—')}</span>
213 )}
214 </TableCell>
215 <TableCell className="text-center">
216 <StatusBadge status={document.status} />
217 </TableCell>
218 <TableCell>
219 {document.comment || (
220 <span className="text-gray-400">{t('none', '—')}</span>
221 )}
222 </TableCell>
223 </tr>
224 ))}
225 </tbody>
226 </table>
227 </div>
228
229 {/* Pagination */}
230 <div className="bg-gray-50 px-6 py-4 flex items-center justify-between border-t border-gray-200">
231 <div className="flex items-center gap-4 text-sm text-gray-700">
232 <div className="flex items-center gap-2">
233 <span>{t('show_rows', 'Прикажи редови')}:</span>
234 <select
235 value={recordsPerPage}
236 onChange={(e) => setRecordsPerPage(Number(e.target.value))}
237 className="border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
238 >
239 <option value={15}>15</option>
240 <option value={25}>25</option>
241 <option value={50}>50</option>
242 </select>
243 </div>
244 <div>
245 {t('page', 'Страница')} <input
246 type="number"
247 min="1"
248 max={totalPages}
249 value={currentPage}
250 onChange={(e) => setCurrentPage(Number(e.target.value))}
251 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"
252 /> {t('of', 'од')} {totalPages}
253 </div>
254 </div>
255
256 <div className="flex items-center gap-2">
257 <button
258 onClick={() => setCurrentPage(1)}
259 disabled={currentPage === 1}
260 className="p-2 text-gray-500 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
261 >
262 <FontAwesomeIcon icon={faAngleDoubleLeft} className="w-4 h-4" />
263 </button>
264 <button
265 onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
266 disabled={currentPage === 1}
267 className="p-2 text-gray-500 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
268 >
269 <FontAwesomeIcon icon={faChevronLeft} className="w-4 h-4" />
270 </button>
271 <span className="px-4 py-2 bg-primary text-white rounded text-sm font-medium">
272 {t('first', 'Прва')}
273 </span>
274 <button
275 onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
276 disabled={currentPage === totalPages}
277 className="p-2 text-gray-500 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
278 >
279 <FontAwesomeIcon icon={faChevronRight} className="w-4 h-4" />
280 </button>
281 <button
282 onClick={() => setCurrentPage(totalPages)}
283 disabled={currentPage === totalPages}
284 className="p-2 text-gray-500 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
285 >
286 <FontAwesomeIcon icon={faAngleDoubleRight} className="w-4 h-4" />
287 </button>
288 <span className="ml-4 text-sm text-gray-700">
289 {t('last', 'Последна')}
290 </span>
291 </div>
292
293 <div className="text-sm text-gray-700">
294 {t('total', 'Вкупно')}: {documents.length}
295 </div>
296 </div>
297
298 {/* Payment Info */}
299 <div className="bg-blue-50 border-t border-blue-100 p-4">
300 <div className="flex items-start gap-3">
301 <FontAwesomeIcon icon={faMoneyBillWave} className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
302 <p className="text-sm text-blue-800 leading-relaxed">
303 {t('documents_payment_info', paymentInfo)}
304 </p>
305 </div>
306 </div>
307 </div>
308
309 {/* Statistics Cards */}
310 <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
311 <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
312 <div className="flex items-center gap-4">
313 <div className="bg-blue-100 text-blue-600 rounded-full p-3">
314 <FontAwesomeIcon icon={faFileText} className="text-xl" />
315 </div>
316 <div>
317 <h3 className="text-lg font-bold text-gray-900">{t('total_documents', 'Вкупно документи')}</h3>
318 <p className="text-2xl font-bold text-blue-600">
319 {documents.length}
320 </p>
321 </div>
322 </div>
323 </div>
324
325 <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
326 <div className="flex items-center gap-4">
327 <div className="bg-green-100 text-green-600 rounded-full p-3">
328 <FontAwesomeIcon icon={faCheckCircle} className="text-xl" />
329 </div>
330 <div>
331 <h3 className="text-lg font-bold text-gray-900">{t('approved', 'Одобрени')}</h3>
332 <p className="text-2xl font-bold text-green-600">
333 {documents.filter(doc => doc.status === "approved").length}
334 </p>
335 </div>
336 </div>
337 </div>
338
339 <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
340 <div className="flex items-center gap-4">
341 <div className="bg-yellow-100 text-yellow-600 rounded-full p-3">
342 <FontAwesomeIcon icon={faMoneyBillWave} className="text-xl" />
343 </div>
344 <div>
345 <h3 className="text-lg font-bold text-gray-900">{t('total_price', 'Вкупна цена')}</h3>
346 <p className="text-2xl font-bold text-yellow-600">
347 {documents.reduce((sum, doc) => sum + doc.price, 0).toFixed(2)} {t('mkd', 'мкд')}
348 </p>
349 </div>
350 </div>
351 </div>
352 </div>
353 </div>
354 );
355}
Note: See TracBrowser for help on using the repository browser.