Quellcode durchsuchen

Implement Grading Functionality for Subjects

Eldar Mukhtarov vor 9 Monaten
Ursprung
Commit
672a113cd8

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

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


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

@@ -0,0 +1,79 @@
+<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>
+        <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" style="width: 60px;">
+                        <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();
+  });
+});

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

@@ -0,0 +1,108 @@
+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);
+    }
+  }
+
+  addGrade(): void {
+    this.student = this.studentList?.find(student => student.id === Number(this.form.student));
+    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> {
-    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!'))
     );
   }

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


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

@@ -0,0 +1,33 @@
+<div>
+  <div>
+    <form name="form" (ngSubmit)="f.form.valid && addSubject()" #f="ngForm" novalidate>
+      <div>
+        <input type="text" name="name" placeholder="Name" [(ngModel)]="form.name" #name="ngModel" required />
+        @if (f.submitted && name.invalid) {
+          <div>Name is required</div>
+        }
+      </div>
+      <div>
+        <button type="submit">Add subject</button>
+        @if (f.submitted && isAddFailed) {
+          <div>
+            Subject addition failed: {{ errorMessage }}
+          </div>
+        }
+      </div>
+    </form>
+    <hr />
+  </div>
+</div>
+
+<div>
+  <h2>Your subjects:</h2>
+  <div *ngIf="subjectList?.length === 0">You have no subjects yet</div>
+  <ul>
+    <li *ngFor="let subject of subjectList">
+      <a [routerLink]="['/subjects/', subject.id]">
+        <h2>{{ subject.name }}</h2>
+      </a>
+    </li>
+  </ul>
+</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();
+  }
+}