diff --git a/CHANGELOG.md b/CHANGELOG.md index f65ce3028..48e0f9bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Integrated the add currency functionality into the market data section of the admin control panel - Improved the language localization for German (`de`) - Upgraded `webpack-bundle-analyzer` from version `4.10.1` to `4.10.2` diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index f0a47ad1b..422ac45a9 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -1,7 +1,10 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; +import { DataService } from '@ghostfolio/client/services/data.service'; +import { PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, OnDestroy, OnInit @@ -15,6 +18,10 @@ import { Validators } from '@angular/forms'; import { MatDialogRef } from '@angular/material/dialog'; +import { uniq } from 'lodash'; +import { Subject, takeUntil } from 'rxjs'; + +import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -25,17 +32,29 @@ import { MatDialogRef } from '@angular/material/dialog'; }) export class CreateAssetProfileDialog implements OnInit, OnDestroy { public createAssetProfileForm: FormGroup; - public mode: 'auto' | 'manual'; + public mode: CreateAssetProfileDialogMode; + + private customCurrencies: string[]; + private unsubscribeSubject = new Subject(); public constructor( public readonly adminService: AdminService, + private readonly changeDetectorRef: ChangeDetectorRef, + private readonly dataService: DataService, public readonly dialogRef: MatDialogRef, public readonly formBuilder: FormBuilder ) {} public ngOnInit() { + this.initializeCustomCurrencies(); + this.createAssetProfileForm = this.formBuilder.group( { + addCurrency: new FormControl(null, [ + Validators.maxLength(3), + Validators.minLength(3), + Validators.required + ]), addSymbol: new FormControl(null, [Validators.required]), searchSymbol: new FormControl(null, [Validators.required]) }, @@ -51,34 +70,75 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy { this.dialogRef.close(); } - public onRadioChange(mode: 'auto' | 'manual') { + public onRadioChange(mode: CreateAssetProfileDialogMode) { this.mode = mode; } public onSubmit() { - this.mode === 'auto' - ? this.dialogRef.close({ - dataSource: - this.createAssetProfileForm.get('searchSymbol').value.dataSource, - symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol + if (this.mode === 'auto') { + this.dialogRef.close({ + dataSource: + this.createAssetProfileForm.get('searchSymbol').value.dataSource, + symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol + }); + } else if (this.mode === 'currency') { + const currency = this.createAssetProfileForm + .get('addCurrency') + .value.toUpperCase(); + + const currencies = uniq([...this.customCurrencies, currency]); + + this.dataService + .putAdminSetting(PROPERTY_CURRENCIES, { + value: JSON.stringify(currencies) }) - : this.dialogRef.close({ - dataSource: 'MANUAL', - symbol: this.createAssetProfileForm.get('addSymbol').value + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.dialogRef.close(); }); + } else if (this.mode === 'manual') { + this.dialogRef.close({ + dataSource: 'MANUAL', + symbol: this.createAssetProfileForm.get('addSymbol').value + }); + } } - public ngOnDestroy() {} + public get showCurrencyErrorMessage() { + const addCurrencyFormControl = + this.createAssetProfileForm.get('addCurrency'); + + if ( + addCurrencyFormControl.hasError('maxlength') || + addCurrencyFormControl.hasError('minlength') + ) { + return true; + } + + return false; + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } private atLeastOneValid(control: AbstractControl): ValidationErrors { + const addCurrencyControl = control.get('addCurrency'); const addSymbolControl = control.get('addSymbol'); const searchSymbolControl = control.get('searchSymbol'); - if (addSymbolControl.valid && searchSymbolControl.valid) { + if ( + addCurrencyControl.valid && + addSymbolControl.valid && + searchSymbolControl.valid + ) { return { atLeastOneValid: true }; } if ( + addCurrencyControl.valid || + !addCurrencyControl || addSymbolControl.valid || !addSymbolControl || searchSymbolControl.valid || @@ -89,4 +149,15 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy { return { atLeastOneValid: true }; } + + private initializeCustomCurrencies() { + this.adminService + .fetchAdminData() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ settings }) => { + this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; + + this.changeDetectorRef.markForCheck(); + }); + } } diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html index 7c228941e..c60ca83b8 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -17,6 +17,9 @@ + + + @@ -37,6 +40,16 @@ + } @else if (mode === 'currency') { +
+ + Currency + + @if (showCurrencyErrorMessage) { + Oops! Invalid currency. + } + +
}
diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts index 16be906c9..4cf24b554 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts @@ -2,3 +2,5 @@ export interface CreateAssetProfileDialogParams { deviceType: string; locale: string; } + +export type CreateAssetProfileDialogMode = 'auto' | 'currency' | 'manual'; diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 9763f960f..15547bb6d 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -126,7 +126,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit { if (currency) { if (currency.length === 3) { - const currencies = uniq([...this.customCurrencies, currency]); + const currencies = uniq([ + ...this.customCurrencies, + currency.toUpperCase() + ]); this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies }); } else { this.notificationService.alert({ diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html index 10cc23cb2..96da35ca9 100644 --- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html +++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html @@ -52,8 +52,10 @@

  1. Go to the Admin Control panel
  2. -
  3. Click on the Add Currency button
  4. -
  5. Insert e.g. EUR in the prompt
  6. +
  7. Go to the Market Data section
  8. +
  9. Click on the + button
  10. +
  11. Switch to Add Currency
  12. +
  13. Insert e.g. EUR for Euro