Changeset 50f2fb4 for src/app/professor


Ignore:
Timestamp:
01/30/26 01:40:27 (8 months ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
ba52069
Parents:
298f9b3
Message:

Added dual language feature.

Location:
src/app/professor
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/app/professor/profile/page.tsx

    r298f9b3 r50f2fb4  
    1818import { getAccessToken } from "@/lib/auth";
    1919import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
     20import { useTranslation } from 'react-i18next';
    2021
    2122type PersonalInfo = {
     
    9495}
    9596
    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>
     97const 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>
    101108    </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};
    107111
    108112interface SectionProps {
     
    112116}
    113117
    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>
     118const 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>
    121129    </div>
    122     <div className="p-6">{children}</div>
    123   </div>
    124 );
     130  );
     131};
    125132
    126133export default function ProfessorProfilePage() {
     
    128135  const [isLoading, setIsLoading] = useState(true);
    129136  const [errorMessage, setErrorMessage] = useState<string | null>(null);
     137  const { t } = useTranslation();
    130138
    131139  useEffect(() => {
     
    177185    return (
    178186      <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>
    180188      </div>
    181189    );
     
    186194      <div className="min-h-screen pb-8">
    187195        <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')}
    189197        </div>
    190198      </div>
     
    210218            </h1>
    211219            <div className="text-lg opacity-90">
    212               Индекс: {personalInfo.index} | ЕМБГ: {personalInfo.embg}
     220              {t('index')}: {personalInfo.index} | {t('embg')}: {personalInfo.embg}
    213221            </div>
    214222          </div>
     
    219227      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
    220228        {/* 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} />
    227235          <InfoRow
    228             label="Пол"
     236            label="gender"
    229237            value={personalInfo.gender}
    230             icon={personalInfo.gender === "машки" ? faMars : faVenus}
     238            icon={personalInfo.gender === t('male') ? faMars : faVenus}
    231239          />
    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} />
    238246          {personalInfo.notes && (
    239247            <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>
    241249              <div className="text-sm text-blue-700">{personalInfo.notes}</div>
    242250            </div>
     
    245253
    246254        {/* 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} />
    251259        </Section>
    252260
    253261        {/* Contact Information */}
    254262        <div className="lg:col-span-2">
    255           <Section title="Контакт" icon={faAddressCard}>
     263          <Section title="contact" icon={faAddressCard}>
    256264            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
    257265              <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} />
    263271              </div>
    264272              <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="Microsoft email" 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} />
    271279              </div>
    272280            </div>
  • src/app/professor/students/page.tsx

    r298f9b3 r50f2fb4  
    22
    33import { useEffect, useMemo, useState } from "react";
     4import { useTranslation } from 'react-i18next';
    45
    56type UsersBySubject = {
     
    3637
    3738export default function ProfessorStudentsPage() {
     39  const { t } = useTranslation();
    3840  const [subjects, setSubjects] = useState<SubjectsAndUsers[]>([]);
    3941  const [loading, setLoading] = useState(true);
     
    129131    <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
    130132      <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
     133
    131134        <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>
    133136          <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).
    135138          </p>
    136139        </div>
     
    138141        <div className="flex flex-col sm:flex-row gap-3">
    139142          <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>
    141144            <select
    142145              className="border border-gray-300 rounded-lg px-3 py-2"
     
    144147              onChange={(e) => setSubjectFilter(e.target.value)}
    145148            >
    146               <option value="all">All subjects</option>
     149              <option value="all">{t('all_subjects')}</option>
    147150              {subjects
    148151                .filter((s) => typeof s.Id === "number")
    149152                .map((s) => (
    150153                  <option key={String(s.Id)} value={String(s.Id)}>
    151                     {s.Name ?? `Subject ${s.Id}`}
     154                    {s.Name ?? `${t('subject')} ${s.Id}`}
    152155                  </option>
    153156                ))}
     
    156159
    157160          <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>
    159162            <input
    160163              className="border border-gray-300 rounded-lg px-3 py-2"
    161               placeholder="e.g. 20001"
     164              placeholder={t('student_id_placeholder')}
    162165              value={studentIdQuery}
    163166              onChange={(e) => setStudentIdQuery(e.target.value)}
     
    177180          <thead className="bg-gray-50">
    178181            <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>
    184187            </tr>
    185188          </thead>
     
    188191              <tr>
    189192                <td className="px-4 py-4 text-gray-700" colSpan={5}>
    190                   Loading...
     193                  {t('loading')}
    191194                </td>
    192195              </tr>
     
    194197              <tr>
    195198                <td className="px-4 py-4 text-gray-700" colSpan={5}>
    196                   No students found.
     199                  {t('no_students_found')}
    197200                </td>
    198201              </tr>
     
    227230                        </select>
    228231                        <span className="text-sm text-gray-600">
    229                           Current: {r.grade > 0 ? r.grade : "—"}
     232                          {t('current')}: {r.grade > 0 ? r.grade : t('none')}
    230233                        </span>
    231234                      </div>
     
    245248                          }}
    246249                        >
    247                           Add grade
     250                          {t('add_grade')}
    248251                        </button>
    249252
     
    260263                          }}
    261264                        >
    262                           Edit grade
     265                          {t('edit_grade')}
    263266                        </button>
    264267
     
    275278                          }}
    276279                        >
    277                           Remove grade
     280                          {t('remove_grade')}
    278281                        </button>
    279282                      </div>
Note: See TracChangeset for help on using the changeset viewer.