diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.scss b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.scss new file mode 100644 index 000000000..5d4e87f30 --- /dev/null +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts new file mode 100644 index 000000000..b62ab4912 --- /dev/null +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts @@ -0,0 +1,77 @@ +import { + ChangeDetectionStrategy, + Component, + OnDestroy, + OnInit +} from '@angular/core'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; +import { MatDialogRef } from '@angular/material/dialog'; +import { Subject } from 'rxjs'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'h-100' }, + selector: 'gf-create-watchlist-item-dialog', + styleUrls: ['./create-watchlist-item-dialog.component.scss'], + templateUrl: 'create-watchlist-item-dialog.html', + standalone: false +}) +export class CreateWatchlistItemDialog implements OnInit, OnDestroy { + public createWatchlistItemForm: FormGroup; + + private unsubscribeSubject = new Subject(); + + public constructor( + public readonly dialogRef: MatDialogRef, + public readonly formBuilder: FormBuilder + ) {} + + public ngOnInit() { + this.createWatchlistItemForm = this.formBuilder.group( + { + searchSymbol: new FormControl(null, [Validators.required]) + }, + { + validators: this.validator + } + ); + } + + public onCancel() { + this.dialogRef.close(); + } + + public onSubmit() { + this.dialogRef.close({ + dataSource: + this.createWatchlistItemForm.get('searchSymbol').value.dataSource, + symbol: this.createWatchlistItemForm.get('searchSymbol').value.symbol + }); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private validator(control: AbstractControl): ValidationErrors { + const searchSymbolControl = control.get('searchSymbol'); + + if ( + searchSymbolControl.valid && + searchSymbolControl.value.dataSource && + searchSymbolControl.value.symbol + ) { + return { incomplete: false }; + } + + return { incomplete: true }; + } +} diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html new file mode 100644 index 000000000..f8ca6f2e0 --- /dev/null +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html @@ -0,0 +1,30 @@ +
+

Add Asset

+
+
+ + Name, symbol or ISIN + + +
+
+
+ + +
+
diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.module.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.module.ts new file mode 100644 index 000000000..167d320b8 --- /dev/null +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.module.ts @@ -0,0 +1,25 @@ +import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; + +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; + +import { CreateWatchlistItemDialog } from './create-watchlist-item-dialog.component'; + +@NgModule({ + declarations: [CreateWatchlistItemDialog], + imports: [ + CommonModule, + FormsModule, + GfSymbolAutocompleteComponent, + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + ReactiveFormsModule + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class GfCreateWatchlistItemDialogModule {} diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts new file mode 100644 index 000000000..c0f74d022 --- /dev/null +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts @@ -0,0 +1,4 @@ +export interface CreateWatchlistItemDialogParams { + deviceType: string; + locale: string; +} diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts index 8405120eb..6a718aca2 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts @@ -1,22 +1,34 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces'; +import { Benchmark, User } from '@ghostfolio/common/interfaces'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit +} from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { CreateWatchlistItemDialog } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component'; +import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces'; + @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'gf-home-watchlist', styleUrls: ['./home-watchlist.scss'], templateUrl: './home-watchlist.html', standalone: false }) export class HomeWatchlistComponent implements OnDestroy, OnInit { - public watchlist: AssetProfileIdentifier[]; public deviceType: string; public user: User; + public watchlist: Benchmark[]; private unsubscribeSubject = new Subject(); @@ -24,10 +36,21 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit { private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, private deviceService: DeviceDetectorService, + private dialog: MatDialog, + private route: ActivatedRoute, + private router: Router, private userService: UserService ) { this.deviceType = this.deviceService.getDeviceInfo().deviceType; + this.route.queryParams + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((params) => { + if (params['createWatchlistItemDialog']) { + this.openCreateWatchlistItemDialog(); + } + }); + this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((state) => { @@ -40,18 +63,68 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit { } public ngOnInit() { + this.loadWatchlistData(); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private loadWatchlistData() { this.dataService .fetchWatchlist() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((watchlist) => { - this.watchlist = watchlist; + this.watchlist = watchlist.map((item) => ({ + dataSource: item.dataSource, + symbol: item.symbol, + name: item.symbol, + marketCondition: 'NEUTRAL_MARKET', + performances: { + allTimeHigh: { + date: new Date(), + performancePercent: 0 + } + }, + trend200d: 'UNKNOWN', + trend50d: 'UNKNOWN' + })); this.changeDetectorRef.markForCheck(); }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + private openCreateWatchlistItemDialog() { + this.userService + .get() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((user) => { + this.user = user; + + const dialogRef = this.dialog.open(CreateWatchlistItemDialog, { + autoFocus: false, + data: { + deviceType: this.deviceType, + locale: this.user?.settings?.locale + } as CreateWatchlistItemDialogParams, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); + + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ dataSource, symbol } = {}) => { + if (dataSource && symbol) { + this.dataService + .postWatchlist({ dataSource, symbol }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe({ + next: () => this.loadWatchlistData() + }); + } + this.router.navigate(['.'], { relativeTo: this.route }); + }); + }); } } diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.html b/apps/client/src/app/components/home-watchlist/home-watchlist.html index f722fbec8..0150640f2 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.html +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.html @@ -16,7 +16,7 @@ class="align-items-center d-flex justify-content-center" color="primary" mat-fab - [queryParams]="{ createDialog: true }" + [queryParams]="{ createWatchlistItemDialog: true }" [routerLink]="[]" > diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.module.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.module.ts index c2f3331cd..176d98d90 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.module.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.module.ts @@ -5,12 +5,19 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; +import { GfCreateWatchlistItemDialogModule } from './create-watchlist-item-dialog/create-watchlist-item-dialog.module'; import { HomeWatchlistComponent } from './home-watchlist.component'; @NgModule({ declarations: [HomeWatchlistComponent], exports: [HomeWatchlistComponent], - imports: [CommonModule, GfBenchmarkComponent, MatButtonModule, RouterModule], + imports: [ + CommonModule, + GfBenchmarkComponent, + GfCreateWatchlistItemDialogModule, + MatButtonModule, + RouterModule + ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class GfHomeWatchlistModule {}