Browse Source

feat(app): enable adding new watchlist item

pull/4604/head
KenTandrian 4 months ago
parent
commit
45d0272caa
  1. 3
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.scss
  2. 77
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts
  3. 30
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html
  4. 25
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.module.ts
  5. 4
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts
  6. 87
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  7. 2
      apps/client/src/app/components/home-watchlist/home-watchlist.html
  8. 9
      apps/client/src/app/components/home-watchlist/home-watchlist.module.ts

3
apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.scss

@ -0,0 +1,3 @@
:host {
display: block;
}

77
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<void>();
public constructor(
public readonly dialogRef: MatDialogRef<CreateWatchlistItemDialog>,
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 };
}
}

30
apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html

@ -0,0 +1,30 @@
<form
class="d-flex flex-column h-100"
[formGroup]="createWatchlistItemForm"
(keyup.enter)="createWatchlistItemForm.valid && onSubmit()"
(ngSubmit)="onSubmit()"
>
<h1 i18n mat-dialog-title>Add Asset</h1>
<div class="flex-grow-1 py-3" mat-dialog-content>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Name, symbol or ISIN</mat-label>
<gf-symbol-autocomplete
formControlName="searchSymbol"
[includeIndices]="true"
/>
</mat-form-field>
</div>
</div>
<div class="d-flex justify-content-end" mat-dialog-actions>
<button i18n mat-button type="button" (click)="onCancel()">Cancel</button>
<button
color="primary"
mat-flat-button
type="submit"
[disabled]="createWatchlistItemForm.hasError('incomplete')"
>
<ng-container i18n>Save</ng-container>
</button>
</div>
</form>

25
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 {}

4
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;
}

87
apps/client/src/app/components/home-watchlist/home-watchlist.component.ts

@ -1,22 +1,34 @@
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.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 { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; 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({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-home-watchlist', selector: 'gf-home-watchlist',
styleUrls: ['./home-watchlist.scss'], styleUrls: ['./home-watchlist.scss'],
templateUrl: './home-watchlist.html', templateUrl: './home-watchlist.html',
standalone: false standalone: false
}) })
export class HomeWatchlistComponent implements OnDestroy, OnInit { export class HomeWatchlistComponent implements OnDestroy, OnInit {
public watchlist: AssetProfileIdentifier[];
public deviceType: string; public deviceType: string;
public user: User; public user: User;
public watchlist: Benchmark[];
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -24,10 +36,21 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private dialog: MatDialog,
private route: ActivatedRoute,
private router: Router,
private userService: UserService private userService: UserService
) { ) {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (params['createWatchlistItemDialog']) {
this.openCreateWatchlistItemDialog();
}
});
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
@ -40,18 +63,68 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.loadWatchlistData();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private loadWatchlistData() {
this.dataService this.dataService
.fetchWatchlist() .fetchWatchlist()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((watchlist) => { .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(); this.changeDetectorRef.markForCheck();
}); });
} }
public ngOnDestroy() { private openCreateWatchlistItemDialog() {
this.unsubscribeSubject.next(); this.userService
this.unsubscribeSubject.complete(); .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 });
});
});
} }
} }

2
apps/client/src/app/components/home-watchlist/home-watchlist.html

@ -16,7 +16,7 @@
class="align-items-center d-flex justify-content-center" class="align-items-center d-flex justify-content-center"
color="primary" color="primary"
mat-fab mat-fab
[queryParams]="{ createDialog: true }" [queryParams]="{ createWatchlistItemDialog: true }"
[routerLink]="[]" [routerLink]="[]"
> >
<ion-icon name="add-outline" size="large" /> <ion-icon name="add-outline" size="large" />

9
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 { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { GfCreateWatchlistItemDialogModule } from './create-watchlist-item-dialog/create-watchlist-item-dialog.module';
import { HomeWatchlistComponent } from './home-watchlist.component'; import { HomeWatchlistComponent } from './home-watchlist.component';
@NgModule({ @NgModule({
declarations: [HomeWatchlistComponent], declarations: [HomeWatchlistComponent],
exports: [HomeWatchlistComponent], exports: [HomeWatchlistComponent],
imports: [CommonModule, GfBenchmarkComponent, MatButtonModule, RouterModule], imports: [
CommonModule,
GfBenchmarkComponent,
GfCreateWatchlistItemDialogModule,
MatButtonModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })
export class GfHomeWatchlistModule {} export class GfHomeWatchlistModule {}

Loading…
Cancel
Save