From bd1233b0e2c4b7b506a54be0f1bed60db51d282a Mon Sep 17 00:00:00 2001 From: ksyasuda <ksyasuda@umich.edu> Date: Thu, 8 Aug 2024 19:35:20 -0700 Subject: [PATCH] Add expand/collapse for Top/Bottom secion off portfolio analysis page - Add button to expand/collapse section - Show all positions when expanded - Add pagination with 10 items per page --- .gitignore | 2 + .../analysis/analysis-page.component.ts | 40 +++++++++++++++-- .../portfolio/analysis/analysis-page.html | 44 ++++++++++++++++--- .../analysis/analysis-page.module.ts | 4 ++ .../portfolio/analysis/analysis-page.scss | 4 ++ 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d7e5e5eb..83a78305 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # See http://help.github.com/ignore-files/ for more about ignoring files. +sync.sh + # compiled output /out-tsc /tmp diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 0450e32a..3b3a7894 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -2,6 +2,7 @@ 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 { MatPaginator, PageEvent } from '@angular/material/paginator'; import { HistoricalDataItem, PortfolioInvestments, @@ -31,6 +32,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { public benchmarkDataItems: HistoricalDataItem[] = []; public benchmarks: Partial<SymbolProfile>[]; public bottom3: PortfolioPosition[]; + public bottomx: PortfolioPosition[]; public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS; public daysInMarket: number; public deviceType: string; @@ -56,9 +58,17 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { public portfolioEvolutionDataLabel = $localize`Investment`; public streaks: PortfolioInvestments['streaks']; public top3: PortfolioPosition[]; + public topx: PortfolioPosition[]; + public positions: PortfolioPosition[]; + public positionsReversed: PortfolioPosition[]; public unitCurrentStreak: string; public unitLongestStreak: string; public user: User; + public showAllTop: boolean = false; + public showAllBottom: boolean = false; + public pageSize: number = 10; + public pageIndexTop: number = 0; + public pageIndexBottom: number = 0; private unsubscribeSubject = new Subject<void>(); @@ -73,6 +83,25 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { this.benchmarks = benchmarks; } + public pageChanged(event: PageEvent, section: 'top' | 'bottom') { + const newPageIndex = event.pageIndex; + if (section === 'top') { + this.pageIndexTop = newPageIndex; + } else if (section === 'bottom') { + this.pageIndexBottom = newPageIndex; + } + } + + public toggleList(l: string) { + if (l === 'top') { + this.showAllTop = !this.showAllTop; + this.pageIndexTop = 0; + } else if (l === 'bottom') { + this.showAllBottom = !this.showAllBottom; + this.pageIndexBottom = 0; + } + } + get savingsRate() { const savingsRatePerMonth = this.hasImpersonationId || this.user.settings.isRestrictedView @@ -257,14 +286,17 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { 'netPerformancePercentWithCurrencyEffect' ).reverse(); - this.top3 = holdingsSorted.slice(0, 3); + this.topx = holdingsSorted.slice(0, 5); - if (holdings?.length > 3) { - this.bottom3 = holdingsSorted.slice(-3).reverse(); + if (holdings?.length > 5) { + this.bottomx = holdingsSorted.slice(-5).reverse(); } else { - this.bottom3 = []; + this.bottomx = []; } + this.positions = [...holdingsSorted] + this.positionsReversed = [...holdingsSorted].reverse(); + this.changeDetectorRef.markForCheck(); }); diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html index 970a89f7..66f8bf8a 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -168,7 +168,7 @@ </mat-card-header> <mat-card-content> <ol class="mb-0 ml-1 pl-3"> - @for (holding of top3; track holding) { + <div *ngFor="let holding of (showAllTop ? positions.slice(pageIndexTop * pageSize, (pageIndexTop + 1) * pageSize) : positions | slice:0:5); track holding"> <li class="py-1"> <a class="d-flex" @@ -192,10 +192,10 @@ </div> </a> </li> - } + </div> </ol> <div> - @if (!top3) { + @if (!topx) { <ngx-skeleton-loader animation="pulse" [theme]="{ @@ -206,7 +206,23 @@ } </div> </mat-card-content> + <mat-paginator + *ngIf="showAllTop && positions.length > 10" + [length]="positions.length" + [pageSize]="pageSize" + [pageSizeOptions]="[10, 20, 30, 40]" + (page)="pageChanged($event, 'top')" + > + </mat-paginator> </mat-card> + <button + class="toggle-performance-list" + color="{{showAllTop ? 'accent' : 'primary'}}" + mat-button + (click)="toggleList('top')" + > + {{ showAllTop ? 'Collapse' : 'Expand' }} + </button> </div> <div class="col-md-6"> <mat-card appearance="outlined" class="mb-3"> @@ -217,7 +233,7 @@ </mat-card-header> <mat-card-content> <ol class="mb-0 ml-1 pl-3"> - @for (holding of bottom3; track holding) { + <div *ngFor="let holding of (showAllBottom ? positionsReversed.slice(pageIndexBottom * pageSize, (pageIndexBottom + 1) * pageSize) : positionsReversed | slice:0:5); track holding"> <li class="py-1"> <a class="d-flex" @@ -241,10 +257,10 @@ </div> </a> </li> - } + </div> </ol> <div> - @if (!bottom3) { + @if (!bottomx) { <ngx-skeleton-loader animation="pulse" [theme]="{ @@ -255,7 +271,23 @@ } </div> </mat-card-content> + <mat-paginator + *ngIf="showAllBottom && positionsReversed.length > 10" + [length]="positions.length" + [pageSize]="pageSize" + [pageSizeOptions]="[10, 20, 30, 40]" + (page)="pageChanged($event, 'bottom')" + > + </mat-paginator> </mat-card> + <button + class="toggle-performance-list" + color="{{showAllBottom ? 'accent' : 'primary'}}" + mat-button + (click)="toggleList('bottom')" + > + {{ showAllBottom ? 'Collapse' : 'Expand' }} + </button> </div> </div> diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts index b3390545..cf0e57c1 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts @@ -4,6 +4,8 @@ import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.modu import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; +import { MatButtonModule } from '@angular/material/button'; +import { MatPaginatorModule } from '@angular/material/paginator'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; @@ -25,6 +27,8 @@ import { AnalysisPageComponent } from './analysis-page.component'; GfToggleModule, GfValueComponent, MatCardModule, + MatButtonModule, + MatPaginatorModule, NgxSkeletonLoaderModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.scss b/apps/client/src/app/pages/portfolio/analysis/analysis-page.scss index c6fb687e..01265cae 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.scss +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.scss @@ -4,4 +4,8 @@ .chart-container { aspect-ratio: 16 / 9; } + + .toggle-performance-list { + margin-bottom: 1rem; + } }