- •Введение
- •Анализ исходных данных и разработка тз
- •Основание и назначение разработки
- •Минимальные требования к составу и параметрам технических средств: эвм, внешние устройства
- •Требования к информационной и программной совместимости
- •Выбор и обоснование языков программирования и используемых инструментальных средств
- •Выбор метода реализации разработки веб приложения и его обоснование.
- •Функциональная схема. Описание интерфейса.
- •Руководство пользователя
- •Назначение программы
- •Описание интерфейса
- •Руководство программиста
- •Заключение
- •Литература
- •Приложение
Заключение
В рамках курсовой работы было разработано веб-приложение для записи на секцию для студентов НГТУ им. Алексеева. В процессе работы над проектом были изучены современные технологии, такие как Angular и Node.js, а также принцип работы с JSON Server, что стало важным опытом в области веб-разработки. Анализ необходимости внедрения такого решения в университете показывает, что разработанное приложение способно сэкономить время и улучшить эффективность работы сотрудников.
В ходе разработки были выполнены следующие задачи:
Создание интуитивно понятного дизайна: Разработан простой и доступный интерфейс.
Разработка реактивного приложения: в ходе работы была изучена архитектура, реактивные формы и основные компоненты Angular.
Взаимодействие с сервером: Настроена работа с сервером для получения и обновления данных о студентах, секциях и других данных. Это обеспечило надежное хранение информации и быстрый доступ к ней.
Реализация механизмов обновления данных: Внедрены функции для редактирования и обновления информации, что позволяет пользователю управлять своими данными с минимальными усилиями.
Таким образом, достигнутые результаты соответствуют поставленным целям и задачам. Веб-приложение, реализованное в рамках данной курсовой работы, представляет собой многообещающее решение, которое может быть расширено и улучшено в будущем, включая дополнительные функциональные возможности и интеграцию с существующими образовательными системами. Это позволит дальнейшим поколениям студентов НГТУ им. Алексеева более эффективно управлять своим временем и ресурсами, что является важным аспектом их образовательного процесса.
Литература
Angular 2 [электронный ресурс]. – Режим доступа: https://metanit.com/web/angular2/ , дата обращения 25.10.2024.
Angular: Getting started [электронный ресурс]. – Режим доступа: https://developer.mozilla.org/ru/docs/Learn_web_development/Core/Frameworks_libraries/Angular_getting_started , дата обращения 5.11.2024
Overview [электронный ресурс]. – Режим доступа: https://angular.dev/overview , дата обращения 5.11.2024.
Angular [электронный ресурс]. – Режим доступа: https://forum.itvdn.com/c/frontend-developer/angular/216 , дата обращения 20.11.2024.
AngularJS – форум [электронный ресурс]. – Режим доступа: https://www.cyberforum.ru/angularjs/ , дата обращения 20.12.2024
Приложение
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
}
]
}
