Переглянути джерело

Write HTML & CSS for Subject Modification Page by Teacher

Eldar Mukhtarov 9 місяців тому
батько
коміт
490cd0f868

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

@@ -0,0 +1,325 @@
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+        transform: translateY(10px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+/* Main Container */
+.grades-container {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 40px 20px;
+    animation: fadeIn 0.6s ease-in;
+}
+
+/* Header Section */
+.header-section {
+margin-bottom: 32px;
+}
+
+.subject-title {
+font-size: 48px;
+font-weight: 700;
+letter-spacing: -1px;
+color: #ffffff;
+margin-bottom: 8px;
+}
+
+/* Search Section */
+.search-section {
+background: rgba(255, 255, 255, 0.02);
+border: 1px solid rgba(255, 255, 255, 0.1);
+border-radius: 12px;
+padding: 20px;
+margin-bottom: 24px;
+transition: all 0.3s ease;
+}
+
+.search-section:hover {
+background: rgba(255, 255, 255, 0.05);
+border-color: rgba(255, 255, 255, 0.15);
+}
+
+.search-label {
+display: block;
+font-size: 14px;
+font-weight: 500;
+color: #e0e0e0;
+margin-bottom: 8px;
+}
+
+.search-input {
+width: 100%;
+padding: 12px 16px;
+background: rgba(255, 255, 255, 0.05);
+border: 1px solid rgba(255, 255, 255, 0.1);
+border-radius: 8px;
+color: #ffffff;
+font-size: 14px;
+font-family: inherit;
+transition: all 0.3s ease;
+}
+
+.search-input:focus {
+outline: none;
+border-color: rgba(255, 255, 255, 0.2);
+background: rgba(255, 255, 255, 0.08);
+}
+
+.search-input::placeholder {
+color: #888;
+}
+
+/* Form Section */
+.form-section {
+    background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 16px;
+    padding: 30px;
+    margin-bottom: 32px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+}
+
+.grade-form {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20px;
+    align-items: end;
+}
+
+.form-group {
+    display: flex;
+    flex-direction: column;
+}
+
+.form-label {
+    font-size: 14px;
+    font-weight: 500;
+    color: #e0e0e0;
+    margin-bottom: 8px;
+}
+
+.form-select {
+    padding: 12px 16px;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 8px;
+    color: #ffffff;
+    font-size: 14px;
+    font-family: inherit;
+    transition: all 0.3s ease;
+}
+
+.form-select:focus {
+    outline: none;
+    border-color: rgba(255, 255, 255, 0.2);
+    background: rgba(255, 255, 255, 0.08);
+}
+
+.form-select option {
+    background: #2a2a2a;
+    color: #ffffff;
+}
+
+.error-message {
+    color: #ff6b6b;
+    font-size: 12px;
+    margin-top: 4px;
+}
+
+.duplicate-error {
+    grid-column: 1 / -1;
+    background: rgba(255, 107, 107, 0.1);
+    border: 1px solid rgba(255, 107, 107, 0.3);
+    border-radius: 8px;
+    padding: 12px;
+    color: #ff6b6b;
+    font-size: 14px;
+    text-align: center;
+}
+
+.submit-button {
+    background: linear-gradient(135deg, #8C231E 0%, #a52a24 100%);
+    color: #ffffff;
+    border: none;
+    padding: 12px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-family: inherit;
+}
+
+.submit-button:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(140, 35, 30, 0.3);
+}
+
+.submit-button:active {
+    transform: translateY(0);
+}
+
+/* Table Section */
+.table-section {
+background: rgba(255, 255, 255, 0.02);
+border: 1px solid rgba(255, 255, 255, 0.1);
+border-radius: 16px;
+overflow: hidden;
+}
+
+.grades-table {
+width: 100%;
+border-collapse: collapse;
+}
+
+.table-header {
+background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
+}
+
+.table-header th {
+padding: 16px 20px;
+text-align: left;
+font-size: 14px;
+font-weight: 600;
+color: #ffffff;
+border-bottom: 2px solid #333;
+}
+
+.sort-button {
+background: none;
+border: none;
+color: #e0e0e0;
+cursor: pointer;
+margin-left: 8px;
+font-size: 16px;
+transition: all 0.3s ease;
+}
+
+.sort-button:hover {
+color: #ffffff;
+transform: scale(1.1);
+}
+
+.table-row {
+transition: all 0.3s ease;
+}
+
+.table-row:hover {
+background: rgba(255, 255, 255, 0.05);
+}
+
+.table-row.even-row {
+background: rgba(255, 255, 255, 0.02);
+}
+
+.table-row.even-row:hover {
+background: rgba(255, 255, 255, 0.08);
+}
+
+.table-cell {
+padding: 16px 20px;
+font-size: 14px;
+color: #e0e0e0;
+border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+/* Grade Management */
+.grade-container {
+display: flex;
+flex-wrap: wrap;
+gap: 8px;
+}
+
+.grade-item {
+display: flex;
+align-items: center;
+gap: 8px;
+background: rgba(255, 255, 255, 0.05);
+border: 1px solid rgba(255, 255, 255, 0.1);
+border-radius: 8px;
+padding: 8px 12px;
+}
+
+.grade-value {
+font-family: 'Courier New', monospace;
+font-weight: 600;
+color: #ffffff;
+}
+
+.grade-edit-select {
+padding: 4px 8px;
+background: rgba(255, 255, 255, 0.1);
+border: 1px solid rgba(255, 255, 255, 0.2);
+border-radius: 4px;
+color: #ffffff;
+font-size: 12px;
+font-family: 'Courier New', monospace;
+}
+
+.grade-edit-select option {
+background: #2a2a2a;
+}
+
+.action-button {
+background: rgba(255, 255, 255, 0.1);
+color: #e0e0e0;
+border: 1px solid rgba(255, 255, 255, 0.1);
+padding: 4px 8px;
+border-radius: 4px;
+font-size: 12px;
+cursor: pointer;
+transition: all 0.3s ease;
+font-family: inherit;
+}
+
+.action-button:hover {
+background: rgba(255, 255, 255, 0.15);
+color: #ffffff;
+transform: translateY(-1px);
+}
+
+.action-button.save {
+background: linear-gradient(135deg, #8C231E 0%, #a52a24 100%);
+color: #ffffff;
+border-color: #8C231E;
+}
+
+.action-button.save:hover {
+    box-shadow: 0 2px 8px rgba(140, 35, 30, 0.3);
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+    .grades-container {
+        padding: 20px 15px;
+    }
+
+    .subject-title {
+        font-size: 36px;
+    }
+
+    .grade-form {
+        grid-template-columns: 1fr;
+        gap: 16px;
+    }
+
+    .grades-table {
+        font-size: 12px;
+    }
+
+    .table-header th,
+    .table-cell {
+        padding: 12px 16px;
+    }
+
+    .grade-container {
+        flex-direction: column;
+        gap: 4px;
+    }
+}

+ 91 - 58
project/frontend-angular/src/app/grade-teacher/grade-teacher.html

@@ -1,81 +1,114 @@
-<div>
-  <div>
-    <h1>{{subject?.name}}</h1>
+<div class="grades-container">
+  <div class="header-section">
+    <h1 class="subject-title">{{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>
+  <div class="search-section">
+    <label class="search-label" for="search">Search by Name:</label>
+    <input 
+      type="text" 
+      id="search" 
+      class="search-input"
+      [(ngModel)]="searchTerm" 
+      (input)="filterStudents()" 
+      placeholder="Enter student name"
+    >
+  </div>
+  <div class="form-section">
+    <form class="grade-form" (ngSubmit)="f.form.valid && addGrade()" #f="ngForm" novalidate>
+      <div class="form-group">
+        <label class="form-label" for="grade">Grade</label>
+        <select 
+          id="grade" 
+          name="grade" 
+          class="form-select"
+          [(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 class="error-message">
+            @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>
+      <div class="form-group">
+        <label class="form-label" for="student">Student</label>
+        <select 
+          id="student" 
+          name="student" 
+          class="form-select"
+          [(ngModel)]="form.student" 
+          #student="ngModel" 
+          required
+        >
+          <option *ngFor="let student of filteredStudentList" [value]="student.id">
+            {{ student.firstname }} {{ student.lastname }}
+          </option>
         </select>
         @if (f.submitted && student.invalid) {
-          <div>
+          <div class="error-message">
             @if (student.errors?.['required']) {
               <div>Student is required</div>
             }
           </div>
         }
       </div>
-      <div>
-        <button type="submit">Add Grade</button>
+      <div *ngIf="errorMessage === 'Student already has a grade'" class="duplicate-error">
+        Student already has a grade
+      </div>
+      <div class="form-group">
+        <button type="submit" class="submit-button">Add Grade</button>
       </div>
     </form>
   </div>
-
-  <div>
-    <table>
-      <thead>
-      <tr>
-        <th>Name</th>
-        <th>Last Name</th>
-        <th>Grades</th>
-      </tr>
+  <div class="table-section">
+    <table class="grades-table">
+      <thead class="table-header">
+        <tr>
+          <th>
+            Name
+            @if (sortDirection==='desc') {
+              <button class="sort-button" (click)="sortByName()">↓</button>
+            }
+            @if (sortDirection==='asc') {
+              <button class="sort-button" (click)="sortByName()">↑</button>
+            }
+          </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>
+        <ng-container *ngFor="let student of filteredStudentList; index as i">
+          <tr class="table-row" [class.even-row]="i % 2 === 1">
+            <td class="table-cell">{{ student.firstname }}</td>
+            <td class="table-cell">{{ student.lastname }}</td>
+            <td class="table-cell">
+              <div class="grade-container">
+                <ng-container *ngFor="let grade of gradeList">
+                  @if (grade.student.id === student.id) {
+                    <div class="grade-item">
+                      @if (selectedGrade === grade) {
+                        <select class="grade-edit-select" [(ngModel)]="selectedGradeValue">
+                          <option *ngFor="let g of gradeOptions" [value]="g">{{ g }}</option>
+                        </select>
+                        <button class="action-button save" (click)="saveGrade()">Save</button>
+                        <button class="action-button" (click)="cancelEdit()">Cancel</button>
+                      } @else {
+                        <span class="grade-value">{{ grade.grade }}</span>
+                        <button class="action-button" (click)="editGrade(grade)">Edit</button>
+                      }
+                    </div>
+                  }
+                </ng-container>
+              </div>
+            </td>
+          </tr>
+        </ng-container>
       </tbody>
     </table>
   </div>

+ 33 - 2
project/frontend-angular/src/app/grade-teacher/grade-teacher.ts

@@ -34,7 +34,16 @@ export class GradeTeacher implements OnInit {
   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) {}
+  searchTerm: string = '';
+  filteredStudentList: Student[] = [];
+  sortDirection: 'asc' | 'desc' = 'asc';
+
+  constructor(
+    private studentService: StudentService,
+    private gradeService: GradeService,
+    private subjectService: SubjectService,
+    private route: ActivatedRoute
+  ) {}
 
   ngOnInit() {
     this.getStudents();
@@ -45,7 +54,29 @@ export class GradeTeacher implements OnInit {
 
   getStudents(): void {
     this.studentService.getStudents()
-      .subscribe(studentList => this.studentList = studentList);
+      .subscribe(studentList => {
+        this.studentList = studentList;
+        this.filterStudents();
+      });
+  }
+
+  filterStudents(): void {
+    if (!this.studentList) {
+      this.filteredStudentList = [];
+      return;
+    }
+    let filtered = this.studentList.filter(student =>
+      (student.firstname + ' ' + student.lastname).toLowerCase().includes(this.searchTerm.toLowerCase())
+    );
+    filtered = this.sortDirection === 'asc'
+      ? filtered.sort((a, b) => (a.firstname + a.lastname).localeCompare(b.firstname + b.lastname))
+      : filtered.sort((a, b) => (b.firstname + b.lastname).localeCompare(a.firstname + a.lastname));
+    this.filteredStudentList = filtered;
+  }
+
+  sortByName(): void {
+    this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
+    this.filterStudents();
   }
 
   getGrades(): void {

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

@@ -1,11 +1,9 @@
-/* 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);
@@ -29,7 +27,6 @@
   text-align: center;
 }
 
-/* Form Styling */
 .subject-form {
   display: flex;
   flex-direction: column;
@@ -71,7 +68,6 @@
   background: rgba(255, 255, 255, 0.08);
 }
 
-/* Button Styling */
 .btn-primary {
   background: linear-gradient(135deg, #8C231E, #a52a24);
   color: white;
@@ -95,7 +91,6 @@
   transform: translateY(0);
 }
 
-/* Form Actions */
 .form-actions {
   display: flex;
   flex-direction: column;
@@ -103,7 +98,6 @@
   align-items: center;
 }
 
-/* Error Messages */
 .error-message {
   color: #ff6b6b;
   font-size: 13px;
@@ -115,7 +109,6 @@
   text-align: center;
 }
 
-/* Empty State */
 .empty-state {
   text-align: center;
   padding: 40px 20px;
@@ -127,7 +120,6 @@
   font-style: italic;
 }
 
-/* Subjects Grid */
 .subjects-grid {
   display: grid;
   grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@@ -188,7 +180,6 @@
   transform: translateX(4px);
 }
 
-/* Responsive Design */
 @media (max-width: 768px) {
   .subjects-container {
     padding: 0 15px;
@@ -227,7 +218,6 @@
   }
 }
 
-/* Animation */
 .subjects-container {
   animation: fadeIn 0.6s ease-in;
 }