Dashboard

This commit is contained in:
Bastian Wagner
2025-01-02 15:55:56 +01:00
parent efbfc2eb01
commit 5c6516095b
24 changed files with 490 additions and 36 deletions

View File

@@ -0,0 +1,28 @@
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { User } from './user.entity';
import { KeySystem } from './system.entity';
@Entity()
export class Activity {
@PrimaryGeneratedColumn('uuid')
id: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@ManyToOne(() => User)
user: User;
@ManyToOne(() => KeySystem)
system: KeySystem;
@Column({ type: 'text'})
message: string;
}

View File

@@ -6,3 +6,4 @@ export * from './key.entity';
export * from './key_activity.entity'; export * from './key_activity.entity';
export * from './customer.entity'; export * from './customer.entity';
export * from './key-handout.entity'; export * from './key-handout.entity';
export * from './activity.entity';

View File

@@ -8,6 +8,7 @@ import {
} from 'typeorm'; } from 'typeorm';
import { Key } from './key.entity'; import { Key } from './key.entity';
import { Customer } from './customer.entity'; import { Customer } from './customer.entity';
import { User } from './user.entity';
@Entity() @Entity()
export class KeyHandout { export class KeyHandout {
@@ -29,6 +30,9 @@ export class KeyHandout {
@CreateDateColumn() @CreateDateColumn()
created: Date; created: Date;
@ManyToOne(() => User)
user: User;
@BeforeInsert() @BeforeInsert()
insertTimestamp() { insertTimestamp() {
if (this.timestamp == null) { if (this.timestamp == null) {

View File

@@ -6,12 +6,14 @@ import {
JoinTable, JoinTable,
ManyToMany, ManyToMany,
ManyToOne, ManyToOne,
OneToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
UpdateDateColumn, UpdateDateColumn,
} from 'typeorm'; } from 'typeorm';
import { Cylinder } from './cylinder.entity'; import { Cylinder } from './cylinder.entity';
import { IKey } from '../interface/key.interface'; import { IKey } from '../interface/key.interface';
import { Customer } from './customer.entity'; import { Customer } from './customer.entity';
import { KeyHandout } from './key-handout.entity';
@Entity() @Entity()
export class Key implements IKey { export class Key implements IKey {
@@ -45,4 +47,7 @@ export class Key implements IKey {
@DeleteDateColumn() @DeleteDateColumn()
deletedAt: Date; deletedAt: Date;
@OneToMany(() => KeyHandout, (handout) => handout.key)
handouts: KeyHandout[];
} }

View File

@@ -0,0 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Repository, DataSource } from 'typeorm';
import { Activity } from '../entitites';
@Injectable()
export class ActivityRepository extends Repository<Activity> {
constructor(dataSource: DataSource) {
super(Activity, dataSource.createEntityManager());
}
}

View File

@@ -6,3 +6,4 @@ export * from './cylinder.repository';
export * from './key.repository'; export * from './key.repository';
export * from './key_activity.repository'; export * from './key_activity.repository';
export * from './customer.repository'; export * from './customer.repository';
export * from './activity.repository';

View File

@@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Customer, Cylinder, Key, User } from 'src/model/entitites'; import { Customer, Cylinder, Key, User } from 'src/model/entitites';
import { IUser } from 'src/model/interface'; import { IUser } from 'src/model/interface';
import { import {
ActivityRepository,
CylinderRepository, CylinderRepository,
KeyActivityRepository, KeyActivityRepository,
KeyRepository, KeyRepository,
@@ -18,6 +19,7 @@ export class KeyService {
private readonly systemRepo: KeySystemRepository, private readonly systemRepo: KeySystemRepository,
private activityRepo: KeyActivityRepository, private activityRepo: KeyActivityRepository,
private handoverRepo: KeyHandoutRepository, private handoverRepo: KeyHandoutRepository,
private systemActivityRepo: ActivityRepository,
) {} ) {}
async getUsersKeys(user: User): Promise<Key[]> { async getUsersKeys(user: User): Promise<Key[]> {
@@ -74,19 +76,30 @@ export class KeyService {
async handoverKey(user: IUser, data: any, keyID: string) { async handoverKey(user: IUser, data: any, keyID: string) {
const key: Key = await this.keyrepository.findOneOrFail({ const key: Key = await this.keyrepository.findOneOrFail({
where: { id: keyID, cylinder: { system: { managers: { id: user.id } } } }, where: { id: keyID, cylinder: { system: { managers: { id: user.id } } } },
relations: [ 'cylinder', 'cylinder.system' ]
}); });
key.handedOut = data.direction == 'out'; key.handedOut = data.direction == 'out';
this.keyrepository.save(key); this.keyrepository.save(key);
return this.handoverRepo.save( const res = await this.handoverRepo.save(
this.handoverRepo.create({ this.handoverRepo.create({
customer: data.customer, customer: data.customer,
direction: data.direction, direction: data.direction,
timestamp: data.timestamp, timestamp: data.timestamp,
key: key, key: key,
user: user as any
}), }),
); );
const msg = `Schlüssel ${key.nr} ${res.direction == 'out' ? 'ausgegeben an ' : 'zurückgegeben von '}${res.customer.name}`
this.systemActivityRepo.save(
this.systemActivityRepo.create({
system: key.cylinder[0].system,
user: user as any,
message: msg,
}))
return res;
} }
getKeyHandovers(user: User, keyID: string) { getKeyHandovers(user: User, keyID: string) {
@@ -121,8 +134,14 @@ export class KeyService {
} }
} }
createKey(user: User, key: any) { async createKey(user: User, key: any) {
return this.keyrepository.save(this.keyrepository.create(key)); const k = await this.keyrepository.save(this.keyrepository.create(key));
this.systemActivityRepo.save({
message: `Schlüssel ${(k as any).nr} angelegt`,
user: user,
system: (k as any).cylinder[0].system
});
return k;
} }
async deleteKey(user: User, id: string) { async deleteKey(user: User, id: string) {

View File

@@ -32,17 +32,21 @@ export class SystemController {
findAll(@Req() req: AuthenticatedRequest) { findAll(@Req() req: AuthenticatedRequest) {
return this.systemService.findAll(req.user); return this.systemService.findAll(req.user);
} }
@Get(':id/manager')
getManagers(@Param('id') id: string) {
return this.systemService.getManagers(id);
}
@Post(':id/manager')
manaManager(@Param('id') id: string, @Body() body: any){
return this.systemService.manageManagers(id, body);
}
@Get(':id') @Get(':id')
findOne(@Param('id') id: string) { findOne(@Param('id') id: string) {
return this.systemService.findOne(id); return this.systemService.findOne(id);
} }
@Get(':id/manager')
getManagers(@Param('id') id: string) {
return this.systemService.getManagers(id);
}
@Patch(':id') @Patch(':id')
update(@Param('id') id: string, @Body() updateSystemDto: UpdateSystemDto) { update(@Param('id') id: string, @Body() updateSystemDto: UpdateSystemDto) {
return this.systemService.update(id, updateSystemDto); return this.systemService.update(id, updateSystemDto);
@@ -52,9 +56,4 @@ export class SystemController {
remove(@Param('id') id: string) { remove(@Param('id') id: string) {
return this.systemService.remove(id); return this.systemService.remove(id);
} }
@Post(':id/manager')
manaManager(@Param('id') id: string, @Body() body: any){
return this.systemService.manageManagers(id, body);
}
} }

View File

@@ -3,6 +3,7 @@ import { CreateSystemDto } from './dto/create-system.dto';
import { UpdateSystemDto } from './dto/update-system.dto'; import { UpdateSystemDto } from './dto/update-system.dto';
import { KeySystemRepository, UserRepository } from 'src/model/repositories'; import { KeySystemRepository, UserRepository } from 'src/model/repositories';
import { User } from 'src/model/entitites'; import { User } from 'src/model/entitites';
import { IUser } from 'src/model/interface';
@Injectable() @Injectable()
export class SystemService { export class SystemService {

View File

@@ -22,6 +22,16 @@ export class UserController {
return this.userService.saveUser(user); return this.userService.saveUser(user);
} }
@Get('stats')
getStats(@Req() req: AuthenticatedRequest) {
return this.userService.getUserStats(req.user);
}
@Get('activities')
getUseractivities(@Req() req: AuthenticatedRequest) {
return this.userService.getActivitiesforUser(req.user)
}
@Delete(':id') @Delete(':id')
deleteUserWithId(@Req() req: AuthenticatedRequest, @Param('id') id: string) { deleteUserWithId(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
if (req.user.role.name != "admin") { if (req.user.role.name != "admin") {

View File

@@ -1,13 +1,16 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { User } from 'src/model/entitites'; import { User } from 'src/model/entitites';
import { IUser } from 'src/model/interface'; import { IUser } from 'src/model/interface';
import { RoleRepository, UserRepository } from 'src/model/repositories'; import { ActivityRepository, KeyActivityRepository, KeySystemRepository, RoleRepository, UserRepository } from 'src/model/repositories';
import { FindOperator, FindOperators } from 'typeorm';
@Injectable() @Injectable()
export class UserService { export class UserService {
constructor( constructor(
private readonly userRepo: UserRepository, private readonly userRepo: UserRepository,
private readonly roleRepo: RoleRepository, private readonly roleRepo: RoleRepository,
private readonly systemRepo: KeySystemRepository,
private readonly systemActivityRepo: ActivityRepository
) {} ) {}
getAllUsers(): Promise<User[]> { getAllUsers(): Promise<User[]> {
@@ -28,4 +31,30 @@ export class UserService {
return this.userRepo.deleteUserById(id); return this.userRepo.deleteUserById(id);
} }
async getUserStats(user: IUser) {
const systems = await this.systemRepo.find({
where: { managers: { id: user.id }},
relations: ['cylinders', 'cylinders.keys'],
});
const cylinders = systems.map(s => s.cylinders).flat()
const keys = cylinders.map(c => c.keys).flat().map(k => k.id);
const keycount = [...new Set(keys)]
return {
keys: keycount.length,
cylinders: cylinders.length,
systems: systems.length
}
}
async getActivitiesforUser(user: IUser) {
const activities = await this.systemActivityRepo.find({
where: { system: { managers: { id: user.id } }},
order: { createdAt: 'DESC' },
relations: ['user']
});
return activities;
}
} }

View File

@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { import {
Activity,
Customer, Customer,
Cylinder, Cylinder,
Key, Key,
@@ -12,6 +13,7 @@ import {
} from 'src/model/entitites'; } from 'src/model/entitites';
import { KeySystem } from 'src/model/entitites/system.entity'; import { KeySystem } from 'src/model/entitites/system.entity';
import { import {
ActivityRepository,
CustomerRepository, CustomerRepository,
CylinderRepository, CylinderRepository,
KeyActivityRepository, KeyActivityRepository,
@@ -33,6 +35,7 @@ const ENTITIES = [
KeyActivity, KeyActivity,
Customer, Customer,
KeyHandout, KeyHandout,
Activity,
]; ];
const REPOSITORIES = [ const REPOSITORIES = [
UserRepository, UserRepository,
@@ -44,6 +47,7 @@ const REPOSITORIES = [
KeyActivityRepository, KeyActivityRepository,
CustomerRepository, CustomerRepository,
KeyHandoutRepository, KeyHandoutRepository,
ActivityRepository,
]; ];
@Module({ @Module({

View File

@@ -0,0 +1,67 @@
<div class="dashboard-container">
<!-- Welcome Section -->
<div class="welcome-section">
<h1>Willkommen bei Keyvault Pro</h1>
<p>Verwalte deine Schlüssel und Systeme</p>
</div>
<!-- Quick Stats Cards -->
<div class="stats-grid">
<mat-card class="stat-card">
<mat-card-header>
<mat-icon>key</mat-icon>
<mat-card-title>Schlüssel</mat-card-title>
</mat-card-header>
<mat-card-content>
<span class="stat-number">{{ keyCount }}</span>
<p>Aktive Schlüssel</p>
</mat-card-content>
<mat-card-actions>
<button mat-button routerLink="/keys">Verwalten</button>
</mat-card-actions>
</mat-card>
<mat-card class="stat-card">
<mat-card-header>
<mat-icon>lock</mat-icon>
<mat-card-title>Zylinder</mat-card-title>
</mat-card-header>
<mat-card-content>
<span class="stat-number">{{ cylinderCount }}</span>
<p>Registrierte Zylinder</p>
</mat-card-content>
<mat-card-actions>
<button mat-button routerLink="/cylinders">Verwalten</button>
</mat-card-actions>
</mat-card>
<mat-card class="stat-card">
<mat-card-header>
<mat-icon>admin_panel_settings</mat-icon>
<mat-card-title>Systeme</mat-card-title>
</mat-card-header>
<mat-card-content>
<span class="stat-number">{{ systemCount }}</span>
<p>Aktive Systeme</p>
</mat-card-content>
<mat-card-actions>
<button mat-button routerLink="/systems">Verwalten</button>
</mat-card-actions>
</mat-card>
</div>
<!-- Recent Activity Section -->
<div class="recent-activity">
<h2>Letzte Aktivitäten</h2>
<mat-card>
<mat-card-content>
<ag-grid-angular
style="width: 100%; height: 300px;"
[gridOptions]="gridOptions"
(gridReady)="onGridReady($event)"
>
</ag-grid-angular>
</mat-card-content>
</mat-card>
</div>
</div>

View File

@@ -0,0 +1,85 @@
.dashboard-container {
padding: 24px;
height: calc(100% - 48px);
display: flex;
flex-direction: column;
gap: 24px;
.welcome-section {
h1 {
font-size: 24px;
margin: 0;
color: #333;
}
p {
margin: 8px 0 0 0;
color: #666;
}
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
.stat-card {
background: white;
border-radius: 8px;
mat-card-header {
padding: 16px 16px 0;
gap: 12px;
mat-icon {
color: #2D6B05;
}
}
mat-card-content {
padding: 16px;
text-align: center;
.stat-number {
font-size: 36px;
font-weight: 500;
color: #2D6B05;
}
p {
margin: 8px 0 0;
color: #666;
}
}
mat-card-actions {
padding: 8px;
display: flex;
justify-content: center;
}
}
}
.recent-activity {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
min-height: 0;
h2 {
margin: 0;
font-size: 20px;
color: #333;
}
mat-card {
flex: 1;
min-height: 0;
mat-card-content {
height: 100%;
padding: 16px;
}
}
}
}

View File

@@ -1,15 +1,62 @@
import { Component } from '@angular/core'; import { DatePipe } from '@angular/common';
import { Component, inject } from '@angular/core';
import { AgGridAngular } from 'ag-grid-angular'; import { AgGridAngular } from 'ag-grid-angular';
import { ColDef } from 'ag-grid-community'; // Column Definition Type Interface import { ColDef, GridOptions, GridReadyEvent } from 'ag-grid-community'; // Column Definition Type Interface
import { ApiService } from '../../shared/api.service';
import { MatIconModule } from '@angular/material/icon';
import {MatCardModule} from '@angular/material/card';
import { RouterModule } from '@angular/router';
import { AgLoadingComponent } from '../../shared/ag-grid/components/ag-loading/ag-loading.component';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
standalone: true, standalone: true,
imports: [AgGridAngular], imports: [AgGridAngular, MatIconModule, AgGridAngular, MatCardModule, RouterModule],
providers: [DatePipe],
templateUrl: './dashboard.component.html', templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss' styleUrl: './dashboard.component.scss'
}) })
export class DashboardComponent { export class DashboardComponent {
private api = inject(ApiService);
private datePipe = inject(DatePipe);
gridOptions: GridOptions = {
columnDefs: [
{ field: 'createdAt', headerName: 'Zeitpunkt', width: 160,
cellRenderer: (data: any) => this.datePipe.transform(new Date(data.value)) },
{ field: 'message', headerName: 'Aktion', flex: 1 },
{ field: 'user', headerName: 'Benutzer', flex: 0,
cellRenderer: (data: any) => `${data.value?.firstName} ${data.value?.lastName}` }
],
pagination: true,
paginationPageSize: 10,
loading: true,
loadingOverlayComponent: AgLoadingComponent
};
// Stats
keyCount = 0;
cylinderCount = 0;
systemCount = 0;
ngOnInit() {
this.loadStats();
}
loadStats() {
this.api.getStats().subscribe(stats => {
this.keyCount = stats.keys;
this.cylinderCount = stats.cylinders;
this.systemCount = stats.systems;
});
}
onGridReady(params: GridReadyEvent) {
params.api.setGridOption("loading", true);
this.api.getActivities().subscribe(activity => {
params.api.setGridOption("rowData", activity)
params.api.setGridOption("loading", false);
});
}
} }

View File

@@ -0,0 +1,22 @@
<h2 mat-dialog-title>Manager entfernen</h2>
<mat-dialog-content>
<div class="warning-message">
<mat-icon color="warn">warning</mat-icon>
<p>
<b>{{ manager.firstName }} {{ manager.lastName }}</b> wirklich entfernen?
Der Benutzer hat dann keinen Zugriff mehr auf das System mit allen seinen Daten.
</p>
<p class="additional-info">
<!-- Additional information -->
<small>Diese Aktion kann nicht rückgängig gemacht werden.</small>
</p>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button [mat-dialog-close]="false">Abbrechen</button>
<button mat-raised-button color="warn" [mat-dialog-close]="true">
<mat-icon>delete</mat-icon>
Entfernen
</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,19 @@
.warning-message {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 1rem;
mat-icon {
font-size: 48px;
height: 48px;
width: 48px;
margin-bottom: 1rem;
}
.additional-info {
margin-top: 1rem;
color: rgba(0, 0, 0, 0.6);
}
}

View File

@@ -0,0 +1,34 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RemoveManagerPopupComponent } from './remove-manager-popup.component';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
describe('RemoveManagerPopupComponent', () => {
let component: RemoveManagerPopupComponent;
let fixture: ComponentFixture<RemoveManagerPopupComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RemoveManagerPopupComponent],
providers: [
{
provide: MatDialogRef,
useValue: []
},
{
provide: MAT_DIALOG_DATA,
useValue: ''
},
]
})
.compileComponents();
fixture = TestBed.createComponent(RemoveManagerPopupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { IUser } from '../../../../model/interface/user.interface';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-remove-manager-popup',
standalone: true,
imports: [MatButtonModule, MatDialogModule, MatIconModule],
templateUrl: './remove-manager-popup.component.html',
styleUrl: './remove-manager-popup.component.scss'
})
export class RemoveManagerPopupComponent {
readonly dialogRef = inject(MatDialogRef<RemoveManagerPopupComponent>);
readonly manager = inject<IUser>(MAT_DIALOG_DATA);
}

View File

@@ -11,13 +11,20 @@
/> />
</div> </div>
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center p-4 bg-gray-50 rounded-md shadow-sm">
<mat-form-field class="flex-1"> <mat-form-field class="flex-1">
<mat-label>Email</mat-label> <mat-label>Email</mat-label>
<input matInput [(ngModel)]="email"> <input matInput [(ngModel)]="email" placeholder="beispiel@email.com">
<mat-hint>Emailadresse des neuen Users eingeben</mat-hint> <mat-hint>Emailadresse des neuen Users eingeben</mat-hint>
<mat-icon matPrefix class="text-gray-400 mr-2">email</mat-icon>
</mat-form-field> </mat-form-field>
<button mat-button (click)="addManagerByEmail()" [disabled]="email == '' || email == null">Hinzufügen</button> <button mat-raised-button
color="primary"
(click)="addManagerByEmail()"
[disabled]="email == '' || email == null">
<mat-icon>person_add</mat-icon>
Hinzufügen
</button>
</div> </div>
</div> </div>
</mat-dialog-content> </mat-dialog-content>

View File

@@ -1,3 +1,31 @@
:host { :host {
min-height: 500px; min-height: 500px;
}
::ng-deep .ag-theme-alpine {
--ag-row-hover-color: rgb(243 244 246);
--ag-selected-row-background-color: rgb(229 231 235);
}
.p-4 {
padding: 1rem;
}
.bg-gray-50 {
background-color: rgb(249 250 251);
}
.rounded-md {
border-radius: 0.375rem;
}
.shadow-sm {
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
.text-gray-400 {
color: rgb(156 163 175);
}
.mr-2 {
margin-right: 0.5rem;
} }

View File

@@ -12,17 +12,20 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AuthService } from '../../../../core/auth/auth.service'; import { AuthService } from '../../../../core/auth/auth.service';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { RemoveManagerPopupComponent } from '../remove-manager-popup/remove-manager-popup.component';
import { IUser } from '../../../../model/interface/user.interface';
import { MatIconModule } from '@angular/material/icon';
@Component({ @Component({
selector: 'app-system-manager', selector: 'app-system-manager',
standalone: true, standalone: true,
imports: [AgGridAngular, MatDialogModule, MatButtonModule, MatInputModule, MatFormFieldModule, CommonModule, FormsModule], imports: [AgGridAngular, MatDialogModule, MatButtonModule, MatInputModule, MatFormFieldModule, CommonModule, FormsModule, MatIconModule],
templateUrl: './system-manager.component.html', templateUrl: './system-manager.component.html',
styleUrl: './system-manager.component.scss' styleUrl: './system-manager.component.scss'
}) })
export class SystemManagerComponent { export class SystemManagerComponent {
gridApi!: GridApi; gridApi!: GridApi;
gridOptions: GridOptions = HELPER.getGridOptions(); gridOptions: GridOptions = HELPER.getGridOptions();
readonly dialogRef = inject(MatDialogRef<SystemManagerComponent>); readonly dialogRef = inject(MatDialogRef<SystemManagerComponent>);
@@ -51,7 +54,7 @@ export class SystemManagerComponent {
return; return;
} }
if (event.colDef.colId == 'delete') { if (event.colDef.colId == 'delete') {
this.removeManagerByEmail(event.data.username); this.removeManager(event.data);
} }
}, },
} }
@@ -95,12 +98,25 @@ export class SystemManagerComponent {
}); });
} }
removeManagerByEmail(username: string) { openPopup(manager: IUser): Promise<boolean> {
const resume = confirm('Soll der Manager wirklich entfernt werden?'); return new Promise(resolve => {
if (!resume) return; this.dialog.open(RemoveManagerPopupComponent, {
data: manager,
disableClose: false,
autoFocus: false,
}).afterClosed().subscribe({
next: (n: boolean) => resolve(n)
})
})
}
async removeManager(manager: IUser) {
const resume = await this.openPopup(manager);
if (!resume) return;
this.gridApi.setGridOption("loading", true); this.gridApi.setGridOption("loading", true);
this.api.removeManager(this.system.id, username) this.api.removeManager(this.system.id, manager.username)
.pipe( .pipe(
this.toast.observe({ this.toast.observe({
loading: 'Manager entfernen', loading: 'Manager entfernen',

View File

@@ -43,13 +43,5 @@ export class AgSystemManagerComponent implements ICellRendererAngularComp {
height: "70vh", height: "70vh",
disableClose: false disableClose: false
}) })
// ref.afterClosed().subscribe({
// next: n => {
// if (n) {
// this.deleteThisKey();
// }
// }
// })
} }
} }

View File

@@ -103,4 +103,12 @@ export class ApiService {
action: 'remove' action: 'remove'
}); });
} }
getStats(): Observable<{ keys: number, cylinders: number, systems: number }> {
return this.http.get<{ keys: number, cylinders: number, systems: number }>('api/user/stats');
}
getActivities(): Observable<any[]> {
return this.http.get<any[]>('api/user/activities');
}
} }