diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.html b/uniplanWeb/src/app/core/shared/main-panel/main-panel.html new file mode 100644 index 0000000..8955f00 --- /dev/null +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.html @@ -0,0 +1,74 @@ +
+ @if (currentView === 'faculty') { +
+ + +
+ } + + @if (currentView === 'home') { +
+

Main!

+
+ } + + @if (currentView === 'login') { + + } + + @if (currentView === 'major') { +
+
+ + + +
+ + +
+ } + + @if (currentView === 'student') { +
+
+ + + +
+ + +
+ } +
+
diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss b/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss new file mode 100644 index 0000000..4f6858d --- /dev/null +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss @@ -0,0 +1,98 @@ +.mainPanel { + height: 100%; + width: 100%; + overflow: auto; + display: flex; + justify-content: center; +} + +.component-panel.login { + width: 100%; + min-height: calc(100vh - 80px); + display: flex; + align-items: center; + justify-content: center; + padding: 32px; + box-sizing: border-box; +} + +.components-wrapper { + position: relative; + margin-left: 8%; + margin-right: 13%; + max-width: 2000px; + width: 100%; + + @media screen and (max-width: 1024px) { + margin-left: 5%; + padding-left: 1%; + margin-right: 5%; + } + + @media screen and (max-width: 768px) { + margin-left: 2%; + padding-left: 1%; + margin-right: 2%; + } + + @media screen and (max-width: 480px) { + margin-left: 1%; + padding-left: 1%; + margin-right: 1%; + } +} + +.component-panel .major .component-panel .student { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + gap: 16px; + height: 100%; + padding: 5px; + margin-top: 1%; +} + +.component-panel { + margin-top: 4%; +} + +.controls-row { + display: grid; + grid-template-columns: auto 1fr; + gap: 16px; + align-items: center; +} + +.options { + padding-bottom: 20px; +} + +.mainPanel.login-view { + align-items: center; + justify-content: center; +} + +.mainPanel.login-view .components-wrapper { + margin-left: 0; + margin-right: 0; + padding-left: 0; + width: 100%; + max-width: none; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.component-panel.login { + width: 100%; + min-height: 100%; + margin-top: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 32px; + box-sizing: border-box; +} + + diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts new file mode 100644 index 0000000..cd64c75 --- /dev/null +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts @@ -0,0 +1,106 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +import { FacultyOptions } from '../../../features/faculty/faculty-options/faculty-options'; +import { ViewService } from './view.service'; +import { MajorOptions } from '../../../features/major/major-options/major-options'; +import { FacultyTable } from '../../../features/faculty/faculty-table/faculty-table'; +import { MajorTable } from '../../../features/major/major-table/major-table'; +import { MajorFilters } from '../../../features/major/major-filters/major-filters'; +import { StudentOptions } from '../../../features/student/student-options/student-options'; +import { + StudentTable, + ELEMENT_STUDENT_DATA, +} from '../../../features/student/student-table/student-table'; +import { StudentFilters } from '../../../features/student/student-filters/student-filters'; +import { MajorElm } from '../../interfaces/major-elm'; +import { MajorService } from '../../../features/major/major-service'; +import { FacultyService } from '../../../features/faculty/faculty-service'; +import { LoginForm } from '../../../features/login/login-form/login-form'; + +@Component({ + selector: 'app-main-panel', + imports: [ + FacultyOptions, + MajorOptions, + FacultyTable, + MajorTable, + MajorFilters, + StudentOptions, + StudentTable, + StudentFilters, + LoginForm, +], + standalone: true, + templateUrl: './main-panel.html', + changeDetection: ChangeDetectionStrategy.Eager, + styleUrl: './main-panel.scss', +}) +export class MainPanel { + currentView = 'home'; + + majors: MajorElm[] = []; + students = ELEMENT_STUDENT_DATA; + + searchText = ''; + searchFacNum = ''; + searchMajor = ''; + selectedStudentSubtype = ''; + studentSubtypes: string[] = []; + + selectedFaculty = ''; + selectedType = ''; + selectedSubtype = ''; + + faculties: { id: string; name: string }[] = []; + types: string[] = []; + subtypes: string[] = []; + + private facultyMap = new Map(); + + constructor( + private viewService: ViewService, + private majorService: MajorService, + private facultyService: FacultyService + ) { + this.viewService.currentView$.subscribe((view) => { + this.currentView = view; + }); + } + + ngOnInit(): void { + this.loadMajorFilters(); + this.loadStudentFilters(); + + this.majorService.refreshNeeded.subscribe(() => { + this.loadMajorFilters(); + }); + + this.viewService.currentView$.subscribe((view) => { + this.currentView = view; + }); + } + + private loadMajorFilters(): void { + this.facultyService.getFaculties().subscribe((faculties) => { + this.facultyMap = new Map(faculties.map((f) => [f.id, f.facultyName])); + + this.majorService.getMajors().subscribe((data) => { + this.majors = data; + + const filterOptionsMajor = MajorTable.getFilterOptions( + this.majors, + this.facultyMap + ); + + this.faculties = filterOptionsMajor.faculties; + this.types = filterOptionsMajor.types; + this.subtypes = filterOptionsMajor.subtypes; + }); + }); + } + + private loadStudentFilters(): void { + const filterOptionsStudent = StudentTable.getFilterOptions(this.students); + this.studentSubtypes = filterOptionsStudent.subtypes; + } +} diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html index 781afb7..1cf14c4 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html @@ -60,30 +60,39 @@

Logo

- @if (authService.isLoggedIn()) { - + } + + diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts index 926e096..527ca1d 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { TranslatePipe } from '@ngx-translate/core'; +import { ViewService } from '../../main-panel/view.service'; import { LoginAuthService } from '../../../services/login-auth-service'; @Component({ @@ -41,4 +42,33 @@ export class NavmenuComponent implements OnInit { toggleSidebar(): void { this.isSidebarCollapsed = !this.isSidebarCollapsed; } + + onHomeClick(): void { + this.viewService.setView('home'); + } + + onFacultyClick(): void { + this.viewService.setView('faculty'); + } + + onMajorClick(): void { + this.viewService.setView('major'); + } + + onStudentClick(): void { + this.viewService.setView('student'); + } + + protected onLoginClick(): void { + this.viewService.setView('login'); + } + + protected onLogoutClick(): void { + this.authService.logout(); + this.viewService.setView('home'); + } + + protected onProfileOpenClick(): void { + this.viewService.setView('home'); + } } diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.html b/uniplanWeb/src/app/features/login/login-form/login-form.html new file mode 100644 index 0000000..9ed19d4 --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.html @@ -0,0 +1,31 @@ +
+

Вход

+ +
+
+ + +
+ +
+ + +
+ + @if (errorMessage) { +

{{ errorMessage }}

+ } + + +
+

diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.scss b/uniplanWeb/src/app/features/login/login-form/login-form.scss new file mode 100644 index 0000000..24f943d --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.scss @@ -0,0 +1,100 @@ +:host { + display: block; + width: 100%; + max-width: 900px; +} + +* { + box-sizing: border-box; +} + +.login-form { + width: 100%; + padding: 64px 96px; + border-radius: 24px; + background: #ffffff; + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1); +} + +.login-form h2 { + margin: 0 0 32px; + font-size: 28px; + font-weight: 700; + color: #1f2937; + text-align: center; +} + +.login-form label { + display: block; + margin-bottom: 8px; + font-size: 15px; + font-weight: 600; + color: #374151; +} + +.login-form input { + width: 100%; + height: 56px; + padding: 0 16px; + border: 1px solid #d1d5db; + border-radius: 12px; + font-size: 15px; + color: #111827; + background: #ffffff; + outline: none; +} + +.login-form button { + width: 100%; + height: 58px; + border: none; + border-radius: 12px; + font-size: 16px; + font-weight: 700; + color: #ffffff; + background: #3f51b5; + cursor: pointer; +} + +.error-message { + margin: 0; + padding: 12px 14px; + border-radius: 10px; + font-size: 14px; + color: #b91c1c; + background: #fee2e2; +} + +.login-form form { + display: flex; + flex-direction: column; + gap: 28px; +} + + +.login-form input:focus { + border-color: #3f51b5; + box-shadow: 0 0 0 4px rgba(63, 81, 181, 0.14); +} + +.login-form button:hover { + background: #303f9f; +} + +.login-form button:active { + transform: translateY(1px); +} + +@media screen and (max-width: 768px) { + :host { + max-width: 100%; + } + + .login-form { + padding: 40px 28px; + } + + .login-form h2 { + font-size: 30px; + } +} diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.spec.ts b/uniplanWeb/src/app/features/login/login-form/login-form.spec.ts new file mode 100644 index 0000000..4aba92a --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginForm } from './login-form'; + +describe('LoginForm', () => { + let component: LoginForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LoginForm] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoginForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.ts b/uniplanWeb/src/app/features/login/login-form/login-form.ts new file mode 100644 index 0000000..82b4eb2 --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.ts @@ -0,0 +1,52 @@ +import { Component, inject } from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {LoginAuthService} from '../../../services/login-auth-service'; +import {ViewService} from '../../../core/shared/main-panel/view.service'; + +@Component({ + selector: 'app-login-form', + imports: [FormsModule], + templateUrl: './login-form.html', + styleUrl: './login-form.scss', +}) +export class LoginForm { + protected username: string = ''; + protected password: string = ''; + protected errorMessage: string = ''; + + private readonly authService: LoginAuthService = inject(LoginAuthService); + private readonly viewService: ViewService = inject(ViewService); + + protected login(event: Event): void { + this.errorMessage = ''; + + if (!this.username.trim()) { + this.errorMessage = 'Моля, въведете потребителско име.'; + return; + } + + if (!this.password.trim()) { + this.errorMessage = 'Моля въведете парола.'; + return; + } + + const isLoggedIn: boolean = this.authService.login(this.username, this.password); + + if (!isLoggedIn) { + this.errorMessage = 'Невалидни потребителски данни.'; + return; + } + + this.viewService.setView('home'); + } + + protected onUsernameChange(event: Event): void { + const input = event.target as HTMLInputElement; + this.username = input.value; + } + + protected onPasswordChange(event: Event): void { + const input = event.target as HTMLInputElement; + this.password = input.value; + } +} diff --git a/uniplanWeb/src/app/services/login-auth-service.ts b/uniplanWeb/src/app/services/login-auth-service.ts index 3ec49fe..3dc20f6 100644 --- a/uniplanWeb/src/app/services/login-auth-service.ts +++ b/uniplanWeb/src/app/services/login-auth-service.ts @@ -1,21 +1,29 @@ import { Injectable } from '@angular/core'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class LoginAuthService { - private loggedIn = false; - constructor() { - this.loggedIn = !!localStorage.getItem('user'); + public login(username: string, password: string): boolean { + if (!username || !password) { + return false; + } + + localStorage.setItem('user', username); + + return true; } - isLoggedIn(): boolean { - return this.loggedIn; + public isLoggedIn(): boolean { + return !!localStorage.getItem('user'); } - logout(): void { + public logout(): void { localStorage.removeItem('user'); - this.loggedIn = false; + } + + public getUsername(): string { + return localStorage.getItem('user') ?? ''; } }