diff --git a/api/src/model/entitites/activity.entity.ts b/api/src/model/entitites/activity.entity.ts new file mode 100644 index 0000000..b2b12c5 --- /dev/null +++ b/api/src/model/entitites/activity.entity.ts @@ -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; +} diff --git a/api/src/model/entitites/index.ts b/api/src/model/entitites/index.ts index 6751e80..abea36b 100644 --- a/api/src/model/entitites/index.ts +++ b/api/src/model/entitites/index.ts @@ -6,3 +6,4 @@ export * from './key.entity'; export * from './key_activity.entity'; export * from './customer.entity'; export * from './key-handout.entity'; +export * from './activity.entity'; diff --git a/api/src/model/entitites/key-handout.entity.ts b/api/src/model/entitites/key-handout.entity.ts index 7d3dbd4..32e1bb8 100644 --- a/api/src/model/entitites/key-handout.entity.ts +++ b/api/src/model/entitites/key-handout.entity.ts @@ -8,6 +8,7 @@ import { } from 'typeorm'; import { Key } from './key.entity'; import { Customer } from './customer.entity'; +import { User } from './user.entity'; @Entity() export class KeyHandout { @@ -29,6 +30,9 @@ export class KeyHandout { @CreateDateColumn() created: Date; + @ManyToOne(() => User) + user: User; + @BeforeInsert() insertTimestamp() { if (this.timestamp == null) { diff --git a/api/src/model/entitites/key.entity.ts b/api/src/model/entitites/key.entity.ts index 9a98efa..cb9672d 100644 --- a/api/src/model/entitites/key.entity.ts +++ b/api/src/model/entitites/key.entity.ts @@ -6,12 +6,14 @@ import { JoinTable, ManyToMany, ManyToOne, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; import { Cylinder } from './cylinder.entity'; import { IKey } from '../interface/key.interface'; import { Customer } from './customer.entity'; +import { KeyHandout } from './key-handout.entity'; @Entity() export class Key implements IKey { @@ -45,4 +47,7 @@ export class Key implements IKey { @DeleteDateColumn() deletedAt: Date; + + @OneToMany(() => KeyHandout, (handout) => handout.key) + handouts: KeyHandout[]; } diff --git a/api/src/model/repositories/activity.repository.ts b/api/src/model/repositories/activity.repository.ts new file mode 100644 index 0000000..8ba1ad5 --- /dev/null +++ b/api/src/model/repositories/activity.repository.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { Repository, DataSource } from 'typeorm'; +import { Activity } from '../entitites'; + +@Injectable() +export class ActivityRepository extends Repository { + constructor(dataSource: DataSource) { + super(Activity, dataSource.createEntityManager()); + } +} diff --git a/api/src/model/repositories/index.ts b/api/src/model/repositories/index.ts index 3d023ad..d581d63 100644 --- a/api/src/model/repositories/index.ts +++ b/api/src/model/repositories/index.ts @@ -6,3 +6,4 @@ export * from './cylinder.repository'; export * from './key.repository'; export * from './key_activity.repository'; export * from './customer.repository'; +export * from './activity.repository'; diff --git a/api/src/modules/key/key.service.ts b/api/src/modules/key/key.service.ts index d03f8ee..31ea8a0 100644 --- a/api/src/modules/key/key.service.ts +++ b/api/src/modules/key/key.service.ts @@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { Customer, Cylinder, Key, User } from 'src/model/entitites'; import { IUser } from 'src/model/interface'; import { + ActivityRepository, CylinderRepository, KeyActivityRepository, KeyRepository, @@ -18,6 +19,7 @@ export class KeyService { private readonly systemRepo: KeySystemRepository, private activityRepo: KeyActivityRepository, private handoverRepo: KeyHandoutRepository, + private systemActivityRepo: ActivityRepository, ) {} async getUsersKeys(user: User): Promise { @@ -74,19 +76,30 @@ export class KeyService { async handoverKey(user: IUser, data: any, keyID: string) { const key: Key = await this.keyrepository.findOneOrFail({ where: { id: keyID, cylinder: { system: { managers: { id: user.id } } } }, + relations: [ 'cylinder', 'cylinder.system' ] }); key.handedOut = data.direction == 'out'; this.keyrepository.save(key); - return this.handoverRepo.save( + const res = await this.handoverRepo.save( this.handoverRepo.create({ customer: data.customer, direction: data.direction, timestamp: data.timestamp, 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) { @@ -121,8 +134,14 @@ export class KeyService { } } - createKey(user: User, key: any) { - return this.keyrepository.save(this.keyrepository.create(key)); + async createKey(user: User, key: any) { + 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) { diff --git a/api/src/modules/system/system.controller.ts b/api/src/modules/system/system.controller.ts index 8f83056..d61b723 100644 --- a/api/src/modules/system/system.controller.ts +++ b/api/src/modules/system/system.controller.ts @@ -32,17 +32,21 @@ export class SystemController { findAll(@Req() req: AuthenticatedRequest) { 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') findOne(@Param('id') id: string) { return this.systemService.findOne(id); } - @Get(':id/manager') - getManagers(@Param('id') id: string) { - return this.systemService.getManagers(id); - } - @Patch(':id') update(@Param('id') id: string, @Body() updateSystemDto: UpdateSystemDto) { return this.systemService.update(id, updateSystemDto); @@ -52,9 +56,4 @@ export class SystemController { remove(@Param('id') id: string) { return this.systemService.remove(id); } - - @Post(':id/manager') - manaManager(@Param('id') id: string, @Body() body: any){ - return this.systemService.manageManagers(id, body); - } } diff --git a/api/src/modules/system/system.service.ts b/api/src/modules/system/system.service.ts index 4cf7622..3c93ec0 100644 --- a/api/src/modules/system/system.service.ts +++ b/api/src/modules/system/system.service.ts @@ -3,6 +3,7 @@ import { CreateSystemDto } from './dto/create-system.dto'; import { UpdateSystemDto } from './dto/update-system.dto'; import { KeySystemRepository, UserRepository } from 'src/model/repositories'; import { User } from 'src/model/entitites'; +import { IUser } from 'src/model/interface'; @Injectable() export class SystemService { diff --git a/api/src/modules/user/user.controller.ts b/api/src/modules/user/user.controller.ts index c3c54a1..9a8b7ea 100644 --- a/api/src/modules/user/user.controller.ts +++ b/api/src/modules/user/user.controller.ts @@ -22,6 +22,16 @@ export class UserController { 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') deleteUserWithId(@Req() req: AuthenticatedRequest, @Param('id') id: string) { if (req.user.role.name != "admin") { diff --git a/api/src/modules/user/user.service.ts b/api/src/modules/user/user.service.ts index de41de7..7d39f38 100644 --- a/api/src/modules/user/user.service.ts +++ b/api/src/modules/user/user.service.ts @@ -1,13 +1,16 @@ import { Injectable } from '@nestjs/common'; import { User } from 'src/model/entitites'; 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() export class UserService { constructor( private readonly userRepo: UserRepository, private readonly roleRepo: RoleRepository, + private readonly systemRepo: KeySystemRepository, + private readonly systemActivityRepo: ActivityRepository ) {} getAllUsers(): Promise { @@ -28,4 +31,30 @@ export class UserService { 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; + } } diff --git a/api/src/shared/database/database.module.ts b/api/src/shared/database/database.module.ts index d33150b..019acf7 100644 --- a/api/src/shared/database/database.module.ts +++ b/api/src/shared/database/database.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { + Activity, Customer, Cylinder, Key, @@ -12,6 +13,7 @@ import { } from 'src/model/entitites'; import { KeySystem } from 'src/model/entitites/system.entity'; import { + ActivityRepository, CustomerRepository, CylinderRepository, KeyActivityRepository, @@ -33,6 +35,7 @@ const ENTITIES = [ KeyActivity, Customer, KeyHandout, + Activity, ]; const REPOSITORIES = [ UserRepository, @@ -44,6 +47,7 @@ const REPOSITORIES = [ KeyActivityRepository, CustomerRepository, KeyHandoutRepository, + ActivityRepository, ]; @Module({ diff --git a/client/src/app/modules/dashboard/dashboard.component.html b/client/src/app/modules/dashboard/dashboard.component.html index e69de29..365f96b 100644 --- a/client/src/app/modules/dashboard/dashboard.component.html +++ b/client/src/app/modules/dashboard/dashboard.component.html @@ -0,0 +1,67 @@ +
+ +
+

Willkommen bei Keyvault Pro

+

Verwalte deine Schlüssel und Systeme

+
+ + +
+ + + key + Schlüssel + + + {{ keyCount }} +

Aktive Schlüssel

+
+ + + +
+ + + + lock + Zylinder + + + {{ cylinderCount }} +

Registrierte Zylinder

+
+ + + +
+ + + + admin_panel_settings + Systeme + + + {{ systemCount }} +

Aktive Systeme

+
+ + + +
+
+ + +
+

Letzte Aktivitäten

+ + + + + + +
+
\ No newline at end of file diff --git a/client/src/app/modules/dashboard/dashboard.component.scss b/client/src/app/modules/dashboard/dashboard.component.scss index e69de29..d629631 100644 --- a/client/src/app/modules/dashboard/dashboard.component.scss +++ b/client/src/app/modules/dashboard/dashboard.component.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/client/src/app/modules/dashboard/dashboard.component.ts b/client/src/app/modules/dashboard/dashboard.component.ts index 36dc5b2..d90bcd4 100644 --- a/client/src/app/modules/dashboard/dashboard.component.ts +++ b/client/src/app/modules/dashboard/dashboard.component.ts @@ -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 { 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({ selector: 'app-dashboard', standalone: true, - imports: [AgGridAngular], + imports: [AgGridAngular, MatIconModule, AgGridAngular, MatCardModule, RouterModule], + providers: [DatePipe], templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss' }) 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); + }); + } } diff --git a/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.html b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.html new file mode 100644 index 0000000..57bd3b6 --- /dev/null +++ b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.html @@ -0,0 +1,22 @@ + +

Manager entfernen

+ +
+ warning +

+ {{ manager.firstName }} {{ manager.lastName }} wirklich entfernen? + Der Benutzer hat dann keinen Zugriff mehr auf das System mit allen seinen Daten. +

+

+ + Diese Aktion kann nicht rückgängig gemacht werden. +

+
+
+ + + + \ No newline at end of file diff --git a/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.scss b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.scss new file mode 100644 index 0000000..aa03e62 --- /dev/null +++ b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.scss @@ -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); + } +} \ No newline at end of file diff --git a/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.spec.ts b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.spec.ts new file mode 100644 index 0000000..2156d47 --- /dev/null +++ b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.ts b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.ts new file mode 100644 index 0000000..d5df6b4 --- /dev/null +++ b/client/src/app/modules/system/components/remove-manager-popup/remove-manager-popup.component.ts @@ -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); + readonly manager = inject(MAT_DIALOG_DATA); + +} diff --git a/client/src/app/modules/system/components/system-manager/system-manager.component.html b/client/src/app/modules/system/components/system-manager/system-manager.component.html index 17fecf4..0ffd5b1 100644 --- a/client/src/app/modules/system/components/system-manager/system-manager.component.html +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.html @@ -11,13 +11,20 @@ /> -
+
Email - + Emailadresse des neuen Users eingeben + email - +
diff --git a/client/src/app/modules/system/components/system-manager/system-manager.component.scss b/client/src/app/modules/system/components/system-manager/system-manager.component.scss index 6ff7993..56afdbe 100644 --- a/client/src/app/modules/system/components/system-manager/system-manager.component.scss +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.scss @@ -1,3 +1,31 @@ :host { 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; } \ No newline at end of file diff --git a/client/src/app/modules/system/components/system-manager/system-manager.component.ts b/client/src/app/modules/system/components/system-manager/system-manager.component.ts index 9640801..59b821a 100644 --- a/client/src/app/modules/system/components/system-manager/system-manager.component.ts +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.ts @@ -12,17 +12,20 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { AuthService } from '../../../../core/auth/auth.service'; 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({ selector: 'app-system-manager', standalone: true, - imports: [AgGridAngular, MatDialogModule, MatButtonModule, MatInputModule, MatFormFieldModule, CommonModule, FormsModule], + imports: [AgGridAngular, MatDialogModule, MatButtonModule, MatInputModule, MatFormFieldModule, CommonModule, FormsModule, MatIconModule], templateUrl: './system-manager.component.html', styleUrl: './system-manager.component.scss' }) export class SystemManagerComponent { - gridApi!: GridApi; + gridApi!: GridApi; gridOptions: GridOptions = HELPER.getGridOptions(); readonly dialogRef = inject(MatDialogRef); @@ -51,7 +54,7 @@ export class SystemManagerComponent { return; } if (event.colDef.colId == 'delete') { - this.removeManagerByEmail(event.data.username); + this.removeManager(event.data); } }, } @@ -95,12 +98,25 @@ export class SystemManagerComponent { }); } - removeManagerByEmail(username: string) { - const resume = confirm('Soll der Manager wirklich entfernt werden?'); - if (!resume) return; + openPopup(manager: IUser): Promise { + return new Promise(resolve => { + 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.api.removeManager(this.system.id, username) + this.api.removeManager(this.system.id, manager.username) .pipe( this.toast.observe({ loading: 'Manager entfernen', diff --git a/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts index 6626f78..57e31c3 100644 --- a/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts +++ b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts @@ -43,13 +43,5 @@ export class AgSystemManagerComponent implements ICellRendererAngularComp { height: "70vh", disableClose: false }) - - // ref.afterClosed().subscribe({ - // next: n => { - // if (n) { - // this.deleteThisKey(); - // } - // } - // }) } } diff --git a/client/src/app/shared/api.service.ts b/client/src/app/shared/api.service.ts index 4810258..0d4ce93 100644 --- a/client/src/app/shared/api.service.ts +++ b/client/src/app/shared/api.service.ts @@ -103,4 +103,12 @@ export class ApiService { 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 { + return this.http.get('api/user/activities'); + } }