Browse Source

feat: frontend form logic and error handling

pull/4469/head
tobikugel 4 weeks ago
parent
commit
68cbe69d06
  1. 12
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  2. 74
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  3. 21
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  4. 3
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts

12
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 });
}
}
);
});
}

74
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<AssetProfileIdentifier>(
{ symbol: null, dataSource: null },
[Validators.required]
)
});
public assetProfileIdentifierForm = this.formBuilder.group(
{
editedSearchSymbol: new FormControl<AssetProfileIdentifier>(
{ symbol: null, dataSource: null },
[Validators.required]
)
},
{
validators: (control) => this.isNewSymbolValid(control)
}
);
public assetProfileForm = this.formBuilder.group({
assetClass: new FormControl<AssetClass>(undefined),
assetSubClass: new FormControl<AssetSubClass>(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();
}
}

21
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -87,8 +87,9 @@
<div class="row">
@if (isEditSymbolMode) {
<div class="col-12 mb-3">
<div class="col-12 mb-4">
<form
class="align-items-center d-flex"
[formGroup]="assetProfileIdentifierForm"
(keyup.enter)="
assetProfileIdentifierForm.valid &&
@ -96,7 +97,7 @@
"
(ngSubmit)="onSubmitAssetProfileIdentifierForm()"
>
<mat-form-field appearance="outline">
<mat-form-field appearance="outline" class="gf-spacer without-hint">
<mat-label i18n>Name, symbol or ISIN</mat-label>
<gf-symbol-autocomplete
formControlName="editedSearchSymbol"
@ -108,7 +109,12 @@
color="primary"
mat-flat-button
type="submit"
[disabled]="!assetProfileIdentifierForm.valid"
[disabled]="
assetProfileIdentifierForm.hasError(
'invalidData',
'editedSearchSymbol'
) || assetProfileIdentifierForm.hasError('isPreviousIdentifier')
"
>
Apply
</button>
@ -128,8 +134,9 @@
>Symbol</gf-value
>
</div>
<div class="col-4 mb-3">
<div class="col-6 mb-3 d-flex justify-content-between">
<gf-value
class="col-11"
i18n
size="medium"
[value]="
@ -137,10 +144,8 @@
"
>Data Source</gf-value
>
</div>
<div class="col-1 mb-3">
<button
class="mx-1 no-min-width px-2"
class="col-1 mx-1 no-min-width px-2"
mat-button
type="button"
[disabled]="!isSymbolEditable"
@ -507,7 +512,7 @@
color="primary"
mat-flat-button
[disabled]="!(assetProfileForm.dirty && assetProfileForm.valid)"
(click)="triggerIdentifierSubmit()"
(click)="triggerProfileFormSubmit()"
>
<ng-container i18n>Save</ng-container>
</button>

3
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;

Loading…
Cancel
Save