Archive
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
@@ -18,7 +19,7 @@ export class Key implements IKey {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column({ name: 'key_number', unique: true })
|
@Column({ name: 'key_number', unique: false })
|
||||||
nr: number;
|
nr: number;
|
||||||
|
|
||||||
@Column({ name: 'handed_out', default: false })
|
@Column({ name: 'handed_out', default: false })
|
||||||
@@ -35,4 +36,7 @@ export class Key implements IKey {
|
|||||||
|
|
||||||
@UpdateDateColumn({ name: 'updatet_at' })
|
@UpdateDateColumn({ name: 'updatet_at' })
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@DeleteDateColumn()
|
||||||
|
deletedAt: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
@@ -13,7 +16,6 @@ import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.
|
|||||||
import { AuthGuard } from 'src/core/guards/auth.guard';
|
import { AuthGuard } from 'src/core/guards/auth.guard';
|
||||||
import { Key } from 'src/model/entitites';
|
import { Key } from 'src/model/entitites';
|
||||||
import { CreateKeySystemDto } from 'src/model/dto/create-key-system.dto';
|
import { CreateKeySystemDto } from 'src/model/dto/create-key-system.dto';
|
||||||
import { HandoverKeyDto } from 'src/model/dto';
|
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@Controller('key')
|
@Controller('key')
|
||||||
@@ -25,11 +27,26 @@ export class KeyController {
|
|||||||
return this.service.getUsersKeys(req.user);
|
return this.service.getUsersKeys(req.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
postKey(@Req() req: AuthenticatedRequest, @Body() key: Key) {
|
||||||
|
return this.service.createKey(req.user, key);
|
||||||
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
updateKey(@Req() req: AuthenticatedRequest, @Body() key: Key) {
|
updateKey(@Req() req: AuthenticatedRequest, @Body() key: Key) {
|
||||||
return this.service.updateKey(req.user, key);
|
return this.service.updateKey(req.user, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Put(':id/restore')
|
||||||
|
restoreKey(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||||
|
return this.service.restoreKey(req.user, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
deleteKey(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||||
|
return this.service.deleteKey(req.user, id);
|
||||||
|
}
|
||||||
|
|
||||||
@Get('cylinder')
|
@Get('cylinder')
|
||||||
getCylinders(@Req() req: AuthenticatedRequest) {
|
getCylinders(@Req() req: AuthenticatedRequest) {
|
||||||
return this.service.getUsersCylinders(req.user);
|
return this.service.getUsersCylinders(req.user);
|
||||||
@@ -56,4 +73,9 @@ export class KeyController {
|
|||||||
getKeyHandouts(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
getKeyHandouts(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||||
return this.service.getKeyHandovers(req.user, id);
|
return this.service.getKeyHandovers(req.user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('Archive')
|
||||||
|
getArchive(@Req() req: AuthenticatedRequest) {
|
||||||
|
return this.service.getDeletedKeys(req.user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
KeySystemRepository,
|
KeySystemRepository,
|
||||||
} from 'src/model/repositories';
|
} from 'src/model/repositories';
|
||||||
import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository';
|
import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository';
|
||||||
|
import { IsNull, Not } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KeyService {
|
export class KeyService {
|
||||||
@@ -108,4 +109,35 @@ export class KeyService {
|
|||||||
relations: ['customer'],
|
relations: ['customer'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createKey(user: User, key: any) {
|
||||||
|
return this.keyrepository.save(this.keyrepository.create(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteKey(user: User, id: string) {
|
||||||
|
const key = await this.keyrepository.findOneOrFail({
|
||||||
|
where: { id, cylinder: { system: { managers: { id: user.id } } } },
|
||||||
|
});
|
||||||
|
return this.keyrepository.softRemove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeletedKeys(user: User) {
|
||||||
|
return this.keyrepository.find({
|
||||||
|
where: {
|
||||||
|
cylinder: { system: { managers: { id: user.id } } },
|
||||||
|
deletedAt: Not(IsNull()),
|
||||||
|
},
|
||||||
|
withDeleted: true,
|
||||||
|
order: { deletedAt: { direction: 'DESC' } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreKey(user: User, keyID: string) {
|
||||||
|
const key = await this.keyrepository.findOneOrFail({
|
||||||
|
where: { cylinder: { system: { managers: { id: user.id } } }, id: keyID },
|
||||||
|
withDeleted: true,
|
||||||
|
});
|
||||||
|
key.deletedAt = null;
|
||||||
|
return this.keyrepository.save(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ export interface IKey {
|
|||||||
handedOut: boolean;
|
handedOut: boolean;
|
||||||
cylinder: any;
|
cylinder: any;
|
||||||
nr: number;
|
nr: number;
|
||||||
|
deletedAt?: string;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<h2 mat-dialog-title>Gelöschte Schlüssel</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]="dataChanged">Schließen</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mat-dialog-content{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ArchiveComponent } from './archive.component';
|
||||||
|
|
||||||
|
describe('ArchiveComponent', () => {
|
||||||
|
let component: ArchiveComponent;
|
||||||
|
let fixture: ComponentFixture<ArchiveComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ArchiveComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ArchiveComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
|
||||||
|
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { AgGridAngular } from 'ag-grid-angular';
|
||||||
|
import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community';
|
||||||
|
import { ApiService } from '../../../../shared/api.service';
|
||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { IKey } from '../../../../model/interface/key.interface';
|
||||||
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
|
import { AgLoadingComponent } from '../../../../shared/ag-grid/components/ag-loading/ag-loading.component';
|
||||||
|
import { HELPER } from '../../../../shared/helper.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-archive',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatDialogModule, AgGridAngular, MatButtonModule, MatIconModule],
|
||||||
|
providers: [DatePipe, { provide: LOCALE_ID, useValue: 'de-DE' }],
|
||||||
|
templateUrl: './archive.component.html',
|
||||||
|
styleUrl: './archive.component.scss'
|
||||||
|
})
|
||||||
|
export class ArchiveComponent {
|
||||||
|
private api: ApiService = inject(ApiService);
|
||||||
|
private datePipe = inject(DatePipe);
|
||||||
|
private toast = inject(HotToastService);
|
||||||
|
|
||||||
|
public dataChanged = false;
|
||||||
|
|
||||||
|
gridApi!: GridApi;
|
||||||
|
|
||||||
|
gridOptions: GridOptions = HELPER.getGridOptions();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.gridOptions.columnDefs = [
|
||||||
|
{ colId: 'name', field: 'name' , headerName: 'Name', flex: 1, editable: true, sort: 'asc', filter: true },
|
||||||
|
{ colId: 'nr', field: 'nr' , headerName: 'Schlüsselnummer', flex: 1, editable: true, filter: true },
|
||||||
|
{
|
||||||
|
field: 'deletedAt'
|
||||||
|
, headerName: 'Gelöscht'
|
||||||
|
, width: 160
|
||||||
|
, type: 'date'
|
||||||
|
, cellRenderer: (data: any) => this.datePipe.transform(new Date(data.value), 'short')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 40,
|
||||||
|
cellRenderer: () => '<div class="icon-btn-sm restore" ></div>',
|
||||||
|
onCellClicked: (event) => { this.restoreKey(event.data);}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
onGridReady(params: GridReadyEvent) {
|
||||||
|
this.gridApi = params.api;
|
||||||
|
this.loadKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
loadKeys() {
|
||||||
|
this.gridApi.setGridOption("loading", true);
|
||||||
|
this.api.getKeyArchive().subscribe({
|
||||||
|
next: n => {
|
||||||
|
this.gridApi.setGridOption("rowData", n);
|
||||||
|
this.gridApi.setGridOption("loading", false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreKey(key: IKey) {
|
||||||
|
this.gridApi.setGridOption("loading", true);
|
||||||
|
key.deletedAt = undefined;
|
||||||
|
this.api.restoreKey(key.id)
|
||||||
|
.pipe(
|
||||||
|
this.toast.observe({
|
||||||
|
loading: 'Stelle wiederher...',
|
||||||
|
success: 'Schlüssel wiederhergestellt',
|
||||||
|
error: 'Es ist ein Fehler aufgetreten'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.dataChanged = true;
|
||||||
|
this.loadKeys();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.gridApi.setGridOption("loading", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<h2 mat-dialog-title>Schlüssel {{key.name}} löschen?</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<p>Soll der Schlüssel wirklich gelöscht werden?</p>
|
||||||
|
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button [mat-dialog-close]="false" >Abbrechen</button>
|
||||||
|
<button mat-button [mat-dialog-close]="true" color="warn">Ja, löschen</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DeleteKeyComponent } from './delete-key.component';
|
||||||
|
|
||||||
|
describe('DeleteKeyComponent', () => {
|
||||||
|
let component: DeleteKeyComponent;
|
||||||
|
let fixture: ComponentFixture<DeleteKeyComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DeleteKeyComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DeleteKeyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { IKey } from '../../../../model/interface/key.interface';
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-delete-key',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatDialogModule, MatButtonModule],
|
||||||
|
templateUrl: './delete-key.component.html',
|
||||||
|
styleUrl: './delete-key.component.scss'
|
||||||
|
})
|
||||||
|
export class DeleteKeyComponent {
|
||||||
|
readonly dialogRef = inject(MatDialogRef<DeleteKeyComponent>);
|
||||||
|
readonly key = inject<IKey>(MAT_DIALOG_DATA);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ export class HandoverDialogComponent {
|
|||||||
gridOptions: GridOptions = {
|
gridOptions: GridOptions = {
|
||||||
localeText: AG_GRID_LOCALE_DE,
|
localeText: AG_GRID_LOCALE_DE,
|
||||||
rowData: [],
|
rowData: [],
|
||||||
|
isRowSelectable: () => false,
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{ colId: 'customer', field: 'customer.name' , headerName: 'Kunde', flex: 1, editable: false, filter: false},
|
{ colId: 'customer', field: 'customer.name' , headerName: 'Kunde', flex: 1, editable: false, filter: false},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
<h2 mat-dialog-title>Schlüssel anlegen</h2>
|
<h2 mat-dialog-title>Neuen Schlüssel anlegen</h2>
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<form [formGroup]="createForm" >
|
<form [formGroup]="createForm" >
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
<input type="text" matInput formControlName="name">
|
<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>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Schlüsselnummer</mat-label>
|
<mat-label>Schlüsselnummer</mat-label>
|
||||||
<input type="text" matInput formControlName="nr">
|
<input type="number" matInput formControlName="nr" min="0" max="999999999999">
|
||||||
|
<mat-hint>Nummer auf dem Schlüssel</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
@@ -19,6 +25,7 @@
|
|||||||
<mat-option [value]="item">{{ item.name }}</mat-option>
|
<mat-option [value]="item">{{ item.name }}</mat-option>
|
||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
<mat-hint>Wo sperrt der Schlüssel?</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { ApiService } from '../../../shared/api.service';
|
import { ApiService } from '../../../shared/api.service';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { map, Observable, startWith } from 'rxjs';
|
import { map, Observable, startWith } from 'rxjs';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create',
|
selector: 'app-create',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [MatDialogModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule],
|
imports: [MatDialogModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatDialogModule],
|
||||||
templateUrl: './create.component.html',
|
templateUrl: './create.component.html',
|
||||||
styleUrl: './create.component.scss'
|
styleUrl: './create.component.scss'
|
||||||
})
|
})
|
||||||
export class CreateKeyComponent {
|
export class CreateKeyComponent {
|
||||||
|
|
||||||
private api: ApiService = inject(ApiService);
|
private api: ApiService = inject(ApiService);
|
||||||
|
private toast: HotToastService = inject(HotToastService);
|
||||||
|
readonly dialogRef = inject(MatDialogRef<CreateKeyComponent>);
|
||||||
|
|
||||||
createForm = new FormGroup({
|
createForm = new FormGroup({
|
||||||
name: new FormControl(null, Validators.required),
|
name: new FormControl(null, Validators.required),
|
||||||
@@ -54,7 +57,19 @@ export class CreateKeyComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
console.log(this.createForm.value)
|
|
||||||
this.api.createKey(this.createForm.value as any)
|
this.api.createKey(this.createForm.value as any)
|
||||||
|
.pipe(
|
||||||
|
this.toast.observe({
|
||||||
|
error: 'Konnte nicht angelegt werden...',
|
||||||
|
loading: 'Speichern...',
|
||||||
|
success: 'Gespeichert'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: key => {
|
||||||
|
this.createForm.reset();
|
||||||
|
this.dialogRef.close(key);
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
@if (gridOptions || true) {
|
<ag-grid-angular
|
||||||
<ag-grid-angular
|
style="width: 100%; height: 100%;"
|
||||||
style="width: 100%; height: 100%;"
|
(gridReady)="onGridReady($event)"
|
||||||
(gridReady)="onGridReady($event)"
|
[gridOptions]="gridOptions!"
|
||||||
[gridOptions]="gridOptions!"
|
/>
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<button mat-flat-button class="btn-create" (click)="openCreateKey()" >Schlüssel anlegen</button>
|
<div class="floating-btn-container">
|
||||||
|
<button mat-raised-button class="btn-create" (click)="openCreateKey()" >Schlüssel anlegen</button>
|
||||||
|
<button mat-mini-fab (click)="openArchive()"><mat-icon>inventory_2</mat-icon></button>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
.btn-create {
|
.floating-btn-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,15 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||||
import { CreateKeyComponent } from './create/create.component';
|
import { CreateKeyComponent } from './create/create.component';
|
||||||
import { AgOpenHandoutComponent } from '../../shared/ag-grid/components/ag-open-handout/ag-open-handout.component';
|
import { AgOpenHandoutComponent } from '../../shared/ag-grid/components/ag-open-handout/ag-open-handout.component';
|
||||||
|
import { AgDeleteKeyComponent } from '../../shared/ag-grid/components/ag-delete-key/ag-delete-key.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { ArchiveComponent } from './components/archive/archive.component';
|
||||||
|
import { AgLoadingComponent } from '../../shared/ag-grid/components/ag-loading/ag-loading.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-keys',
|
selector: 'app-keys',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AgGridAngular, MatButtonModule, MatDialogModule],
|
imports: [AgGridAngular, MatButtonModule, MatDialogModule, MatIconModule],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
templateUrl: './keys.component.html',
|
templateUrl: './keys.component.html',
|
||||||
styleUrl: './keys.component.scss'
|
styleUrl: './keys.component.scss'
|
||||||
@@ -70,9 +74,24 @@ export class KeysComponent {
|
|||||||
, cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-'
|
, cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-'
|
||||||
, tooltipValueGetter: (data: any) => this.datePipe.transform(new Date(data.value), 'medium')
|
, tooltipValueGetter: (data: any) => this.datePipe.transform(new Date(data.value), 'medium')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
,{
|
||||||
|
colId: 'delete'
|
||||||
|
, headerName: 'Löschen'
|
||||||
|
, width: 120
|
||||||
|
, cellRenderer: AgDeleteKeyComponent
|
||||||
|
// , onCellClicked: (event) => { this.deleteKey(event.data.id)}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
loading: true,
|
loading: true,
|
||||||
rowHeight: 36,
|
rowHeight: 36,
|
||||||
|
loadingOverlayComponent: AgLoadingComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteKey(id: string) {
|
||||||
|
this.api.deleteKey(id).subscribe({
|
||||||
|
next: n => console.log(n)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -118,6 +137,31 @@ export class KeysComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openCreateKey() {
|
openCreateKey() {
|
||||||
this.dialog.open(CreateKeyComponent)
|
this.dialog.open(CreateKeyComponent).afterClosed().subscribe({
|
||||||
|
next: key => {
|
||||||
|
if (key) {
|
||||||
|
let d = [...this.gridApi.getGridOption("rowData") || [], key];
|
||||||
|
this.gridApi.setGridOption("rowData", d);
|
||||||
|
this.loadKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
openArchive() {
|
||||||
|
this.dialog.open(ArchiveComponent, {
|
||||||
|
maxHeight: "calc(100vh - 24px)",
|
||||||
|
maxWidth: "calc(100vw - 24px)",
|
||||||
|
width: "50vw",
|
||||||
|
minWidth: "300px",
|
||||||
|
height: "70vh",
|
||||||
|
disableClose: true
|
||||||
|
}).afterClosed().subscribe({
|
||||||
|
next: changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.loadKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<div class="delete icon-btn-sm" (click)="delete()" ></div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AgDeleteKeyComponent } from './ag-delete-key.component';
|
||||||
|
|
||||||
|
describe('AgDeleteKeyComponent', () => {
|
||||||
|
let component: AgDeleteKeyComponent;
|
||||||
|
let fixture: ComponentFixture<AgDeleteKeyComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AgDeleteKeyComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AgDeleteKeyComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { ICellRendererAngularComp } from 'ag-grid-angular';
|
||||||
|
import { ICellRendererParams } from 'ag-grid-community';
|
||||||
|
import { IKey } from '../../../../model/interface/key.interface';
|
||||||
|
import { ApiService } from '../../../api.service';
|
||||||
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { DeleteKeyComponent } from '../../../../modules/keys/components/delete-key/delete-key.component';
|
||||||
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ag-delete-key',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatDialogModule],
|
||||||
|
templateUrl: './ag-delete-key.component.html',
|
||||||
|
styleUrl: './ag-delete-key.component.scss'
|
||||||
|
})
|
||||||
|
export class AgDeleteKeyComponent implements ICellRendererAngularComp {
|
||||||
|
key!: IKey;
|
||||||
|
params!: ICellRendererParams<any, any, any>;
|
||||||
|
|
||||||
|
private api: ApiService = inject(ApiService);
|
||||||
|
private dialog: MatDialog = inject(MatDialog);
|
||||||
|
private toast = inject(HotToastService);
|
||||||
|
|
||||||
|
agInit(params: ICellRendererParams<any, any, any>): void {
|
||||||
|
this.params = params;
|
||||||
|
this.key = params.data;
|
||||||
|
}
|
||||||
|
refresh(params: ICellRendererParams<any, any, any>): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
const ref = this.dialog.open(DeleteKeyComponent, {
|
||||||
|
data: this.key,
|
||||||
|
autoFocus: false
|
||||||
|
})
|
||||||
|
|
||||||
|
ref.afterClosed().subscribe({
|
||||||
|
next: n => {
|
||||||
|
if (n) {
|
||||||
|
this.deleteThisKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteThisKey() {
|
||||||
|
this.params.api.setGridOption("loading", true);
|
||||||
|
this.api.deleteKey(this.key.id).pipe(
|
||||||
|
this.toast.observe({
|
||||||
|
loading: 'Lösche Schlüssel ' + this.key.name,
|
||||||
|
success: 'Schlüssel ' + this.key.name + ' gelöscht',
|
||||||
|
error: 'Konnte nicht gelöscht werden'
|
||||||
|
})
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
let data = this.params.api.getGridOption("rowData");
|
||||||
|
data = data?.filter(d => d.id != this.key.id);
|
||||||
|
this.params.api.setGridOption("rowData", data);
|
||||||
|
this.params.api.setGridOption("loading", false);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.params.api.setGridOption("loading", false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<mat-spinner></mat-spinner>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AgLoadingComponent } from './ag-loading.component';
|
||||||
|
|
||||||
|
describe('AgLoadingComponent', () => {
|
||||||
|
let component: AgLoadingComponent;
|
||||||
|
let fixture: ComponentFixture<AgLoadingComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AgLoadingComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AgLoadingComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { ILoadingOverlayAngularComp } from 'ag-grid-angular';
|
||||||
|
import { ILoadingOverlayParams } from 'ag-grid-community';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ag-loading',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatProgressSpinnerModule],
|
||||||
|
templateUrl: './ag-loading.component.html',
|
||||||
|
styleUrl: './ag-loading.component.scss'
|
||||||
|
})
|
||||||
|
export class AgLoadingComponent implements ILoadingOverlayAngularComp {
|
||||||
|
agInit(params: ILoadingOverlayParams<any, any>): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
refresh?(params: ILoadingOverlayParams<any, any>): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createKey(key: any) {
|
createKey(key: any) {
|
||||||
|
return this.http.post<IKey>('api/key', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
postKeySystem(keySystem: any) {
|
postKeySystem(keySystem: any) {
|
||||||
@@ -60,4 +61,15 @@ export class ApiService {
|
|||||||
return this.http.get<any[]>('api/customer')
|
return this.http.get<any[]>('api/customer')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteKey(id: string) {
|
||||||
|
return this.http.delete(`api/key/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyArchive(): Observable<IKey[]> {
|
||||||
|
return this.http.get<IKey[]>('api/key/archive');
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreKey(id: string) {
|
||||||
|
return this.http.put(`api/key/${id}/restore`, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
client/src/app/shared/helper.service.ts
Normal file
16
client/src/app/shared/helper.service.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { AG_GRID_LOCALE_DE } from "@ag-grid-community/locale";
|
||||||
|
import { GridOptions } from "ag-grid-community";
|
||||||
|
import { AgLoadingComponent } from "./ag-grid/components/ag-loading/ag-loading.component";
|
||||||
|
|
||||||
|
export class HELPER {
|
||||||
|
|
||||||
|
static getGridOptions(): GridOptions {
|
||||||
|
return {
|
||||||
|
localeText: AG_GRID_LOCALE_DE,
|
||||||
|
rowData: [],
|
||||||
|
columnDefs: [],
|
||||||
|
loading: true,
|
||||||
|
loadingOverlayComponent: AgLoadingComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
client/src/assets/img/delete.svg
Normal file
1
client/src/assets/img/delete.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="427pt" viewBox="-40 0 427 427.00131" width="427pt" xmlns="http://www.w3.org/2000/svg"><path d="m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/><path d="m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/><path d="m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0"/><path d="m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
60
client/src/assets/img/inbox.svg
Normal file
60
client/src/assets/img/inbox.svg
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M312.461,332.734H199.539c-8.511,0-15.434,6.923-15.434,15.434v34.634c0,8.511,6.923,15.435,15.434,15.435h112.923
|
||||||
|
c8.511,0,15.435-6.923,15.435-15.435v-34.634C327.895,339.658,320.972,332.734,312.461,332.734z M308.051,378.393H203.948v-25.814
|
||||||
|
h104.103V378.393z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M506.976,246.958l0.159-0.08L432.73,99.774c-6.015-11.89-18.025-19.275-31.346-19.275h-14.141V66.824
|
||||||
|
c0-5.48-4.442-9.922-9.922-9.922H134.68c-5.48,0-9.922,4.442-9.922,9.922v13.675h-14.141c-13.321,0-25.331,7.385-31.346,19.275
|
||||||
|
L4.865,246.878l0.159,0.08C1.837,252.207,0,258.363,0,264.939v155.409c0,19.162,15.59,34.751,34.752,34.751h442.497
|
||||||
|
c19.162,0,34.751-15.59,34.751-34.751V264.939C512,258.363,510.163,252.207,506.976,246.958z M387.242,102.548h14.141
|
||||||
|
c4.959,0,9.43,2.751,11.671,7.179l60.93,120.462h-41.431v-37.066c0-5.48-4.442-9.922-9.922-9.922h-12.275v-53.227
|
||||||
|
c0-5.48-4.442-9.922-9.922-9.922h-13.192V102.548z M412.71,203.044v27.144h-52.359c-8.984,0-17.174,5.293-20.865,13.482
|
||||||
|
l-14.296,31.71c-0.136,0.299-0.435,0.493-0.764,0.493H187.575c-0.329,0-0.628-0.194-0.764-0.494l-14.295-31.708
|
||||||
|
c-3.692-8.19-11.882-13.483-20.866-13.483H99.291v-27.144H412.71z M144.602,76.746h222.796v43.305H144.602V76.746z
|
||||||
|
M390.512,139.895v43.305H121.488v-43.305H390.512z M98.946,109.727c2.24-4.429,6.712-7.179,11.671-7.179h14.141v17.503h-13.192
|
||||||
|
c-5.48,0-9.922,4.442-9.922,9.922v53.227H89.369c-5.48,0-9.922,4.442-9.922,9.922v37.066H38.016L98.946,109.727z M477.249,433.049
|
||||||
|
H34.752c-7.004,0-12.703-5.699-12.703-12.701V264.939c0-7.003,5.698-12.701,12.703-12.701H151.65c0.328,0,0.629,0.194,0.765,0.495
|
||||||
|
l14.295,31.708c3.692,8.19,11.881,13.481,20.865,13.481h136.85c8.984,0,17.174-5.292,20.865-13.48l14.296-31.709v-0.001
|
||||||
|
c0.136-0.3,0.435-0.494,0.764-0.494h116.898c7.004,0,12.701,5.699,12.701,12.701v155.409h0.001
|
||||||
|
C489.951,427.352,484.253,433.049,477.249,433.049z"/>
|
||||||
|
</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: 2.3 KiB |
1
client/src/assets/img/restore.svg
Normal file
1
client/src/assets/img/restore.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M440-320h80v-166l64 62 56-56-160-160-160 160 56 56 64-62v166ZM280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520Zm-400 0v520-520Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 331 B |
@@ -51,6 +51,13 @@ html, body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
background-image: url("./assets/img/delete.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.restore {
|
||||||
|
background-image: url("./assets/img/restore.svg");
|
||||||
|
}
|
||||||
|
|
||||||
/* Core Data Grid CSS */
|
/* Core Data Grid CSS */
|
||||||
@import "ag-grid-community/styles/ag-grid.css";
|
@import "ag-grid-community/styles/ag-grid.css";
|
||||||
|
|||||||
Reference in New Issue
Block a user