Archive und Logging
This commit is contained in:
@@ -55,7 +55,6 @@ export class AuthService {
|
||||
const payload: IExternalAccessPayload = this.jwt.decode(access_token);
|
||||
return new Promise<User>(async (resolve) => {
|
||||
let user = await this.userRepo.findByUsername(payload.username, { settings: true });
|
||||
|
||||
if (!user) {
|
||||
user = await this.userRepo.createUser({
|
||||
username: payload.username,
|
||||
|
||||
@@ -24,6 +24,11 @@ export class CylinderController {
|
||||
return this.service.getCylinders(req.user);
|
||||
}
|
||||
|
||||
@Get('archive')
|
||||
getCylinderArchive(@Req() req: AuthenticatedRequest): Promise<Cylinder[]> {
|
||||
return this.service.getDeletedCylinders(req.user);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
deleteKey(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||
return this.service.deleteCylinder(req.user, id);
|
||||
@@ -44,4 +49,9 @@ export class CylinderController {
|
||||
) {
|
||||
return this.service.createCylinder(req.user, b);
|
||||
}
|
||||
|
||||
@Put(':id/restore')
|
||||
restoreKey(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
|
||||
return this.service.restoreCylinder(req.user, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Cylinder, User } from 'src/model/entitites';
|
||||
import { ActivityRepository, CylinderRepository, KeyRepository } from 'src/model/repositories';
|
||||
import { ActivityHelperService } from 'src/shared/service/activity.logger.service';
|
||||
import { HelperService } from 'src/shared/service/system.helper.service';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class CylinderService {
|
||||
@@ -11,7 +13,8 @@ export class CylinderService {
|
||||
private readonly keyRepo: KeyRepository,
|
||||
private systemActivityRepo: ActivityRepository,
|
||||
private readonly helper: HelperService,
|
||||
private readonly configService: ConfigService
|
||||
private readonly configService: ConfigService,
|
||||
private activityService: ActivityHelperService
|
||||
) {}
|
||||
|
||||
get isDevelopMode(): boolean {
|
||||
@@ -39,7 +42,8 @@ export class CylinderService {
|
||||
|
||||
const keysToDelete = cylinder.keys.filter(k => k.cylinder.length == 1);
|
||||
await this.keyRepo.softRemove(keysToDelete);
|
||||
await this.cylinderRepo.softDelete({id: cylinder.id})
|
||||
await this.cylinderRepo.softDelete({id: cylinder.id});
|
||||
this.activityService.logCylinderDeleted(user, cylinder)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,4 +69,27 @@ export class CylinderService {
|
||||
});
|
||||
return c
|
||||
}
|
||||
|
||||
getDeletedCylinders(user: User) {
|
||||
return this.cylinderRepo.find({
|
||||
where: {
|
||||
system: { managers: { id: user.id } },
|
||||
deletedAt: Not(IsNull()),
|
||||
},
|
||||
withDeleted: true,
|
||||
order: { deletedAt: { direction: 'DESC' } },
|
||||
});
|
||||
}
|
||||
|
||||
async restoreCylinder(user: User, keyID: string) {
|
||||
|
||||
const cylinder = await this.cylinderRepo.findOneOrFail({
|
||||
where: { system: { managers: { id: user.id } } , id: keyID },
|
||||
withDeleted: true,
|
||||
});
|
||||
cylinder.deletedAt = null;
|
||||
await this.activityService.logCylinderRestored(user, cylinder);
|
||||
await this.helper.deleteKeyArchiveCache();
|
||||
return this.cylinderRepo.save(cylinder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export class KeyController {
|
||||
return this.service.getKeyHandovers(req.user, id);
|
||||
}
|
||||
|
||||
@Get('Archive')
|
||||
@Get('archive')
|
||||
getArchive(@Req() req: AuthenticatedRequest) {
|
||||
return this.service.getDeletedKeys(req.user);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,19 @@ export class KeyService {
|
||||
}
|
||||
if (k.keyLost != key.keyLost) {
|
||||
await this.activityService.logKeyLostUpdate(user, key, key.keyLost);
|
||||
}
|
||||
try {
|
||||
const k = await this.keyrepository.findOne({
|
||||
where: { id: key.id },
|
||||
relations: ['cylinder', 'cylinder.system', 'cylinder.system.managers', 'cylinder.system.managers.settings'],
|
||||
withDeleted: false
|
||||
});
|
||||
for (const to of k.cylinder[0].system.managers.filter(m => m.settings.sendSystemUpdateMails)) {
|
||||
this.mailService.sendKeyLostOrFoundMail({ key, to } )
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return this.keyrepository.save(this.keyrepository.create(key));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ export enum LogType {
|
||||
export enum EmailEvent {
|
||||
GrantSystemAccess,
|
||||
RemoveSystemAccess,
|
||||
KeyHandout
|
||||
KeyHandout,
|
||||
KeyLostOrFound
|
||||
}
|
||||
|
||||
export interface EmailLogDto {
|
||||
|
||||
@@ -10,10 +10,52 @@ export class MailService {
|
||||
constructor(
|
||||
private mailserService: MailerService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly logService: LogService,
|
||||
private readonly logService: LogService
|
||||
) {
|
||||
}
|
||||
|
||||
async sendKeyLostOrFoundMail({to, key}: {to: User, key: Key}) {
|
||||
// const subject
|
||||
const keyAction = key.keyLost == null ? 'wurde gefunden' : 'wurde als verloren gemeldet';
|
||||
const keyExtendedAction = key.keyLost == null ? `wurde als gefunden gemeldet` : `wurde am ${new Date(key.keyLost).toLocaleDateString()} als verloren gemeldet`;
|
||||
const subject = key.keyLost == null ? 'Schlüssel gefunden' : 'Schlüssel verloren';
|
||||
const context = {
|
||||
keyAction,
|
||||
keyExtendedAction,
|
||||
firstName: to.firstName,
|
||||
keyNr: key.nr,
|
||||
keyName: key.name,
|
||||
url: 'https://keyvaultpro.de/keys?nr=' + key.nr
|
||||
}
|
||||
|
||||
this.mailserService.sendMail({
|
||||
template: './key-handout-changed',
|
||||
to: to.username,
|
||||
from: this.configService.get<string>('MAILER_FROM'),
|
||||
subject: subject,
|
||||
context
|
||||
}).then(v => {
|
||||
|
||||
this.logService.log(LogType.Mail, {
|
||||
to: to.username,
|
||||
success: true,
|
||||
message: v.response,
|
||||
type: EmailEvent.KeyLostOrFound,
|
||||
system: key.cylinder[0].system,
|
||||
context: JSON.stringify(key)
|
||||
})
|
||||
}).catch(e => {
|
||||
this.logService.log(LogType.Mail, {
|
||||
to,
|
||||
success: false,
|
||||
message: e.response,
|
||||
type: EmailEvent.KeyLostOrFound,
|
||||
system: key.cylinder[0].system,
|
||||
context: JSON.stringify(key)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async sendKeyHandoutMail({to, key, handoutAction}: {to: User, key: Key, handoutAction: KeyHandout}) {
|
||||
const keyAction = handoutAction.direction == 'out' ? 'wurde ausgegeben' : 'wurde zurückgegeben';
|
||||
const keyExtendedAction = handoutAction.direction == 'return' ? `wurde von ${handoutAction.customer.name} zurückgegeben` : `wurde an ${handoutAction.customer.name} ausgegeben`;
|
||||
|
||||
@@ -44,6 +44,7 @@ export class SystemService {
|
||||
let systems = await this.systemRepo.find({
|
||||
where: { managers: { id: user.id } },
|
||||
order: { name: { direction: 'ASC' } },
|
||||
relations: ['cylinders']
|
||||
});
|
||||
|
||||
if (this.isDevelopMode) {
|
||||
|
||||
@@ -4,8 +4,6 @@ import { UserService } from './user.service';
|
||||
import { User } from 'src/model/entitites';
|
||||
import { IUser } from 'src/model/interface';
|
||||
import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface';
|
||||
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';
|
||||
import { HttpStatusCode } from 'axios';
|
||||
import { UserSettings } from 'src/model/entitites/user/user.settings.entity';
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
|
||||
@@ -46,7 +46,7 @@ export class UserService {
|
||||
const keys = cylinders.map(c => c.keys).flat().map(k => k.id);
|
||||
const keycount = [...new Set(keys)]
|
||||
|
||||
const handedOut = (await this.helper.getUsersKeys(user)).filter(k => k.handedOut).length;
|
||||
const handedOut = (await this.helper.getUsersKeys(user)).filter(k => k.handedOut && k.keyLost == null).length;
|
||||
return {
|
||||
keys: keycount.length,
|
||||
cylinders: cylinders.length,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Key, KeyHandout, User } from "src/model/entitites";
|
||||
import { Cylinder, Key, KeyHandout, User } from "src/model/entitites";
|
||||
import { KeySystem } from "src/model/entitites/system.entity";
|
||||
import { ActivityRepository, CylinderRepository, KeyRepository } from "src/model/repositories";
|
||||
import { HelperService } from "./system.helper.service";
|
||||
@@ -115,4 +115,30 @@ export class ActivityHelperService {
|
||||
message: msg,
|
||||
}))
|
||||
}
|
||||
|
||||
async logCylinderRestored(user: User, cylinder: Cylinder) {
|
||||
let msg = `Zylinder ${cylinder.name} wiederhergestellt`;
|
||||
|
||||
const system: KeySystem = await this.helper.getSystemOCylinder(cylinder);
|
||||
|
||||
this.activityRepo.save(
|
||||
this.activityRepo.create({
|
||||
system,
|
||||
user,
|
||||
message: msg,
|
||||
}))
|
||||
}
|
||||
|
||||
async logCylinderDeleted(user: User, cylinder: Cylinder) {
|
||||
let msg = `Zylinder ${cylinder.name} gelöscht`;
|
||||
|
||||
const system: KeySystem = await this.helper.getSystemOCylinder(cylinder);
|
||||
|
||||
this.activityRepo.save(
|
||||
this.activityRepo.create({
|
||||
system,
|
||||
user,
|
||||
message: msg,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,16 @@ export class HelperService {
|
||||
return found.system;
|
||||
}
|
||||
|
||||
async getSystemOCylinder(cylinder: Cylinder): Promise<KeySystem> {
|
||||
const k = await this.cylinderRepository.findOne({
|
||||
where: { id: cylinder.id },
|
||||
relations: ['system'],
|
||||
withDeleted: true,
|
||||
});
|
||||
this.cache()
|
||||
return k.system;
|
||||
}
|
||||
|
||||
async cache() {
|
||||
const value = await this.cacheManager.store.keys()
|
||||
console.log(value)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { tokenInterceptor } from './core/interceptor/token.interceptor';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { provideServiceWorker } from '@angular/service-worker';
|
||||
import { OVERLAY_DEFAULT_CONFIG } from "@angular/cdk/overlay";
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withInterceptors([tokenInterceptor]))
|
||||
@@ -17,11 +18,18 @@ export const appConfig: ApplicationConfig = {
|
||||
theme: 'toast',
|
||||
autoClose: true,
|
||||
dismissible: false,
|
||||
duration: 5000
|
||||
duration: 5000,
|
||||
|
||||
}),
|
||||
provideAnimationsAsync(), provideServiceWorker('ngsw-worker.js', {
|
||||
enabled: !isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
})
|
||||
}),
|
||||
{
|
||||
provide: OVERLAY_DEFAULT_CONFIG,
|
||||
useValue: {
|
||||
usePopover: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<h2 mat-dialog-title>Gelöschte Zylinder</h2>
|
||||
<mat-dialog-content>
|
||||
@if(myTheme && gridOptions) {
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
[theme]="myTheme"
|
||||
/>
|
||||
}
|
||||
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button matButton mat-dialog-close>Schließen</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CylinderArchiveComponent } from './cylinder-archive.component';
|
||||
|
||||
describe('CylinderArchiveComponent', () => {
|
||||
let component: CylinderArchiveComponent;
|
||||
let fixture: ComponentFixture<CylinderArchiveComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CylinderArchiveComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CylinderArchiveComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||
import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
|
||||
import { CommonModule, DatePipe } from '@angular/common';
|
||||
import { HotToastService } from '@ngxpert/hot-toast';
|
||||
import { ApiService } from '../../../../shared/api.service';
|
||||
import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community';
|
||||
import { HELPER } from '../../../../shared/helper.service';
|
||||
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 { ICylinder } from '../../../../model/interface/cylinder.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cylinder-archive',
|
||||
imports: [MatDialogModule, AgGridAngular, MatButtonModule, MatIconModule, CommonModule],
|
||||
providers: [DatePipe, { provide: LOCALE_ID, useValue: 'de-DE' }],
|
||||
templateUrl: './cylinder-archive.component.html',
|
||||
styleUrl: './cylinder-archive.component.scss',
|
||||
})
|
||||
export class CylinderArchiveComponent extends AgGridContainerComponent {
|
||||
private api: ApiService = inject(ApiService);
|
||||
private datePipe = inject(DatePipe);
|
||||
private toast = inject(HotToastService);
|
||||
|
||||
gridApi!: GridApi;
|
||||
|
||||
gridOptions: GridOptions = HELPER.getGridOptions();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.createGridOptions();
|
||||
|
||||
}
|
||||
|
||||
private createGridOptions() {
|
||||
this.gridOptions.columnDefs = [
|
||||
{ colId: 'name', field: 'name' , headerName: 'Name', flex: 1, editable: true, sort: 'asc', filter: true },
|
||||
{ colId: 'nr', field: 'nr' , headerName: 'Name', flex: 1, editable: true, filter: true },
|
||||
{
|
||||
field: 'deletedAt'
|
||||
, headerName: 'Gelöscht'
|
||||
, width: 160
|
||||
, cellRenderer: (data: any) => this.datePipe.transform(new Date(data.value), 'short')
|
||||
},
|
||||
{
|
||||
width: 40,
|
||||
cellRenderer: () => '<div class="icon-btn-sm restore icon-btn-xs" ></div>',
|
||||
onCellClicked: (event) => { this.restoreCylinder(event.data);},
|
||||
tooltipValueGetter: () => 'Wiederherstellen',
|
||||
sortable: false
|
||||
}
|
||||
];
|
||||
this.gridOptions.rowHeight = 36;
|
||||
this.gridOptions.overlayNoRowsTemplate = 'Bisher wurden keine Zylinder gelöscht. Sobald dies der Fall ist, werden sie hier angezeigt.';
|
||||
}
|
||||
|
||||
|
||||
async restoreCylinder(data: ICylinder) {
|
||||
this.gridApi.setGridOption("loading", true);
|
||||
await this.api.restoreCylinder(data.id);
|
||||
this.loadCylinders();
|
||||
}
|
||||
|
||||
onGridReady(params: GridReadyEvent) {
|
||||
this.gridApi = params.api;
|
||||
this.loadCylinders();
|
||||
}
|
||||
|
||||
async loadCylinders() {
|
||||
this.gridApi.setGridOption("loading", true);
|
||||
const cylinders = await this.api.getCylinderArchive();
|
||||
this.gridApi.setGridOption("rowData", cylinders);
|
||||
this.gridApi.setGridOption("loading", false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,5 +8,5 @@
|
||||
}
|
||||
<div class="floating-btn-container">
|
||||
<button mat-flat-button class="btn-create mat-elevation-z8" (click)="openCreateCylinder()" >Zylinder anlegen</button>
|
||||
<button mat-mini-fab disabled><mat-icon>inventory_2</mat-icon></button>
|
||||
<button mat-mini-fab (click)="openArchive()"><mat-icon>inventory_2</mat-icon></button>
|
||||
</div>
|
||||
@@ -10,6 +10,7 @@ import { CreateCylinderComponent } from './components/create-cylinder/create-cyl
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AgGridContainerComponent } from '../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
|
||||
import { CylinderArchiveComponent } from './components/cylinder-archive/cylinder-archive.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cylinder',
|
||||
@@ -76,4 +77,14 @@ export class CylinderComponent extends AgGridContainerComponent {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openArchive() {
|
||||
this.dialog.open(CylinderArchiveComponent, {
|
||||
maxHeight: "calc(100vh - 24px)",
|
||||
maxWidth: "calc(100vw - 24px)",
|
||||
width: "50vw",
|
||||
minWidth: "min(700px,calc(100vw - 24px))",
|
||||
height: "70vh",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<h2 mat-dialog-title>Gelöschte Schlüssel</h2>
|
||||
<mat-dialog-content>
|
||||
<ag-grid-angular
|
||||
@if(myTheme) {
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
[theme]="myTheme"
|
||||
/>
|
||||
}
|
||||
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-archive',
|
||||
@@ -19,7 +20,7 @@ import { HELPER } from '../../../../shared/helper.service';
|
||||
templateUrl: './archive.component.html',
|
||||
styleUrl: './archive.component.scss'
|
||||
})
|
||||
export class ArchiveComponent {
|
||||
export class ArchiveComponent extends AgGridContainerComponent {
|
||||
private api: ApiService = inject(ApiService);
|
||||
private datePipe = inject(DatePipe);
|
||||
private toast = inject(HotToastService);
|
||||
@@ -31,6 +32,7 @@ export class ArchiveComponent {
|
||||
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 },
|
||||
@@ -87,6 +89,9 @@ export class ArchiveComponent {
|
||||
},
|
||||
error: () => {
|
||||
this.gridApi.setGridOption("loading", false);
|
||||
},
|
||||
complete: () => {
|
||||
this.api.refreshKeys();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<h2 mat-dialog-title>Verlorene Schlüssel</h2>
|
||||
<mat-dialog-content>
|
||||
<ag-grid-angular
|
||||
@if(myTheme) {
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
[theme]="myTheme"
|
||||
/>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button matButton [mat-dialog-close]="dataChanged">Schließen</button>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { HELPER } from '../../../../shared/helper.service';
|
||||
import { AgGridAngular } from 'ag-grid-angular';
|
||||
import { LostKeyComponent } from '../lost-key/lost-key.component';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lost-keys',
|
||||
@@ -18,7 +19,7 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
templateUrl: './lost-keys.component.html',
|
||||
styleUrl: './lost-keys.component.scss'
|
||||
})
|
||||
export class LostKeysComponent {
|
||||
export class LostKeysComponent extends AgGridContainerComponent {
|
||||
private api: ApiService = inject(ApiService);
|
||||
private datePipe = inject(DatePipe);
|
||||
private dialog: MatDialog = inject(MatDialog);
|
||||
@@ -31,6 +32,7 @@ export class LostKeysComponent {
|
||||
gridOptions: GridOptions = HELPER.getGridOptions();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.gridOptions.columnDefs = [
|
||||
{ colId: 'name', field: 'name', headerName: 'Name', sort: 'asc', flex: 1, filter: true },
|
||||
{ colId: 'nr', field: 'nr', headerName: 'Schlüsselnummer', flex: 1, filter: true },
|
||||
@@ -83,6 +85,7 @@ export class LostKeysComponent {
|
||||
next: () => {
|
||||
this.toast.success('Schlüssel als gefunden markiert');
|
||||
this.loadLostKeys();
|
||||
this.api.refreshKeys();
|
||||
}
|
||||
});
|
||||
this.dataChanged = true;
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
<mat-dialog-content>
|
||||
<div style="display: flex; flex-direction: column; height: calc(100% - 4px);" class="gap-2">
|
||||
<div class="flex-auto">
|
||||
<ag-grid-angular
|
||||
@if(myTheme) {
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
[theme]="myTheme"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-center p-4 bg-gray-50 rounded-md shadow-sm">
|
||||
<mat-form-field class="flex-1">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput [(ngModel)]="email" placeholder="beispiel@email.com">
|
||||
<mat-hint>Emailadresse des neuen Users eingeben</mat-hint>
|
||||
<mat-hint>Emailadresse des Benutzers eingeben</mat-hint>
|
||||
<mat-icon matPrefix class="text-gray-400 mr-2">email</mat-icon>
|
||||
</mat-form-field>
|
||||
<button matButton="elevated"
|
||||
|
||||
@@ -15,6 +15,7 @@ import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { RemoveManagerPopupComponent } from '../remove-manager-popup/remove-manager-popup.component';
|
||||
import { IUser } from '../../../../model/interface/user.interface';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-system-manager',
|
||||
@@ -22,7 +23,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
templateUrl: './system-manager.component.html',
|
||||
styleUrl: './system-manager.component.scss'
|
||||
})
|
||||
export class SystemManagerComponent {
|
||||
export class SystemManagerComponent extends AgGridContainerComponent {
|
||||
|
||||
gridApi!: GridApi;
|
||||
|
||||
@@ -39,10 +40,11 @@ export class SystemManagerComponent {
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
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},
|
||||
{ colId: 'delete', headerName: '', width: 50,
|
||||
{ colId: 'delete', headerName: '', width: 50, sortable: false,
|
||||
cellRenderer: (params: any) => {
|
||||
if (this.authService.user.username == params.data.username) return '';
|
||||
return `<div class="delete icon icon-btn-xs"></div>`;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||
import { AgGridAngular } from 'ag-grid-angular';
|
||||
import { GridApi, GridOptions, GridReadyEvent, Theme, ThemeDefaultParams } from 'ag-grid-community';
|
||||
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';
|
||||
import { AgGridService } from '../../shared/ag-grid/ag-grid.service';
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'app-system',
|
||||
imports: [AgGridAngular, MatButtonModule, MatDialogModule],
|
||||
providers: [DatePipe],
|
||||
providers: [DatePipe, { provide: LOCALE_ID, useValue: 'de-DE' }],
|
||||
templateUrl: './system.component.html',
|
||||
styleUrl: './system.component.scss'
|
||||
})
|
||||
@@ -31,6 +30,7 @@ export class SystemComponent extends AgGridContainerComponent {
|
||||
super();
|
||||
this.gridOptions.columnDefs = [
|
||||
{ colId: 'name', field: 'name', headerName: 'Name', sort: 'asc', flex: 1},
|
||||
{ colId: 'cylinderCount', field: 'cylinders', headerName: 'Zylinderanzahl', flex: 0, cellRenderer: (data: any) => data.value.length},
|
||||
{ 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)) : '-' },
|
||||
{
|
||||
|
||||
@@ -27,20 +27,11 @@ export class AgDeleteCylinderComponent extends AgBaseComponentComponent {
|
||||
})
|
||||
}
|
||||
|
||||
deleteThisCylinder() {
|
||||
this.api.deleteCylinder(this.params.data)
|
||||
.pipe(
|
||||
this.toast.observe({
|
||||
loading: 'Löschen...',
|
||||
success: 'Gelöscht!',
|
||||
error: 'Konnte nicht gelöscht werden'
|
||||
})
|
||||
).subscribe({
|
||||
next: () => {
|
||||
const rows = this.params.api.getGridOption("rowData")?.filter(r => r.id != this.params.data.id);
|
||||
this.params.api.setGridOption("rowData", rows);
|
||||
}
|
||||
})
|
||||
async deleteThisCylinder() {
|
||||
await this.api.deleteCylinder(this.params.data);
|
||||
const rows = this.params.api.getGridOption("rowData")?.filter(r => r.id != this.params.data.id);
|
||||
this.params.api.setGridOption("rowData", rows);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, map, Observable } from 'rxjs';
|
||||
import { IUser } from '../model/interface/user.interface';
|
||||
import { IKey } from '../model/interface/key.interface';
|
||||
import { ICylinder } from '../model/interface/cylinder.interface';
|
||||
@@ -121,6 +121,20 @@ export class ApiService {
|
||||
return this.http.get<ICylinder[]>('api/cylinder');
|
||||
}
|
||||
|
||||
getCylinderArchive(): Promise<ICylinder[]> {
|
||||
return new Promise<ICylinder[]>(resolve => {
|
||||
this.http.get<ICylinder[]>('api/cylinder/archive').subscribe({
|
||||
next: val => {
|
||||
return resolve(val);
|
||||
},
|
||||
error: () => {
|
||||
this.toast.error('Gelöschte Zylinder konnten nicht geladen werden');
|
||||
return resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert die Zylinder im Behaviour Subject
|
||||
* cylinders
|
||||
@@ -140,8 +154,37 @@ export class ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
deleteCylinder(cylinder: ICylinder): Observable<any> {
|
||||
return this.http.delete(`api/cylinder/${cylinder.id}`)
|
||||
deleteCylinder(cylinder: ICylinder): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
this.http.delete(`api/cylinder/${cylinder.id}`).pipe(
|
||||
this.toast.observe({
|
||||
loading: `Lösche Zylinder ${cylinder.name}...`,
|
||||
success: 'Zylinder gelöscht',
|
||||
error: 'Es ist ein Fehler aufgetreten'
|
||||
})
|
||||
).subscribe({
|
||||
next: () => resolve(true),
|
||||
error: () => resolve(false),
|
||||
complete: () => this.refreshCylinders()
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
restoreCylinder(id: string): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
this.http.put(`api/cylinder/${id}/restore`, null).pipe(
|
||||
this.toast.observe({
|
||||
loading: 'Stelle wiederher...',
|
||||
success: 'Zylinder wiederhergestellt',
|
||||
error: 'Es ist ein Fehler aufgetreten'
|
||||
})).subscribe({
|
||||
next: () => resolve(true),
|
||||
error: () => resolve(false),
|
||||
complete: () => this.refreshCylinders()
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
deleteUser(id: string) {
|
||||
|
||||
Reference in New Issue
Block a user