create cylinder

This commit is contained in:
Bastian Wagner
2025-01-02 16:53:16 +01:00
parent 5c6516095b
commit a47bbe29fe
11 changed files with 175 additions and 9 deletions

View File

@@ -1,12 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Cylinder, User } from 'src/model/entitites'; import { Cylinder, User } from 'src/model/entitites';
import { CylinderRepository, KeyRepository } from 'src/model/repositories'; import { ActivityRepository, CylinderRepository, KeyRepository } from 'src/model/repositories';
@Injectable() @Injectable()
export class CylinderService { export class CylinderService {
constructor( constructor(
private readonly cylinderRepo: CylinderRepository, private readonly cylinderRepo: CylinderRepository,
private readonly keyRepo: KeyRepository, private readonly keyRepo: KeyRepository,
private systemActivityRepo: ActivityRepository,
) {} ) {}
async getCylinders(user: User): Promise<Cylinder[]> { async getCylinders(user: User): Promise<Cylinder[]> {
@@ -40,7 +41,14 @@ export class CylinderService {
return this.cylinderRepo.save(original); return this.cylinderRepo.save(original);
} }
createCylinder(user: User, cylinder: Partial<Cylinder>) { async createCylinder(user: User, cylinder: Partial<Cylinder>) {
return this.cylinderRepo.save(this.cylinderRepo.create(cylinder)); const c = await this.cylinderRepo.save(this.cylinderRepo.create(cylinder));
this.systemActivityRepo.save({
message: `Zylinder ${(c as any).name} angelegt`,
user: user,
system: (c as any).system
});
return c
} }
} }

View File

@@ -1,19 +1,31 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSystemDto } from './dto/create-system.dto'; import { CreateSystemDto } from './dto/create-system.dto';
import { UpdateSystemDto } from './dto/update-system.dto'; import { UpdateSystemDto } from './dto/update-system.dto';
import { KeySystemRepository, UserRepository } from 'src/model/repositories'; import { ActivityRepository, KeySystemRepository, UserRepository } from 'src/model/repositories';
import { User } from 'src/model/entitites'; import { User } from 'src/model/entitites';
import { IUser } from 'src/model/interface'; import { IUser } from 'src/model/interface';
@Injectable() @Injectable()
export class SystemService { export class SystemService {
constructor(private systemRepo: KeySystemRepository, private userRepo: UserRepository) {} constructor(
private systemRepo: KeySystemRepository,
private userRepo: UserRepository,
private systemActivityRepo: ActivityRepository,
) {}
async create(user: User, createSystemDto: CreateSystemDto) { async create(user: User, createSystemDto: CreateSystemDto) {
const sys = this.systemRepo.create(createSystemDto); const sys = this.systemRepo.create(createSystemDto);
sys.managers = [user]; sys.managers = [user];
try { try {
const res = await this.systemRepo.save(sys); const res = await this.systemRepo.save(sys);
this.systemActivityRepo.save({
message: `Schließanlage ${(res as any).name} angelegt`,
user: user,
system: res
});
return res; return res;
} catch (e) { } catch (e) {
throw new HttpException(e.code, HttpStatus.UNPROCESSABLE_ENTITY); throw new HttpException(e.code, HttpStatus.UNPROCESSABLE_ENTITY);

View File

@@ -0,0 +1,30 @@
<h2 mat-dialog-title>Neuen Zylinder anlegen</h2>
<mat-dialog-content>
<form [formGroup]="createForm" class="flex flex-col gap-3">
<mat-form-field>
<mat-label>Name</mat-label>
<input type="text" matInput formControlName="name" maxlength="100">
@if ((createForm.controls.name.value || '').length > 20) {
<mat-hint>{{ (createForm.controls.name.value || '').length }} / 100 Zeichen</mat-hint>
} @else {
<mat-hint>Wie soll der Zylinder heißen?</mat-hint>
}
</mat-form-field>
<mat-form-field>
<mat-label>Schließanlage</mat-label>
<mat-select formControlName="system">
@for (item of systems; track $index) {
<mat-option [value]="item">{{ item.name }}</mat-option>
}
</mat-select>
<mat-hint>Zu welcher Schließanlage gehört der Zylinder?</mat-hint>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>Abbrechen</button>
<button mat-button (click)="save()" [disabled]="createForm.disabled || createForm.invalid">Speichern</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,34 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateCylinderComponent } from './create-cylinder.component';
import { MatDialogRef } from '@angular/material/dialog';
import { HotToastService } from '@ngxpert/hot-toast';
import { ApiService } from '../../../../shared/api.service';
import { MockApiService } from '../../../../../../mocks/services/mock.api.service';
import { MockHotToastService } from '../../../../../../mocks/services/mock.hottoast.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('CreateCylinderComponent', () => {
let component: CreateCylinderComponent;
let fixture: ComponentFixture<CreateCylinderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CreateCylinderComponent, NoopAnimationsModule],
providers: [
{ provide: ApiService, useClass: MockApiService },
{ provide: MatDialogRef, useValue: [] },
{ provide: HotToastService, useClass: MockHotToastService }
]
})
.compileComponents();
fixture = TestBed.createComponent(CreateCylinderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,53 @@
import { Component, inject } from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { HotToastService } from '@ngxpert/hot-toast';
import { ApiService } from '../../../../shared/api.service';
import { CommonModule } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-create-cylinder',
standalone: true,
imports: [CommonModule, MatFormFieldModule, MatInputModule, MatDialogModule, ReactiveFormsModule, FormsModule, MatSelectModule, MatButtonModule],
templateUrl: './create-cylinder.component.html',
styleUrl: './create-cylinder.component.scss'
})
export class CreateCylinderComponent {
private api: ApiService = inject(ApiService);
private toast: HotToastService = inject(HotToastService);
readonly dialogRef = inject(MatDialogRef<CreateCylinderComponent>);
systems: any[] = [];
createForm = new FormGroup({
name: new FormControl<string | null>(null, Validators.required),
system: new FormControl<any>(null, Validators.required)
});
ngOnInit() {
this.api.getSystems().subscribe({
next: systems => {
this.systems = systems;
}
});
}
save() {
this.api.createCylinder(this.createForm.value as any)
.pipe(
this.toast.observe({
loading: 'Speichern...',
success: 'Gespeichert!',
error: 'Konnte nicht gespeichert werden'
})
).subscribe({
next: cylinder => {
this.dialogRef.close(cylinder);
}
});
}
}

View File

@@ -2,4 +2,8 @@
style="width: 100%; height: 100%;" style="width: 100%; height: 100%;"
(gridReady)="onGridReady($event)" (gridReady)="onGridReady($event)"
[gridOptions]="gridOptions!" [gridOptions]="gridOptions!"
/> />
<div class="floating-btn-container">
<button mat-flat-button class="btn-create mat-elevation-z8" (click)="openCreateCylinder()" color="accent" >Zylinder anlegen</button>
<button mat-mini-fab disabled><mat-icon>inventory_2</mat-icon></button>
</div>

View File

@@ -5,11 +5,15 @@ import { AgGridAngular } from 'ag-grid-angular';
import { ApiService } from '../../shared/api.service'; import { ApiService } from '../../shared/api.service';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { AgDeleteCylinderComponent } from '../../shared/ag-grid/components/ag-delete-cylinder/ag-delete-cylinder.component'; import { AgDeleteCylinderComponent } from '../../shared/ag-grid/components/ag-delete-cylinder/ag-delete-cylinder.component';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { CreateCylinderComponent } from './components/create-cylinder/create-cylinder.component';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
@Component({ @Component({
selector: 'app-cylinder', selector: 'app-cylinder',
standalone: true, standalone: true,
imports: [AgGridAngular], imports: [AgGridAngular, MatDialogModule, MatIconModule, MatButtonModule],
providers: [DatePipe], providers: [DatePipe],
templateUrl: './cylinder.component.html', templateUrl: './cylinder.component.html',
styleUrl: './cylinder.component.scss' styleUrl: './cylinder.component.scss'
@@ -17,6 +21,7 @@ import { AgDeleteCylinderComponent } from '../../shared/ag-grid/components/ag-de
export class CylinderComponent { export class CylinderComponent {
private api: ApiService = inject(ApiService); private api: ApiService = inject(ApiService);
private datePipe = inject(DatePipe); private datePipe = inject(DatePipe);
private dialog = inject(MatDialog);
gridApi!: GridApi; gridApi!: GridApi;
@@ -38,7 +43,6 @@ export class CylinderComponent {
, cellRenderer: AgDeleteCylinderComponent , cellRenderer: AgDeleteCylinderComponent
} }
] ]
} }
loadCylinders() { loadCylinders() {
@@ -54,4 +58,19 @@ export class CylinderComponent {
this.gridApi = params.api; this.gridApi = params.api;
this.loadCylinders(); this.loadCylinders();
} }
openCreateCylinder() {
this.dialog.open(CreateCylinderComponent, {
maxWidth: "calc(100vw - 24px)",
width: "30vw",
minWidth: "200px",
disableClose: true
}).afterClosed().subscribe({
next: (cylinder) => {
if (cylinder) {
this.loadCylinders();
}
}
});
}
} }

View File

@@ -56,7 +56,7 @@
<mat-card> <mat-card>
<mat-card-content> <mat-card-content>
<ag-grid-angular <ag-grid-angular
style="width: 100%; height: 300px;" style="width: 100%; height: 100%;"
[gridOptions]="gridOptions" [gridOptions]="gridOptions"
(gridReady)="onGridReady($event)" (gridReady)="onGridReady($event)"
> >

View File

@@ -7,6 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
import {MatCardModule} from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { AgLoadingComponent } from '../../shared/ag-grid/components/ag-loading/ag-loading.component'; import { AgLoadingComponent } from '../../shared/ag-grid/components/ag-loading/ag-loading.component';
import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@@ -31,6 +32,7 @@ export class DashboardComponent {
pagination: true, pagination: true,
paginationPageSize: 10, paginationPageSize: 10,
loading: true, loading: true,
localeText: AG_GRID_LOCALE_DE,
loadingOverlayComponent: AgLoadingComponent loadingOverlayComponent: AgLoadingComponent
}; };

View File

@@ -111,4 +111,8 @@ export class ApiService {
getActivities(): Observable<any[]> { getActivities(): Observable<any[]> {
return this.http.get<any[]>('api/user/activities'); return this.http.get<any[]>('api/user/activities');
} }
createCylinder(data: any) {
return this.http.post<ICylinder>('api/cylinder', data);
}
} }