schlüssel neuen zylindern zuordnen geht

This commit is contained in:
Bastian Wagner
2026-02-22 17:08:50 +01:00
parent 6797b73eb1
commit e5c590165c
13 changed files with 163 additions and 1338 deletions

View File

@@ -122,5 +122,8 @@
"@schematics/angular:resolver": {
"typeSeparator": "."
}
},
"cli": {
"analytics": false
}
}

1293
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -78,16 +78,11 @@ export class LostKeysComponent extends AgGridContainerComponent {
markAsFound(key: IKey) {
this.dialog.open(LostKeyComponent, { data: key, autoFocus: false }).afterClosed().subscribe({
next: (result) => {
next: async (result) => {
if (result == "") {
key.keyLost = null;
this.api.updateKey(key).subscribe({
next: () => {
this.toast.success('Schlüssel als gefunden markiert');
this.loadLostKeys();
this.api.refreshKeys();
}
});
await this.api.updateKey(key);
this.loadLostKeys();
this.dataChanged = true;
}
}

View File

@@ -0,0 +1,23 @@
<h2 mat-dialog-title>Mehrere Schließanlagen gewählt!</h2>
<mat-dialog-content>
<div class="warning-message">
<mat-icon>warning</mat-icon>
<p>
Der Schlüssel ist Zylindern in mehreren Schließanlagen zugeordnet!
</p>
<p class="additional-info">
<!-- Additional information -->
<small>Zum Korrigieren abbrechen, ansonsten speichern</small>
</p>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button matButton [mat-dialog-close]="false">
<mat-icon>close</mat-icon>
Vorgang abbrechen
</button>
<button matButton="elevated" [mat-dialog-close]="true" class="btn-warning">
<mat-icon>check</mat-icon>
Schlüssel speichern
</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MultipleCylinderSystemsDialogComponent } from './multiple-cylinder-systems-dialog.component';
describe('MultipleCylinderSystemsDialogComponent', () => {
let component: MultipleCylinderSystemsDialogComponent;
let fixture: ComponentFixture<MultipleCylinderSystemsDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MultipleCylinderSystemsDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(MultipleCylinderSystemsDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-multiple-cylinder-systems-dialog',
imports: [MatDialogModule, MatButtonModule, MatIconModule],
templateUrl: './multiple-cylinder-systems-dialog.component.html',
styleUrl: './multiple-cylinder-systems-dialog.component.scss',
})
export class MultipleCylinderSystemsDialogComponent {
}

View File

@@ -11,6 +11,6 @@
</mat-dialog-content>
<mat-dialog-actions>
<button matButton [mat-dialog-close]="null">Abbrechen</button>
<button matButton [mat-dialog-close]="selectedCylinders">Übernehmen</button>
<button matButton (click)="close(false)">Abbrechen</button>
<button matButton="elevated" (click)="close(true)" class="btn-primary" [disabled]="selectedCylinders.length < 1" >Übernehmen</button>
</mat-dialog-actions>

View File

@@ -3,11 +3,12 @@ import { ApiService } from '../../../../shared/api.service';
import { ICylinder } from '../../../../model/interface/cylinder.interface';
import { HotToastService } from '@ngxpert/hot-toast';
import { AgGridAngular } from 'ag-grid-angular';
import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community';
import { GridApi, GridOptions, GridReadyEvent, IRowNode } from 'ag-grid-community';
import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MAT_DIALOG_DATA, MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
import { MultipleCylinderSystemsDialogComponent } from './multiple-cylinder-systems-dialog/multiple-cylinder-systems-dialog.component';
@Component({
selector: 'app-select-key-cylinder',
@@ -20,6 +21,8 @@ export class SelectKeyCylinderComponent extends AgGridContainerComponent {
readonly dialogRef = inject(MatDialogRef<SelectKeyCylinderComponent>);
readonly cylinders = inject<ICylinder[]>(MAT_DIALOG_DATA);
private dialog = inject(MatDialog);
gridApi!: GridApi;
gridOptions: GridOptions = {
@@ -48,6 +51,46 @@ export class SelectKeyCylinderComponent extends AgGridContainerComponent {
}
selectionChanged(): void {
this.selectedCylinders =this.gridApi?.getSelectedRows();
this.selectedCylinders = this.gridApi?.getSelectedRows();
if (this.selectedCylinders.length == 0 ) {
this.toast.info('Jeder Schlüssel muss mindestens einem Zylinder zugeordnet sein. Bitte Zylinder wählen.')
}
}
preselectCylinders(cylinders: ICylinder[]) {
this.gridApi.setGridOption('loading', true)
const nodesToSelect: IRowNode<ICylinder>[] = [];
this.gridApi.forEachNode(node => {
if (cylinders.some(c => c.id == node.data.id )) {
nodesToSelect.push(node);
}
})
this.gridApi.setNodesSelected({
nodes: nodesToSelect,
newValue: true
})
this.gridApi.setGridOption('loading', false)
}
async close(data = false) {
if (!data) { return this.dialogRef.close() }
const amountOfSystems = Array.from(new Set(this.selectedCylinders.map( c => c.system.id )));
if (amountOfSystems.length == 1) {
return this.dialogRef.close(this.selectedCylinders);
}
this.dialog.open(MultipleCylinderSystemsDialogComponent, {}).afterClosed().subscribe({
next: val => {
if (val) {
return this.dialogRef.close(this.selectedCylinders);
}
}
})
}
}

View File

@@ -1,7 +1,7 @@
import { Component, inject } from '@angular/core';
import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
import { AgGridAngular } from 'ag-grid-angular';
import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent, ICellEditorParams, FilterActionParams, FilterAction, themeQuartz, Theme, ThemeDefaultParams } from 'ag-grid-community';
import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent, ICellEditorParams, FilterActionParams, FilterAction, themeQuartz, Theme, ThemeDefaultParams, AgGridEvent, CellClickedEvent, CellDoubleClickedEvent } from 'ag-grid-community';
import { DatePipe } from '@angular/common';
import { ApiService } from '../../shared/api.service';
import { IKey } from '../../model/interface/key.interface';
@@ -54,7 +54,9 @@ export class KeysComponent extends AgGridContainerComponent {
valueFormatter: (data: any) => { return data; },
cellRenderer: (data: any) => {return data.value?.map((m: ICylinder) => m.name).join(', ')},
tooltipValueGetter: (data: any) => data.value?.map((m: ICylinder) => m.name).join(','),
onCellDoubleClicked(event) {},
onCellDoubleClicked: (event) => {
this.openSelectCylinder(event)
},
cellEditorPopup: true,
filterValueGetter: (params: any) => {return params.data.cylinder?.map((m: ICylinder) => m.name).join(', ')},
},
@@ -157,24 +159,13 @@ export class KeysComponent extends AgGridContainerComponent {
this.setFilterToParams();
}
cellEditEnd(event: CellEditingStoppedEvent) {
async cellEditEnd(event: CellEditingStoppedEvent) {
const key: IKey = event.data;
if (!event.valueChanged || event.newValue == event.oldValue) { return; }
this.gridApi.setGridOption("loading", true);
this.api.updateKey(key)
.pipe(
this.toast.observe({
loading: 'speichern...',
success: 'Änderungen gespeichert',
error: 'Änderungen konnten nicht gespeichert werden!'
})
).subscribe({
next: () => {this.gridApi.setGridOption("loading", false);},
error: () => {
this.loadKeys();
}
})
await this.api.updateKey(key)
this.gridApi.setGridOption("loading", false);
}
openCreateKey() {
@@ -195,17 +186,33 @@ export class KeysComponent extends AgGridContainerComponent {
})
}
openSelectCylinder(params: any) {
this.dialog.open(SelectKeyCylinderComponent, {
async openSelectCylinder(event: CellDoubleClickedEvent) {
const key: IKey = event.data;
this.gridApi.setGridOption("loading", true);
const cylinders = await this.api.refreshCylinders()
this.gridApi.setGridOption('loading', false)
const ref = this.dialog.open(SelectKeyCylinderComponent, {
data: cylinders,
maxHeight: "calc(100vh - 24px)",
maxWidth: "calc(100vw - 24px)",
width: "30vw",
minWidth: "200px",
disableClose: true
}).afterClosed().subscribe({
next: key => {
console.log(key)
width: "50vw",
minWidth: "300px",
height: "70vh",
disableClose: true,
});
ref.afterOpened().subscribe({
next: () => {
ref.componentInstance.preselectCylinders(event.data.cylinder);
}
})
ref.afterClosed().subscribe({
next: (cylinders: ICylinder[]) => {
if (cylinders == null) { return; }
key.cylinder = cylinders;
this.api.updateKey(key)
}
});
}
openArchive() {

View File

@@ -105,8 +105,7 @@ export class AgKeyActionsComponent implements ICellRendererAngularComp {
}
this.key.keyLost = n;
this.params.api.refreshCells();
await this.api.updateKey(this.key).subscribe();
this.api.refreshKeys();
await this.api.updateKey(this.key)
}
}
})

View File

@@ -76,8 +76,20 @@ export class ApiService {
return this.http.get<IKey[]>('api/key/lost')
}
updateKey(key: IKey): Observable<IKey> {
return this.http.put<IKey>('api/key', key);
updateKey(key: IKey): Promise<IKey | null> {
return new Promise<IKey | null>(resolve => {
this.http.put<IKey>('api/key', key).pipe(
this.toast.observe({
loading: `Speichere Schlüssel ${key.name}....`,
success: `Schlüssel ${key.name} gespeichert.`,
error: `Es ist ein Fehler aufgetreten!`
})
).subscribe({
next: (key: IKey) => resolve(key),
error: () => resolve(null),
complete: () => { this.refreshKeys(); }
})
})
}
updateCylinder(cylinder: ICylinder): Promise<boolean> {
@@ -275,8 +287,8 @@ export class ApiService {
* cylinders
* @returns Promise wenn geladen
*/
refreshCylinders(): Promise<void> {
return new Promise<void>(resolve => {
refreshCylinders(): Promise<ICylinder[]> {
return new Promise<ICylinder[]>(resolve => {
this.getCylinders().subscribe({
next: data => {
this.cylinders.next(data);
@@ -284,7 +296,7 @@ export class ApiService {
error: () => {
this.toast.error('Fehler beim Laden der Zylinder')
},
complete: () => resolve()
complete: () => resolve(this.cylinders.value)
})
})
}