Changeset 50f2fb4 for src/app/professor
- Timestamp:
- 01/30/26 01:40:27 (8 months ago)
- Branches:
- master
- Children:
- ba52069
- Parents:
- 298f9b3
- Location:
- src/app/professor
- Files:
-
- 2 edited
-
profile/page.tsx (modified) (9 diffs)
-
students/page.tsx (modified) (13 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/app/professor/profile/page.tsx
r298f9b3 r50f2fb4 18 18 import { getAccessToken } from "@/lib/auth"; 19 19 import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; 20 import { useTranslation } from 'react-i18next'; 20 21 21 22 type PersonalInfo = { … … 94 95 } 95 96 96 const InfoRow = ({ label, value, icon }: InfoRowProps) => ( 97 <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0"> 98 <div className="flex items-center gap-2 text-gray-600 font-medium"> 99 {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />} 100 <span>{label}:</span> 97 const InfoRow = ({ label, value, icon }: InfoRowProps) => { 98 const { t } = useTranslation(); 99 return ( 100 <div className="flex justify-between items-center py-3 border-b border-gray-100 last:border-b-0"> 101 <div className="flex items-center gap-2 text-gray-600 font-medium"> 102 {icon && <FontAwesomeIcon icon={icon} className="w-4 h-4" />} 103 <span>{t(label)}:</span> 104 </div> 105 <div className="text-gray-900 font-semibold text-right max-w-xs break-words"> 106 {value || t('n_a')} 107 </div> 101 108 </div> 102 <div className="text-gray-900 font-semibold text-right max-w-xs break-words"> 103 {value || "N/A"} 104 </div> 105 </div> 106 ); 109 ); 110 }; 107 111 108 112 interface SectionProps { … … 112 116 } 113 117 114 const Section = ({ title, icon, children }: SectionProps) => ( 115 <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> 116 <div className="bg-primary text-white px-6 py-4"> 117 <div className="flex items-center gap-3"> 118 <FontAwesomeIcon icon={icon} className="text-xl" /> 119 <h2 className="text-xl font-bold">{title}</h2> 120 </div> 118 const Section = ({ title, icon, children }: SectionProps) => { 119 const { t } = useTranslation(); 120 return ( 121 <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> 122 <div className="bg-primary text-white px-6 py-4"> 123 <div className="flex items-center gap-3"> 124 <FontAwesomeIcon icon={icon} className="text-xl" /> 125 <h2 className="text-xl font-bold">{t(title)}</h2> 126 </div> 127 </div> 128 <div className="p-6">{children}</div> 121 129 </div> 122 <div className="p-6">{children}</div> 123 </div> 124 ); 130 ); 131 }; 125 132 126 133 export default function ProfessorProfilePage() { … … 128 135 const [isLoading, setIsLoading] = useState(true); 129 136 const [errorMessage, setErrorMessage] = useState<string | null>(null); 137 const { t } = useTranslation(); 130 138 131 139 useEffect(() => { … … 177 185 return ( 178 186 <div className="min-h-screen pb-8"> 179 <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6"> Loading...</div>187 <div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">{t('loading')}</div> 180 188 </div> 181 189 ); … … 186 194 <div className="min-h-screen pb-8"> 187 195 <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700"> 188 {errorMessage ?? "Failed to load profile."}196 {errorMessage ?? t('failed_to_load_profile')} 189 197 </div> 190 198 </div> … … 210 218 </h1> 211 219 <div className="text-lg opacity-90"> 212 Индекс: {personalInfo.index} | ЕМБГ: {personalInfo.embg}220 {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg} 213 221 </div> 214 222 </div> … … 219 227 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> 220 228 {/* Personal Information */} 221 <Section title=" Лични податоци" icon={faIdCard}>222 <InfoRow label=" Име" value={personalInfo.firstName} />223 <InfoRow label=" Средно име" value={personalInfo.middleName} />224 <InfoRow label=" Презиме" value={personalInfo.lastName} />225 <InfoRow label=" Моминско презиме" value={personalInfo.maidenName} />226 <InfoRow label=" Датум на раѓање" value={personalInfo.dateOfBirth} icon={faCalendarAlt} />229 <Section title="personal_info" icon={faIdCard}> 230 <InfoRow label="first_name" value={personalInfo.firstName} /> 231 <InfoRow label="middle_name" value={personalInfo.middleName} /> 232 <InfoRow label="last_name" value={personalInfo.lastName} /> 233 <InfoRow label="maiden_name" value={personalInfo.maidenName} /> 234 <InfoRow label="date_of_birth" value={personalInfo.dateOfBirth} icon={faCalendarAlt} /> 227 235 <InfoRow 228 label=" Пол"236 label="gender" 229 237 value={personalInfo.gender} 230 icon={personalInfo.gender === "машки"? faMars : faVenus}238 icon={personalInfo.gender === t('male') ? faMars : faVenus} 231 239 /> 232 <InfoRow label=" Националност" value={personalInfo.nationality} icon={faFlag} />233 <InfoRow label=" Државјанство" value={personalInfo.citizenship} />234 <InfoRow label=" Стипендија" value={personalInfo.scholarship} />235 <InfoRow label=" Тековен план" value={personalInfo.currentPlan} />236 <InfoRow label=" Бр. во матична книга" value={personalInfo.registryNumber} />237 <InfoRow label=" Група на студирање" value={personalInfo.studyGroup} />240 <InfoRow label="nationality" value={personalInfo.nationality} icon={faFlag} /> 241 <InfoRow label="citizenship" value={personalInfo.citizenship} /> 242 <InfoRow label="scholarship" value={personalInfo.scholarship} /> 243 <InfoRow label="current_plan" value={personalInfo.currentPlan} /> 244 <InfoRow label="registry_number" value={personalInfo.registryNumber} /> 245 <InfoRow label="study_group" value={personalInfo.studyGroup} /> 238 246 {personalInfo.notes && ( 239 247 <div className="mt-4 p-4 bg-blue-50 rounded-lg"> 240 <div className="text-sm font-medium text-blue-800 mb-1"> Забелешка:</div>248 <div className="text-sm font-medium text-blue-800 mb-1">{t('note')}:</div> 241 249 <div className="text-sm text-blue-700">{personalInfo.notes}</div> 242 250 </div> … … 245 253 246 254 {/* Birth Information */} 247 <Section title=" Податоци за раѓање" icon={faMapMarkerAlt}>248 <InfoRow label=" Место на раѓање" value={birthInfo.placeOfBirth} />249 <InfoRow label=" Општина на раѓање" value={birthInfo.municipalityOfBirth} />250 <InfoRow label=" Земја" value={birthInfo.country} />255 <Section title="birth_info" icon={faMapMarkerAlt}> 256 <InfoRow label="place_of_birth" value={birthInfo.placeOfBirth} /> 257 <InfoRow label="municipality_of_birth" value={birthInfo.municipalityOfBirth} /> 258 <InfoRow label="country" value={birthInfo.country} /> 251 259 </Section> 252 260 253 261 {/* Contact Information */} 254 262 <div className="lg:col-span-2"> 255 <Section title=" Контакт" icon={faAddressCard}>263 <Section title="contact" icon={faAddressCard}> 256 264 <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> 257 265 <div> 258 <InfoRow label=" Место на живеење" value={contact.placeOfResidence} icon={faMapMarkerAlt} />259 <InfoRow label=" Општина на живеење" value={contact.municipalityOfResidence} />260 <InfoRow label=" Држава" value={contact.country} />261 <InfoRow label=" Адреса" value={contact.address} />262 <InfoRow label=" Адреса на престој" value={contact.temporaryAddress} />266 <InfoRow label="place_of_residence" value={contact.placeOfResidence} icon={faMapMarkerAlt} /> 267 <InfoRow label="municipality_of_residence" value={contact.municipalityOfResidence} /> 268 <InfoRow label="country" value={contact.country} /> 269 <InfoRow label="address" value={contact.address} /> 270 <InfoRow label="temporary_address" value={contact.temporaryAddress} /> 263 271 </div> 264 272 <div> 265 <InfoRow label=" Телефон" value={contact.phone} icon={faPhone} />266 <InfoRow label=" Моб. телефон" value={contact.mobilePhone} icon={faPhone} />267 <InfoRow label=" Број на пасош" value={contact.passportNumber} icon={faPassport} />268 <InfoRow label=" Датум на истекување на пасошот" value={contact.passportExpiryDate} />269 <InfoRow label=" Е-пошта" value={contact.email} icon={faEnvelope} />270 <InfoRow label=" Microsoftemail" value={contact.microsoftEmail} icon={faEnvelope} />273 <InfoRow label="phone" value={contact.phone} icon={faPhone} /> 274 <InfoRow label="mobile_phone" value={contact.mobilePhone} icon={faPhone} /> 275 <InfoRow label="passport_number" value={contact.passportNumber} icon={faPassport} /> 276 <InfoRow label="passport_expiry_date" value={contact.passportExpiryDate} /> 277 <InfoRow label="email" value={contact.email} icon={faEnvelope} /> 278 <InfoRow label="microsoft_email" value={contact.microsoftEmail} icon={faEnvelope} /> 271 279 </div> 272 280 </div> -
src/app/professor/students/page.tsx
r298f9b3 r50f2fb4 2 2 3 3 import { useEffect, useMemo, useState } from "react"; 4 import { useTranslation } from 'react-i18next'; 4 5 5 6 type UsersBySubject = { … … 36 37 37 38 export default function ProfessorStudentsPage() { 39 const { t } = useTranslation(); 38 40 const [subjects, setSubjects] = useState<SubjectsAndUsers[]>([]); 39 41 const [loading, setLoading] = useState(true); … … 129 131 <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6"> 130 132 <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between"> 133 131 134 <div> 132 <h1 className="text-2xl font-semibold text-gray-900"> Professor • Students</h1>135 <h1 className="text-2xl font-semibold text-gray-900">{t('prof_students_title')}</h1> 133 136 <p className="text-gray-700 mt-1"> 134 Data comes from<span className="font-mono">/api/prof/students</span> (demo store).137 {t('prof_students_data_note')} <span className="font-mono">/api/prof/students</span> (demo store). 135 138 </p> 136 139 </div> … … 138 141 <div className="flex flex-col sm:flex-row gap-3"> 139 142 <div className="flex flex-col"> 140 <label className="text-sm text-gray-700"> Filter by subject</label>143 <label className="text-sm text-gray-700">{t('filter_by_subject')}</label> 141 144 <select 142 145 className="border border-gray-300 rounded-lg px-3 py-2" … … 144 147 onChange={(e) => setSubjectFilter(e.target.value)} 145 148 > 146 <option value="all"> All subjects</option>149 <option value="all">{t('all_subjects')}</option> 147 150 {subjects 148 151 .filter((s) => typeof s.Id === "number") 149 152 .map((s) => ( 150 153 <option key={String(s.Id)} value={String(s.Id)}> 151 {s.Name ?? ` Subject${s.Id}`}154 {s.Name ?? `${t('subject')} ${s.Id}`} 152 155 </option> 153 156 ))} … … 156 159 157 160 <div className="flex flex-col"> 158 <label className="text-sm text-gray-700"> Find student by index / ID</label>161 <label className="text-sm text-gray-700">{t('find_student_by_id')}</label> 159 162 <input 160 163 className="border border-gray-300 rounded-lg px-3 py-2" 161 placeholder= "e.g. 20001"164 placeholder={t('student_id_placeholder')} 162 165 value={studentIdQuery} 163 166 onChange={(e) => setStudentIdQuery(e.target.value)} … … 177 180 <thead className="bg-gray-50"> 178 181 <tr> 179 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b"> Student</th>180 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b"> ID</th>181 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b"> Subject</th>182 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b"> Grade</th>183 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b"> Actions</th>182 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('student')}</th> 183 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('id')}</th> 184 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('subject')}</th> 185 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('grade')}</th> 186 <th className="text-left text-sm font-semibold text-gray-700 px-4 py-3 border-b">{t('actions')}</th> 184 187 </tr> 185 188 </thead> … … 188 191 <tr> 189 192 <td className="px-4 py-4 text-gray-700" colSpan={5}> 190 Loading...193 {t('loading')} 191 194 </td> 192 195 </tr> … … 194 197 <tr> 195 198 <td className="px-4 py-4 text-gray-700" colSpan={5}> 196 No students found.199 {t('no_students_found')} 197 200 </td> 198 201 </tr> … … 227 230 </select> 228 231 <span className="text-sm text-gray-600"> 229 Current: {r.grade > 0 ? r.grade : "—"}232 {t('current')}: {r.grade > 0 ? r.grade : t('none')} 230 233 </span> 231 234 </div> … … 245 248 }} 246 249 > 247 Add grade250 {t('add_grade')} 248 251 </button> 249 252 … … 260 263 }} 261 264 > 262 Edit grade265 {t('edit_grade')} 263 266 </button> 264 267 … … 275 278 }} 276 279 > 277 Remove grade280 {t('remove_grade')} 278 281 </button> 279 282 </div>
Note:
See TracChangeset
for help on using the changeset viewer.
