Usersettings
This commit is contained in:
@@ -20,7 +20,7 @@ export class UserRepository extends Repository<User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findById(id: string): Promise<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> {
|
async createUser(createUserDto: CreateUserDto): Promise<User> {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IUser } from 'src/model/interface';
|
|||||||
import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface';
|
import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface';
|
||||||
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';
|
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';
|
||||||
import { HttpStatusCode } from 'axios';
|
import { HttpStatusCode } from 'axios';
|
||||||
|
import { UserSettings } from 'src/model/entitites/user/user.settings.entity';
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
@@ -39,4 +40,14 @@ export class UserController {
|
|||||||
}
|
}
|
||||||
return this.userService.deleteUserById(id);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { User } from 'src/model/entitites';
|
import { User } from 'src/model/entitites';
|
||||||
|
import { UserSettings } from 'src/model/entitites/user/user.settings.entity';
|
||||||
import { IUser } from 'src/model/interface';
|
import { IUser } from 'src/model/interface';
|
||||||
import { ActivityRepository, KeySystemRepository, RoleRepository, UserRepository, UserSettingsRepository } from 'src/model/repositories';
|
import { ActivityRepository, KeySystemRepository, RoleRepository, UserRepository, UserSettingsRepository } from 'src/model/repositories';
|
||||||
import { HelperService } from 'src/shared/service/system.helper.service';
|
import { HelperService } from 'src/shared/service/system.helper.service';
|
||||||
@@ -62,4 +63,8 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
return activities;
|
return activities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSettings(settings: UserSettings) {
|
||||||
|
return this.userSettingsRepository.save(settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
@if (isAdmin) {
|
@if (isAdmin) {
|
||||||
<button mat-button routerLink="/users" routerLinkActive="mat-elevation-z1">Alle User</button>
|
<button mat-button routerLink="/users" routerLinkActive="mat-elevation-z1">Alle User</button>
|
||||||
}
|
}
|
||||||
|
<button mat-button (click)="openSidebar()">Einstellungen</button>
|
||||||
|
|
||||||
</mat-drawer>
|
</mat-drawer>
|
||||||
|
|
||||||
|
|
||||||
@@ -30,4 +32,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
</mat-drawer-container>
|
</mat-drawer-container>
|
||||||
|
|
||||||
|
<app-settings #settings/>
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject, ViewChild } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
import { SettingsComponent } from '../../modules/settings/settings.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-layout',
|
selector: 'app-layout',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [MatButtonModule, MatIconModule, MatSidenavModule, RouterModule, MatToolbarModule],
|
imports: [MatButtonModule, MatIconModule, MatSidenavModule, RouterModule, MatToolbarModule, SettingsComponent],
|
||||||
templateUrl: './layout.component.html',
|
templateUrl: './layout.component.html',
|
||||||
styleUrl: './layout.component.scss'
|
styleUrl: './layout.component.scss'
|
||||||
})
|
})
|
||||||
export class LayoutComponent {
|
export class LayoutComponent {
|
||||||
private authService: AuthService = inject(AuthService);
|
private authService: AuthService = inject(AuthService);
|
||||||
|
@ViewChild('settings') settings!: SettingsComponent;
|
||||||
|
|
||||||
|
openSidebar() {
|
||||||
|
console.log(this.settings)
|
||||||
|
this.settings.open();
|
||||||
|
}
|
||||||
|
|
||||||
logout(){
|
logout(){
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export interface IUser {
|
|||||||
username: string;
|
username: string;
|
||||||
id: string;
|
id: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
firstName: String;
|
firstName: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
role: string;
|
role: string;
|
||||||
|
|||||||
53
client/src/app/modules/settings/settings.component.html
Normal file
53
client/src/app/modules/settings/settings.component.html
Normal 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>
|
||||||
46
client/src/app/modules/settings/settings.component.scss
Normal file
46
client/src/app/modules/settings/settings.component.scss
Normal 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;
|
||||||
|
}
|
||||||
34
client/src/app/modules/settings/settings.component.spec.ts
Normal file
34
client/src/app/modules/settings/settings.component.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
103
client/src/app/modules/settings/settings.component.ts
Normal file
103
client/src/app/modules/settings/settings.component.ts
Normal 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')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -119,4 +119,12 @@ export class ApiService {
|
|||||||
createCylinder(data: any) {
|
createCylinder(data: any) {
|
||||||
return this.http.post<ICylinder>('api/cylinder', data);
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,4 +148,9 @@ div.ag-row {
|
|||||||
|
|
||||||
text-decoration: line-through !important;
|
text-decoration: line-through !important;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-notched-outline__notch
|
||||||
|
{
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user