diff --git a/api/src/model/entitites/system.entity.ts b/api/src/model/entitites/system.entity.ts
index 4d9fcda..ed288fe 100644
--- a/api/src/model/entitites/system.entity.ts
+++ b/api/src/model/entitites/system.entity.ts
@@ -1,6 +1,7 @@
import {
Column,
CreateDateColumn,
+ DeleteDateColumn,
Entity,
JoinTable,
ManyToMany,
@@ -32,4 +33,7 @@ export class KeySystem implements IKeySystem {
@UpdateDateColumn({ name: 'updatet_at' })
updatedAt: Date;
+
+ @DeleteDateColumn()
+ deletedAt: Date;
}
diff --git a/api/src/modules/system/system.controller.ts b/api/src/modules/system/system.controller.ts
index 816d5ca..1967fe2 100644
--- a/api/src/modules/system/system.controller.ts
+++ b/api/src/modules/system/system.controller.ts
@@ -33,6 +33,11 @@ export class SystemController {
findAll(@Req() req: AuthenticatedRequest) {
return this.systemService.findAll(req.user);
}
+
+ @Get('archive')
+ findDeleted(@Req() req: AuthenticatedRequest) {
+ return this.systemService.findDeleted(req.user);
+ }
@Get(':id/manager')
getManagers(@Param('id') id: string) {
@@ -57,4 +62,9 @@ export class SystemController {
remove(@Param('id') id: string) {
return this.systemService.remove(id);
}
+
+ @Put(':id/restore')
+ restoreKey(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
+ return this.systemService.restore(req.user, id);
+ }
}
diff --git a/api/src/modules/system/system.service.ts b/api/src/modules/system/system.service.ts
index ae1af35..372bc34 100644
--- a/api/src/modules/system/system.service.ts
+++ b/api/src/modules/system/system.service.ts
@@ -7,6 +7,7 @@ import { IUser } from 'src/model/interface';
import { MailService } from '../mail/mail.service';
import { ConfigService } from '@nestjs/config';
import { ActivityHelperService } from 'src/shared/service/activity.logger.service';
+import { IsNull, Not } from 'typeorm';
@Injectable()
export class SystemService {
@@ -56,6 +57,20 @@ export class SystemService {
return systems;
}
+ async findDeleted(user: User) {
+ let systems = await this.systemRepo.find({
+ where: { managers: { id: user.id }, deletedAt: Not(IsNull()) },
+ order: { name: { direction: 'ASC' } },
+ withDeleted: true,
+ });
+
+ if (this.isDevelopMode) {
+ systems = systems.filter(s => s.name.toLocaleLowerCase().includes('develop'));
+ }
+
+ return systems;
+ }
+
findOne(id: string) {
return this.systemRepo.findOne({ where: { id: id } });
}
@@ -136,4 +151,15 @@ export class SystemService {
return sys.managers;
}
+
+ async restore(user: User, id: string) {
+ const key = await this.systemRepo.findOneOrFail({
+ where: { id: id, managers: { id: user.id } },
+ withDeleted: true,
+ });
+ key.deletedAt = null;
+ // await this.activityService.logKeyRestored(user, key);
+ // await this.helper.deleteKeyArchiveCache();
+ return this.systemRepo.save(key);
+ }
}
diff --git a/client/src/app/model/interface/keysystem.interface.ts b/client/src/app/model/interface/keysystem.interface.ts
new file mode 100644
index 0000000..22a45d2
--- /dev/null
+++ b/client/src/app/model/interface/keysystem.interface.ts
@@ -0,0 +1,7 @@
+export interface ISystem {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt?: string;
+ name: string;
+}
\ No newline at end of file
diff --git a/client/src/app/modules/system/components/system-archive/system-archive.component.html b/client/src/app/modules/system/components/system-archive/system-archive.component.html
new file mode 100644
index 0000000..3ec5f58
--- /dev/null
+++ b/client/src/app/modules/system/components/system-archive/system-archive.component.html
@@ -0,0 +1,15 @@
+
Gelöschte Schließanlagen
+
+ @if(myTheme) {
+
+ }
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/app/modules/system/components/system-archive/system-archive.component.scss b/client/src/app/modules/system/components/system-archive/system-archive.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/client/src/app/modules/system/components/system-archive/system-archive.component.spec.ts b/client/src/app/modules/system/components/system-archive/system-archive.component.spec.ts
new file mode 100644
index 0000000..d2cf062
--- /dev/null
+++ b/client/src/app/modules/system/components/system-archive/system-archive.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SystemArchiveComponent } from './system-archive.component';
+
+describe('SystemArchiveComponent', () => {
+ let component: SystemArchiveComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SystemArchiveComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(SystemArchiveComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/client/src/app/modules/system/components/system-archive/system-archive.component.ts b/client/src/app/modules/system/components/system-archive/system-archive.component.ts
new file mode 100644
index 0000000..99ce31f
--- /dev/null
+++ b/client/src/app/modules/system/components/system-archive/system-archive.component.ts
@@ -0,0 +1,70 @@
+import { Component, inject, LOCALE_ID } from '@angular/core';
+import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
+import { DatePipe } from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatIconModule } from '@angular/material/icon';
+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 { ISystem } from '../../../../model/interface/keysystem.interface';
+
+@Component({
+ selector: 'app-system-archive',
+ imports: [MatDialogModule, AgGridAngular, MatButtonModule, MatIconModule],
+ templateUrl: './system-archive.component.html',
+ styleUrl: './system-archive.component.scss',
+ providers: [DatePipe, { provide: LOCALE_ID, useValue: 'de-DE' }]
+})
+export class SystemArchiveComponent extends AgGridContainerComponent {
+ private api: ApiService = inject(ApiService);
+ private datePipe = inject(DatePipe);
+
+ gridApi!: GridApi;
+
+ gridOptions: GridOptions = HELPER.getGridOptions();
+
+ constructor() {
+ super();
+
+ 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: () => '',
+ onCellClicked: (event) => { this.restoreSystem(event.data);},
+ tooltipValueGetter: () => 'Wiederherstellen',
+ sortable: false
+ }
+ ];
+ this.gridOptions.rowHeight = 36;
+ this.gridOptions.overlayNoRowsTemplate = 'Bisher wurden keine Schließanlagen gelöscht. Sobald dies der Fall ist, werden sie hier angezeigt.';
+ }
+
+ onGridReady(params: GridReadyEvent) {
+ this.gridApi = params.api;
+ this.loadSystems();
+ }
+
+ async loadSystems() {
+ this.gridApi.setGridOption("loading", true);
+ const systems = await this.api.getSystemArchive()
+ this.gridApi.setGridOption("rowData", systems);
+ this.gridApi.setGridOption("loading", false);
+ }
+
+ async restoreSystem(system: ISystem) {
+ await this.api.restoreSystem(system);
+ this.loadSystems();
+ }
+
+}
diff --git a/client/src/app/modules/system/system.component.html b/client/src/app/modules/system/system.component.html
index 1228c81..2b47100 100644
--- a/client/src/app/modules/system/system.component.html
+++ b/client/src/app/modules/system/system.component.html
@@ -8,4 +8,5 @@
}
+
\ 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 d41477c..e9368a6 100644
--- a/client/src/app/modules/system/system.component.ts
+++ b/client/src/app/modules/system/system.component.ts
@@ -9,10 +9,12 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { CreateSystemComponent } from './create/create.component';
import { AgGridContainerComponent } from '../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
import { AgSystemManagerComponent } from '../../shared/ag-grid/components/ag-system-manager/ag-system-manager.component';
+import { SystemArchiveComponent } from './components/system-archive/system-archive.component';
+import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-system',
- imports: [AgGridAngular, MatButtonModule, MatDialogModule],
+ imports: [AgGridAngular, MatButtonModule, MatDialogModule, MatIconModule],
providers: [DatePipe, { provide: LOCALE_ID, useValue: 'de-DE' }],
templateUrl: './system.component.html',
styleUrl: './system.component.scss'
@@ -41,12 +43,15 @@ export class SystemComponent extends AgGridContainerComponent {
// , onCellClicked: (event) => { this.deleteKey(event.data.id)}
}
];
+
}
async loadSystems() {
this.gridApi.setGridOption("loading", true);
await this.api.refreshSystems();
this.gridApi.setGridOption("loading", false);
+ const a = await this.api.getSystemArchive();
+ console.log(a)
}
onGridReady(params: GridReadyEvent) {
@@ -81,4 +86,15 @@ export class SystemComponent extends AgGridContainerComponent {
}
})
}
+
+ openArchive() {
+ this.dialog.open(SystemArchiveComponent, {
+ maxHeight: "calc(100vh - 24px)",
+ maxWidth: "calc(100vw - 24px)",
+ width: "50vw",
+ minWidth: "300px",
+ height: "70vh",
+ disableClose: true
+ })
+ }
}
diff --git a/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.html b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.html
new file mode 100644
index 0000000..b8b66b1
--- /dev/null
+++ b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.html
@@ -0,0 +1,20 @@
+Schließanlage {{key.name}} löschen?
+
+
+
warning
+
+ {{key.name}} wirklich entfernen?
+
+
+
+ Gelöschte Schließanlagen können über das Archiv wiederhergestellt werden.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.scss b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.spec.ts b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.spec.ts
new file mode 100644
index 0000000..8172754
--- /dev/null
+++ b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AgDeleteSystemComponent } from './ag-delete-system.component';
+
+describe('AgDeleteSystemComponent', () => {
+ let component: AgDeleteSystemComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AgDeleteSystemComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(AgDeleteSystemComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.ts b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.ts
new file mode 100644
index 0000000..fe17199
--- /dev/null
+++ b/client/src/app/shared/ag-grid/components/ag-delete-system/ag-delete-system.component.ts
@@ -0,0 +1,17 @@
+import { Component, inject } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatIconModule } from '@angular/material/icon';
+import { IKey } from '../../../../model/interface/key.interface';
+
+@Component({
+ selector: 'app-ag-delete-system',
+ imports: [MatDialogModule, MatButtonModule, MatIconModule],
+ templateUrl: './ag-delete-system.component.html',
+ styleUrl: './ag-delete-system.component.scss',
+})
+export class AgDeleteSystemComponent {
+ readonly dialogRef = inject(MatDialogRef);
+ readonly key = inject(MAT_DIALOG_DATA);
+
+}
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
index bc13d73..44e85c9 100644
--- 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
@@ -1 +1,2 @@
-
\ No newline at end of file
+
+
\ 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
index 5ad052b..3b76452 100644
--- a/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts
+++ b/client/src/app/shared/ag-grid/components/ag-system-manager/ag-system-manager.component.ts
@@ -6,6 +6,7 @@ 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';
+import { AgDeleteSystemComponent } from '../ag-delete-system/ag-delete-system.component';
@Component({
selector: 'app-ag-system-manager',
@@ -32,15 +33,39 @@ export class AgSystemManagerComponent implements ICellRendererAngularComp {
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: false
- })
- }
+ 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: false
+ })
+ }
+
+ delete() {
+ const ref = this.dialog.open(AgDeleteSystemComponent, {
+ data: this.system,
+ autoFocus: false,
+ width: '500px',
+ maxWidth: 'calc(100vw - 24px)',
+ })
+
+ ref.afterClosed().subscribe({
+ next: n => {
+ if (n) {
+ this.deleteThisKey();
+ }
+ }
+ })
+ }
+
+ async deleteThisKey() {
+ this.params.api.setGridOption("loading", true);
+ await this.api.deleteSystem(this.system);
+ this.params.api.setGridOption("loading", false);
+
+ }
}
diff --git a/client/src/app/shared/api.service.ts b/client/src/app/shared/api.service.ts
index 1e06b33..8c08d76 100644
--- a/client/src/app/shared/api.service.ts
+++ b/client/src/app/shared/api.service.ts
@@ -6,6 +6,7 @@ import { IKey } from '../model/interface/key.interface';
import { ICylinder } from '../model/interface/cylinder.interface';
import { HotToastService } from '@ngxpert/hot-toast';
import { ICustomer } from '../model/interface/customer.interface';
+import { ISystem } from '../model/interface/keysystem.interface';
@Injectable({
providedIn: 'root'
@@ -198,7 +199,7 @@ export class ApiService {
*/
deleteSystem(system: any): Promise {
return new Promise(resolve => {
- this.http.delete(`api/system${system.id}`).pipe(
+ this.http.delete(`api/system/${system.id}`).pipe(
this.toast.observe({
loading: `Lösche Schließanlage ${system.name}...`,
success: `Schließanlage ${system.name} wurde gelöscht.`,
@@ -222,6 +223,21 @@ export class ApiService {
return this.http.put(`api/key/${id}/restore`, null);
}
+ restoreSystem(system: ISystem): Promise {
+ return new Promise(resolve => {
+ this.http.put(`api/system/${system.id}/restore`, null).subscribe({
+ next: () => {
+ this.refreshSystems();
+ resolve(true);
+ },
+ error: () => {
+ this.toast.error('Schließanlage konnte nicht wiederhergestellt werden');
+ resolve(false)
+ }
+ })
+ })
+ }
+
private getCylinders(): Observable {
return this.http.get('api/cylinder');
}
@@ -240,6 +256,20 @@ export class ApiService {
})
}
+ getSystemArchive(): Promise {
+ return new Promise(resolve => {
+ this.http.get('api/system/archive').subscribe({
+ next: val => {
+ return resolve(val);
+ },
+ error: () => {
+ this.toast.error('Gelöschte Schließanlagen konnten nicht geladen werden');
+ return resolve([])
+ }
+ })
+ })
+ }
+
/**
* Aktualisiert die Zylinder im Behaviour Subject
* cylinders