diff --git a/api/src/model/entitites/key.entity.ts b/api/src/model/entitites/key.entity.ts index 20ab033..ab569cc 100644 --- a/api/src/model/entitites/key.entity.ts +++ b/api/src/model/entitites/key.entity.ts @@ -25,6 +25,9 @@ export class Key implements IKey { @Column({ name: 'handed_out', default: false }) handedOut: boolean; + @Column({ name: 'lost', default: false }) + keyLost: boolean; + @ManyToOne(() => Cylinder, (cylinder) => cylinder.keys) cylinder: Cylinder; diff --git a/api/src/model/entitites/role.entity.ts b/api/src/model/entitites/role.entity.ts index ffbbfdd..7333b69 100644 --- a/api/src/model/entitites/role.entity.ts +++ b/api/src/model/entitites/role.entity.ts @@ -16,4 +16,7 @@ export class Role { @Column({ nullable: true }) name: string; + + @Column({ name: 'is_default', default: false}) + defaultRole: boolean; } diff --git a/api/src/model/entitites/system.entity.ts b/api/src/model/entitites/system.entity.ts index e5ef465..a3f62d9 100644 --- a/api/src/model/entitites/system.entity.ts +++ b/api/src/model/entitites/system.entity.ts @@ -20,7 +20,7 @@ export class KeySystem implements IKeySystem { @CreateDateColumn({ name: 'created_at' }) createdAt: Date; - @Column({ nullable: false, unique: true }) + @Column({ nullable: false, unique: false }) name: string; @ManyToMany(() => User, (manager) => manager.systems) diff --git a/api/src/model/repositories/role.repository.ts b/api/src/model/repositories/role.repository.ts index 8f8007e..7179bdf 100644 --- a/api/src/model/repositories/role.repository.ts +++ b/api/src/model/repositories/role.repository.ts @@ -9,6 +9,6 @@ export class RoleRepository extends Repository { } getStandardRole(): Promise { - return this.findOne({ where: { name: 'user' } }); + return this.findOne({ where: { defaultRole: true } }); } } diff --git a/api/src/modules/key/key.service.ts b/api/src/modules/key/key.service.ts index caa0c83..d03f8ee 100644 --- a/api/src/modules/key/key.service.ts +++ b/api/src/modules/key/key.service.ts @@ -1,5 +1,5 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { Cylinder, Key, User } from 'src/model/entitites'; +import { Customer, Cylinder, Key, User } from 'src/model/entitites'; import { IUser } from 'src/model/interface'; import { CylinderRepository, @@ -21,10 +21,14 @@ export class KeyService { ) {} async getUsersKeys(user: User): Promise { - return this.keyrepository.find({ + const keys = await this.keyrepository.find({ where: { cylinder: { system: { managers: { id: user.id } } } }, relations: ['cylinder', 'cylinder.system', 'customer'], }); + for (let k of keys) { + k.customer = await this.getCustomerOfLastHandout(user, k.id); + } + return keys; } async updateKey(user: User, key: Key) { @@ -98,6 +102,25 @@ export class KeyService { }); } + async getCustomerOfLastHandout (user: User, keyID: string): Promise { + const handover = await this.handoverRepo.find({ + where: { + key: { cylinder: { system: { managers: { id: user.id } } }, id: keyID }, + }, + order: { + timestamp: { direction: 'DESC' }, + created: { direction: 'DESC' }, + }, + relations: ['customer'], + take: 1 + }); + if (handover.length == 1 && handover[0].direction == 'out') { + return handover[0].customer; + } else { + return null; + } + } + createKey(user: User, key: any) { return this.keyrepository.save(this.keyrepository.create(key)); } diff --git a/api/src/modules/system/system.controller.ts b/api/src/modules/system/system.controller.ts index 2b05524..366942e 100644 --- a/api/src/modules/system/system.controller.ts +++ b/api/src/modules/system/system.controller.ts @@ -38,6 +38,11 @@ export class SystemController { 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); diff --git a/api/src/modules/system/system.service.ts b/api/src/modules/system/system.service.ts index 5f5a499..8a41023 100644 --- a/api/src/modules/system/system.service.ts +++ b/api/src/modules/system/system.service.ts @@ -43,4 +43,13 @@ export class SystemService { const system = await this.systemRepo.findOne({ where: { id } }); return this.systemRepo.softRemove(system); } + + async getManagers(id: string) { + const system = await this.systemRepo.findOne({ + where: { id: id }, + relations: ['managers'] + }); + + return system.managers; + } } diff --git a/client/README.md b/client/README.md index 93a8279..1846f81 100644 --- a/client/README.md +++ b/client/README.md @@ -1,27 +1,2 @@ -# Client - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.2. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. +## Tailwind: +https://tailwindcss.com/docs/installation \ No newline at end of file diff --git a/client/mocks/services/mock.api.service.ts b/client/mocks/services/mock.api.service.ts index 27ab216..dc0a5ee 100644 --- a/client/mocks/services/mock.api.service.ts +++ b/client/mocks/services/mock.api.service.ts @@ -8,4 +8,7 @@ export class MockApiService { of([]) ); getKeyArchive = jest.fn().mockReturnValue(of([{ name: 'Key 1' }])); + getSystems = jest.fn().mockReturnValue(of([{ name: 'System 1' }])); + createSystem = jest.fn().mockReturnValue(of({name: 'Test System 1'})); + getSystemManagers = jest.fn().mockReturnValue(of([{ name: 'System Manager 1' }, { name: 'System Manager 2' }])); } \ No newline at end of file diff --git a/client/src/app/core/layout/layout.component.html b/client/src/app/core/layout/layout.component.html index 523af4a..9d76345 100644 --- a/client/src/app/core/layout/layout.component.html +++ b/client/src/app/core/layout/layout.component.html @@ -16,7 +16,9 @@ - + @if (isAdmin) { + + } diff --git a/client/src/app/core/layout/layout.component.ts b/client/src/app/core/layout/layout.component.ts index 1b2d958..57a3de2 100644 --- a/client/src/app/core/layout/layout.component.ts +++ b/client/src/app/core/layout/layout.component.ts @@ -23,4 +23,8 @@ export class LayoutComponent { get userName(): string { return `${this.authService.user.firstName} ${this.authService.user.lastName}` } + + get isAdmin(): boolean { + return this.authService.user.role === 'admin'; + } } diff --git a/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts b/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts index 5d46749..81516cb 100644 --- a/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts +++ b/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts @@ -158,7 +158,8 @@ export class HandoverDialogComponent { this._bottomSheet.open(BottomSheetCreateCustomer).afterDismissed().subscribe({ next: async n => { if (!n) { return; } - await this.createCustomer(val.customer); + const customer = await this.createCustomer(val.customer); + dto.customer = customer; this.saveIt(dto); } }) @@ -168,7 +169,7 @@ export class HandoverDialogComponent { } saveIt(data: any) { - this.api.handoverKey(data) + this.api.handoverKey(data) .pipe( this.toast.observe({ loading: 'Speichern...', @@ -178,17 +179,20 @@ export class HandoverDialogComponent { ) .subscribe({ next: n => { - this.dialogRef.close(data.direction == 'out') + this.dialogRef.close(data.direction == 'out'); } }) } - createCustomer(name: string) { + createCustomer(name: string): Promise { this.isLoading = true; - this.api.createCustomer({ name, system: this.data.cylinder.system}).subscribe({ - next: n => { - this.isLoading = false; - } + return new Promise((resolve, reject) => { + this.api.createCustomer({ name, system: this.data.cylinder.system}).subscribe({ + next: n => { + this.isLoading = false; + resolve(n); + } + }) }) } diff --git a/client/src/app/modules/keys/create/create.component.html b/client/src/app/modules/keys/create/create.component.html index b10bf2a..0bd1bfb 100644 --- a/client/src/app/modules/keys/create/create.component.html +++ b/client/src/app/modules/keys/create/create.component.html @@ -14,7 +14,7 @@ Schlüsselnummer - + Nummer auf dem Schlüssel diff --git a/client/src/app/modules/keys/keys.component.scss b/client/src/app/modules/keys/keys.component.scss index ae1108a..e69de29 100644 --- a/client/src/app/modules/keys/keys.component.scss +++ b/client/src/app/modules/keys/keys.component.scss @@ -1,7 +0,0 @@ -.floating-btn-container { - position: absolute; - bottom: 48px; - right: 24px; - display: flex; - gap: 12px; -} \ No newline at end of file diff --git a/client/src/app/modules/keys/keys.component.ts b/client/src/app/modules/keys/keys.component.ts index c14a33f..00a0ba7 100644 --- a/client/src/app/modules/keys/keys.component.ts +++ b/client/src/app/modules/keys/keys.component.ts @@ -52,6 +52,7 @@ export class KeysComponent { cellEditorPopup: false }, { colId: 'system', field: 'cylinder.system' , headerName: 'Schließanlage', flex: 1, editable: false, filter: true, cellRenderer: (data: any) => {return data.value?.name} }, + { colId: 'customer', field: 'customer' , headerName: 'Kunde', flex: 1, editable: false, filter: true, cellRenderer: (data: any) => {return data.value?.name} }, { field: 'createdAt' , headerName: 'Erstellt' 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 new file mode 100644 index 0000000..72e8fe1 --- /dev/null +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.html @@ -0,0 +1,14 @@ + + +

Manager

+ + + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000..6ff7993 --- /dev/null +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.scss @@ -0,0 +1,3 @@ +:host { + min-height: 500px; +} \ No newline at end of file diff --git a/client/src/app/modules/system/components/system-manager/system-manager.component.spec.ts b/client/src/app/modules/system/components/system-manager/system-manager.component.spec.ts new file mode 100644 index 0000000..532e7aa --- /dev/null +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.spec.ts @@ -0,0 +1,64 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SystemManagerComponent } from './system-manager.component'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { AgGridAngular } from 'ag-grid-angular'; +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'; + + + +describe('SystemManagerComponent', () => { + let component: SystemManagerComponent; + let fixture: ComponentFixture; + let api: ApiService; + + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SystemManagerComponent, AgGridAngular, MatDialogModule], + providers: [ + HotToastService, + { provide: ApiService, useClass: MockApiService }, + { + provide: MatDialogRef, + useValue: [] + }, + { + provide: MAT_DIALOG_DATA, + useValue: [] + } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SystemManagerComponent); + component = fixture.componentInstance; + api = component['api'] + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize gridApi and gridColumnApi on gridReady and fill data', () => { + // Mock des GridReadyEvent + let mockData = [{ id: 1, name: 'Test' }]; + const mockGridReadyEvent: GridReadyEvent = { + api: { setGridOption: jest.fn() }, + columnApi: { someColumnApiMethod: jest.fn() }, + type: 'gridReady', + } as any; + + // Methode aufrufen + component.onGridReady(mockGridReadyEvent); + + // Assertions + expect(component.gridApi).toBe(mockGridReadyEvent.api); + expect(api.getSystemManagers).toHaveBeenCalled(); + expect(component.gridApi.setGridOption).toHaveBeenCalled(); + }); +}); \ 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 new file mode 100644 index 0000000..c086a2d --- /dev/null +++ b/client/src/app/modules/system/components/system-manager/system-manager.component.ts @@ -0,0 +1,50 @@ +import { Component, inject } from '@angular/core'; +import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'; +import { HELPER } from '../../../../shared/helper.service'; +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'; + +@Component({ + selector: 'app-system-manager', + standalone: true, + imports: [AgGridAngular, MatDialogModule], + templateUrl: './system-manager.component.html', + styleUrl: './system-manager.component.scss' +}) +export class SystemManagerComponent { + + gridApi!: GridApi; + + gridOptions: GridOptions = HELPER.getGridOptions(); + readonly dialogRef = inject(MatDialogRef); + readonly system = inject(MAT_DIALOG_DATA); + + private api: ApiService = inject(ApiService); + private dialog: MatDialog = inject(MatDialog); + private toast = inject(HotToastService); + + + 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}, + ] + } + + + onGridReady(params: GridReadyEvent) { + this.gridApi = params.api; + this.loadManagers(); + } + + loadManagers() { + this.api.getSystemManagers(this.system.id).subscribe({ + next: n => { + this.gridApi.setGridOption("rowData", n); + this.gridApi.setGridOption("loading", false); + } + }) + } +} diff --git a/client/src/app/modules/system/create/create.component.html b/client/src/app/modules/system/create/create.component.html new file mode 100644 index 0000000..73b0df1 --- /dev/null +++ b/client/src/app/modules/system/create/create.component.html @@ -0,0 +1,19 @@ +

Neuen Schlüssel anlegen

+ +
+ + + Name + + @if ((createForm.controls.name.value || '').length > 20) { + {{ (createForm.controls.name.value || '').length }} / 100 Zeichen + } @else { + Wie soll der Schlüssel heißen? + } + +
+
+ + + + \ No newline at end of file diff --git a/client/src/app/modules/system/create/create.component.scss b/client/src/app/modules/system/create/create.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/modules/system/create/create.component.spec.ts b/client/src/app/modules/system/create/create.component.spec.ts new file mode 100644 index 0000000..7de9e8b --- /dev/null +++ b/client/src/app/modules/system/create/create.component.spec.ts @@ -0,0 +1,66 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { CreateSystemComponent } from './create.component'; +import { ApiService } from '../../../shared/api.service'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { of, throwError } from 'rxjs'; +import { HotToastService } from '@ngxpert/hot-toast'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MockApiService } from '../../../../../mocks/services/mock.api.service'; + +describe('CreateComponent', () => { + let component: CreateSystemComponent; + let fixture: ComponentFixture; + let apiService: ApiService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CreateSystemComponent, NoopAnimationsModule, FormsModule, ReactiveFormsModule], + providers: [ + HotToastService, + { provide: ApiService, useClass: MockApiService }, + { + provide: MatDialogRef, + useValue: [] + }, + { + provide: MAT_DIALOG_DATA, + useValue: [] + } + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CreateSystemComponent); + component = fixture.componentInstance; + apiService = component['api']; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call apiService.createSystem when createSystem is called', () => { + expect(apiService.createSystem).not.toHaveBeenCalled(); + component.createForm.setValue({ name: 'Test System' }); + component.save(); + expect(apiService.createSystem).toHaveBeenCalledWith({ name: 'Test System' }); + }); + + it('should handle success response correctly', () => { + jest.spyOn(apiService, 'createSystem').mockReturnValue(of({})); + const toastSpy = jest.spyOn(component['toast'], 'observe'); + component.createForm.setValue({ name: 'Test System' }); + component.save(); + expect(toastSpy).toHaveBeenCalled(); + }); + + it('should handle error response correctly', () => { + jest.spyOn(apiService, 'createSystem').mockReturnValue(throwError(() => new Error('Test Error'))); + const toastSpy = jest.spyOn(component['toast'], 'observe'); + component.save(); + expect(toastSpy).toHaveBeenCalled(); + }); +}); + diff --git a/client/src/app/modules/system/create/create.component.ts b/client/src/app/modules/system/create/create.component.ts new file mode 100644 index 0000000..196501d --- /dev/null +++ b/client/src/app/modules/system/create/create.component.ts @@ -0,0 +1,43 @@ +import { Component, inject } from '@angular/core'; +import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { HotToastService } from '@ngxpert/hot-toast'; +import { ApiService } from '../../../shared/api.service'; +import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; + +@Component({ + selector: 'app-create', + standalone: true, + imports: [MatDialogModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatFormFieldModule, MatInputModule, MatDialogModule], + templateUrl: './create.component.html', + styleUrl: './create.component.scss' +}) +export class CreateSystemComponent { + private api: ApiService = inject(ApiService); + private toast: HotToastService = inject(HotToastService); + readonly dialogRef = inject(MatDialogRef); + + + createForm = new FormGroup({ + name: new FormControl(null, Validators.required), + }) + + + save() { + this.api.createSystem(this.createForm.value as any) + .pipe( + this.toast.observe({ + error: 'Konnte nicht angelegt werden...', + loading: 'Speichern...', + success: 'Gespeichert' + })) + .subscribe({ + next: (sys) => { + this.dialogRef.close(sys); + }, + error: e => {} + }); + } +} diff --git a/client/src/app/modules/system/system.component.html b/client/src/app/modules/system/system.component.html index 112977c..83a4783 100644 --- a/client/src/app/modules/system/system.component.html +++ b/client/src/app/modules/system/system.component.html @@ -2,4 +2,8 @@ style="width: 100%; height: 100%;" (gridReady)="onGridReady($event)" [gridOptions]="gridOptions!" -/> \ No newline at end of file +/> + +
+ +
\ No newline at end of file diff --git a/client/src/app/modules/system/system.component.ts b/client/src/app/modules/system/system.component.ts index e0a58e6..f1a22c2 100644 --- a/client/src/app/modules/system/system.component.ts +++ b/client/src/app/modules/system/system.component.ts @@ -4,11 +4,15 @@ import { AgGridAngular } from 'ag-grid-angular'; import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'; import { ApiService } from '../../shared/api.service'; import { HELPER } from '../../shared/helper.service'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { CreateSystemComponent } from './create/create.component'; +import { AgSystemManagerComponent } from '../../shared/ag-grid/components/ag-system-manager/ag-system-manager.component'; @Component({ selector: 'app-system', standalone: true, - imports: [AgGridAngular], + imports: [AgGridAngular, MatButtonModule, MatDialogModule], providers: [DatePipe], templateUrl: './system.component.html', styleUrl: './system.component.scss' @@ -16,6 +20,7 @@ import { HELPER } from '../../shared/helper.service'; export class SystemComponent { private api: ApiService = inject(ApiService); private datePipe = inject(DatePipe); + private dialog: MatDialog = inject(MatDialog); gridApi!: GridApi; @@ -26,6 +31,13 @@ export class SystemComponent { { colId: 'name', field: 'name', headerName: 'Name', sort: 'asc', flex: 1}, { field: 'createdAt', headerName: 'Angelegt', cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-' }, { field: 'updatedAt', headerName: 'Upgedated', cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-' }, + { + colId: 'actions' + , headerName: 'Aktionen' + , width: 120 + , cellRenderer: AgSystemManagerComponent + // , onCellClicked: (event) => { this.deleteKey(event.data.id)} + } ] } @@ -44,4 +56,16 @@ export class SystemComponent { this.loadSystems(); } + openCreateSystem() { + this.dialog.open(CreateSystemComponent).afterClosed().subscribe({ + next: sys => { + if (sys) { + let d = [...this.gridApi.getGridOption("rowData") || [], sys]; + this.gridApi.setGridOption("rowData", d); + this.loadSystems(); + } + + } + }) + } } diff --git a/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.html b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.html new file mode 100644 index 0000000..bc13d73 --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.scss b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.scss new file mode 100644 index 0000000..6bcd355 --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + gap: 12px; + } + + .manage { + background-image: url('../../../../../assets/img/manager.svg'); + } \ No newline at end of file diff --git a/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.spec.ts b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.spec.ts new file mode 100644 index 0000000..e62ba6e --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.spec.ts @@ -0,0 +1,47 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AgSystemManagerComponent } from './ag-system-manager.component'; +import { MatDialogModule } from '@angular/material/dialog'; +import { AgGridAngular } from 'ag-grid-angular'; +import { ApiService } from '../../../api.service'; +import { of } from 'rxjs'; +import { HotToastService } from '@ngxpert/hot-toast'; + +describe('AgSystemManagerComponent', () => { + let component: AgSystemManagerComponent; + let fixture: ComponentFixture; + let mockApiService: MockApiService; + + const mockHotToastService = { + observe: jest.fn().mockImplementation(() => ({ + loading: 'speichern...', + success: 'Änderungen gespeichert', + error: 'Änderungen konnten nicht gespeichert werden!', + subscribe: jest.fn().mockReturnValue(of([])) + })) + }; + + beforeEach(async () => { + mockApiService = new MockApiService(); + await TestBed.configureTestingModule({ + imports: [AgSystemManagerComponent, AgGridAngular, MatDialogModule], + providers: [ + { provide: ApiService, useValue: mockApiService }, + { provide: HotToastService, useValue: mockHotToastService }, + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AgSystemManagerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); + +class MockApiService { + getSystems = jest.fn(); +} \ No newline at end of file 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 new file mode 100644 index 0000000..66cd19d --- /dev/null +++ b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts @@ -0,0 +1,55 @@ +import { Component, inject } from '@angular/core'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { HotToastService } from '@ngxpert/hot-toast'; +import { ICellRendererAngularComp } from 'ag-grid-angular'; +import { ICellRendererParams } from 'ag-grid-community'; +import { ApiService } from '../../../api.service'; +import { SystemManagerComponent } from '../../../../modules/system/components/system-manager/system-manager.component'; + +@Component({ + selector: 'app-ag-system-manager', + standalone: true, + imports: [MatDialogModule, MatTooltipModule], + templateUrl: './ag-system-manager.component.html', + styleUrl: './ag-system-manager.component.scss' +}) +export class AgSystemManagerComponent implements ICellRendererAngularComp { + + private api: ApiService = inject(ApiService); + private dialog: MatDialog = inject(MatDialog); + private toast = inject(HotToastService); + + params!: ICellRendererParams; + system: any; + + agInit(params: ICellRendererParams): void { + this.params = params; + this.system = params.data; + } + refresh(params: ICellRendererParams): boolean { + return false; + } + + + openManager() { + const ref = this.dialog.open(SystemManagerComponent, { + data: this.system, + autoFocus: false, + maxHeight: "calc(100vh - 24px)", + maxWidth: "calc(100vw - 24px)", + width: "50vw", + minWidth: "300px", + height: "70vh", + disableClose: true + }) + + // 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 9e0cf2e..2731329 100644 --- a/client/src/app/shared/api.service.ts +++ b/client/src/app/shared/api.service.ts @@ -46,6 +46,10 @@ export class ApiService { return this.http.get('api/system'); } + getSystemManagers(id: string): Observable { + return this.http.get(`api/system/${id}/manager`); + } + handoverKey(data: any) { return this.http.post(`api/key/${data.key.id}/handover`, data); } diff --git a/client/src/app/shared/helper.service.ts b/client/src/app/shared/helper.service.ts index 57faaad..9481fdf 100644 --- a/client/src/app/shared/helper.service.ts +++ b/client/src/app/shared/helper.service.ts @@ -12,6 +12,7 @@ export class HELPER { loading: true, loadingOverlayComponent: AgLoadingComponent, rowHeight: 54, + } } } \ No newline at end of file diff --git a/client/src/assets/img/manager.svg b/client/src/assets/img/manager.svg new file mode 100644 index 0000000..7ff0736 --- /dev/null +++ b/client/src/assets/img/manager.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/styles.scss b/client/src/styles.scss index 07e34b1..f46ec27 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -83,4 +83,14 @@ html, body { } +div.ag-row { + // font-size: 16px !important; +} +.floating-btn-container { + position: absolute; + bottom: 48px; + right: 24px; + display: flex; + gap: 12px; +} \ No newline at end of file