diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index 354ed2e2e..05d99c7ce 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -6,13 +6,13 @@ import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.com import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { Position, User } from '@ghostfolio/common/interfaces'; +import { Filter, Position, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { DateRange } from '@ghostfolio/common/types'; -import { DataSource } from '@prisma/client'; +import { AssetClass, DataSource } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { PositionDetailDialogParams } from '../position/position-detail-dialog/interfaces/interfaces'; @@ -22,8 +22,12 @@ import { PositionDetailDialogParams } from '../position/position-detail-dialog/i templateUrl: './home-holdings.html' }) export class HomeHoldingsComponent implements OnDestroy, OnInit { + public activeFilters: Filter[] = []; + public allFilters: Filter[]; public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS; public deviceType: string; + public filterPlaceholder = ''; + public filters$ = new Subject(); public hasImpersonationId: boolean; public hasPermissionToCreateOrder: boolean; public positions: Position[]; @@ -67,6 +71,39 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit { permissions.createOrder ); + const accountFilters: Filter[] = this.user.accounts + .filter(({ accountType }) => { + return accountType === 'SECURITIES'; + }) + .map(({ id, name }) => { + return { + id, + label: name, + type: 'ACCOUNT' + }; + }); + const assetClassFilters: Filter[] = []; + for (const assetClass of Object.keys(AssetClass)) { + assetClassFilters.push({ + id: assetClass, + label: assetClass, + type: 'ASSET_CLASS' + }); + } + const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => { + return { + id, + label: name, + type: 'TAG' + }; + }); + + this.allFilters = [ + ...accountFilters, + ...assetClassFilters, + ...tagFilters + ]; + this.update(); } }); @@ -82,6 +119,18 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit { this.hasImpersonationId = !!aId; }); + this.filters$ + .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .subscribe((filters) => { + this.activeFilters = filters; + this.filterPlaceholder = + this.activeFilters.length <= 0 + ? $localize`Filter by account or tag...` + : ''; + this.update(); + this.changeDetectorRef.markForCheck(); + }); + this.update(); } @@ -152,7 +201,10 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit { this.positions = undefined; this.dataService - .fetchPositions({ range: this.user?.settings?.dateRange }) + .fetchPositions({ + range: this.user?.settings?.dateRange, + filters: this.activeFilters + }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((response) => { this.positions = response.positions; diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index 3740f0c6b..0c02c7c02 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -1,4 +1,14 @@
+
+
+ +
+
(); public hasError: boolean; public hasImpersonationId: boolean; public hasPermissionToCreateOrder: boolean; @@ -55,6 +61,39 @@ export class HomeOverviewComponent implements OnDestroy, OnInit { permissions.createOrder ); + const accountFilters: Filter[] = this.user.accounts + .filter(({ accountType }) => { + return accountType === 'SECURITIES'; + }) + .map(({ id, name }) => { + return { + id, + label: name, + type: 'ACCOUNT' + }; + }); + const assetClassFilters: Filter[] = []; + for (const assetClass of Object.keys(AssetClass)) { + assetClassFilters.push({ + id: assetClass, + label: assetClass, + type: 'ASSET_CLASS' + }); + } + const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => { + return { + id, + label: name, + type: 'TAG' + }; + }); + + this.allFilters = [ + ...accountFilters, + ...assetClassFilters, + ...tagFilters + ]; + this.update(); } }); @@ -72,6 +111,18 @@ export class HomeOverviewComponent implements OnDestroy, OnInit { this.changeDetectorRef.markForCheck(); }); + this.filters$ + .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .subscribe((filters) => { + this.activeFilters = filters; + this.filterPlaceholder = + this.activeFilters.length <= 0 + ? $localize`Filter by account or tag...` + : ''; + this.update(); + this.changeDetectorRef.markForCheck(); + }); + this.showDetails = !this.hasImpersonationId && !this.user.settings.isRestrictedView && @@ -108,7 +159,8 @@ export class HomeOverviewComponent implements OnDestroy, OnInit { this.dataService .fetchPortfolioPerformance({ range: this.user?.settings?.dateRange, - version: this.user?.settings?.isExperimentalFeatures ? 2 : 1 + version: this.user?.settings?.isExperimentalFeatures ? 2 : 1, + filters: this.activeFilters }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((response) => { @@ -128,7 +180,8 @@ export class HomeOverviewComponent implements OnDestroy, OnInit { this.dataService .fetchChart({ range: this.user?.settings?.dateRange, - version: 1 + version: 1, + filters: this.activeFilters }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((chartData) => { diff --git a/apps/client/src/app/components/home-overview/home-overview.html b/apps/client/src/app/components/home-overview/home-overview.html index 0a47689c8..e8afac67b 100644 --- a/apps/client/src/app/components/home-overview/home-overview.html +++ b/apps/client/src/app/components/home-overview/home-overview.html @@ -1,3 +1,15 @@ +
+
+
+ +
+
+
diff --git a/apps/client/src/app/components/home-overview/home-overview.module.ts b/apps/client/src/app/components/home-overview/home-overview.module.ts index 68dbe6c59..4d4219b9a 100644 --- a/apps/client/src/app/components/home-overview/home-overview.module.ts +++ b/apps/client/src/app/components/home-overview/home-overview.module.ts @@ -3,6 +3,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module'; import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module'; +import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module'; import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module'; import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info'; @@ -12,6 +13,7 @@ import { HomeOverviewComponent } from './home-overview.component'; declarations: [HomeOverviewComponent], imports: [ CommonModule, + GfActivitiesFilterModule, GfLineChartModule, GfNoTransactionsInfoModule, GfPortfolioPerformanceModule, diff --git a/apps/client/src/app/components/home-summary/home-summary.component.ts b/apps/client/src/app/components/home-summary/home-summary.component.ts index 106aba6c9..148434afb 100644 --- a/apps/client/src/app/components/home-summary/home-summary.component.ts +++ b/apps/client/src/app/components/home-summary/home-summary.component.ts @@ -2,10 +2,11 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { PortfolioSummary, User } from '@ghostfolio/common/interfaces'; +import { Filter, PortfolioSummary, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { AssetClass } from '@prisma/client'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-home-summary', @@ -13,6 +14,10 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './home-summary.html' }) export class HomeSummaryComponent implements OnDestroy, OnInit { + public activeFilters: Filter[] = []; + public allFilters: Filter[]; + public filterPlaceholder = ''; + public filters$ = new Subject(); public hasImpersonationId: boolean; public hasPermissionToUpdateUserSettings: boolean; public isLoading = true; @@ -38,6 +43,39 @@ export class HomeSummaryComponent implements OnDestroy, OnInit { permissions.updateUserSettings ); + const accountFilters: Filter[] = this.user.accounts + .filter(({ accountType }) => { + return accountType === 'SECURITIES'; + }) + .map(({ id, name }) => { + return { + id, + label: name, + type: 'ACCOUNT' + }; + }); + const assetClassFilters: Filter[] = []; + for (const assetClass of Object.keys(AssetClass)) { + assetClassFilters.push({ + id: assetClass, + label: assetClass, + type: 'ASSET_CLASS' + }); + } + const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => { + return { + id, + label: name, + type: 'TAG' + }; + }); + + this.allFilters = [ + ...accountFilters, + ...assetClassFilters, + ...tagFilters + ]; + this.update(); } }); @@ -51,6 +89,18 @@ export class HomeSummaryComponent implements OnDestroy, OnInit { this.hasImpersonationId = !!aId; }); + this.filters$ + .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .subscribe((filters) => { + this.activeFilters = filters; + this.filterPlaceholder = + this.activeFilters.length <= 0 + ? $localize`Filter by account or tag...` + : ''; + this.update(); + this.changeDetectorRef.markForCheck(); + }); + this.update(); } @@ -81,7 +131,7 @@ export class HomeSummaryComponent implements OnDestroy, OnInit { this.isLoading = true; this.dataService - .fetchPortfolioSummary() + .fetchPortfolioSummary(this.activeFilters) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((response) => { this.summary = response; diff --git a/apps/client/src/app/components/home-summary/home-summary.html b/apps/client/src/app/components/home-summary/home-summary.html index 9401db451..667ac0b27 100644 --- a/apps/client/src/app/components/home-summary/home-summary.html +++ b/apps/client/src/app/components/home-summary/home-summary.html @@ -1,4 +1,14 @@
+
+
+ +
+
diff --git a/apps/client/src/app/components/home-summary/home-summary.module.ts b/apps/client/src/app/components/home-summary/home-summary.module.ts index 348009dc7..5a1208d9c 100644 --- a/apps/client/src/app/components/home-summary/home-summary.module.ts +++ b/apps/client/src/app/components/home-summary/home-summary.module.ts @@ -3,6 +3,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { GfPortfolioSummaryModule } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.module'; +import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module'; import { HomeSummaryComponent } from './home-summary.component'; @@ -10,6 +11,7 @@ import { HomeSummaryComponent } from './home-summary.component'; declarations: [HomeSummaryComponent], imports: [ CommonModule, + GfActivitiesFilterModule, GfPortfolioSummaryModule, MatCardModule, RouterModule