| @@ -1,29 +1,41 @@ | |||||
| import { Directive, EventEmitter, Input, Output, OnInit } from '@angular/core'; | import { Directive, EventEmitter, Input, Output, OnInit } from '@angular/core'; | ||||
| import { FormGroup } from '@angular/forms'; | import { FormGroup } from '@angular/forms'; | ||||
| import { ModalStatus } from '@app/_helpers/modal.states'; | |||||
| import { Observable } from 'rxjs'; | import { Observable } from 'rxjs'; | ||||
| import { Router } from '@angular/router'; | |||||
| import { TranslateService } from '@ngx-translate/core'; | |||||
| import {ModalStatus} from "@app/_helpers/modal.states"; | |||||
| export type SaveFunction<T> = (item: T) => Observable<T>; | export type SaveFunction<T> = (item: T) => Observable<T>; | ||||
| export type UpdateFunction<T> = (id: string | number, item: T) => Observable<T>; | export type UpdateFunction<T> = (id: string | number, item: T) => Observable<T>; | ||||
| export type DeleteFunction = (id: string | number) => Observable<any>; | |||||
| export enum FormMode { | export enum FormMode { | ||||
| Create = 'create', | Create = 'create', | ||||
| Edit = 'edit' | Edit = 'edit' | ||||
| } | } | ||||
| export interface FormSubmitEvent<T> { | |||||
| status: ModalStatus; | |||||
| data: T | null; | |||||
| } | |||||
| @Directive() | @Directive() | ||||
| export abstract class AbstractDataFormComponent<T extends { [key: string]: any }> implements OnInit { | export abstract class AbstractDataFormComponent<T extends { [key: string]: any }> implements OnInit { | ||||
| @Input() data?: T; | @Input() data?: T; | ||||
| @Input() mode: FormMode = FormMode.Create; | @Input() mode: FormMode = FormMode.Create; | ||||
| @Input() id?: string | number; | @Input() id?: string | number; | ||||
| @Output() submit: EventEmitter<ModalStatus> = new EventEmitter<ModalStatus>(); | |||||
| @Input() redirectAfterDelete?: string; | |||||
| @Output() submit = new EventEmitter<FormSubmitEvent<T>>(); | |||||
| @Output() deleted: EventEmitter<void> = new EventEmitter<void>(); | |||||
| protected form!: FormGroup; | protected form!: FormGroup; | ||||
| constructor( | constructor( | ||||
| protected formConfig: FormGroup, | protected formConfig: FormGroup, | ||||
| protected createFn: SaveFunction<T>, | protected createFn: SaveFunction<T>, | ||||
| protected updateFn: UpdateFunction<T> | |||||
| protected updateFn: UpdateFunction<T>, | |||||
| protected deleteFn?: DeleteFunction, | |||||
| protected translateService?: TranslateService, | |||||
| protected router?: Router | |||||
| ) { | ) { | ||||
| this.form = formConfig; | this.form = formConfig; | ||||
| } | } | ||||
| @@ -32,12 +44,14 @@ export abstract class AbstractDataFormComponent<T extends { [key: string]: any } | |||||
| if (this.data) { | if (this.data) { | ||||
| this.form.patchValue(this.data); | this.form.patchValue(this.data); | ||||
| } else if (this.mode === FormMode.Create) { | } else if (this.mode === FormMode.Create) { | ||||
| this.data = this.getInitialData(); | |||||
| this.data = this.getNewDataSet(); | |||||
| this.form.patchValue(this.data); | this.form.patchValue(this.data); | ||||
| } | } | ||||
| } | } | ||||
| protected abstract getInitialData(): T; | |||||
| protected getNewDataSet(): T { | |||||
| return {} as T; | |||||
| } | |||||
| onSubmit(): void { | onSubmit(): void { | ||||
| if (!this.form.valid) return; | if (!this.form.valid) return; | ||||
| @@ -48,12 +62,40 @@ export abstract class AbstractDataFormComponent<T extends { [key: string]: any } | |||||
| : this.updateFn(this.id!, formData); | : this.updateFn(this.id!, formData); | ||||
| request$.subscribe({ | request$.subscribe({ | ||||
| next: () => { | |||||
| // this.form.reset(); | |||||
| this.submit.emit(ModalStatus.Submitted); | |||||
| next: (response) => { | |||||
| this.submit.emit({ | |||||
| status: ModalStatus.Submitted, | |||||
| data: response | |||||
| }); | |||||
| }, | }, | ||||
| error: (error) => { | error: (error) => { | ||||
| console.error('Error saving data:', error); | console.error('Error saving data:', error); | ||||
| this.submit.emit({ | |||||
| status: ModalStatus.Cancelled, // Statt Error verwenden wir Cancelled | |||||
| data: null | |||||
| }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| onDelete(): void { | |||||
| if (!this.isEditMode() || !this.deleteFn || !this.id || !this.translateService) { | |||||
| return; | |||||
| } | |||||
| this.translateService.get('basic.delete_confirm').subscribe((confirmMessage: string) => { | |||||
| if (confirm(confirmMessage)) { | |||||
| this.deleteFn!(this.id!).subscribe({ | |||||
| next: () => { | |||||
| this.deleted.emit(); | |||||
| if (this.redirectAfterDelete && this.router) { | |||||
| this.router.navigate([this.redirectAfterDelete]); | |||||
| } | |||||
| }, | |||||
| error: (error) => { | |||||
| console.error('Error deleting data:', error); | |||||
| } | |||||
| }); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -118,14 +118,14 @@ | |||||
| class="btn btn-primary spt-icon-details" | class="btn btn-primary spt-icon-details" | ||||
| data-type="user-tool" | data-type="user-tool" | ||||
| data-action="edit" | data-action="edit" | ||||
| target="_blank" | |||||
| [target]="detailLinkNewTab ? '_blank' : '_self'" | |||||
| [routerLink]="[appHelperService.getLink(element, column.url)]"> | [routerLink]="[appHelperService.getLink(element, column.url)]"> | ||||
| </a> | </a> | ||||
| <a *ngIf="getCustomDetailLinkFunction" | <a *ngIf="getCustomDetailLinkFunction" | ||||
| class="btn btn-primary spt-icon-details" | class="btn btn-primary spt-icon-details" | ||||
| data-type="user-tool" | data-type="user-tool" | ||||
| data-action="edit" | data-action="edit" | ||||
| target="_blank" | |||||
| [target]="detailLinkNewTab ? '_blank' : '_self'" | |||||
| [routerLink]="['/' + getCustomDetailLinkFunction(element)]"> | [routerLink]="['/' + getCustomDetailLinkFunction(element)]"> | ||||
| </a> | </a> | ||||
| </ng-container> | </ng-container> | ||||
| @@ -38,6 +38,7 @@ export class ListComponent implements OnInit, AfterViewInit, OnDestroy { | |||||
| @Input() public displayOptions!: { [key: string]: string }; | @Input() public displayOptions!: { [key: string]: string }; | ||||
| @Input() public defaultDisplayOption!: string; | @Input() public defaultDisplayOption!: string; | ||||
| @Input() public refreshIntervalSeconds?: number; | @Input() public refreshIntervalSeconds?: number; | ||||
| @Input() public detailLinkNewTab?: boolean; | |||||
| @ViewChild(MatSort) sort; | @ViewChild(MatSort) sort; | ||||
| @ViewChild("pagingComponent", {static: false}) protected pagingComponent!: PagingComponent; | @ViewChild("pagingComponent", {static: false}) protected pagingComponent!: PagingComponent; | ||||
| @ViewChild("filterBarComponent", {static: false}) protected filterBarComponent!: FilterBarComponent; | @ViewChild("filterBarComponent", {static: false}) protected filterBarComponent!: FilterBarComponent; | ||||
| @@ -90,7 +91,7 @@ export class ListComponent implements OnInit, AfterViewInit, OnDestroy { | |||||
| this.hidePageSize = false; | this.hidePageSize = false; | ||||
| this.dataSource = new MatTableDataSource<any>(); | this.dataSource = new MatTableDataSource<any>(); | ||||
| this.filterConfig = null; | this.filterConfig = null; | ||||
| this.detailLinkNewTab = false; | |||||
| } | } | ||||
| ngOnInit(): void { | ngOnInit(): void { | ||||
| @@ -2,6 +2,7 @@ import {DomSanitizer, SafeHtml} from "@angular/platform-browser"; | |||||
| import {Injectable} from "@angular/core"; | import {Injectable} from "@angular/core"; | ||||
| import {NgbModal, NgbModalOptions} from "@ng-bootstrap/ng-bootstrap"; | import {NgbModal, NgbModalOptions} from "@ng-bootstrap/ng-bootstrap"; | ||||
| import {ModalStatus} from "@app/_helpers/modal.states"; | import {ModalStatus} from "@app/_helpers/modal.states"; | ||||
| import {FormSubmitEvent} from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| @Injectable({providedIn: 'root'}) | @Injectable({providedIn: 'root'}) | ||||
| export class AppHelperService { | export class AppHelperService { | ||||
| @@ -50,8 +51,8 @@ export class AppHelperService { | |||||
| modalRef.componentInstance[key] = data[key]; | modalRef.componentInstance[key] = data[key]; | ||||
| } | } | ||||
| return modalRef.componentInstance.submit.subscribe((modalStatus: ModalStatus) => { | |||||
| if (modalStatus === ModalStatus.Submitted) { | |||||
| return modalRef.componentInstance.submit.subscribe((event: FormSubmitEvent<any>) => { | |||||
| if (event.status === ModalStatus.Submitted) { | |||||
| modalRef.dismiss(); | modalRef.dismiss(); | ||||
| if (callback) { | if (callback) { | ||||
| callback(callbackParam); | callback(callbackParam); | ||||
| @@ -4,13 +4,7 @@ | |||||
| [data]="location" | [data]="location" | ||||
| [mode]="FormMode.Edit" | [mode]="FormMode.Edit" | ||||
| [id]="appHelperService.extractId(location.id!)" | [id]="appHelperService.extractId(location.id!)" | ||||
| (submit)="onFormSubmit()" | |||||
| (submit)="onFormUpdate($event)" | |||||
| ></app-location-form> | ></app-location-form> | ||||
| <div class="mt-4 flex gap-2"> | |||||
| <button (click)="apiDeleteLocation()" class="spt-button spt-button-danger"> | |||||
| {{ 'basic.delete' | translate }} {{ 'model.location' | translate }} | |||||
| </button> | |||||
| </div> | |||||
| </div> | </div> | ||||
| } | |||||
| } | |||||
| @@ -1,10 +1,9 @@ | |||||
| import {Component, OnInit} from '@angular/core'; | |||||
| import {LocationJsonld, LocationService} from "@app/core/api/v1"; | |||||
| import {AppHelperService} from "@app/_helpers/app-helper.service"; | |||||
| import {ActivatedRoute, Router} from "@angular/router"; | |||||
| import {ROUTE_LOCATIONS} from "@app/app-routing.module"; | |||||
| import {TranslateService} from "@ngx-translate/core"; | |||||
| import {FormMode} from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| import { Component, OnInit } from '@angular/core'; | |||||
| import { LocationJsonld, LocationService } from "@app/core/api/v1"; | |||||
| import { AppHelperService } from "@app/_helpers/app-helper.service"; | |||||
| import { ActivatedRoute } from "@angular/router"; | |||||
| import { FormMode, FormSubmitEvent } from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| import {ModalStatus} from "@app/_helpers/modal.states"; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-location-detail', | selector: 'app-location-detail', | ||||
| @@ -17,39 +16,22 @@ export class LocationDetailComponent implements OnInit { | |||||
| constructor( | constructor( | ||||
| private locationService: LocationService, | private locationService: LocationService, | ||||
| protected appHelperService: AppHelperService, | protected appHelperService: AppHelperService, | ||||
| protected translateService: TranslateService, | |||||
| private route: ActivatedRoute, | |||||
| protected router: Router | |||||
| private route: ActivatedRoute | |||||
| ) {} | ) {} | ||||
| ngOnInit() { | ngOnInit() { | ||||
| this.route.params.subscribe(params => { | this.route.params.subscribe(params => { | ||||
| this.apiGetLocationData(params['id']); | |||||
| this.locationService.locationsIdGet(params['id']).subscribe( | |||||
| data => { | |||||
| this.location = data; | |||||
| } | |||||
| ); | |||||
| }); | }); | ||||
| } | } | ||||
| apiGetLocationData(locationId: string) { | |||||
| this.locationService.locationsIdGet(locationId).subscribe( | |||||
| data => { | |||||
| console.log(data); | |||||
| this.location = data; | |||||
| } | |||||
| ); | |||||
| onFormUpdate(event: FormSubmitEvent<LocationJsonld>) { | |||||
| if (event.status === ModalStatus.Submitted && event.data) { | |||||
| this.location = event.data; | |||||
| } | |||||
| } | } | ||||
| apiDeleteLocation() { | |||||
| this.translateService.get('basic.delete_confirm').subscribe((confirmMessage: string) => { | |||||
| if (confirm(confirmMessage)) { | |||||
| this.locationService.locationsIdDelete( | |||||
| this.appHelperService.extractId(this.location.id!) | |||||
| ).subscribe(() => { | |||||
| this.router.navigate(['/' + ROUTE_LOCATIONS]); | |||||
| }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| onFormSubmit() { | |||||
| this.apiGetLocationData(this.appHelperService.extractId(this.location.id!)); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -23,8 +23,17 @@ | |||||
| </app-search-select> | </app-search-select> | ||||
| <input id="zone" type="hidden" formControlName="zone"/> | <input id="zone" type="hidden" formControlName="zone"/> | ||||
| </div> | </div> | ||||
| <button type="submit" class="btn btn-primary" [disabled]="form.invalid"> | |||||
| {{ (isEditMode() ? 'basic.save' : 'basic.create') | translate }} | |||||
| </button> | |||||
| <div class="flex gap-2"> | |||||
| <button type="submit" class="btn btn-primary" [disabled]="form.invalid"> | |||||
| {{ 'basic.save' | translate }} | |||||
| </button> | |||||
| @if (isEditMode()) { | |||||
| <button type="button" class="spt-button spt-button-danger" (click)="onDelete()"> | |||||
| {{ 'basic.delete' | translate }} {{ 'model.location' | translate }} | |||||
| </button> | |||||
| } | |||||
| </div> | |||||
| </form> | </form> | ||||
| </div> | </div> | ||||
| @@ -1,10 +1,13 @@ | |||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||
| import {AbstractDataFormComponent} from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| import {LocationJsonld, LocationService, ZoneService} from "@app/core/api/v1"; | |||||
| import {SearchSelectComponent} from "@app/_components/search-select/search-select.component"; | |||||
| import {ListColDefinition} from "@app/_components/list/list-col-definition"; | |||||
| import {locationForm} from "@app/_forms/apiForms"; | |||||
| import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-function-type"; | |||||
| import { AbstractDataFormComponent } from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| import { LocationJsonld, LocationService, ZoneService } from "@app/core/api/v1"; | |||||
| import { SearchSelectComponent } from "@app/_components/search-select/search-select.component"; | |||||
| import { ListColDefinition } from "@app/_components/list/list-col-definition"; | |||||
| import { locationForm } from "@app/_forms/apiForms"; | |||||
| import { ListGetDataFunctionType } from "@app/_components/list/list-get-data-function-type"; | |||||
| import { TranslateService } from "@ngx-translate/core"; | |||||
| import { Router } from "@angular/router"; | |||||
| import { ROUTE_BASE_DATA } from "@app/app-routing.module"; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-location-form', | selector: 'app-location-form', | ||||
| @@ -16,22 +19,24 @@ export class LocationFormComponent extends AbstractDataFormComponent<LocationJso | |||||
| constructor( | constructor( | ||||
| private locationService: LocationService, | private locationService: LocationService, | ||||
| private zoneService: ZoneService | |||||
| private zoneService: ZoneService, | |||||
| translateService: TranslateService, | |||||
| router: Router | |||||
| ) { | ) { | ||||
| super( | super( | ||||
| locationForm, | |||||
| (data: LocationJsonld) => locationService.locationsPost(data), | |||||
| (id: string | number, data: LocationJsonld) => locationService.locationsIdPatch(id.toString(), data) | |||||
| locationForm, | |||||
| (data: LocationJsonld) => this.locationService.locationsPost(data), | |||||
| (id: string | number, data: LocationJsonld) => this.locationService.locationsIdPatch(id.toString(), data), | |||||
| (id: string | number) => this.locationService.locationsIdDelete(id.toString()), | |||||
| translateService, | |||||
| router | |||||
| ); | ); | ||||
| this.redirectAfterDelete = '/' + ROUTE_BASE_DATA; | |||||
| this.zoneColDefinitions = SearchSelectComponent.getDefaultColDefZones(); | this.zoneColDefinitions = SearchSelectComponent.getDefaultColDefZones(); | ||||
| } | } | ||||
| protected override getInitialData(): LocationJsonld { | |||||
| return {} as LocationJsonld; | |||||
| } | |||||
| getZones: ListGetDataFunctionType = (index: number, pageSize: number, term?: string) => { | getZones: ListGetDataFunctionType = (index: number, pageSize: number, term?: string) => { | ||||
| return this.zoneService.zonesGetCollection(index, pageSize, term); | return this.zoneService.zonesGetCollection(index, pageSize, term); | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -20,6 +20,6 @@ | |||||
| <button type="submit" | <button type="submit" | ||||
| class="btn btn-primary" | class="btn btn-primary" | ||||
| [disabled]="form.invalid"> | [disabled]="form.invalid"> | ||||
| {{ (isEditMode() ? 'basic.save' : 'basic.create') | translate }} | |||||
| {{ 'basic.save' | translate }} | |||||
| </button> | </button> | ||||
| </form> | </form> | ||||
| @@ -18,7 +18,7 @@ export class ShippingCompanyFormComponent extends AbstractDataFormComponent<Ship | |||||
| ); | ); | ||||
| } | } | ||||
| protected override getInitialData(): ShippingCompanyJsonld { | |||||
| protected override getNewDataSet(): ShippingCompanyJsonld { | |||||
| return {} as ShippingCompanyJsonld; | return {} as ShippingCompanyJsonld; | ||||
| } | } | ||||
| } | } | ||||
| @@ -32,6 +32,6 @@ | |||||
| <button type="submit" | <button type="submit" | ||||
| class="btn btn-primary" | class="btn btn-primary" | ||||
| [disabled]="form.invalid"> | [disabled]="form.invalid"> | ||||
| {{ (isEditMode() ? 'basic.save' : 'basic.create') | translate }} | |||||
| {{ 'basic.save' | translate }} | |||||
| </button> | </button> | ||||
| </form> | </form> | ||||
| @@ -27,7 +27,7 @@ export class VesselFormComponent extends AbstractDataFormComponent<VesselJsonld> | |||||
| this.shippingCompanyColDefinitions = SearchSelectComponent.getDefaultColDefShippingCompanies(); | this.shippingCompanyColDefinitions = SearchSelectComponent.getDefaultColDefShippingCompanies(); | ||||
| } | } | ||||
| protected override getInitialData(): VesselJsonld { | |||||
| protected override getNewDataSet(): VesselJsonld { | |||||
| return {} as VesselJsonld; | return {} as VesselJsonld; | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| <h2>{{ (isEditMode() ? 'basic.edit' : 'basic.new') | translate }} {{ 'model.location' | translate }}</h2> | |||||
| <h2>{{ (isEditMode() ? 'basic.edit' : 'basic.new') | translate }} {{ 'model.zone' | translate }}</h2> | |||||
| <div class="spt-form"> | <div class="spt-form"> | ||||
| <form [formGroup]="form" (ngSubmit)="onSubmit()"> | <form [formGroup]="form" (ngSubmit)="onSubmit()"> | ||||
| <div class="mb-3"> | <div class="mb-3"> | ||||
| @@ -10,7 +10,7 @@ | |||||
| <input type="text" class="form-control" id="code" formControlName="code" required/> | <input type="text" class="form-control" id="code" formControlName="code" required/> | ||||
| </div> | </div> | ||||
| <button type="submit" class="btn btn-primary" [disabled]="form.invalid"> | <button type="submit" class="btn btn-primary" [disabled]="form.invalid"> | ||||
| {{ (isEditMode() ? 'basic.save' : 'basic.create') | translate }} | |||||
| {{ 'basic.save' | translate }} | |||||
| </button> | </button> | ||||
| </form> | </form> | ||||
| </div> | </div> | ||||
| @@ -22,8 +22,5 @@ export class ZoneFormComponent extends AbstractDataFormComponent<ZoneJsonld> { | |||||
| ); | ); | ||||
| } | } | ||||
| protected override getInitialData(): ZoneJsonld { | |||||
| return {} as ZoneJsonld; | |||||
| } | |||||
| } | } | ||||