3 Commits ce88875da0 ... e7af4c0745

Auteur SHA1 Message Date
  Eldar Mukhtarov e7af4c0745 Write HTML & CSS for subjects page il y a 9 mois
  Eldar Mukhtarov 3e3a13fe9a Fix Bug: Each student can get only one grade per subject il y a 9 mois
  Eldar Mukhtarov 672a113cd8 Implement Grading Functionality for Subjects il y a 9 mois

+ 9 - 9
project/frontend-angular/src/app/app.html

@@ -8,24 +8,24 @@
           <div class="brand-subtitle">Academic Management Platform</div>
           <div class="brand-subtitle">Academic Management Platform</div>
         </div>
         </div>
       </div>
       </div>
-      
+
       <ul class="nav-menu">
       <ul class="nav-menu">
         <li class="nav-item">
         <li class="nav-item">
           <a routerLink="/" class="nav-link">Home</a>
           <a routerLink="/" class="nav-link">Home</a>
         </li>
         </li>
-        
+
         @if (authority === 'student') {
         @if (authority === 'student') {
           <li class="nav-item">
           <li class="nav-item">
-            <a class="nav-link">Grades</a>
+            <a routerLink="grade-student" class="nav-link">Grades</a>
           </li>
           </li>
         }
         }
-        
+
         @if (authority === 'teacher') {
         @if (authority === 'teacher') {
           <li class="nav-item">
           <li class="nav-item">
             <a routerLink="subjects" class="nav-link">Subjects</a>
             <a routerLink="subjects" class="nav-link">Subjects</a>
           </li>
           </li>
         }
         }
-        
+
         @if (authority === 'admin') {
         @if (authority === 'admin') {
           <li class="nav-item">
           <li class="nav-item">
             <a routerLink="admin" class="nav-link">Manage Accounts</a>
             <a routerLink="admin" class="nav-link">Manage Accounts</a>
@@ -34,19 +34,19 @@
             <a routerLink="auth/signup" class="nav-link">Add Accounts</a>
             <a routerLink="auth/signup" class="nav-link">Add Accounts</a>
           </li>
           </li>
         }
         }
-        
+
         @if (!authority) {
         @if (!authority) {
           <li class="nav-item">
           <li class="nav-item">
             <a routerLink="auth/signin" class="nav-link primary">Login</a>
             <a routerLink="auth/signin" class="nav-link primary">Login</a>
           </li>
           </li>
         }
         }
-        
+
         @if (authority) {
         @if (authority) {
           <li class="nav-item">
           <li class="nav-item">
             <a (click)="logout()" class="nav-link">Logout</a>
             <a (click)="logout()" class="nav-link">Logout</a>
           </li>
           </li>
         }
         }
-        
+
         @if (authority && loggedUser) {
         @if (authority && loggedUser) {
           <li class="nav-item">
           <li class="nav-item">
             <span class="user-info">
             <span class="user-info">
@@ -61,4 +61,4 @@
 
 
 <div class="main-content">
 <div class="main-content">
   <router-outlet></router-outlet>
   <router-outlet></router-outlet>
-</div>
+</div>

+ 11 - 5
project/frontend-angular/src/app/app.routes.ts

@@ -4,11 +4,17 @@ import {Login} from './login/login';
 import {authGuard} from './guards/auth.guard';
 import {authGuard} from './guards/auth.guard';
 import {Admin} from './admin/admin';
 import {Admin} from './admin/admin';
 import {Home} from './home/home';
 import {Home} from './home/home';
+import {Subjects} from './subjects/subjects';
+import {GradeTeacher} from './grade-teacher/grade-teacher';
+import {GradeStudent} from './grade-student/grade-student';
 
 
 export const routes: Routes = [
 export const routes: Routes = [
-    {path: 'home', component: Home},
-    { path: 'auth/signin', component: Login },
-    { path: 'auth/signup', component: Register, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN'] },},
-    { path: 'admin', component: Admin, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN'] },},
-    { path: '', redirectTo: 'home', pathMatch: 'full' }
+  {path: 'home', component: Home},
+  { path: 'auth/signin', component: Login },
+  { path: 'auth/signup', component: Register, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN'] },},
+  { path: 'admin', component: Admin, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN'] },},
+  { path: 'subjects', component: Subjects, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN', "ROLE_TEACHER"] },},
+  { path: 'subjects/:id', component: GradeTeacher, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN', "ROLE_TEACHER"] },},
+  { path: 'grade-student', component: GradeStudent, canActivate: [authGuard], data: { roles: ['ROLE_ADMIN', "ROLE_STUDENT"] },},
+  { path: '', redirectTo: 'home', pathMatch: 'full' }
 ];
 ];

+ 0 - 0
project/frontend-angular/src/app/grade-student/grade-student.css


+ 38 - 0
project/frontend-angular/src/app/grade-student/grade-student.html

@@ -0,0 +1,38 @@
+<div>
+  <div>
+    <label for="search">Search by Name:</label>
+    <input type="text" id="search" [(ngModel)]="searchTerm" (input)="filterSubjects()" placeholder="Enter subject name">
+  </div>
+  <div>
+    <table>
+      <thead>
+      <tr>
+        <th>
+          Subject 
+          @if (sortDirection==='desc') {
+            <span (click)="sortByName()">↓</span>
+          }
+          @if (sortDirection==='asc') {
+            <span (click)="sortByName()">↑</span>
+          }
+        </th>
+        <th>Grades</th>
+      </tr>
+      </thead>
+      <tbody>
+      <ng-container *ngFor="let subject of filteredSubjectList; index as i">
+        <tr [class.even-row]="i % 2 === 1">
+          <td>{{ subject.name }}</td>
+          <td>
+            <ul *ngFor="let grade of gradeList">
+              @if (grade.subject.id === subject.id) {
+                <li>{{ grade.grade }}</li>
+              }
+            </ul>
+          </td>
+        </tr>
+      </ng-container>
+      </tbody>
+    </table>
+  </div>
+</div>

+ 23 - 0
project/frontend-angular/src/app/grade-student/grade-student.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GradeStudent } from './grade-student';
+
+describe('GradeStudent', () => {
+  let component: GradeStudent;
+  let fixture: ComponentFixture<GradeStudent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [GradeStudent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(GradeStudent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 62 - 0
project/frontend-angular/src/app/grade-student/grade-student.ts

@@ -0,0 +1,62 @@
+import {Component, OnInit} from '@angular/core';
+import {Grade} from "../models/grade";
+import {Subject} from "../models/subject";
+import {GradeService} from "../services/grade";
+import {SubjectService} from "../services/subject";
+import {FormsModule} from '@angular/forms';
+import {NgForOf} from '@angular/common';
+
+@Component({
+  selector: 'app-grade-student',
+  templateUrl: './grade-student.html',
+  imports: [
+    FormsModule,
+    NgForOf
+  ],
+  styleUrls: ['./grade-student.css']
+})
+export class GradeStudent implements OnInit{
+  subjectList?: Subject[];
+  subject?: Subject;
+  gradeList?: Grade[];
+  grade?: Grade;
+  filteredSubjectList: Subject[] = [];
+  searchTerm: string = '';
+  sortDirection: string = 'asc';
+
+  constructor(private gradeService: GradeService, private subjectService: SubjectService) { }
+
+  ngOnInit() {
+    this.getSubjects();
+    this.getGrades();
+  }
+
+  getSubjects(): void {
+    this.subjectService.getSubjects().subscribe(subjects => {
+      this.subjectList = subjects;
+      this.filterSubjects();
+    });
+  }
+
+  filterSubjects(): void {
+    if (this.subjectList) {
+      this.filteredSubjectList = this.subjectList.filter(subject =>
+        subject.name.toLowerCase().includes(this.searchTerm.toLowerCase())
+      );
+    }
+  }
+
+  getGrades(): void {
+    this.gradeService.getStudentGrades().subscribe(gradeList => this.gradeList = gradeList);
+  }
+
+  sortByName(): void {
+    if (this.sortDirection === 'asc') {
+      this.filteredSubjectList?.sort((a, b) => a.name.localeCompare(b.name));
+      this.sortDirection = 'desc';
+    } else {
+      this.filteredSubjectList?.sort((a, b) => b.name.localeCompare(a.name));
+      this.sortDirection = 'asc';
+    }
+  }
+}

+ 0 - 0
project/frontend-angular/src/app/grade-teacher/grade-teacher.css


+ 82 - 0
project/frontend-angular/src/app/grade-teacher/grade-teacher.html

@@ -0,0 +1,82 @@
+<div>
+  <div>
+    <h1>{{subject?.name}}</h1>
+  </div>
+
+  <div>
+    <form (ngSubmit)="f.form.valid && addGrade()" #f="ngForm" novalidate>
+      <div>
+        <label for="grade">Grade</label>
+        <select id="grade" name="grade" [(ngModel)]="form.grade" #grade="ngModel" required>
+          <option *ngFor="let g of gradeOptions" [value]="g">{{ g }}</option>
+        </select>
+        @if (f.submitted && grade.invalid) {
+          <div>
+        @if (grade.errors?.['required']) {
+          <div>Grade is required</div>
+        }
+          </div>
+        }
+      </div>
+      <div *ngIf="errorMessage === 'Student already has a grade'">
+        Student already has a grade
+      </div>
+      <div>
+        <label for="student">Student</label>
+        <select id="student" name="student" [(ngModel)]="form.student" #student="ngModel" required>
+          <option *ngFor="let student of studentList" [value]="student.id">{{ student.firstname }} {{ student.lastname }}</option>
+        </select>
+        @if (f.submitted && student.invalid) {
+          <div>
+            @if (student.errors?.['required']) {
+              <div>Student is required</div>
+            }
+          </div>
+        }
+      </div>
+      <div>
+        <button type="submit">Add Grade</button>
+      </div>
+    </form>
+  </div>
+
+  <div>
+    <table>
+      <thead>
+      <tr>
+        <th>Name</th>
+        <th>Last Name</th>
+        <th>Grades</th>
+      </tr>
+      </thead>
+      <tbody>
+      <ng-container *ngFor="let student of studentList; index as i">
+        <tr [class.even-row]="i % 2 === 1">
+          <td>{{ student.firstname }}</td>
+          <td>{{ student.lastname }}</td>
+          <td>
+            <div>
+              <ng-container *ngFor="let grade of gradeList">
+                @if (grade.student.id === student.id) {
+                  <div>
+                    @if (selectedGrade === grade) {
+                      <select [(ngModel)]="selectedGradeValue">
+                        <option *ngFor="let g of gradeOptions" [value]="g">{{ g }}</option>
+                      </select>
+                      <button (click)="saveGrade()">Save</button>
+                      <button (click)="cancelEdit()">Cancel</button>
+                    } @else {
+                      <span>{{ grade.grade }}</span>
+                      <button (click)="editGrade(grade)">Edit</button>
+                    }
+                  </div>
+                }
+              </ng-container>
+            </div>
+          </td>
+        </tr>
+      </ng-container>
+      </tbody>
+    </table>
+  </div>
+</div>

+ 23 - 0
project/frontend-angular/src/app/grade-teacher/grade-teacher.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GradeTeacher } from './grade-teacher';
+
+describe('GradeTeacher', () => {
+  let component: GradeTeacher;
+  let fixture: ComponentFixture<GradeTeacher>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [GradeTeacher]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(GradeTeacher);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 125 - 0
project/frontend-angular/src/app/grade-teacher/grade-teacher.ts

@@ -0,0 +1,125 @@
+import {Component, OnInit} from '@angular/core';
+import {Student} from "../models/student";
+import {StudentService} from "../services/student";
+import {GradeService} from "../services/grade";
+import {Grade} from "../models/grade";
+import {ActivatedRoute} from "@angular/router";
+import {Subject} from "../models/subject";
+import {SubjectService} from "../services/subject";
+import {FormsModule} from '@angular/forms';
+import {NgForOf, NgIf} from '@angular/common';
+
+@Component({
+  selector: 'app-grade-teacher',
+  templateUrl: './grade-teacher.html',
+  imports: [
+    FormsModule,
+    NgForOf,
+    NgIf
+  ],
+  styleUrls: ['./grade-teacher.css']
+})
+
+export class GradeTeacher implements OnInit {
+  form: any = {};
+  studentList?: Student[];
+  student?: Student;
+  gradeToAdd?: Grade;
+  gradeList?: Grade[];
+  grade?: Grade;
+  subject?: Subject;
+  errorMessage = '';
+  subjectId?: number;
+  selectedGrade: Grade | null = null;
+  selectedGradeValue: number | null = null;
+  gradeOptions = [2, 3, 3.5, 4, 4.5, 5];
+
+  constructor(private studentService: StudentService, private gradeService: GradeService, private subjectService: SubjectService, private route: ActivatedRoute) {}
+
+  ngOnInit() {
+    this.getStudents();
+    this.subjectId = Number(this.route.snapshot.paramMap.get('id'));
+    this.subjectService.getSubject(this.subjectId).subscribe(subject => this.subject = subject);
+    this.getGrades();
+  }
+
+  getStudents(): void {
+    this.studentService.getStudents()
+      .subscribe(studentList => this.studentList = studentList);
+  }
+
+  getGrades(): void {
+    if(this.subjectId){
+      this.gradeService.getGradesFromSubject(this.subjectId)
+        .subscribe(gradeList => this.gradeList = gradeList);
+    }
+  }
+  hasGradeForStudentInSubject(studentId: number, subjectId: number): boolean {
+    return !!this.gradeList?.some(
+      grade => grade.student.id === studentId && grade.subject.id === subjectId
+    );
+  }
+
+  addGrade(): void {
+    this.student = this.studentList?.find(student => student.id === Number(this.form.student));
+    if (!this.student) {
+      this.errorMessage = 'Please select a student.';
+      return;
+    }
+    if (
+      this.student.id !== undefined &&
+      this.subjectId !== undefined &&
+      this.hasGradeForStudentInSubject(this.student.id, this.subjectId)
+    ) {
+      this.errorMessage = 'Student already has a grade';
+      return;
+    }
+    if (this.subject && this.student) {
+      this.gradeToAdd = new Grade(this.form.grade, this.subject, this.student);
+    }
+    if (this.gradeToAdd) {
+      this.gradeService.addGrade(this.gradeToAdd).subscribe(
+        data => {
+          console.log(data);
+          this.reloadPage();
+        },
+        error => {
+          console.log(error);
+          this.errorMessage = error.error.message;
+        }
+      );
+    }
+  }
+
+  editGrade(grade: Grade): void {
+    this.selectedGrade = grade;
+    this.selectedGradeValue = grade.grade;
+  }
+
+  cancelEdit(): void {
+    this.selectedGrade = null;
+    this.selectedGradeValue = null;
+  }
+
+  saveGrade(): void {
+    if (this.selectedGrade && this.selectedGradeValue) {
+      this.selectedGrade.grade = this.selectedGradeValue;
+      this.gradeService.editGrade(this.selectedGrade).subscribe(
+        data => {
+          console.log(data);
+          this.reloadPage();
+        },
+        error => {
+          console.log(error);
+          this.errorMessage = error.error.message;
+        }
+      );
+    }
+    this.selectedGrade = null;
+    this.selectedGradeValue = null;
+  }
+
+  reloadPage(): void {
+    window.location.reload();
+  }
+}

+ 1 - 1
project/frontend-angular/src/app/services/grade.ts

@@ -34,7 +34,7 @@ export class GradeService {
   }
   }
 
 
   editGrade(grade: Grade): Observable<Grade> {
   editGrade(grade: Grade): Observable<Grade> {
-    return this.http.patch<Grade>(`${this.gradesUrl}/edit/${grade.id}`, grade.grade, httpOptions).pipe(
+    return this.http.patch<Grade>(`${this.gradesUrl}/edit/${grade.id}`, { grade: grade.grade }, httpOptions).pipe(
       catchError(this.handleError<Grade>('editGrade', 'Editing grade failed!'))
       catchError(this.handleError<Grade>('editGrade', 'Editing grade failed!'))
     );
     );
   }
   }

+ 244 - 0
project/frontend-angular/src/app/subjects/subjects.css

@@ -0,0 +1,244 @@
+/* Subjects Container */
+.subjects-container {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 0;
+}
+
+/* Section Styling */
+.add-subject-section {
+  background: linear-gradient(135deg, rgba(140, 35, 30, 0.1), rgba(255, 255, 255, 0.02));
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 16px;
+  padding: 30px;
+  margin-bottom: 40px;
+}
+
+.subjects-list-section {
+  background: rgba(255, 255, 255, 0.02);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 16px;
+  padding: 30px;
+}
+
+.section-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #ffffff;
+  margin-bottom: 24px;
+  text-align: center;
+}
+
+/* Form Styling */
+.subject-form {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  max-width: 400px;
+  margin: 0 auto;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.form-input {
+  background: rgba(255, 255, 255, 0.05);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 8px;
+  padding: 12px 16px;
+  font-size: 14px;
+  color: #ffffff;
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  transition: all 0.3s ease;
+}
+
+.form-input::placeholder {
+  color: #888;
+}
+
+.form-input:focus {
+  outline: none;
+  border-color: rgba(140, 35, 30, 0.5);
+  background: rgba(255, 255, 255, 0.08);
+  box-shadow: 0 0 0 2px rgba(140, 35, 30, 0.2);
+}
+
+.form-input:hover {
+  border-color: rgba(255, 255, 255, 0.2);
+  background: rgba(255, 255, 255, 0.08);
+}
+
+/* Button Styling */
+.btn-primary {
+  background: linear-gradient(135deg, #8C231E, #a52a24);
+  color: white;
+  border: none;
+  border-radius: 8px;
+  padding: 12px 24px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.btn-primary:hover {
+  background: linear-gradient(135deg, #a52a24, #8C231E);
+  transform: translateY(-2px);
+  box-shadow: 0 4px 15px rgba(140, 35, 30, 0.4);
+}
+
+.btn-primary:active {
+  transform: translateY(0);
+}
+
+/* Form Actions */
+.form-actions {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  align-items: center;
+}
+
+/* Error Messages */
+.error-message {
+  color: #ff6b6b;
+  font-size: 13px;
+  font-weight: 500;
+  background: rgba(255, 107, 107, 0.1);
+  border: 1px solid rgba(255, 107, 107, 0.2);
+  border-radius: 6px;
+  padding: 8px 12px;
+  text-align: center;
+}
+
+/* Empty State */
+.empty-state {
+  text-align: center;
+  padding: 40px 20px;
+}
+
+.empty-message {
+  color: #888;
+  font-size: 16px;
+  font-style: italic;
+}
+
+/* Subjects Grid */
+.subjects-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  gap: 16px;
+  margin-top: 20px;
+}
+
+.subject-card {
+  background: rgba(255, 255, 255, 0.05);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 12px;
+  transition: all 0.3s ease;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.subject-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
+  border-color: rgba(140, 35, 30, 0.3);
+  background: rgba(255, 255, 255, 0.08);
+}
+
+.subject-link {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  text-decoration: none;
+  color: inherit;
+  width: 100%;
+  height: 100%;
+  gap: 12px;
+}
+
+.subject-name {
+  font-size: 18px;
+  font-weight: 600;
+  color: #ffffff;
+  margin: 0;
+  flex: 1;
+  text-align: center;
+}
+
+.subject-arrow {
+  color: #888;
+  font-size: 20px;
+  font-weight: bold;
+  transition: all 0.3s ease;
+  opacity: 0.6;
+}
+
+.subject-card:hover .subject-arrow {
+  color: #8C231E;
+  opacity: 1;
+  transform: translateX(4px);
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+  .subjects-container {
+    padding: 0 15px;
+  }
+
+  .add-subject-section,
+  .subjects-list-section {
+    padding: 20px;
+    margin-bottom: 24px;
+  }
+
+  .section-title {
+    font-size: 20px;
+    margin-bottom: 20px;
+  }
+
+  .subject-form {
+    max-width: 100%;
+  }
+
+  .subjects-grid {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+
+  .subject-card {
+    border-radius: 8px;
+  }
+
+  .subject-link {
+    padding: 16px;
+  }
+
+  .subject-name {
+    font-size: 16px;
+  }
+}
+
+/* Animation */
+.subjects-container {
+  animation: fadeIn 0.6s ease-in;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}

+ 42 - 0
project/frontend-angular/src/app/subjects/subjects.html

@@ -0,0 +1,42 @@
+<div class="subjects-container">
+  <div class="add-subject-section">
+    <h1 class="section-title">Add New Subject</h1>
+    <form name="form" (ngSubmit)="f.form.valid && addSubject()" #f="ngForm" novalidate class="subject-form">
+      <div class="form-group">
+        <input 
+          type="text" 
+          name="name" 
+          placeholder="Subject Name" 
+          [(ngModel)]="form.name" 
+          #name="ngModel" 
+          required 
+          class="form-input"
+        />
+        <div class="error-message" *ngIf="f.submitted && name.invalid">
+          Name is required
+        </div>
+      </div>
+      <div class="form-actions">
+        <button type="submit" class="btn-primary">Add Subject</button>
+        <div class="error-message" *ngIf="f.submitted && isAddFailed">
+          Subject addition failed: {{ errorMessage }}
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <div class="subjects-list-section">
+    <h2 class="section-title">Your Subjects</h2>
+    <div class="empty-state" *ngIf="(subjectList?.length || 0) === 0">
+      <p class="empty-message">You have no subjects yet</p>
+    </div>
+    <div class="subjects-grid" *ngIf="(subjectList?.length || 0) > 0">
+      <div class="subject-card" *ngFor="let subject of subjectList">
+        <a [routerLink]="['/subjects/', subject.id]" class="subject-link">
+          <h3 class="subject-name">{{ subject.name }}</h3>
+          <div class="subject-arrow">→</div>
+        </a>
+      </div>
+    </div>
+  </div>
+</div>

+ 23 - 0
project/frontend-angular/src/app/subjects/subjects.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Subjects } from './subjects';
+
+describe('SubjectComponent', () => {
+  let component: Subjects;
+  let fixture: ComponentFixture<Subjects>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [Subjects]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(Subjects);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 56 - 0
project/frontend-angular/src/app/subjects/subjects.ts

@@ -0,0 +1,56 @@
+import {Component, OnInit} from '@angular/core';
+import {SubjectService} from "../services/subject";
+import {Subject} from "../models/subject";
+import {FormsModule} from '@angular/forms';
+import {RouterLink} from '@angular/router';
+import {NgForOf, NgIf} from '@angular/common';
+
+@Component({
+  selector: 'app-subject',
+  templateUrl: './subjects.html',
+  imports: [
+    FormsModule,
+    RouterLink,
+    NgForOf,
+    NgIf
+  ],
+  styleUrls: ['./subjects.css']
+})
+export class Subjects implements OnInit{
+
+  form: any = {};
+  subjectList?: Subject[];
+  subject?: Subject;
+  subjectToAdd?: Subject;
+  isAddFailed = false;
+  errorMessage = '';
+  constructor(private subjectService: SubjectService ) { }
+
+  ngOnInit() { this.getSubjects(); }
+
+  getSubjects(): void {
+    this.subjectService.getTeachersSubjects()
+      .subscribe(subjectList => this.subjectList = subjectList);
+  }
+
+  addSubject(): void {
+    this.subjectToAdd = new Subject(this.form.name);
+    this.subjectService.addSubject(this.subjectToAdd).subscribe(
+      data => {
+        console.log(data);
+        this.isAddFailed = false;
+        this.errorMessage = '';
+        this.reloadPage();
+      },
+      error => {
+        console.log(error);
+        this.isAddFailed = true;
+        this.errorMessage = error.error.message;
+      }
+    );
+  }
+
+  reloadPage(): void {
+    window.location.reload();
+  }
+}