diff --git a/api/src/model/repositories/user.repository.ts b/api/src/model/repositories/user.repository.ts index da14e73..df402d7 100644 --- a/api/src/model/repositories/user.repository.ts +++ b/api/src/model/repositories/user.repository.ts @@ -20,7 +20,7 @@ export class UserRepository extends Repository { } findById(id: string): Promise { - return this.findOne({ where: { id }, relations: ['external'] }); + return this.findOne({ where: { id }, relations: ['external', 'settings'] }); } async createUser(createUserDto: CreateUserDto): Promise { diff --git a/api/src/modules/user/user.controller.ts b/api/src/modules/user/user.controller.ts index 9a8b7ea..187e84d 100644 --- a/api/src/modules/user/user.controller.ts +++ b/api/src/modules/user/user.controller.ts @@ -6,6 +6,7 @@ import { IUser } from 'src/model/interface'; import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface'; import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util'; import { HttpStatusCode } from 'axios'; +import { UserSettings } from 'src/model/entitites/user/user.settings.entity'; @UseGuards(AuthGuard) @Controller('user') @@ -39,4 +40,14 @@ export class UserController { } return this.userService.deleteUserById(id); } + + @Get('settings') + getUserSettings(@Req() req: AuthenticatedRequest) { + return req.user.settings + } + + @Post('settings') + updateUserSettings(@Req() req: AuthenticatedRequest, @Body() settings: UserSettings) { + return this.userService.updateSettings(settings); + } } diff --git a/api/src/modules/user/user.service.ts b/api/src/modules/user/user.service.ts index 251076c..5a1c6a4 100644 --- a/api/src/modules/user/user.service.ts +++ b/api/src/modules/user/user.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { User } from 'src/model/entitites'; +import { UserSettings } from 'src/model/entitites/user/user.settings.entity'; import { IUser } from 'src/model/interface'; import { ActivityRepository, KeySystemRepository, RoleRepository, UserRepository, UserSettingsRepository } from 'src/model/repositories'; import { HelperService } from 'src/shared/service/system.helper.service'; @@ -62,4 +63,8 @@ export class UserService { }); return activities; } + + updateSettings(settings: UserSettings) { + return this.userSettingsRepository.save(settings); + } } diff --git a/client/src/app/core/layout/layout.component.html b/client/src/app/core/layout/layout.component.html index 17f75fc..57f2889 100644 --- a/client/src/app/core/layout/layout.component.html +++ b/client/src/app/core/layout/layout.component.html @@ -19,6 +19,8 @@ @if (isAdmin) { } + + @@ -30,4 +32,6 @@ --> - \ No newline at end of file + + + \ No newline at end of file diff --git a/client/src/app/core/layout/layout.component.ts b/client/src/app/core/layout/layout.component.ts index 57a3de2..954df0f 100644 --- a/client/src/app/core/layout/layout.component.ts +++ b/client/src/app/core/layout/layout.component.ts @@ -1,20 +1,27 @@ -import { Component, inject } from '@angular/core'; +import { Component, inject, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatToolbarModule } from '@angular/material/toolbar'; import { RouterModule } from '@angular/router'; import { AuthService } from '../auth/auth.service'; +import { SettingsComponent } from '../../modules/settings/settings.component'; @Component({ selector: 'app-layout', standalone: true, - imports: [MatButtonModule, MatIconModule, MatSidenavModule, RouterModule, MatToolbarModule], + imports: [MatButtonModule, MatIconModule, MatSidenavModule, RouterModule, MatToolbarModule, SettingsComponent], templateUrl: './layout.component.html', styleUrl: './layout.component.scss' }) export class LayoutComponent { private authService: AuthService = inject(AuthService); + @ViewChild('settings') settings!: SettingsComponent; + + openSidebar() { + console.log(this.settings) + this.settings.open(); + } logout(){ this.authService.logout(); diff --git a/client/src/app/model/interface/user.interface.ts b/client/src/app/model/interface/user.interface.ts index f78ff82..f4a0be3 100644 --- a/client/src/app/model/interface/user.interface.ts +++ b/client/src/app/model/interface/user.interface.ts @@ -2,7 +2,7 @@ export interface IUser { username: string; id: string; lastName: string; - firstName: String; + firstName: string; refreshToken: string; accessToken: string; role: string; diff --git a/client/src/app/modules/settings/settings.component.html b/client/src/app/modules/settings/settings.component.html new file mode 100644 index 0000000..8f64559 --- /dev/null +++ b/client/src/app/modules/settings/settings.component.html @@ -0,0 +1,53 @@ +@if(isOpen) { +
+} + \ No newline at end of file diff --git a/client/src/app/modules/settings/settings.component.scss b/client/src/app/modules/settings/settings.component.scss new file mode 100644 index 0000000..6185ee9 --- /dev/null +++ b/client/src/app/modules/settings/settings.component.scss @@ -0,0 +1,46 @@ +/* Overlay */ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + z-index: 999; +} + +/* Sidebar */ +.sidebar { + position: fixed; + top: 4px; + right: -400px; /* Start außerhalb des Bildschirms */ + width: 400px; + height: calc(100% - 8px); + + background-color: #fff; + box-shadow: -2px 0 5px rgba(0, 0, 0, 0.2); + z-index: 1000; + transition: right 0.3s ease-in-out; + border-radius: 24px; + display: flex; + flex-direction: column; + overflow: hidden; + padding-bottom: 1px; +} + +.sidebar.open { + right: 4px; /* Zeige Sidebar */ +} + +.sidebar-header { + padding: 1rem; + display: flex; + justify-content: flex-end; +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; +} diff --git a/client/src/app/modules/settings/settings.component.spec.ts b/client/src/app/modules/settings/settings.component.spec.ts new file mode 100644 index 0000000..b1b4077 --- /dev/null +++ b/client/src/app/modules/settings/settings.component.spec.ts @@ -0,0 +1,34 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; +import { ApiService } from '../../shared/api.service'; +import { MockApiService } from '../../../../mocks/services/mock.api.service'; +import { AuthService } from '../../core/auth/auth.service'; +import { MockAuthService } from '../../../../mocks/services/mock.auth.service'; +import { HotToastService } from '@ngxpert/hot-toast'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('SettingsComponent', () => { + let component: SettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SettingsComponent, NoopAnimationsModule], + providers: [ + { provide: ApiService, useClass: MockApiService }, + { provide: AuthService, useClass: MockAuthService }, + HotToastService + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/modules/settings/settings.component.ts b/client/src/app/modules/settings/settings.component.ts new file mode 100644 index 0000000..154c962 --- /dev/null +++ b/client/src/app/modules/settings/settings.component.ts @@ -0,0 +1,103 @@ +import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; +import { AuthService } from '../../core/auth/auth.service'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ApiService } from '../../shared/api.service'; +import {MatSlideToggleModule} from '@angular/material/slide-toggle'; +import { HotToastService } from '@ngxpert/hot-toast'; +import {MatProgressBarModule} from '@angular/material/progress-bar'; + +@Component({ + selector: 'app-settings', + standalone: true, + imports: [MatProgressBarModule, MatFormFieldModule, MatInputModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatSlideToggleModule], + templateUrl: './settings.component.html', + styleUrl: './settings.component.scss' +}) +export class SettingsComponent { + @Input() isOpen = false; + @Output() close = new EventEmitter(); + + private authService: AuthService = inject(AuthService); + private api: ApiService = inject(ApiService); + private toast: HotToastService = inject(HotToastService); + + public isLoading = false; + + userData = new FormGroup({ + firstName: new FormControl(this.firstName, Validators.required), + lastName: new FormControl(this.lastName, Validators.required), + userName: new FormControl(this.username, [Validators.required, Validators.email]) + }); + + userSettings = new FormGroup({ + id: new FormControl(), + sendSystemAccessMails: new FormControl(false), + sendSystemUpdateMails: new FormControl(false), + sendUserDisabledMails: new FormControl(false), + }) + + open() { + this.isOpen = true; + this.loadSettings(); + } + + closeSidebar() { + this.isOpen = false; + } + + get $userData() { + return this.userData.controls; + } + + get username(): string { + return this.authService.user.username; + } + + get firstName(): string { + return this.authService.user.firstName; + } + + get lastName(): string { + return this.authService.user.lastName; + } + + ngOnInit() { + + } + + loadSettings() { + this.isLoading = true; + this.api.getSettings().subscribe({ + next: (r: any) => { + this.userSettings.patchValue(r) + this.isLoading = false; + } + }) + } + + save() { + this.isLoading = true; + this.api.updateSettings(this.userSettings.value).subscribe({ + next: () => { + this.toast.success('Gespeichert') + this.isLoading = false; + } + }); + } + + saveUser() { + const user = this.authService.user; + user.firstName = this.$userData.firstName.value!; + user.lastName = this.$userData.lastName.value!; + user.username = this.$userData.userName.value!; + + this.api.saveUser(user).subscribe({ + next: () => { + this.toast.success('Gespeichert') + } + }) + } +} diff --git a/client/src/app/shared/api.service.ts b/client/src/app/shared/api.service.ts index 9326c26..70760c8 100644 --- a/client/src/app/shared/api.service.ts +++ b/client/src/app/shared/api.service.ts @@ -119,4 +119,12 @@ export class ApiService { createCylinder(data: any) { return this.http.post('api/cylinder', data); } + + getSettings(): Observable { + return this.http.get('api/user/settings') + } + + updateSettings(settings: any): Observable { + return this.http.post('api/user/settings', settings) + } } diff --git a/client/src/styles.scss b/client/src/styles.scss index a0e24b3..6caf03e 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -148,4 +148,9 @@ div.ag-row { text-decoration: line-through !important; } +} + +.mdc-notched-outline__notch +{ + border-right: none; } \ No newline at end of file