From 68cbe69d0675eeb7a1dcd0ce9f6561eeda85237c Mon Sep 17 00:00:00 2001 From: tobikugel Date: Fri, 21 Mar 2025 12:19:49 -0300 Subject: [PATCH] feat: frontend form logic and error handling --- .../admin-market-data.component.ts | 12 ++- .../asset-profile-dialog.component.ts | 74 +++++++++++++++---- .../asset-profile-dialog.html | 21 ++++-- .../symbol-autocomplete.component.ts | 3 + 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 30ef457d2..bf067bd7f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -389,9 +389,15 @@ export class AdminMarketDataComponent dialogRef .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(() => { - this.router.navigate(['.'], { relativeTo: this.route }); - }); + .subscribe( + (newAssetProfileIdentifer: AssetProfileIdentifier | undefined) => { + if (newAssetProfileIdentifer) { + this.onOpenAssetProfileDialog(newAssetProfileIdentifer); + } else { + this.router.navigate(['.'], { relativeTo: this.route }); + } + } + ); }); } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 7d925395f..ea5fbd537 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -15,6 +15,7 @@ import { } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; +import { HttpErrorResponse } from '@angular/common/http'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -26,7 +27,13 @@ import { ViewChild, signal } from '@angular/core'; -import { FormBuilder, FormControl, Validators } from '@angular/forms'; +import { + AbstractControl, + FormBuilder, + FormControl, + ValidationErrors, + Validators +} from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; @@ -63,12 +70,18 @@ export class AssetProfileDialog implements OnDestroy, OnInit { return { id: assetSubClass, label: translate(assetSubClass) }; }); public assetProfile: AdminMarketDataDetails['assetProfile']; - public assetProfileIdentifierForm = this.formBuilder.group({ - editedSearchSymbol: new FormControl( - { symbol: null, dataSource: null }, - [Validators.required] - ) - }); + public assetProfileIdentifierForm = this.formBuilder.group( + { + editedSearchSymbol: new FormControl( + { symbol: null, dataSource: null }, + [Validators.required] + ) + }, + { + validators: (control) => this.isNewSymbolValid(control) + } + ); + public assetProfileForm = this.formBuilder.group({ assetClass: new FormControl(undefined), assetSubClass: new FormControl(undefined), @@ -240,6 +253,21 @@ export class AssetProfileDialog implements OnDestroy, OnInit { }); } + private isNewSymbolValid(control: AbstractControl): ValidationErrors { + const currentAssetProfileIdentifier: AssetProfileIdentifier | undefined = + control.get('editedSearchSymbol').value; + console.log(this.assetProfileIdentifierForm?.valid); + + if ( + currentAssetProfileIdentifier.dataSource === this.data?.dataSource && + currentAssetProfileIdentifier.symbol === this.data?.symbol + ) { + return { + isPreviousIdentifier: true + }; + } + } + public get isSymbolEditable() { return !this.assetProfileForm.dirty; } @@ -337,22 +365,36 @@ export class AssetProfileDialog implements OnDestroy, OnInit { ...assetProfileIdentifierData }) .pipe( - catchError(() => { - this.snackBar.open('Conflict', undefined, { - duration: ms('3 seconds') - }); + catchError((error: HttpErrorResponse) => { + if (error.status === 409) { + this.snackBar.open( + $localize`This symbol is already in use`, + undefined, + { + duration: ms('3 seconds') + } + ); + } else { + this.snackBar.open( + $localize`An error occurred while updating the symbol`, + undefined, + { + duration: ms('3 seconds') + } + ); + } return EMPTY; }), takeUntil(this.unsubscribeSubject) ) .subscribe(() => { - this.onOpenAssetProfileDialog({ + const newAssetProfileIdentifer = { dataSource: assetProfileIdentifierData.dataSource, symbol: assetProfileIdentifierData.symbol - }); + }; - this.dialogRef.close(); + this.dialogRef.close(newAssetProfileIdentifer); }); } @@ -521,8 +563,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit { }); } - public triggerIdentifierSubmit() { - if (this.assetProfileIdentifierForm) { + public triggerProfileFormSubmit() { + if (this.assetProfileForm) { this.assetProfileFormElement.nativeElement.requestSubmit(); } } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index b4effc127..ad58862d6 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -87,8 +87,9 @@
@if (isEditSymbolMode) { -
+
- + Name, symbol or ISIN Apply @@ -128,8 +134,9 @@ >Symbol
-
+
Data Source -
-
diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index 3c56c4748..f92635150 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -194,6 +194,9 @@ export class GfSymbolAutocompleteComponent } private validateRequired() { + console.log(`dataSource: ${super.value?.dataSource}`); + console.log(`symbol: ${super.value?.symbol}`); + const requiredCheck = super.required ? !super.value?.dataSource || !super.value?.symbol : false;