changes
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -16,4 +16,7 @@ export class Role {
|
||||
|
||||
@Column({ nullable: true })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'is_default', default: false})
|
||||
defaultRole: boolean;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -9,6 +9,6 @@ export class RoleRepository extends Repository<Role> {
|
||||
}
|
||||
|
||||
getStandardRole(): Promise<Role> {
|
||||
return this.findOne({ where: { name: 'user' } });
|
||||
return this.findOne({ where: { defaultRole: true } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Key[]> {
|
||||
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<Customer> {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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' }]));
|
||||
}
|
||||
@@ -16,7 +16,9 @@
|
||||
<button mat-button routerLink="/keys" routerLinkActive="mat-elevation-z1">Schlüssel</button>
|
||||
<button mat-button routerLink="/cylinders" routerLinkActive="mat-elevation-z1">Zylinder</button>
|
||||
<button mat-button routerLink="/systems" routerLinkActive="mat-elevation-z1">System</button>
|
||||
<button mat-button routerLink="/users" routerLinkActive="mat-elevation-z1">Alle User</button>
|
||||
@if (isAdmin) {
|
||||
<button mat-button routerLink="/users" routerLinkActive="mat-elevation-z1">Alle User</button>
|
||||
}
|
||||
</mat-drawer>
|
||||
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<any> {
|
||||
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);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Schlüsselnummer</mat-label>
|
||||
<input type="number" matInput formControlName="nr" min="0" max="999999999999">
|
||||
<input type="text" matInput formControlName="nr" maxlength="100">
|
||||
<mat-hint>Nummer auf dem Schlüssel</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.floating-btn-container {
|
||||
position: absolute;
|
||||
bottom: 48px;
|
||||
right: 24px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
<h2 mat-dialog-title>Manager</h2>
|
||||
<mat-dialog-content>
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
/>
|
||||
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button [mat-dialog-close]="true">Schließen</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
min-height: 500px;
|
||||
}
|
||||
@@ -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<SystemManagerComponent>;
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -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<SystemManagerComponent>);
|
||||
readonly system = inject<any>(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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
19
client/src/app/modules/system/create/create.component.html
Normal file
19
client/src/app/modules/system/create/create.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<h2 mat-dialog-title>Neuen Schlüssel anlegen</h2>
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="createForm" class="flex flex-col gap-3">
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Name</mat-label>
|
||||
<input type="text" matInput formControlName="name" maxlength="100">
|
||||
@if ((createForm.controls.name.value || '').length > 20) {
|
||||
<mat-hint>{{ (createForm.controls.name.value || '').length }} / 100 Zeichen</mat-hint>
|
||||
} @else {
|
||||
<mat-hint>Wie soll der Schlüssel heißen?</mat-hint>
|
||||
}
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close >Abbrechen</button>
|
||||
<button mat-button (click)="save()" [disabled]="createForm.disabled">Speichern</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -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<CreateSystemComponent>;
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
43
client/src/app/modules/system/create/create.component.ts
Normal file
43
client/src/app/modules/system/create/create.component.ts
Normal file
@@ -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<CreateSystemComponent>);
|
||||
|
||||
|
||||
createForm = new FormGroup({
|
||||
name: new FormControl<string | null>(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 => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,8 @@
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
/>
|
||||
/>
|
||||
|
||||
<div class="floating-btn-container">
|
||||
<button mat-flat-button class="btn-create mat-elevation-z8" (click)="openCreateSystem()" color="accent" >System anlegen</button>
|
||||
</div>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<div class="manage icon-btn-sm" (click)="openManager()" matTooltip="Manager bearbeiten" [matTooltipShowDelay]="600"></div>
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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<AgSystemManagerComponent>;
|
||||
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();
|
||||
}
|
||||
@@ -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<any, any, any>;
|
||||
system: any;
|
||||
|
||||
agInit(params: ICellRendererParams<any, any, any>): void {
|
||||
this.params = params;
|
||||
this.system = params.data;
|
||||
}
|
||||
refresh(params: ICellRendererParams<any, any, any>): 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();
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,10 @@ export class ApiService {
|
||||
return this.http.get<any[]>('api/system');
|
||||
}
|
||||
|
||||
getSystemManagers(id: string): Observable<IUser[]> {
|
||||
return this.http.get<any[]>(`api/system/${id}/manager`);
|
||||
}
|
||||
|
||||
handoverKey(data: any) {
|
||||
return this.http.post(`api/key/${data.key.id}/handover`, data);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export class HELPER {
|
||||
loading: true,
|
||||
loadingOverlayComponent: AgLoadingComponent,
|
||||
rowHeight: 54,
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
147
client/src/assets/img/manager.svg
Normal file
147
client/src/assets/img/manager.svg
Normal file
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 420.588 420.588" style="enable-background:new 0 0 420.588 420.588;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M154.543,19.625l32.674,217.81l-42.682,71.724C77.636,211.551,59.855,19.625,154.543,19.625z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M151.116,19.625c21.934-18.644,77.723-29.49,125.026-7.017
|
||||
c64.981,30.871,64.706,169.038,6.58,283.391c-0.088,0.173-46.062-32.902-46.062-32.902L151.116,19.625z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFD7A3;" d="M256.669,231.968h-0.358v-15.37h-92.124v15.354c0,15.042-0.063,31.392-13.161,41.282v147.354
|
||||
h59.223c0,0,31.011,0,59.223,0V273.004C256.709,263.089,256.669,246.881,256.669,231.968z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#E4EBF0;" d="M308.954,282.401h-2.193c-24.994,0-37.508-6.641-43.869-16.165l-45.915,31.34l0.033,0.042
|
||||
l-6.671,47.939l-6.661-47.874l-46.071-31.446c-6.361,9.524-18.875,16.165-43.869,16.165h-2.193
|
||||
c-39.976,0-72.384,32.407-72.384,72.384v46.062c0,10.902,8.838,19.741,19.741,19.741h111.865h23.163h32.638h36.324h98.705
|
||||
c10.903,0,19.741-8.839,19.741-19.741v-46.062C381.337,314.808,348.93,282.401,308.954,282.401z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FDC88E;" d="M256.401,295.999c2.401,0.655-2.017-6.751,0-6.58l13.07-16.414
|
||||
c-12.762-9.915-12.803-26.124-12.803-41.037h-0.358v-15.37h-72.384l-19.741,6.58C177.347,237.984,211.161,283.661,256.401,295.999
|
||||
z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFE1B2;" d="M302.373,131.054c0,52.696-52.408,118.446-92.124,118.446s-92.124-65.75-92.124-118.446
|
||||
S159.37,58.67,210.249,58.67S302.373,78.358,302.373,131.054z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M197.458,25.769c3.697,0,15.701,4.154,25.512,7.794l0.809-1.214c0,0,1.354,1.064,3.698,2.908
|
||||
c5.457,2.072,9.462,3.672,9.462,3.672l-1.539,2.566c24.976,19.678,81.96,64.693,87.084,69.818
|
||||
c6.58,6.58-32.633,171.062-32.633,75.562c0-79.786-44.327-95.587-85.544-105.285c-18.644-4.387-30.977-11.953-33.17-22.92
|
||||
C168.943,47.703,190.878,25.769,197.458,25.769z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M177.717,70.292c0,0-50.076,15.368-59.116,69.671c-2.1,12.616-19.579-43.019-19.579-43.019
|
||||
s9.87-43.869,12.064-44.966c2.193-1.097,46.611-19.193,46.611-19.193l13.287,11.49L177.717,70.292z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#D0D7DC;" d="M203.668,297.618l-45.417-32.269l-16.089,12.754l30.397,42.324
|
||||
c2.456,3.42,7.448,3.685,10.253,0.544L203.668,297.618z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#D0D7DC;" d="M216.829,297.618l45.417-32.269l16.089,12.754l-30.397,42.324
|
||||
c-2.456,3.42-7.448,3.685-10.253,0.544L216.829,297.618z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#527992;" d="M142.05,278.093c-6.982,2.713-16.148,4.308-28.313,4.308h-2.193
|
||||
c-39.976,0-72.384,32.407-72.384,72.384v46.062c0,10.902,8.838,19.741,19.741,19.741h111.865l-28.604-142.484
|
||||
C142.163,278.104,142.062,278.094,142.05,278.093z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#527992;" d="M278.538,278.093c6.982,2.713,16.148,4.308,28.313,4.308h2.193
|
||||
c39.976,0,72.384,32.407,72.384,72.384v46.062c0,10.902-8.838,19.741-19.741,19.741H249.821l28.604-142.484
|
||||
C278.425,278.104,278.525,278.094,278.538,278.093z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFE1B2;" d="M124.984,140.564c-9.864-2.468-16.326,2.805-16.326,13.161c0,13.709,13.265,47.296,27.21,26.321
|
||||
S124.984,140.564,124.984,140.564z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#545465;" cx="131.375" cy="348.641" r="6.58"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#D0D7DC;" cx="210.249" cy="368.382" r="6.58"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#D0D7DC;" cx="210.339" cy="401.587" r="6.58"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#545465;" cx="137.955" cy="388.123" r="6.58"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M195.596,138.457v4.935c0,14.514-11.808,26.321-26.321,26.321h-6.58
|
||||
c-7.257,0-13.161-5.904-13.161-13.161v-18.096H195.596 M195.596,131.876h-46.062c-3.634,0-6.58,2.946-6.58,6.58v18.096
|
||||
c0,10.903,8.838,19.741,19.741,19.741h6.58c18.171,0,32.902-14.731,32.902-32.902v-4.935
|
||||
C202.177,134.822,199.231,131.876,195.596,131.876L195.596,131.876z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M271.144,138.457v18.096c0,7.257-5.904,13.161-13.161,13.161h-6.58
|
||||
c-14.514,0-26.321-11.808-26.321-26.321v-4.935H271.144 M271.144,131.876h-46.062c-3.634,0-6.58,2.946-6.58,6.58v4.935
|
||||
c0,18.171,14.731,32.902,32.902,32.902h6.58c10.903,0,19.741-8.838,19.741-19.741v-18.096
|
||||
C277.724,134.822,274.778,131.876,271.144,131.876L271.144,131.876z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon style="fill:#545465;" points="118.124,131.876 149.534,131.876 149.534,147.53 118.124,139.962 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon style="fill:#545465;" points="302.554,131.876 271.144,131.876 271.144,147.53 302.554,139.962 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#545465;" d="M225.37,147.53h-6.58c0-2.847-3.949-5.347-8.45-5.347s-8.45,2.5-8.45,5.347h-6.58
|
||||
c0-6.69,6.603-11.927,15.031-11.927S225.37,140.841,225.37,147.53z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#475F7F;" d="M320.692,395.007h-37.971c-5.451,0-9.87,4.42-9.87,9.87v15.71h57.712v-15.71
|
||||
C330.563,399.427,326.144,395.007,320.692,395.007z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user