Dashboard
This commit is contained in:
28
api/src/model/entitites/activity.entity.ts
Normal file
28
api/src/model/entitites/activity.entity.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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[];
|
||||||
}
|
}
|
||||||
|
|||||||
10
api/src/model/repositories/activity.repository.ts
Normal file
10
api/src/model/repositories/activity.repository.ts
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -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();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user