Changeset 4fe4582 for src/app/students
- Timestamp:
- 02/16/26 17:38:16 (7 months ago)
- Branches:
- master
- Children:
- 8496f3c
- Parents:
- d7deae5
- Location:
- src/app/students
- Files:
-
- 3 edited
-
applications/page.tsx (modified) (2 diffs)
-
documents/page.tsx (modified) (7 diffs)
-
semesters/page.tsx (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/app/students/applications/page.tsx
rd7deae5 r4fe4582 30 30 const CompletedBadge = ({ completed }: { completed: string }) => { 31 31 const { t } = useTranslation(); 32 if (completed === t('yes', 'Да')) {32 if (completed === 'Да') { 33 33 return ( 34 34 <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full" title={t('completed')}> … … 230 230 <h3 className="text-lg font-bold text-card-foreground">{t('completed', 'Завршени')}</h3> 231 231 <p className="text-2xl font-bold text-green-600"> 232 {applications.filter(app => app.completed === t('yes', 'Да')).length}232 {applications.filter(app => app.completed === 'Да').length} 233 233 </p> 234 234 </div> -
src/app/students/documents/page.tsx
rd7deae5 r4fe4582 6 6 faChevronDown, 7 7 faCheckCircle, 8 faClock, 8 9 faMoneyBillWave, 9 10 faFileText, … … 11 12 faChevronRight, 12 13 faAngleDoubleLeft, 13 faAngleDoubleRight 14 faAngleDoubleRight, 15 faSpinner 14 16 } from '@fortawesome/free-solid-svg-icons'; 15 17 import { useState } from 'react'; 16 18 import documentsData from '@/data/documents.json'; 17 19 import { useTranslation } from 'react-i18next'; 20 import { getAccessToken } from '@/lib/auth'; 21 import { downloadDocumentPDF } from '@/lib/pdf-generators'; 18 22 19 23 interface TableCellProps { … … 30 34 const StatusBadge = ({ status }: { status: string }) => { 31 35 const { t } = useTranslation(); 32 if (status === t('approved', 'Одобрено')) {36 if (status === 'approved' || status === t('approved', 'Одобрено')) { 33 37 return ( 34 38 <span className="inline-flex items-center justify-center w-8 h-8 bg-green-100 text-green-600 rounded-full"> 35 39 <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5" /> 40 </span> 41 ); 42 } 43 if (status === 'pending') { 44 return ( 45 <span className="inline-flex items-center gap-1 text-xs font-medium text-yellow-700 bg-yellow-100 px-2 py-1 rounded-full"> 46 <FontAwesomeIcon icon={faClock} className="w-3 h-3" /> 47 {t('pending', 'Во обработка')} 36 48 </span> 37 49 ); … … 52 64 }; 53 65 66 interface DocumentRecord { 67 id: number; 68 archive: string; 69 date: string; 70 request: string; 71 price: number; 72 paid: string; 73 document: string; 74 payOnline: boolean; 75 status: string; 76 comment: string; 77 } 78 54 79 export default function DocumentsPage() { 55 80 const [selectedDocumentType, setSelectedDocumentType] = useState("select_document"); … … 58 83 const [currentPage, setCurrentPage] = useState(1); 59 84 const [recordsPerPage, setRecordsPerPage] = useState(15); 85 const [downloadingId, setDownloadingId] = useState<number | null>(null); 86 const [documents, setDocuments] = useState<DocumentRecord[]>(documentsData.documents as DocumentRecord[]); 87 const [isSubmitting, setIsSubmitting] = useState(false); 60 88 const { t } = useTranslation(); 61 89 90 const handleDownload = async (docId: number, request: string, archive: string, date: string) => { 91 const token = getAccessToken(); 92 if (!token) { 93 alert(t('not_authenticated', 'Не сте најавени. Ве молиме најавете се повторно.')); 94 return; 95 } 96 setDownloadingId(docId); 97 try { 98 await downloadDocumentPDF(request, archive, date, token); 99 } catch (err) { 100 console.error('PDF generation error:', err); 101 alert(t('pdf_error', 'Грешка при генерирање на документот. Обидете се повторно.')); 102 } finally { 103 setDownloadingId(null); 104 } 105 }; 106 107 const handleSubmit = () => { 108 if (selectedDocumentType === "select_document") return; 109 110 const docType = documentsData.documentTypes.find(d => d.id === selectedDocumentType); 111 if (!docType) return; 112 113 setIsSubmitting(true); 114 115 // Generate archive number (random 5-digit) 116 const archiveNumber = String(90000 + Math.floor(Math.random() * 10000)); 117 118 // Current date in DD.MM.YYYY format 119 const now = new Date(); 120 const dateStr = `${String(now.getDate()).padStart(2, '0')}.${String(now.getMonth() + 1).padStart(2, '0')}.${now.getFullYear()}`; 121 122 const newDoc: DocumentRecord = { 123 id: documents.length > 0 ? Math.max(...documents.map(d => d.id)) + 1 : 1, 124 archive: archiveNumber, 125 date: dateStr, 126 request: docType.name, 127 price: docType.price, 128 paid: "Не", 129 document: "Преземи", 130 payOnline: docType.price > 0, 131 status: "pending", 132 comment: comment, 133 }; 134 135 setDocuments(prev => [newDoc, ...prev]); 136 setSelectedDocumentType("select_document"); 137 setComment(""); 138 setIsSubmitting(false); 139 alert(t('document_submitted', 'Барањето за документ е успешно поднесено!')); 140 }; 141 62 142 const selectedDocument = documentsData.documentTypes.find(d => d.id === selectedDocumentType); 63 const { documents,paymentInfo } = documentsData;143 const { paymentInfo } = documentsData; 64 144 65 145 const totalPages = Math.ceil(documents.length / recordsPerPage); … … 153 233 <div className="flex justify-end mt-6"> 154 234 <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"} 235 onClick={handleSubmit} 236 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 disabled:opacity-50 disabled:cursor-not-allowed" 237 disabled={selectedDocumentType === "select_document" || isSubmitting} 157 238 > 158 <FontAwesomeIcon icon={ faFileText} className="w-4 h-4"/>159 { t('submit', 'Поднеси')}239 <FontAwesomeIcon icon={isSubmitting ? faSpinner : faFileText} className={`w-4 h-4 ${isSubmitting ? 'animate-spin' : ''}`} /> 240 {isSubmitting ? t('submitting', 'Се поднесува...') : t('submit', 'Поднеси')} 160 241 </button> 161 242 </div> … … 197 278 </TableCell> 198 279 <TableCell className="text-center"> 199 <span className= "text-sm font-medium text-green-600">280 <span className={`text-sm font-medium ${document.paid === 'ДА' ? 'text-green-600' : 'text-red-500'}`}> 200 281 {document.paid} 201 282 </span> 202 283 </TableCell> 203 284 <TableCell className="text-center"> 204 <button className="text-primary hover:text-blue-700 font-medium text-sm underline"> 205 {document.document} 285 <button 286 onClick={() => handleDownload(document.id, document.request, document.archive, document.date)} 287 disabled={downloadingId === document.id} 288 className="text-primary hover:text-blue-700 font-medium text-sm underline disabled:opacity-50 disabled:cursor-wait inline-flex items-center gap-1" 289 > 290 {downloadingId === document.id ? ( 291 <> 292 <FontAwesomeIcon icon={faSpinner} className="w-3 h-3 animate-spin" /> 293 {t('generating', 'Генерира...')} 294 </> 295 ) : ( 296 t('download', document.document) 297 )} 206 298 </button> 207 299 </TableCell> -
src/app/students/semesters/page.tsx
rd7deae5 r4fe4582 57 57 const { t } = useTranslation(); 58 58 const baseClasses = "px-2 py-1 rounded-full text-xs font-medium"; 59 if (status === t('valid')) {59 if (status === 'Валидно') { 60 60 return ( 61 61 <span className={`${baseClasses} bg-green-100 text-green-800 flex items-center gap-1`}> 62 62 <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" /> 63 {t(' valid')}63 {t('Валидно', 'Валидно')} 64 64 </span> 65 65 ); … … 67 67 return ( 68 68 <span className={`${baseClasses} bg-accent text-gray-800`}> 69 {t(status ) || status}69 {t(status, status)} 70 70 </span> 71 71 ); … … 74 74 const YesNoBadge = ({ value }: { value: string }) => { 75 75 const { t } = useTranslation(); 76 if (value === t('yes')) {76 if (value === 'Да') { 77 77 return ( 78 78 <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full"> … … 80 80 </span> 81 81 ); 82 } else if (value === t('no')) {82 } else if (value === 'Не') { 83 83 return ( 84 84 <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full"> … … 313 313 <span className="text-green-600 font-medium flex items-center gap-1"> 314 314 <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" /> 315 { semester.completed}315 {t(semester.completed, semester.completed)} 316 316 </span> 317 317 ) : ( 318 318 <span className="text-red-600 font-medium flex items-center gap-1"> 319 319 <FontAwesomeIcon icon={faTimesCircle} className="w-3 h-3" /> 320 { semester.completed}320 {t('Не', 'Не')} 321 321 </span> 322 322 )}
Note:
See TracChangeset
for help on using the changeset viewer.
