Manage SystemManagers

This commit is contained in:
Bastian Wagner
2025-01-02 13:16:45 +01:00
parent bf64103369
commit efbfc2eb01
18 changed files with 266 additions and 23 deletions

View File

@@ -0,0 +1,5 @@
import { of } from "rxjs";
export class MockAuthService {
user = { id: '1', username: 'test', role: 'admin' };
}

View File

@@ -7,11 +7,12 @@ import { HotToastService } from '@ngxpert/hot-toast';
import { AuthService } from '../../../core/auth/auth.service';
import { DatePipe } from '@angular/common';
import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-all-users',
standalone: true,
imports: [AgGridAngular],
imports: [AgGridAngular, MatButtonModule],
providers: [DatePipe],
templateUrl: './all-users.component.html',
styleUrl: './all-users.component.scss'
@@ -54,7 +55,26 @@ export class AllUsersComponent {
, width: 170
, type: 'date'
, cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value), 'medium') : '-'
}
},
{
field: 'delete',
headerName: '',
width: 50,
cellRenderer: (params: any) => {
if (params.data.id == this.authService.user.id || !this.authService.isAdmin) {
return '';
}
return `<div class="delete icon icon-btn-xs" (click)="deleteUser(${params.data.id})"></div>`;
},
onCellClicked: (event: any) => {
if (event.data.id == this.authService.user.id || !this.authService.isAdmin) {
return;
}
if (event.colDef.field == 'delete') {
this.deleteUser(event.data.id);
}
},
}
],
loading: true,
overlayLoadingTemplate: 'Lade Daten...'
@@ -64,12 +84,36 @@ export class AllUsersComponent {
}
deleteUser(id: string) {
if ( confirm('Soll der Benutzer wirklich gelöscht werden?')) {
this.api.deleteUser(id)
.pipe(
this.toast.observe({
loading: 'löschen...',
success: 'Benutzer gelöscht',
error: 'Benutzer konnte nicht gelöscht werden!'
})
)
.subscribe({
next: () => {
this.loadUsers();
}
})
}
}
loadUsers() {
this.gridApi.setGridOption("loading", true);
this.api.getAllUsers().subscribe({
next: n => {
this.gridApi.setGridOption("rowData", n)
this.gridApi.setGridOption("loading", false);
},
error: () => {
this.toast.error('Benutzer konnten nicht geladen werden!')
this.gridApi.setGridOption("loading", false);
}
})
}

View File

@@ -31,17 +31,16 @@ export class SelectKeyCylinderComponent {
},
rowSelection: 'multiple',
columnDefs: [
// selected rows
{ colId: 'selected', headerName: '', checkboxSelection: true, width: 40, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true },
{ colId: 'name', field: 'name' , headerName: 'Name', flex: 1, editable: true, sort: 'asc', filter: true },
{ colId: 'system', field: 'system' , headerName: 'Schließanlage', flex: 1, editable: false, filter: true, cellRenderer: (data: any) => {return data.value?.name} },
]
};
selectedCylinders: ICylinder[] = [];
ngOnInit(): void {
console.log(this.toast)
this.toast.error('Wähle die Zylinder aus, die dem Schlüssel zugeordnet werden sollen.');
this.toast.info('Wähle die Zylinder aus, die dem Schlüssel zugeordnet werden sollen.');
}
onGridReady(params: GridReadyEvent) {

View File

@@ -1,13 +1,25 @@
<h2 mat-dialog-title>Manager</h2>
<h2 mat-dialog-title>{{ system?.name }} - Manager</h2>
<mat-dialog-content>
<div style="display: flex; flex-direction: column; height: calc(100% - 4px);" class="gap-2">
<div class="flex-auto">
<ag-grid-angular
style="width: 100%; height: 100%;"
(gridReady)="onGridReady($event)"
[gridOptions]="gridOptions!"
/>
/>
</div>
<div class="flex gap-2 items-center">
<mat-form-field class="flex-1">
<mat-label>Email</mat-label>
<input matInput [(ngModel)]="email">
<mat-hint>Emailadresse des neuen Users eingeben</mat-hint>
</mat-form-field>
<button mat-button (click)="addManagerByEmail()" [disabled]="email == '' || email == null">Hinzufügen</button>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="true">Schließen</button>

View File

@@ -7,6 +7,9 @@ import { ApiService } from '../../../../shared/api.service';
import { HotToastService } from '@ngxpert/hot-toast';
import { MockApiService } from '../../../../../../mocks/services/mock.api.service';
import { GridReadyEvent } from 'ag-grid-community';
import { AuthService } from '../../../../core/auth/auth.service';
import { MockAuthService } from '../../../../../../mocks/services/mock.auth.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -18,7 +21,7 @@ describe('SystemManagerComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SystemManagerComponent, AgGridAngular, MatDialogModule],
imports: [SystemManagerComponent, AgGridAngular, MatDialogModule, NoopAnimationsModule],
providers: [
HotToastService,
{ provide: ApiService, useClass: MockApiService },
@@ -29,7 +32,8 @@ describe('SystemManagerComponent', () => {
{
provide: MAT_DIALOG_DATA,
useValue: []
}
},
{ provide: AuthService, useClass: MockAuthService }
]
})
.compileComponents();

View File

@@ -5,11 +5,18 @@ import { AgGridAngular } from 'ag-grid-angular';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog, MatDialogModule } from '@angular/material/dialog';
import { HotToastService } from '@ngxpert/hot-toast';
import { ApiService } from '../../../../shared/api.service';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AuthService } from '../../../../core/auth/auth.service';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'app-system-manager',
standalone: true,
imports: [AgGridAngular, MatDialogModule],
imports: [AgGridAngular, MatDialogModule, MatButtonModule, MatInputModule, MatFormFieldModule, CommonModule, FormsModule],
templateUrl: './system-manager.component.html',
styleUrl: './system-manager.component.scss'
})
@@ -22,14 +29,32 @@ export class SystemManagerComponent {
readonly system = inject<any>(MAT_DIALOG_DATA);
private api: ApiService = inject(ApiService);
private dialog: MatDialog = inject(MatDialog);
private toast = inject(HotToastService);
private dialog: MatDialog = inject(MatDialog);
private toast = inject(HotToastService);
private authService = inject(AuthService);
email = null;
constructor() {
this.gridOptions.columnDefs = [
{ colId: 'name', field: 'firstName', headerName: 'Name', sort: 'asc', flex: 1, cellRenderer: (data: any) => data.data.firstName + ' ' + data.data.lastName, sortable: true, filter: true},
{ colId: 'mail', field: 'username', headerName: 'E-Mail', flex: 1},
{ colId: 'delete', headerName: '', width: 50,
cellRenderer: (params: any) => {
if (this.authService.user.username == params.data.username) return '';
return `<div class="delete icon icon-btn-xs"></div>`;
},
onCellClicked: (event: any) => {
if (this.authService.user.username == event.data.username) {
this.toast.error('Du kannst dich nicht selbst entfernen');
return;
}
if (event.colDef.colId == 'delete') {
this.removeManagerByEmail(event.data.username);
}
},
}
]
}
@@ -47,4 +72,49 @@ export class SystemManagerComponent {
}
})
}
addManagerByEmail() {
if (this.email == null) return;
this.gridApi.setGridOption("loading", true);
this.api.addManager(this.system.id, this.email)
.pipe(
this.toast.observe({
loading: 'Füge Manager hinzu',
success: 'Manager hinzugefügt',
error: (x: any) => x.error.message || 'Fehler beim Hinzufügen des Managers'
})
).subscribe({
next: n => {
this.gridApi.setGridOption("rowData", n);
this.email = null;
this.gridApi.setGridOption("loading", false);
},
error: () => {
this.gridApi.setGridOption("loading", false);
}
});
}
removeManagerByEmail(username: string) {
const resume = confirm('Soll der Manager wirklich entfernt werden?');
if (!resume) return;
this.gridApi.setGridOption("loading", true);
this.api.removeManager(this.system.id, username)
.pipe(
this.toast.observe({
loading: 'Manager entfernen',
success: 'Manager entfernt',
error: (x: any) => x.error.message || 'Fehler beim Entfgernen des Managers'
})
).subscribe({
next: (n: any[]) => {
this.gridApi.setGridOption("rowData", n);
this.gridApi.setGridOption("loading", false);
},
error: () => {
this.gridApi.setGridOption("loading", false);
}
});
}
}

View File

@@ -41,7 +41,7 @@ export class AgSystemManagerComponent implements ICellRendererAngularComp {
width: "50vw",
minWidth: "300px",
height: "70vh",
disableClose: true
disableClose: false
})
// ref.afterClosed().subscribe({

View File

@@ -85,4 +85,22 @@ export class ApiService {
deleteCylinder(cylinder: ICylinder): Observable<any> {
return this.http.delete(`api/cylinder/${cylinder.id}`)
}
deleteUser(id: string) {
return this.http.delete(`api/user/${id}`)
}
addManager(systemID: string, email: string): Observable<IUser[]> {
return this.http.post<IUser[]>(`api/system/${systemID}/manager`, {
email,
action: 'add'
});
}
removeManager(systemID: string, email: string): Observable<IUser[]> {
return this.http.post<IUser[]>(`api/system/${systemID}/manager`, {
email,
action: 'remove'
});
}
}

View File

@@ -19,6 +19,24 @@ html, body {
font-family: Roboto, "Helvetica Neue", sans-serif;
}
.icon {
border: 1px solid #d0d5dd;
background-color: white;
cursor: pointer;
padding: 4px;
box-sizing: border-box;
border-radius: 6px;
background-position: center;
background-repeat: no-repeat;
transition: box-shadow 0.1s ease-in-out;
&:hover {
// box-shadow: 0 0 0 4px transparent,0 1px 2px #0c111d11;
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
0px 6px 10px 0px rgba(0, 0, 0, 0.14),
0px 1px 18px 0px rgba(0, 0, 0, 0.12);
}
}
.icon-btn-sm {
// box-shadow: 0 0 0 4px transparent,0 1px 2px #0c111d11;