Usersettings

This commit is contained in:
Bastian Wagner
2025-01-24 14:11:23 +01:00
parent 2674ec0d24
commit e05b6cfc42
12 changed files with 281 additions and 5 deletions

View File

@@ -20,7 +20,7 @@ export class UserRepository extends Repository<User> {
}
findById(id: string): Promise<User> {
return this.findOne({ where: { id }, relations: ['external'] });
return this.findOne({ where: { id }, relations: ['external', 'settings'] });
}
async createUser(createUserDto: CreateUserDto): Promise<User> {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -19,6 +19,8 @@
@if (isAdmin) {
<button mat-button routerLink="/users" routerLinkActive="mat-elevation-z1">Alle User</button>
}
<button mat-button (click)="openSidebar()">Einstellungen</button>
</mat-drawer>
@@ -30,4 +32,6 @@
</button>
</div> -->
</mat-drawer-container>
</mat-drawer-container>
<app-settings #settings/>

View File

@@ -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();

View File

@@ -2,7 +2,7 @@ export interface IUser {
username: string;
id: string;
lastName: string;
firstName: String;
firstName: string;
refreshToken: string;
accessToken: string;
role: string;

View File

@@ -0,0 +1,53 @@
@if(isOpen) {
<div class="overlay" (click)="closeSidebar()"></div>
}
<div class="sidebar" [class.open]="isOpen">
<div class="sidebar-header">
<button class="close-btn" (click)="closeSidebar()"></button>
</div>
<div class="content" style="flex: 1 1 auto" >
<h4>Einstellungen</h4>
<form [formGroup]="userData" class="flex flex-col p-4">
<mat-form-field appearance="outline">
<mat-label>Vorname</mat-label>
<input type="text" matInput formControlName="firstName">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Nachname</mat-label>
<input type="text" matInput formControlName="lastName">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Email</mat-label>
<input type="text" matInput formControlName="userName">
</mat-form-field>
<button mat-button [disabled]="userData.invalid" (click)="saveUser()">Speichern</button>
</form>
<div class=" px-4">
<div class="text-2xl">Emailbenachrichtigungen</div>
<div>Sende Emails bei: </div>
</div>
<form [formGroup]="userSettings" class="flex flex-col p-4 gap-3">
<mat-slide-toggle formControlName="sendSystemAccessMails" (change)="save()">
Zugriff auf Schließanlage
</mat-slide-toggle>
<mat-slide-toggle formControlName="sendSystemUpdateMails" (change)="save()">
Änderung an Schlueßanlage
</mat-slide-toggle>
<mat-slide-toggle formControlName="sendUserDisabledMails" (change)="save()">
Deaktivierung meines Users
</mat-slide-toggle>
</form>
</div>
@if (isLoading) {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
}
</div>

View File

@@ -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;
}

View File

@@ -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<SettingsComponent>;
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();
});
});

View File

@@ -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<void>();
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')
}
})
}
}

View File

@@ -119,4 +119,12 @@ export class ApiService {
createCylinder(data: any) {
return this.http.post<ICylinder>('api/cylinder', data);
}
getSettings(): Observable<any> {
return this.http.get('api/user/settings')
}
updateSettings(settings: any): Observable<any> {
return this.http.post('api/user/settings', settings)
}
}

View File

@@ -148,4 +148,9 @@ div.ag-row {
text-decoration: line-through !important;
}
}
.mdc-notched-outline__notch
{
border-right: none;
}