Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ПЗ_23-ИСТ-1-1_Какушкина_Ольга_Витальевна.docx
Скачиваний:
9
Добавлен:
23.06.2025
Размер:
2.41 Mб
Скачать
  1. Заключение

В рамках курсовой работы было разработано веб-приложение для записи на секцию для студентов НГТУ им. Алексеева. В процессе работы над проектом были изучены современные технологии, такие как Angular и Node.js, а также принцип работы с JSON Server, что стало важным опытом в области веб-разработки. Анализ необходимости внедрения такого решения в университете показывает, что разработанное приложение способно сэкономить время и улучшить эффективность работы сотрудников.

В ходе разработки были выполнены следующие задачи:

  1. Создание интуитивно понятного дизайна: Разработан простой и доступный интерфейс.

  2. Разработка реактивного приложения: в ходе работы была изучена архитектура, реактивные формы и основные компоненты Angular.

  3. Взаимодействие с сервером: Настроена работа с сервером для получения и обновления данных о студентах, секциях и других данных. Это обеспечило надежное хранение информации и быстрый доступ к ней.

  4. Реализация механизмов обновления данных: Внедрены функции для редактирования и обновления информации, что позволяет пользователю управлять своими данными с минимальными усилиями.

Таким образом, достигнутые результаты соответствуют поставленным целям и задачам. Веб-приложение, реализованное в рамках данной курсовой работы, представляет собой многообещающее решение, которое может быть расширено и улучшено в будущем, включая дополнительные функциональные возможности и интеграцию с существующими образовательными системами. Это позволит дальнейшим поколениям студентов НГТУ им. Алексеева более эффективно управлять своим временем и ресурсами, что является важным аспектом их образовательного процесса.

    1. Литература

  1. Angular 2 [электронный ресурс]. – Режим доступа: https://metanit.com/web/angular2/ , дата обращения 25.10.2024.

  2. Angular: Getting started [электронный ресурс]. – Режим доступа: https://developer.mozilla.org/ru/docs/Learn_web_development/Core/Frameworks_libraries/Angular_getting_started , дата обращения 5.11.2024

  3. Overview [электронный ресурс]. – Режим доступа: https://angular.dev/overview , дата обращения 5.11.2024.

  4. Angular [электронный ресурс]. – Режим доступа: https://forum.itvdn.com/c/frontend-developer/angular/216 , дата обращения 20.11.2024.

  5. AngularJS – форум [электронный ресурс]. – Режим доступа: https://www.cyberforum.ru/angularjs/ , дата обращения 20.12.2024

    1. Приложение

Section.model.ts

export interface Section {

    id: string;         // Уникальный идентификатор секции

    title: string;      // Название секции

    imageUrl: string;   // URL изображения секции

    instructor: string;  // ID преподавателя

    startTime: string;  // Время начала

    endTime: string;    // Время окончания

    day: string;        // День

    capacity: number;   // Вместимость

    reserved?: number;

  }

Student.model.ts

export class Student {

  constructor(

    public id: string,

    public firstName: string,

    public lastName: string,

    public email: string,

    public password: string,

    public group: string,

    public section: string,

    public institute: string,

    public payment: number,

    public grades: number,

    public attendanceCount: number,

   

    public requiredAttendance: number,

    public passStatus: 'pass' | 'fail',

    public photoUrl?: string // Добавлено свойство photoUrl

  ) {}

}

Techer.model.ts

export interface Teacher {

  id: string;

  firstName: string;

  lastName: string;

  email: string;

  password: string;

  experience: string;

  university: string;

  sections: string[];

  photoUrl?: string | null;

  requiredClasses?: number; // Новое поле для требуемых занятий

  isEditing?: boolean;

}

Upload.service.ts

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';

@Injectable({

  providedIn: 'root'

})

export class UploadService {

  private uploadUrl = 'http://localhost:3002/upload'; // Путь к серверу

  constructor(private http: HttpClient) {}

  uploadFile(file: File): Observable<any> {

    const formData = new FormData();

    formData.append('file', file);

    return this.http.post(this.uploadUrl, formData);

  }

  uploadFile2(formData: FormData): Observable<any> {

    // Изменили на FormData

    return this.http.post(this.uploadUrl, formData);

  }

}

Section.service.ts

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable, catchError, throwError, tap,switchMap,map } from 'rxjs';

import { Section } from '../models/section.model';

@Injectable({

  providedIn: 'root'

})

export class SectionService {

  private readonly apiUrl = 'http://localhost:3000/sections';

  private readonly instituteApiUrl = 'http://localhost:3000/institutes';

  constructor(private http: HttpClient) {}

  getSectionByTitle(title: string): Observable<Section | null> {

    return this.getSections().pipe(

        map((sections: Section[]) => sections.find(section => section.title === title) || null),

        catchError((error) => {

            console.error('Ошибка при получении секции по названию:', error);

            return throwError(() => new Error('Не удалось получить секцию по названию.'));

        })

    );

}

getSectionById(sectionId: string): Observable<{ name: string }> {

  return this.http.get<{ name: string }>(`${this.apiUrl}/${sectionId}`).pipe(

    catchError((error) => {

      console.error('Ошибка при получении института:', error);

      return throwError(() => new Error('Не удалось получить институт. Попробуйте позже.'));

    })

  );

}

  // Метод для получения института по ID

  getInstituteById(instituteId: string): Observable<{ name: string }> {

    return this.http.get<{ name: string }>(`${this.instituteApiUrl}/${instituteId}`).pipe(

      catchError((error) => {

        console.error('Ошибка при получении института:', error);

        return throwError(() => new Error('Не удалось получить институт. Попробуйте позже.'));

      })

    );

  }

  getSections(): Observable<Section[]> {

    return this.http.get<Section[]>(this.apiUrl).pipe(

      catchError((error) => {

        console.error('Ошибка при получении секций:', error);

        return throwError(() => new Error('Не удалось получить секции. Попробуйте позже.'));

      })

    );

  }

  reserveSection(sectionId: number): Observable<Section> {

    return this.http.get<Section>(`${this.apiUrl}/${sectionId}`).pipe(

        switchMap(section => {

            if (section.capacity > 0) {

                section.reserved = (section.reserved ?? 0) + 1;

                section.capacity -= 1;

                return this.http.put<Section>(`${this.apiUrl}/${sectionId}`, section);

            } else {

                return throwError(() => new Error('Недостаточно мест.'));

            }

        }),

        catchError(error => {

            console.error('Ошибка при резервировании секции:', error);

            return throwError(() => new Error('Не удалось зарезервировать место. Попробуйте позже.'));

        })

    );

}

cancelReservation(sectionId: number): Observable<Section> {

  return this.getSections().pipe(

      switchMap(sections => {

          const section = sections.find(s => s.id === sectionId.toString());  // Приведение к строке

          if (section && (section.reserved ?? 0) > 0) {

            section.reserved = (section.reserved ?? 0) - 1;

              section.capacity++;

              return this.http.put<Section>(`${this.apiUrl}/${sectionId}`, section).pipe(

                  tap(() => {

                      console.log(`Резервирование для секции ${sectionId} отменено.`);

                  }),

                  catchError(error => {

                      console.error('Ошибка при отмене резервирования:', error);

                      return throwError(() => new Error('Не удалось отменить резервирование. Попробуйте позже.'));

                  })

              );

          } else {

              return throwError(() => new Error('Нет резервированных мест для отмены.'));

          }

      })

  );

}

  // Метод для освобождения секции (возвращение мест)

  releaseSection(sectionId: string, count: number): Observable<void> {

    return this.http.put<void>(`${this.apiUrl}/${sectionId}`, { count }).pipe(

      tap(() => {

        // Не нужно управлять состоянием с BehaviorSubject

      }),

      catchError((error) => {

        console.error('Ошибка при освобождении секции:', error);

        return throwError(() => new Error('Не удалось освободить место. Попробуйте позже.'));

      })

    );

  }

  addSection(section: Section): Observable<Section> {

    return this.http.post<Section>(this.apiUrl, section).pipe(

      catchError((error) => {

        console.error('Ошибка при добавлении секции:', error);

        return throwError(() => new Error('Не удалось добавить секцию. Попробуйте позже.'));

      })

    );

  }

  createSection(post: Omit<Section, 'id'>): Observable<Section> {

    return this.http.post<Section>(this.apiUrl, post).pipe(

      catchError((error) => {

        console.error('Ошибка при создании секции:', error);

        return throwError(() => new Error('Не удалось создать секцию. Попробуйте позже.'));

      })

    );

  }

  updateSection(section: Section): Observable<Section> {

    return this.http.put<Section>(`${this.apiUrl}/${section.id}`, section).pipe(

      catchError((error) => {

        console.error('Ошибка при обновлении секции:', error);

        return throwError(() => new Error('Не удалось обновить секцию. Попробуйте позже.'));

      })

    );

  }

  deleteSection(id: string): Observable<void> {

    return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(

      catchError((error) => {

        console.error('Ошибка при удалении секции:', error);

        return throwError(() => new Error('Не удалось удалить секцию. Попробуйте позже.'));

      })

    );

  }

}

Student.service.ts

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Student } from '../models/student.model';

import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';

import { Observable, of } from 'rxjs';

import { catchError, map } from 'rxjs/operators';

@Injectable({

  providedIn: 'root',

})

export class StudentService {

  private apiUrl = 'http://localhost:3000/students';

  constructor(private http: HttpClient) {}

  updateStudentEmail(student: Student) {

    return this.http.put(`${this.apiUrl}/${student.id}/email`, { email: student.email });

}

updateStudentPassword(studentId: string, newPassword: string) {

    return this.http.put(`${this.apiUrl}/${studentId}/password`, { password: newPassword });

}

  // Метод для отправки данных о студенте

  submitStudent(student: Student): Observable<any> {

    return this.http.post(this.apiUrl, student).pipe(

      catchError(err => {

        console.error('Ошибка при отправке студента', err);

        return of(null);

      })

    );

  }

  // Получить список студентов

  getStudents(): Observable<Student[]> {

    return this.http.get<Student[]>(this.apiUrl);

  }

  // Получить студентов по секциям

  getStudentsBySection(sections: string[]): Observable<Student[]> {

    return this.http.get<Student[]>(`${this.apiUrl}?sections=${sections.join(',')}`);

  }

 

  // Получить уникальные группы студентов

  getUniqueGroups(): Observable<string[]> {

    return this.getStudents().pipe(

      map((students: Student[]) =>

        [...new Set(students.map((student: Student) => student.group).filter(group => group !== undefined))]

      )

    );

  }

  // Метод для проверки существующего пользователя

  checkEmailExists(email: string): Observable<boolean> {

    return this.http.get<Student[]>(`${this.apiUrl}?email=${email}`).pipe(

      map(students => students.length > 0),

      catchError(() => of(false))

    );

  }

  uniqueEmailValidator(): AsyncValidatorFn {

    return (control: AbstractControl): Observable<ValidationErrors | null> => {

      const email = control.value;

      if (!email) {

        return of(null); // Если поле пустое, возвращаем null для валидации

      }

      return this.checkEmailExists(email).pipe(

        map(exists => (exists ? { emailExists: true } : null)),

        catchError(() => of(null)) // Возвращаем null в случае ошибки

      );

    };

  }

 

 

  // Обновить посещаемость студента

  updateStudentAttendance(student: Student) {

    return this.http.put(`${this.apiUrl}/${student.id}`, student).pipe(

      catchError(err => {

        console.error('Ошибка при обновлении посещаемости', err);

        return of(null);

      })

    );

  }

}

Teacher.service.ts

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';

import { map } from 'rxjs/operators';

import { Teacher } from '../models/teacher.model';

import { Student } from '../models/student.model';

import { Section } from '../models/section.model';

@Injectable({

  providedIn: 'root'

})

export class TeacherService {

  private apiUrl = 'http://localhost:3000/teachers'; // URL для JSON Server

  private apiUrl2 = 'http://localhost:3000'; // Основной URL для работы с API

  constructor(private http: HttpClient) {}

  getStudents(): Observable<Student[]> {

    return this.http.get<Student[]>(`${this.apiUrl2}/students`);

  }

  updateStudent(studentId: string, updateData: any): Observable<any> {

    return this.http.put(`${this.apiUrl2}/students/${studentId}`, updateData);

  }

  // Получение уникальных групп студентов по секции

  getUniqueStudentGroupsBySection(sectionId: string): Observable<string[]> {

    return this.getStudentsBySection(sectionId).pipe(

      map(students => Array.from(new Set(students.map(student => student.group).filter((group): group is string => group !== undefined))))

    );

  }

  // Получение секций

  getSections(): Observable<Section[]> {

    return this.http.get<Section[]>(`${this.apiUrl2}/sections`);

  }

  // Получение всех преподавателей

  getTeachers(): Observable<Teacher[]> {

    return this.http.get<Teacher[]>(this.apiUrl);

  }

  // Получение групп студентов

  getStudentGroups(): Observable<string[]> {

    return this.getStudents().pipe(

      map(students => Array.from(new Set(students.map(student => student.group).filter((group): group is string => group !== undefined))))

    );

  }

  // Получение студентов по секции

  getStudentsBySection(sectionId: string): Observable<Student[]> {

    return this.http.get<Student[]>(`${this.apiUrl2}/students?section=${sectionId}`);

  }

  // Получение секций для конкретного преподавателя

  getTeacherSections(): Observable<Section[]> {

    const teacherId = JSON.parse(localStorage.getItem('teacher') || '{}').id;

    return this.http.get<Section[]>(`${this.apiUrl2}/sections`).pipe(

      map(sections => sections.filter(section => section.instructor === teacherId))

    );

  }

  // Получение расписания по ID секции

  getScheduleBySectionId(sectionId: string): Observable<any> {

    return this.http.get<any>(`${this.apiUrl2}/sections/${sectionId}`);

  }

  // Добавление нового преподавателя

  addTeacher(teacher: Teacher): Observable<Teacher> {

    return this.http.post<Teacher>(this.apiUrl, teacher);

  }

  // Обновление информации о преподавателе

  updateTeacher(teacher: Teacher): Observable<Teacher> {

    return this.http.put<Teacher>(`${this.apiUrl}/${teacher.id}`, teacher);

  }

  // Удаление преподавателя

  deleteTeacher(id: string): Observable<void> {

    return this.http.delete<void>(`${this.apiUrl}/${id}`);

  }

  // Получение информации о преподавателе по ID

  getTeacherById(id: string): Observable<Teacher> {

    return this.http.get<Teacher>(`${this.apiUrl}/${id}`);

  }

  // Получение настроек посещаемости для конкретного преподавателя

  getAttendanceSettings(teacherId: string): Observable<{ requiredAttendance: number }> {

    return this.http.get<{ requiredAttendance: number }>(`${this.apiUrl}/${teacherId}/attendance-settings`);

  }

  // Загрузка фото

  uploadPhoto(formData: FormData): Observable<{ imageUrl: string }> {

    return this.http.post<{ imageUrl: string }>('http://localhost:3002/upload', formData);

  }

  // Обновление информации о преподавателе

  updateTeacherInfo(id: string, data: Teacher): Observable<Teacher> {

    return this.http.put<Teacher>(`${this.apiUrl}/${id}`, data);

  }

  // Удаление фото

  deletePhoto(id: string): Observable<void> {

    return this.http.delete<void>(`http://localhost:3002/teachers/${id}/photoUrl`);

  }

  // Получение информации о секции по ID

  getSectionById(sectionId: number): Observable<any> {

    return this.http.get(`${this.apiUrl2}/sections/${sectionId}`);

  }

  // Загрузка изображения, например, для профиля преподавателя

  uploadImage(file: File): Observable<string> {

    const imagePath = `assets/img/teacher_page/${file.name}`;

    return new Observable(observer => {

      observer.next(imagePath);

      observer.complete();

    });

  }

  // Получение названий секций для конкретного преподавателя

  getSectionNames(teacherId: string): Observable<string[]> {

    return new Observable<string[]>((observer) => {

      this.getTeachers().subscribe((teachers) => {

        const teacher = teachers.find(t => t.id === teacherId);

        if (teacher && teacher.sections) {

          observer.next(teacher.sections); // Возвращаем массив секций

        } else {

          observer.next([]); // Возвращаем пустой массив, если секций нет

        }

        observer.complete();

      });

    })}}

Auth.service.ts

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable, of, BehaviorSubject } from 'rxjs';

import { catchError, map } from 'rxjs/operators';

@Injectable({

  providedIn: 'root',

})

export class AuthService {

  private apiUrl = 'http://localhost:3000/students';

  private teachersApiUrl = 'http://localhost:3000/teachers';

  private adminsApiUrl = 'http://localhost:3000/admins';

  private currentUserSubject = new BehaviorSubject<any>(null);

  constructor(private http: HttpClient) {}

  // Метод для входа в систему

  login(email: string, password: string): Observable<any> {

    return this.http.get<any[]>(this.apiUrl).pipe(

      map(users => {

        const user = users.find(u => u.email === email && u.password === password);

        if (user) {

          //localStorage.setItem('institute', 'ИРИТ');

          localStorage.setItem('student', JSON.stringify(user

          this.currentUserSubject.next(user);

          return user; // Возвращаем пользователя

        }

        return null; // Возвращаем null, если пользователь не найден

      }),

      catchError(err => {

        console.error(err);

        return of(null);

      })

    );

  }

  // Получить текущего пользователя

  getCurrentUser(): Observable<any> {

    return this.currentUserSubject.asObservable(); // Возвращаем текущего пользователя как Observable

  }

  // Вход для преподавателей

  loginTeach(email: string, password: string): Observable<any> {

    return this.http.get<any[]>(`${this.teachersApiUrl}?email=${email}&password=${password}`).pipe(

      map(user => {

        if (user) {

          this.currentUserSubject.next(user); // Обновить текущего пользователя

          return user; // Возвращаем пользователя

        }

        return null; // Если не нашли, возвращаем null

      }),

      catchError(err => {

        console.error(err);

        return of(null);

      })

    );

  }

  // Вход для администраторов

  loginAdmin(email: string, password: string): Observable<any> {

    return this.http.get<any[]>(`${this.adminsApiUrl}?email=${email}&password=${password}`).pipe(

      map(user => {

        if (user) {

          this.currentUserSubject.next(user); // Обновить текущего пользователя

          return user; // Возвращаем пользователя

        }

        return null; // Если не нашли, возвращаем null

      }),

      catchError(err => {

        console.error(err);

        return of(null);

      })

    );

  }

}

Student-auth-form

import { Component, OnInit } from '@angular/core';

import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

import { Router } from '@angular/router';

import { CommonModule } from '@angular/common';

import { AuthService } from '../../services/auth.service';

import { HttpClientModule } from "@angular/common/http";

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

@Component({

  selector: 'app-student-auth-form',

  standalone: true,

  imports: [ReactiveFormsModule,

    CommonModule,

    HttpClientModule,

    HeaderComponent,

    FooterComponent],

  templateUrl: './student-auth-form.component.html',

  styleUrls: ['./student-auth-form.component.scss'],

  providers: [AuthService]

})

export class StudentAuthFormComponent implements OnInit {

  form!: FormGroup;

  showPassword: boolean = false;

  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit() {

    this.form = new FormGroup({

      email: new FormControl(null, [Validators.required, Validators.email]),

      password: new FormControl(null, [Validators.required, Validators.minLength(8)]),

    });

  }

  onSubmit() {

    console.log('Форма отправляется', this.form.value);

    if (this.form.valid) {

      this.authService.login(this.form.value.email, this.form.value.password).subscribe({

        next: (response) => {

          if (response) {

            console.log('Успешный вход:', response);

            this.router.navigate(['student-page']);

          } else {

            console.log('Пользователь не найден');

            alert('Неверный логин или пароль');

          }

        },

        error: (error) => {

          console.error('Ошибка авторизации:', error);

          alert('Ошибка при авторизации: ' + error.message);

        }

      });

    } else {

      console.log('Форма невалидна:', this.form.errors);

    }

  }

 

}

Teacher-auth-form

import { Component, OnInit } from '@angular/core';

import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

import { Router } from '@angular/router';

import { CommonModule } from '@angular/common';

import { AuthService } from '../../services/auth.service';

import { HttpClientModule } from "@angular/common/http";

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

@Component({

  selector: 'app-teacher-auth-form',

  imports: [ReactiveFormsModule, CommonModule, HttpClientModule, HeaderComponent, FooterComponent],

  templateUrl: './teacher-auth-form.component.html',

  styleUrls: ['./teacher-auth-form.component.scss'],  // Исправлено на 'styleUrls', а не 'styleUrl'

  providers: [AuthService]

})

export class TeacherAuthFormComponent implements OnInit {

  form!: FormGroup;

  showPassword = false;

  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit() {

    this.form = new FormGroup({

      role: new FormControl('teacher'), // По умолчанию выбрана роль "teacher"

      email: new FormControl(null, [Validators.required, Validators.email]),

      password: new FormControl(null, [Validators.required, Validators.minLength(8)]),

    });

  }

  onRoleChange() {

    // Логика для смены роли и проверки валидности, если это нужно

  }

  onSubmit() {

    if (this.form.valid) {

      const { email, password } = this.form.value;

      if (this.form.get('role')?.value === 'admin') {

        // Логика для администратора

        this.authService.loginAdmin(email, password).subscribe({

          next: (response) => {

            if (response.length > 0) {

              localStorage.setItem('admin', JSON.stringify(response[0])); // Сохранение данных администратора

              alert('Администратор успешно авторизован');

              this.router.navigate(['admin-page']); // Переход на страницу администратора

            } else {

              alert('Неверный логин или пароль для администратора'); // Ошибка при неверном логине или пароле

            }

          },

          error: (error) => {

            alert('Ошибка при авторизации: ' + error.message);

          }

        });

      } else {

        // Логика для преподавателя

        this.authService.loginTeach(email, password).subscribe({

          next: (response) => {

            if (response.length > 0) {

              localStorage.setItem('teacher', JSON.stringify(response[0])); // Сохранение данных преподавателя

              this.router.navigate(['teacher-page']); // Переход на страницу преподавателя

            } else {

              alert('Неверный логин или пароль для преподавателя'); // Ошибка при неверном логине или пароле

            }

          },

          error: (error) => {

            alert('Ошибка при авторизации: ' + error.message);

          }

        });

      }

    } else {

      alert('Заполните все поля корректно!'); // Сообщение об ошибке, если форма не валидна

    }

  }

}

Admin-page.component.ts

import { Component } from '@angular/core';

import { Section } from '../../models/section.model';

import { CommonModule } from '@angular/common';

import { FormsModule } from '@angular/forms';

import { HttpClient } from '@angular/common/http';

import { Router } from '@angular/router';

import { HttpClientModule } from '@angular/common/http';

import { UploadService } from '../../services/upload.service';

import { SectionService } from '../../services/section.service';

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

import { Teacher } from '../../models/teacher.model';

import { TeacherService } from '../../services/teacher.service';

@Component({

  selector: 'app-admin-page',

  imports: [

    HttpClientModule,

    CommonModule,

    FormsModule,

    HeaderComponent,

    FooterComponent,

  ],

  templateUrl: './admin-page.component.html',

  styleUrl: './admin-page.component.scss',

  providers: [UploadService, SectionService, TeacherService],

})

export class AdminPageComponent {

  section: Section = {

    id: this.generateRandomId(),

    title: '',

    imageUrl: '',

    instructor: '',

    startTime: '',

    endTime: '',

    day: '',

    capacity: 0,

  };

  selectedFile: File | null = null;

  sections: Section[] = [];

  teachers: Teacher[] = [];

  editSectionId: string | null = null;

  constructor(

    private uploadService: UploadService,

    private sectionService: SectionService,

    private teacherService: TeacherService

  ) {}

  ngOnInit() {

    this.loadSections();

    this.loadTeachers();

  }

  loadTeachers() {

    this.teacherService.getTeachers().subscribe({

      next: (teachers) => {

        this.teachers = teachers;

      },

      error: (err) => {

        console.error('Ошибка при загрузке преподавателей:', err);

      },

    });

  }

  loadSections(): void {

    this.sectionService.getSections().subscribe({

      next: (sections) => {

        this.sections = sections;

      },

      error: (err) => {

        console.error('Ошибка при загрузке секций:', err);

      },

    });

  }

  private generateRandomId(): string {

    return Math.floor(Math.random() * 1000000000).toString();

  }

  onFileSelected(event: any): void {

    const file = event.target.files[0];

    if (file) {

      this.selectedFile = file;

    }

  }

  uploadImage(section: Section): void {

    if (this.selectedFile) {

      this.uploadService.uploadFile(this.selectedFile).subscribe({

        next: (response) => {

          section.imageUrl = response.imageUrl;

          alert('Изображение загружено успешно!');

          this.selectedFile = null;

        },

        error: (err) => {

          console.error('Ошибка загрузки файла:', err);

          alert('Не удалось загрузить изображение.');

        },

      });

    } else {

      alert('Пожалуйста, выберите файл.');

    }

  }

  onSubmit(): void {

    if (this.validateSection(this.section)) {

      this.section.id = this.generateRandomId();

      this.sectionService.addSection(this.section).subscribe({

        next: (newSection) => {

          alert('Секция успешно создана!');

          this.updateTeacherSections(newSection);

          this.loadSections();

          this.resetForm();

        },

        error: (err) => {

          console.error('Ошибка сохранения секции:', err);

          alert('Не удалось сохранить секцию.');

        },

      });

    } else {

      alert('Заполните все поля.');

    }

  }

  onEdit(section: Section): void {

    this.editSectionId = section.id;

    this.section = { ...section };

    setTimeout(() => {

      const sectionForm = document.getElementById('sectionForm');

      if (sectionForm) {

        sectionForm.scrollIntoView({ behavior: 'smooth', block: 'start' });

      }

    }, 100);

  }

  onSaveEdit(section: Section): void {

    if (this.validateSection(section)) {

      const oldInstructorId = this.sections.find((s) => s.id === section.id)?.instructor;

      this.sectionService.updateSection(section).subscribe({

        next: () => {

          const index = this.sections.findIndex((s) => s.id === section.id);

          if (index !== -1) {

            this.sections[index] = section;

          }

          if (oldInstructorId && oldInstructorId !== section.instructor) {

            this.removeSectionFromTeacher(oldInstructorId, section.id);

            this.updateTeacherSections(section);

          }

          this.loadTeachers();

          alert('Секция успешно обновлена!');

          this.resetForm();

          this.editSectionId = null;

        },

        error: (err) => {

          console.error('Ошибка обновления секции:', err);

          alert('Не удалось обновить секцию.');

        },

      });

    } else {

      alert('Заполните все поля!');

    }

  }

  resetForm(): void {

    this.section = {

      id: this.generateRandomId(),

      title: '',

      imageUrl: '',

      instructor: '',

      startTime: '',

      endTime: '',

      day: '',

      capacity: 0,

      reserved: 0

    };

    this.selectedFile = null;

    this.editSectionId = null;

  }

  onDelete(section: Section): void {

    if (confirm('Вы уверены, что хотите удалить эту секцию?')) {

      this.sectionService.deleteSection(section.id).subscribe({

        next: () => {

          const index = this.sections.findIndex((s) => s.id === section.id);

          if (index !== -1) {

            this.sections.splice(index, 1);

          }

          this.removeSectionFromTeacher(section.instructor, section.id);

          alert('Секция успешно удалена!');

        },

        error: (err) => {

          console.error('Ошибка удаления секции:', err);

          alert('Не удалось удалить секцию.');

        },

      });

    }

  }

  private removeSectionFromTeacher(teacherId: string, sectionId: string): void {

    this.teacherService.getTeacherById(teacherId).subscribe((teacher) => {

      if (teacher) {

        teacher.sections = teacher.sections.filter((id) => id !== sectionId);

        this.teacherService.updateTeacher(teacher).subscribe({

          next: () => {

            this.loadTeachers();

          },

          error: (err) => {

            console.error('Ошибка обновления преподавателя:', err);

          },

        });

      }

    });

  }

  private updateTeacherSections(section: Section): void {

    this.teacherService.getTeacherById(section.instructor).subscribe((teacher) => {

      if (teacher) {

        if (!teacher.sections) {

          teacher.sections = [];

        }

        teacher.sections.push(section.id);

        this.teacherService.updateTeacher(teacher).subscribe({

          next: () => {

            this.loadTeachers();

          },

          error: (err) => {

            console.error('Ошибка обновления преподавателя:', err);

          },

        });

      }

    });

  }

  validateSection(section: Section): boolean {

    return (

      section.title.trim() !== '' &&

      section.instructor.trim() !== '' &&

      section.startTime.trim() !== '' &&

      section.endTime.trim() !== '' &&

      section.day.trim() !== '' &&

      section.capacity > 0

    );

  }

  getTeacherNameById(teacherId: string): string {

    const teacher = this.teachers.find((t) => t.id === teacherId);

    return teacher ? `${teacher.firstName} ${teacher.lastName}` : 'Неизвестный преподаватель';

  }

}

Main-page.component.ts

import { Component, OnInit } from '@angular/core';

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

import { SectionService } from '../../services/section.service';

import { ButtonComponent } from '../../shared/button/button.component';

import { RegistrationComponent } from '../../shared/registration/registration.component';

import { CommonModule } from '@angular/common';

import { Section } from '../../models/section.model';

import { Teacher } from '../../models/teacher.model';

import { TeacherService } from '../../services/teacher.service';

import { HttpClientModule } from '@angular/common/http';

@Component({

  selector: 'app-main-page',

  standalone: true,

  imports: [

    HeaderComponent,

    FooterComponent,

    ButtonComponent,

    RegistrationComponent,

    CommonModule,

    HttpClientModule

  ],

  templateUrl: './main-page.component.html',

  styleUrls: ['./main-page.component.scss'],

  providers: [SectionService,TeacherService ]

})

export class MainPageComponent implements OnInit {

  sections: Section[] = [];

  teachers: Teacher[] = [];

  constructor(

    private sectionService: SectionService,

    private teacherService: TeacherService

  ) {}

  ngOnInit() {

    this.loadSections();

    this.loadTeachers();

   

  }

  loadSections() {

  this.sectionService.getSections().subscribe(sections => {

    this.sections = sections; // Загружаем обновленные секции

  });

}

  loadTeachers() {

    this.teacherService.getTeachers().subscribe({

      next: (teachers) => {

        this.teachers = teachers;

      },

      error: (err) => {

        console.error('Ошибка при загрузке преподавателей:', err);

      },

    });

  }

  getTeacherNameById(teacherId: string): string {

    const teacher = this.teachers.find((t) => t.id === teacherId);

    return teacher ? `${teacher.firstName} ${teacher.lastName}` : 'Неизвестный преподаватель';

  }

    }

Student-page.component.ts

import { Component, OnInit } from '@angular/core';

import { CommonModule } from '@angular/common';

import { HttpClientModule } from "@angular/common/http";

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

import { AuthService } from '../../services/auth.service';

import { FormsModule } from '@angular/forms';

import { DataService } from '../../services/data.service';

import { Student } from '../../models/student.model';

import { TeacherService } from '../../services/teacher.service';

import { StudentService } from '../../services/student.service';

import { SectionService } from '../../services/section.service';

@Component({

  selector: 'app-student-page',

  standalone: true,

  imports: [HeaderComponent, FooterComponent, HttpClientModule, CommonModule, FormsModule],

  templateUrl: './student-page.component.html',

  styleUrls: ['./student-page.component.scss'],

  providers: [AuthService, DataService, TeacherService, StudentService, SectionService]

})

export class StudentPageComponent implements OnInit {

  student: Student | null = null;

  newPassword: string = '';

  updatedEmail: string = '';

  isEditingEmail: boolean = false;

  instituteName: string | undefined;

  sectionName: string | undefined;

  sectionDetails: any;

  constructor(

    private studentService: StudentService,

    private sectionService: SectionService

  ) {}

  ngOnInit() {

    const storedStudent = localStorage.getItem('student');

    if (storedStudent) {

      this.student = JSON.parse(storedStudent);

      this.loadInstituteAndSectionNames();

    } else {

      console.error('Данные студента не найдены.');

    }

  }

  private loadInstituteAndSectionNames() {

    if (this.student) {

      this.sectionService.getInstituteById(this.student.institute).subscribe(

        (response: { name: string }) => {

          this.instituteName = response.name;

        },

        (error: any) => {

          console.error('Ошибка при получении института:', error);

          this.instituteName = 'Не удалось загрузить институт. Попробуйте позже.';

        }

      );

      // Получаем данные секции по ID

      this.sectionService.getSectionById(this.student.section).subscribe(

        (response: any) => { // Убедитесь, что вы используете нужный тип

          this.sectionDetails = response;

          this.sectionName = response.title;

        },

        (error: any) => {

          console.error('Ошибка при загрузке секции:', error);

          this.sectionDetails = null;

          this.sectionName = 'Ошибка загрузки секции. Попробуйте позже.';

        }

      );

    }

  }

  updateEmail() {

    if (this.student) {

      this.student.email = this.updatedEmail;

      this.studentService.updateStudentEmail(this.student).subscribe(

        () => {

          console.log('Email успешно обновлен');

          this.isEditingEmail = false;

          localStorage.setItem('student', JSON.stringify(this.student));

        },

        (error: any) => console.error('Ошибка при обновлении email:', error)

      );

    }

  }

  updatePassword() {

    if (this.student) {

      this.studentService.updateStudentPassword(this.student.id, this.newPassword).subscribe(

        () => {

          console.log('Пароль успешно обновлен');

          this.newPassword = '';

        },

        (error: any) => console.error('Ошибка при обновлении пароля:', error)

      );

    }

  }

}

Teacher-add.component.ts

import { Component, OnInit } from '@angular/core';

import { CommonModule } from '@angular/common';

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { FormsModule } from '@angular/forms';

import { TeacherService } from '../../services/teacher.service';

import { Teacher } from '../../models/teacher.model';

import { Section } from '../../models/section.model';

import { HttpClientModule } from '@angular/common/http';

import { ReactiveFormsModule } from '@angular/forms';

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

@Component({

  selector: 'app-teacher-add',

  imports: [HttpClientModule,

    ReactiveFormsModule,

    CommonModule,

    FormsModule,

    HeaderComponent,

    FooterComponent],

  templateUrl: './teacher-add.component.html',

  styleUrl: './teacher-add.component.scss',

  providers:[TeacherService]

})

export class TeacherAddComponent implements OnInit {

  teacherForm!: FormGroup;

  teachers: Teacher[] = [];

  sections: Section[] = [];

  editMode: boolean = false;

  currentEditingTeacher!: Teacher | null;

  showPassword: boolean = false;

  constructor(private fb: FormBuilder, private teacherService: TeacherService) {

    this.teachers = [];

  }

  ngOnInit(): void {

    this.createForm();

    this.loadTeachers();

    this.loadSections();

  }

  loadSections(): void {

    this.teacherService.getSections().subscribe((data: Section[]) => {

        this.sections = data;

        console.log('Секции загружены:', this.sections); // Выводим массив секций в консоль

    });

}

  togglePasswordVisibility() {

    this.showPassword = !this.showPassword; // Переключение видимости пароля

 }

  createForm() {

    this.teacherForm = this.fb.group({

      firstName: ['', Validators.required],

      lastName: ['', Validators.required],

      email: ['', [Validators.required, Validators.email]],

      password: ['', [Validators.minLength(6)]], // пароля нет в required, чтобы не требовать его при редактировании

      experience: ['', Validators.required],

      university: ['', Validators.required],

      photoUrl: [''],

      sections: [[]]

    });

  }

  loadTeachers() {

    this.teacherService.getTeachers().subscribe((data) => {

      this.teachers = data;

      this.teachers = data.map(teacher => ({ ...teacher, isEditing: false }));

    });

  }

  onSubmit() {

    if (this.teacherForm.valid) {

      const teacher: Teacher = this.teacherForm.value;

      if (this.editMode && this.currentEditingTeacher) {

        // Обновляем данные преподавателя

        teacher.id = this.currentEditingTeacher.id; // Сохраняем id для обновления

        this.teacherService.updateTeacher(teacher).subscribe(() => {

          const index = this.teachers.findIndex(t => t.id === teacher.id);

          if (index !== -1) {

            this.teachers[index] = teacher; // Обновляем данные в списке

          }

          this.resetForm();

        });

      } else {

        // Добавляем нового преподавателя

        teacher.id = Date.now().toString();

        this.teacherService.addTeacher(teacher).subscribe(newTeacher => {

          this.teachers.push(newTeacher);

          this.resetForm();

        });

      }

    }

  }

  editTeacher(teacher: Teacher) {

    this.editMode = true; // Включаем режим редактирования

    this.currentEditingTeacher = teacher; // Сохраняем текущего редактируемого преподавателя

    this.teacherForm.patchValue(teacher); // Заполняем форму данными преподавателя

  }

  resetForm() {

    this.teacherForm.reset();

    this.editMode = false;

    this.currentEditingTeacher = null;

  }

  scrollToForm() {

    const anchor = document.getElementById('form-header');

    if (anchor) {

      anchor.scrollIntoView({ behavior: 'smooth' });

    }

  }

  deleteTeacher(id: string) {

    this.teacherService.deleteTeacher(id).subscribe(() => {

      this.teachers = this.teachers.filter(teacher => teacher.id !== id);

    });

  }

 

  getSectionTitleById(sectionId: string): string {

    console.log('Ищем секцию с ID:', sectionId); // Вывод ID

    const section = this.sections.find(s => s.id === sectionId);

    return section ? section.title : 'Неизвестная секция';

}

}

Teacher-page.component.ts

import { Component, OnInit } from '@angular/core';

import { CommonModule } from '@angular/common';

import { FormsModule } from '@angular/forms';

import { HttpClientModule } from "@angular/common/http";

import { HeaderComponent } from '../../shared/header/header.component';

import { FooterComponent } from '../../shared/footer/footer.component';

import { Router } from '@angular/router';

import { TeacherService } from '../../services/teacher.service';

import { Teacher } from '../../models/teacher.model';

@Component({

  selector: 'app-teacher-page',

  standalone: true,

  imports: [CommonModule, FormsModule, HeaderComponent, FooterComponent, HttpClientModule],

  templateUrl: './teacher-page.component.html',

  styleUrls: ['./teacher-page.component.scss'],

  providers: [ TeacherService],

})

export class TeacherPageComponent implements OnInit {

  currentUser!: Teacher;

  updatedEmail: string = '';

  updatedPassword: string = '';

  selectedFile: File | null = null;

  additionalInfoFields: { label: string; value: string }[] = [];

  newFieldLabel: string = '';

  newFieldValue: string = '';

  isEditingEmail: boolean = false;

  isEditingPassword: boolean = false;

  photoUrl: string | null = null;

  sectionNames: string[] = [];

  schedule: any[] = [];

  constructor(

    private teacherService: TeacherService,

    private router: Router,

  ) {}

  ngOnInit() {

    this.loadCurrentUserData();

  }

  getSectionsNames(sectionIds: string[]) {

    // Тут мы делаем запрос для каждой секции

    sectionIds.forEach(sectionId => {

        // Преобразуем sectionId в number, если это необходимо

        const id = Number(sectionId);

        // Если id - действительно число, можно вызвать метод

        if (!isNaN(id)) {

            this.teacherService.getSectionById(id).subscribe(

                response => {

                    this.sectionNames.push(response.title); // Предполагается, что ответ включает поле `title`

                    this.getSchedule(sectionId);

                },

                error => {

                    console.error('Ошибка при получении названия секции:', error);

                }

            );

        } else {

            console.error('Неверный ID секции:', sectionId);

        }

    });

}

  uploadPhoto() {

    if (!this.selectedFile) {

      return; // Не выполнять действие, если файл не выбран

    }

 

    const formData = new FormData();

    formData.append('file', this.selectedFile);

 

 

    // Загружаем новую фотографию

    this.teacherService.uploadPhoto(formData).subscribe(

      (response) => {

        console.log('Фотография загружена:', response);

        this.photoUrl = response.imageUrl;

        this.currentUser.photoUrl = this.photoUrl; // Обновляем URL в объекте текущего пользователя

        this.updateTeacherInfo(); // Сохраняем изменения в базе данных

        this.selectedFile = null; // Сброс выбора файла

      },

      error => {

        console.error('Ошибка при загрузке фотографии:', error);

      }

    );

  }

  onFileSelected(event: any) {

    this.selectedFile = event.target.files[0];

  }

  async updateTeacherInfo() {

    const updatedData = {

      ...this.currentUser,

      email: this.updatedEmail,

      password: this.updatedPassword, // Сохранение нового поля

      photoUrl: this.photoUrl,

      additionalInfo: this.additionalInfoFields,

    };

    this.teacherService.updateTeacherInfo(this.currentUser.id, updatedData).subscribe(

      () => {

        console.log('Информация о преподавателе успешно обновлена.');

        localStorage.setItem('teacher', JSON.stringify(updatedData));

        this.loadCurrentUserData();

      },

      (error) => {

        console.error('Ошибка при обновлении информации о преподавателе:', error);

      }

    );

  }

  addAdditionalField() {

    if (this.newFieldLabel && this.newFieldValue) {

      this.additionalInfoFields.push({ label: this.newFieldLabel, value: this.newFieldValue });

      this.newFieldLabel = '';

      this.newFieldValue = '';

    }

  }

  removeAdditionalField(index: number) {

    this.additionalInfoFields.splice(index, 1);

  }

  onDoubleClickEmail() {

    this.isEditingEmail = true;

  }

  onBlurEmail() {

    this.isEditingEmail = false;

    this.updateTeacherInfo();

  }

  onDoubleClickPassword() {

    this.isEditingPassword = true; // Включаем режим редактирования

    console.log('Редактирование пароля включено'); // Для отладки

  }

  onBlurPassword() {

    this.isEditingPassword = false; // Выключаем режим редактирования

    this.updateTeacherInfo();

    console.log('Редактирование пароля завершено'); // Для отладки

  }

  loadCurrentUserData() {

    const user = localStorage.getItem('teacher');

    if (user) {

        this.currentUser = JSON.parse(user);

        this.updatedEmail = this.currentUser.email ?? '';

        this.updatedPassword = this.currentUser.password ?? ''; // Це значение должно корректно загружаться из localStorage

        this.photoUrl = this.currentUser.photoUrl ?? '';

        this.getSectionsNames(this.currentUser.sections);

    } else {

        console.error('Учитель не найден, перенаправляем на страницу входа...');

        this.router.navigate(['teacher-auth-form']);

    }

}

getSchedule(sectionId: string) {

    this.teacherService.getScheduleBySectionId(sectionId).subscribe(

        (schedule) => {

            this.schedule.push(schedule); // Добавляем расписание в массив

        },

        error => {

            console.error('Ошибка при получении расписания:', error);

        }

    );

}

}

Server.js

  const express = require('express');

const multer = require('multer');

const cors = require('cors');

const path = require('path');

const fs = require('fs');

/// Настройки для загрузки файлов

const uploadFolder = path.join(__dirname, 'images');

if (!fs.existsSync(uploadFolder)) {

  fs.mkdirSync(uploadFolder);

}

const app = express();

app.use(cors());

app.use(express.json()); // Для обработки JSON

const upload = multer({ dest: uploadFolder });

// Статическая раздача файлов из папки images

app.use('/images', express.static(uploadFolder));

// Роут для загрузки файлов

app.post('/upload', upload.single('file'), (req, res) => {

  if (req.file) {

    const imageUrl = `http://localhost:3002/images/${req.file.filename}`;

    res.json({ imageUrl });

  } else {

    res.status(400).json({ message: 'Файл не загружен' });

  }

});

// Считываем текущие секции из db.json

const getSections = () => {

  if (!fs.existsSync('C:/Users/user/SectionStud/public/db.json')) {

    return [];

  }

  const data = fs.readFileSync('C:/Users/user/SectionStud/public/db.json');

  return JSON.parse(data);

};

// Записываем секции в db.json

const saveSections = (sections) => {

  fs.writeFileSync('C:/Users/user/SectionStud/public/db.json', JSON.stringify(sections, null, 2));

};

// Роут для создания новой секции

app.post('/sections', (req, res) => {

  const sections = getSections();

  const newSection = { id: req.body.id, ...req.body };

  sections.push(newSection);

  saveSections(sections);

  res.status(201).json(newSection);

});

app.put('/sections/:id', (req, res) => {

  const sections = getSections();

  const index = sections.findIndex(s => s.id === req.params.id);

  if (index !== -1) {

      sections[index] = { ...sections[index], ...req.body };

      saveSections(sections);

      res.status(200).json(sections[index]);

  } else {

      res.status(404).json({ message: 'Секция не найдена' });

  }

});

app.delete('/sections/:id', (req, res) => {

  const sections = getSections();

  const index = sections.findIndex(s => s.id === req.params.id);

  if (index !== -1) {

      sections.splice(index, 1);

      saveSections(sections);

      res.status(204).send();

  } else {

      res.status(404).json({ message: 'Секция не найдена' });

  }

});

// Роут для получения всех секций

app.get('/sections', (req, res) => {

  const sections = getSections();

  res.json(sections);

});

// Функции для работы с учителями

const getTeachers = () => {

  const filePath = 'C:/Users/user/SectionStud/public/db.json'; // Укажите правильный путь к вашей базе данных

  if (!fs.existsSync(filePath)) {

    return [];

  }

  const data = fs.readFileSync(filePath);

  return JSON.parse(data).teachers; // Предполагаем, что учителя находятся в массиве teachers

};

const saveTeachers = (teachers) => {

  const filePath = 'C:/Users/user/SectionStud/public/db.json';

  const data = JSON.parse(fs.readFileSync(filePath));

  data.teachers = teachers; // Сохраняем обновленный массив учителей

  fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); // Сохраняем данные обратно в файл

};

// Роут для обновления фотографии преподавателя

app.put('/teachers/:id/photoUrl', upload.single('file'), (req, res) => {

  if (req.file) {

    const imageUrl = `http://localhost:3002/images/${req.file.filename}`;

    const teacherId = req.params.id;

    // Получаем список учителей

    const teachers = getTeachers();

    const teacher = teachers.find(t => t.id === teacherId);

    if (teacher) {

      // Если фото уже есть, удаляем старую фотографию

      if (teacher.photoUrl) {

        const oldImagePath = path.join(__dirname, 'images', teacher.photoUrl.replace('http://localhost:3002/images/', ''));

        fs.unlink(oldImagePath, (err) => {

          if (err) {

            console.error('Ошибка при удалении старой фотографии:', err);

          }

        });

      }

      // Обновляем URL фотографии

      teacher.photoUrl = imageUrl;

      saveTeachers(teachers); // Сохраняем изменения в базе данных

      res.json({ imageUrl });

    } else {

      res.status(404).json({ message: 'Учитель не найден' });

    }

  } else {

    res.status(400).json({ message: 'Файл не загружен' });

  }

});

// Запуск сервера

const PORT = 3002;

app.listen(PORT, () => {

  console.log('Сервер запущен на http://localhost:3002');

});

Registrition.component.ts

import { Component, EventEmitter, OnInit, OnDestroy, Output } from '@angular/core';

import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

import { CommonModule } from '@angular/common';

import { ReactiveFormsModule } from '@angular/forms';

import { PaymentComponent } from "../payment/payment.component";

import { SectionService } from "../../services/section.service";

import { DataService } from "../../services/data.service";

import { StudentService } from "../../services/student.service";

import { HttpErrorResponse } from "@angular/common/http";

import { Section } from "../../models/section.model";

import { v4 as uuidv4 } from 'uuid';

import { Student } from '../../models/student.model';

export interface Institute {

  id: number;

  name: string;

}

@Component({

  selector: 'app-registration',

  standalone: true,

  templateUrl: './registration.component.html',

  styleUrls: ['./registration.component.scss'],

  imports: [CommonModule, ReactiveFormsModule, PaymentComponent],

  providers: [StudentService, SectionService, DataService]

})

export class RegistrationComponent implements OnInit, OnDestroy {

  registrationForm!: FormGroup;

  showPassword = false;

  showPaymentForm = false;

  showSuccessMessage = false;

  emailExists = false;

  formInvalid = false; // Переменная для отслеживания валидности формы

  sections: Section[] = [];

  institutes: Institute[] = [];

  remainingTime = 60; // Время в секундах

  timer: any; // Таймер

  sectionId: number | null = null; // ID резервируемой секции

  @Output() sectionUpdated = new EventEmitter<void>();

  constructor(

    private fb: FormBuilder,

    private sectionService: SectionService,

    private studentService: StudentService,

    private dataService: DataService,

  ) {}

  ngOnInit(): void {

    this.registrationForm = this.fb.group({

      firstName: ['', [Validators.required, this.nameValidator()]],

      lastName: ['', [Validators.required]],

      email: ['', [Validators.required, Validators.email]],

      password: ['', [Validators.required, Validators.minLength(8), this.passwordStrengthValidator]],

      section: ['', [Validators.required]],

      group: ['', [Validators.required, this.groupValidator]],

      institute: ['', [Validators.required]],

      payment: ['', [Validators.required]]

    });

    // Подписка на изменения email

    this.registrationForm.get('email')?.valueChanges.subscribe(email => {

      if (this.registrationForm.get('email')?.valid) {

        this.checkEmailExists(email);

      } else {

        this.emailExists = false; // Сброс статуса, если email невалиден

      }

    });

    this.loadSections();

    this.loadInstitutes();

  }

  private checkEmailExists(email: string): void {

    this.studentService.checkEmailExists(email).subscribe(emailExists => {

      this.emailExists = emailExists; // Обновление статуса существования email

    }, (error: HttpErrorResponse) => {

      console.error('Ошибка при проверке email:', error);

    });

  }

  private loadSections(): void {

    this.sectionService.getSections().subscribe(sections => {

      this.sections = sections.filter(section => section.capacity > 0);

    }, error => {

      console.error('Ошибка загрузки секций:', error);

    });

  }

  private loadInstitutes(): void {

    this.dataService.getInstitutes().subscribe((institutes: Institute[]) => {

      this.institutes = institutes;

    }, (error: HttpErrorResponse) => {

      console.error('Ошибка загрузки институтов:', error);

    });

  }

  onRegister() {

    if (this.registrationForm.invalid) {

      this.formInvalid = true; // Пометка формы как невалидной

      this.emailExists = false; // Сброс флага существования email

      return; // Прекращаем выполнение метода, если форма невалидна

    }

    this.studentService.checkEmailExists(this.registrationForm.value.email).subscribe(emailExists => {

      this.emailExists = emailExists;

      if (!emailExists) {

        const selectedSectionId = this.registrationForm.value.section;

        this.sectionService.reserveSection(selectedSectionId).subscribe({

          next: () => {

            this.sectionUpdated.emit();

            this.showPaymentForm = true;

            this.startTimer(selectedSectionId);

            this.refreshSections();

          },

          error: error => {

            console.error(error);

            alert('Не удалось зарезервировать место: ' + error);

          }

        });

      }

    });

  }

  onPaymentSuccess(): void {

    const studentId = uuidv4();

    const user = new Student(

      studentId,

      this.registrationForm.value.firstName,

      this.registrationForm.value.lastName,

      this.registrationForm.value.email,

      this.registrationForm.value.password,

      this.registrationForm.value.group,

      this.registrationForm.value.section,

      this.registrationForm.value.institute,

      this.registrationForm.value.payment,

      this.registrationForm.value.grades, // Если вы решите использовать эту переменную

      this.registrationForm.value.attendance, // Если вы решите использовать эту переменную

      0, // Для attendanceCount

      'fail' // Или используйте другой подход для passStatus

    );

    this.studentService.submitStudent(user).subscribe({

      next: () => {

        this.showSuccessMessage = true;

        this.registrationForm.reset();

        this.showPaymentForm = false;

        clearInterval(this.timer);

        this.refreshSections(); // Обновление секций

      },

      error: (error: HttpErrorResponse) => {

        console.error('Ошибка при отправке данных:', error);

      }

    });

  }

  startTimer(sectionId: number): void {

    this.sectionId = sectionId;

    this.remainingTime = 60; // Устанавливаем таймер на 1 минуту

    this.timer = setInterval(() => {

      this.remainingTime--;

      if (this.remainingTime <= 0) {

        clearInterval(this.timer);

        this.cancelReservation(sectionId); // Отменяем резервирование, когда время вышло

      }

    }, 1000);

  }

  cancelReservation(sectionId: number): void {

    this.sectionService.cancelReservation(sectionId).subscribe(() => {

      this.sectionUpdated.emit();

      console.log("Резервирование отменено, место стало доступным");

      this.refreshSections();

      this.showPaymentForm = false;

      this.registrationForm.reset();

    });

  }

  ngOnDestroy(): void {

    clearInterval(this.timer);

  }

  private refreshSections(): void {

    this.sectionService.getSections().subscribe(sections => {

      this.sections = sections;

      console.log('Секции обновлены:', sections);

    });

  }

  private nameValidator(): ValidatorFn {

    return (control: AbstractControl): ValidationErrors | null => {

      const value = control.value;

      if (value && !/^[а-яА-ЯёЁa-zA-Z]+$/.test(value)) {

        return { invalidName: true };

      }

      return null;

    };

  }

  private passwordStrengthValidator(control: AbstractControl): ValidationErrors | null {

    const password = control.value;

    if (!password) {

      return null; // Если password пустой или null, просто пропускаем валидацию

    }

    const hasUpperCase = /[A-Z]/.test(password);

    const hasLowerCase = /[a-z]/.test(password);

    const hasNumeric = /\d/.test(password);

    const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);

    const isValidLength = password.length >= 8;

    return (!isValidLength || !hasUpperCase || !hasLowerCase || !hasNumeric || !hasSpecialChar)

      ? { passwordWeak: true }

      : null;

  }

  private groupValidator(control: AbstractControl): ValidationErrors | null {

    const value = control.value;

    const regex = /^\d{2}-[А-ЯЁа-яё]{1,3}-\d-\d$/; // Формат XX-ЛЛЛ-X-X

    return (value && !regex.test(value)) ? { invalidGroup: true } : null;

  }

}

Pay.component.ts

import { Component, EventEmitter, OnInit, Output, Input } from '@angular/core';

import { CommonModule } from '@angular/common';

import { ReactiveFormsModule, FormBuilder, FormGroup, AbstractControl,ValidationErrors, Validators } from '@angular/forms';

@Component({

  selector: 'app-payment',

  standalone: true,

  imports: [ReactiveFormsModule, CommonModule],

  templateUrl: './payment.component.html',

  styleUrls: ['./payment.component.scss']

})

export class PaymentComponent implements OnInit {

  @Input() registrationForm!: FormGroup;

  paymentForm!: FormGroup;

  @Output() paymentSuccess = new EventEmitter<void>(); // Событие для успешной оплаты

  constructor(private fb: FormBuilder) {

   

  }

  ngOnInit() {

    this.paymentForm = this.fb.group({

      cardNumber: ['', [Validators.required, Validators.pattern(/^\d{4}(\s\d{4}){3}$/), this.cardNumberValidator.bind(this)]], // 16 цифр с пробелами

      expiryDate: ['', [Validators.required, Validators.pattern(/^(0[1-9]|1[0-2])\/\d{2}$/)]], // MM/YY

      cvv: ['', [Validators.required, Validators.pattern(/^\d{3}$/)]] // 3 цифры

    });

  }

  // Метод для обработки отправки формы

  onPay() {

    if (this.paymentForm.valid) {

      console.log('Оплата успешно выполнена!');

      this.paymentSuccess.emit();

    } else {

      this.paymentForm.markAllAsTouched(); // Убедитесь, что все ошибки отображаются

    }

  }

  cardNumberValidator(control: AbstractControl): ValidationErrors | null {

    const cardNumber = control.value;

    console.log('Valuating card number:', cardNumber);

    if (cardNumber && !this.validateCardNumber(cardNumber)) {

        console.log('Card number is invalid');

        return { invalidCardNumber: true }; // Возвращаем ошибку с правильным именем

    }

    return null;

  }

  // Валидация номера карты по алгоритму Луна

  validateCardNumber(cardNumber: string): boolean {

    const sanitizedCardNumber = cardNumber.replace(/\D/g, ''); // Убираем все нецифровые символы

    let sum = 0;

    let alternate = false;

    for (let i = sanitizedCardNumber.length - 1; i >= 0; i--) {

        let n = parseInt(sanitizedCardNumber.charAt(i), 10);

        if (alternate) {

            n *= 2;

            if (n > 9) n -= 9;

        }

        sum += n;

        alternate = !alternate;

    }

    return sum % 10 === 0;

  }

  // Метод для форматирования номера карты

  formatCardNumber(event: Event): void {

    const input = event.target as HTMLInputElement;

    const value = input.value.replace(/\s/g, ''); // Убираем пробелы

    let formattedValue = '';

    for (let i = 0; i < value.length; i++) {

      if (i > 0 && i % 4 === 0) {

        formattedValue += ' '; // Добавляем пробел после каждых 4 символов

      }

      formattedValue += value[i];

    }

    input.value = formattedValue; // Устанавливаем форматированное значение

  }

  // Метод для форматирования даты

  formatExpiryDate(event: Event): void {

    const input = event.target as HTMLInputElement;

    const value = input.value.replace(/\//g, ''); // Убираем старый разделитель /

    let formattedValue = '';

    for (let i = 0; i < value.length; i++) {

      if (i === 2 && value.length > 2) {

        formattedValue += '/'; // Подставляем / после 2 символов

      }

      formattedValue += value[i];

    }

    input.value = formattedValue; // Устанавливаем форматированное значение

  }

  // Геттеры для доступа к контролям формы

  get cardNumber() {

    return this.paymentForm.get('cardNumber');

  }

  get expiryDate() {

    return this.paymentForm.get('expiryDate');

  }

  get cvv() {

    return this.paymentForm.get('cvv');

  }

}

Header.component.ts

import { Component, ChangeDetectorRef } from '@angular/core';

import { CommonModule } from '@angular/common';

import { Router, NavigationEnd } from '@angular/router';

import { filter } from 'rxjs/operators';

import { ButtonComponent } from '../button/button.component';

@Component({

  selector: 'app-header',

  standalone: true,

  imports: [ButtonComponent,CommonModule ],

  templateUrl: './header.component.html',

  styleUrls: ['./header.component.scss']

})

export class HeaderComponent {

  headerTitle: string = 'Главная страница';

  showButtons: boolean = true;

  constructor(private router: Router, private cdr: ChangeDetectorRef) {

    this.router.events.pipe(

      filter(event => event instanceof NavigationEnd) // Отслеживаем только завершение навигации

    ).subscribe(() => {

      this.updateButtonVisibility();

      this.cdr.detectChanges(); // Убедитесь, что представление обновлено

    });

  }

  private updateButtonVisibility() {

    const currentRoute = this.router.url;

    // Показывать кнопки только на главной странице

    this.showButtons = currentRoute === '/' || currentRoute === '/main-page';

  }

  onSelectForm(role: 'student' | 'teacher') {

    const loginRoute = role === 'student' ? 'student-auth-form' : 'teacher-auth-form';

    this.router.navigate(['auth', loginRoute]);

  }

}

Footer.component.ts

import { Component } from '@angular/core';

@Component({

  selector: 'app-footer',

  standalone: true,

  imports: [],

  templateUrl: './footer.component.html',

  styleUrl: './footer.component.scss'

})

export class FooterComponent {

}

Button.component.ts

import { Component, Input } from '@angular/core';

import { Router } from '@angular/router';

@Component({

  selector: 'app-button',

  standalone: true,

  imports: [],

  templateUrl: './button.component.html',

  styleUrl: './button.component.scss'

})

export class ButtonComponent {

  @Input() buttonText: string = 'Кнопка';

  @Input() role: 'student' | 'teacher' = 'student';

  @Input() anchor: string = '';

  @Input() redirectTo: string = ''; // Путь для навигации

  @Input() styleClass: string = '';

  constructor(private router: Router) {}

  onClick() {

    if (this.redirectTo) {

      this.router.navigate([this.redirectTo]);

    } else if (this.anchor) {

      const element = document.getElementById(this.anchor);

      if (element) {

        element.scrollIntoView({ behavior: 'smooth' });

      }

    }

  }

}

Db.js

{

  "admins": [

    {

      "email": "admin@mail.ru",

      "password": "admin2004"

    }

  ],

  "teachers": [

    {

      "firstName": "Сергей",

      "lastName": "Коршунов",

      "email": "ser@mail.ru",

      "password": "serg2466DFGDF$",

      "experience": "25 лет",

      "university": "Мининский",

      "photoUrl": "http://localhost:3002/images/ea95d264f6835acacfec694eabe9d36e",

      "sections": [

        "881548423"

      ],

      "id": "1734882157398"

    },

    {

      "id": "1735237358092",

      "firstName": "Светлана",

      "lastName": "Тимофеева",

      "email": "timof@mail.ru",

      "password": "TIMOFEEVA24008",

      "experience": "25 лет",

      "university": "Мининский",

      "photoUrl": "http://localhost:3002/images/b2046256b32d47892a41cf04675924cc",

      "sections": [

        "477923120"

      ],

      "additionalInfo": []

    },

    {

      "id": "1735237449741",

      "firstName": "Анна",

      "lastName": "Короленко",

      "email": "korolenko@mail.ru",

      "password": "korolenko2004",

      "experience": "20 лет",

      "university": "Мининский",

      "photoUrl": null,

      "sections": [

        "579076107"

      ]

    },

    {

      "id": "1735237517357",

      "firstName": "Сергей",

      "lastName": "Корепанов",

      "email": "korepanov@mail.ru",

      "password": "korepanov2004",

      "experience": "15 лет",

      "university": "Мининский",

      "photoUrl": null,

      "sections": [

        "826491596"

      ]

    },

    {

      "id": "1735237580610",

      "firstName": "Евгений",

      "lastName": "Голубев",

      "email": "golubev@mail.ru",

      "password": "golubev2004",

      "experience": "25 лет",

      "university": "Мининский",

      "photoUrl": null,

      "sections": [

        "742651188"

      ]

    },

    {

      "id": "1735237641529",

      "firstName": "Анастасия",

      "lastName": "Романова",

      "email": "romanova@mail.ru",

      "password": "romanova2004",

      "experience": "20 лет",

      "university": "Мининский",

      "photoUrl": null,

      "sections": [

        "614393975"

      ]

    },

    {

      "id": "1735237724286",

      "firstName": "Людмила ",

      "lastName": "Синицына",

      "email": "sinicina@mail.ru",

      "password": "sinica2004",

      "experience": "20 лет",

      "university": "Мининский",

      "photoUrl": null,

      "sections": [

        "847374114"

      ]

    }

  ],

  "students": [

    {

      "id": "d8302811-845c-4dd4-b8c2-3406afede292",

      "firstName": "Сергей",

      "lastName": "Иванов",

      "email": "sergivan@mail.ru",

      "password": "ivanS323@",

      "group": "23-ИСТ-1-2",

      "section": "477923120",

      "institute": "7",

      "payment": "5040",

      "requiredAttendance": 0,

      "passStatus": "fail"

    }

  ],

  "institutes": [

    {

      "id": "1",

      "name": "ИРИТ"

    },

    {

      "id": "2",

      "name": "ИТС"

    },

    {

      "id": "3",

      "name": "ИПТМ"

    },

    {

      "id": "4",

      "name": "ИНЭУ"

    },

    {

      "id": "5",

      "name": "ИЯЭиТФ"

    },

    {

      "id": "6",

      "name": "ИФХТиМ"

    },

    {

      "id": "7",

      "name": "ИНЭЛ"

    }

  ],

  "sections": [

    {

      "id": "477923120",

      "title": "Фитнес",

      "imageUrl": "http://localhost:3002/images/c02efdec35adcbce76bebb4a1692d8f5",

      "instructor": "1735237358092",

      "startTime": "12:00",

      "endTime": "18:00",

      "day": "ПН ВТ СР ",

      "capacity": 14,

      "reserved": 1

    },

    {

      "id": "579076107",

      "title": "Настольный теннис",

      "imageUrl": "http://localhost:3002/images/0c06ecbf4a3c72e265bf44511d44e9eb",

      "instructor": "1735237449741",

      "startTime": "12:00",

      "endTime": "20:00",

      "day": "ПН ВТ ",

      "capacity": 30,

      "reserved": 0

    },

    {

      "id": "826491596",

      "title": "Джиу-джитсу",

      "imageUrl": "http://localhost:3002/images/dc358f22401275c1f43b811dcb26d870",

      "instructor": "1735237517357",

      "startTime": "18:00",

      "endTime": "20:00",

      "day": "ВТ",

      "capacity": 20,

      "reserved": 0

    },

    {

      "id": "614393975",

      "title": "Волейбол",

      "imageUrl": "http://localhost:3002/images/6da571430f6a57029c0ee712ba865840",

      "instructor": "1735237641529",

      "startTime": "12:00",

      "endTime": "18:00",

      "day": "ЧТ ПТ",

      "capacity": 30,

      "reserved": 0

    },

    {

      "id": "742651188",

      "title": "Мини-футбол",

      "imageUrl": "http://localhost:3002/images/dcda3b255c88c1d3e7b3b84c24498846",

      "instructor": "1735237580610",

      "startTime": "14:00",

      "endTime": "16:00",

      "day": "ВТ СР",

      "capacity": 20,

      "reserved": 0

    },

    {

      "id": "847374114",

      "title": "Шахматы",

      "imageUrl": "http://localhost:3002/images/e397aacb7e457bde528d5473993ac9d3",

      "instructor": "1735237724286",

      "startTime": "16:00",

      "endTime": "18:00",

      "day": "ПН",

      "capacity": 6,

      "reserved": 0

    },

    {

      "id": "881548423",

      "title": "Дартс",

      "imageUrl": "http://localhost:3002/images/98171f4c14c9010c2b2435fd422b197e",

      "instructor": "1734882157398",

      "startTime": "18:00",

      "endTime": "20:00",

      "day": "ПН ВТ СР ",

      "capacity": 20

    }

  ]

}