Browse Source

Refactoring

pull/4044/head
Thomas Kaul 9 months ago
parent
commit
bf9ebdb84e
  1. 2
      apps/client/src/app/pages/portfolio/allocations/allocations-page.html
  2. 97
      libs/ui/src/lib/top-holdings/top-holdings.component.html
  3. 65
      libs/ui/src/lib/top-holdings/top-holdings.component.scss
  4. 76
      libs/ui/src/lib/top-holdings/top-holdings.component.ts

2
apps/client/src/app/pages/portfolio/allocations/allocations-page.html

@ -347,7 +347,7 @@
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[pageSize]="10" [pageSize]="10"
[topHoldings]="topHoldings" [topHoldings]="topHoldings"
(proportionChartClicked)="onSymbolChartClicked($event)" (holdingClicked)="onSymbolChartClicked($event)"
/> />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

97
libs/ui/src/lib/top-holdings/top-holdings.component.html

@ -2,14 +2,18 @@
<table <table
class="gf-table holdings-table w-100" class="gf-table holdings-table w-100"
mat-table mat-table
matSort
matSortActive="allocationInPercentage"
matSortDirection="desc"
multiTemplateDataRows multiTemplateDataRows
style="table-layout: auto"
[dataSource]="dataSource" [dataSource]="dataSource"
> >
<colgroup>
<col class="w-100" />
<col />
<col />
</colgroup>
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th *matHeaderCellDef class="px-2" mat-header-cell mat-sort-header> <th *matHeaderCellDef class="px-2" mat-header-cell>
<ng-container i18n>Name</ng-container> <ng-container i18n>Name</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-2" mat-cell> <td *matCellDef="let element" class="px-2" mat-cell>
@ -18,12 +22,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="valueInBaseCurrency"> <ng-container matColumnDef="valueInBaseCurrency">
<th <th *matHeaderCellDef class="px-2 text-right" mat-header-cell>
*matHeaderCellDef
class="justify-content-end px-2"
mat-header-cell
mat-sort-header
>
<ng-container i18n>Value</ng-container> <ng-container i18n>Value</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-2" mat-cell> <td *matCellDef="let element" class="px-2" mat-cell>
@ -38,12 +37,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="allocationInPercentage" stickyEnd> <ng-container matColumnDef="allocationInPercentage" stickyEnd>
<th <th *matHeaderCellDef class="justify-content-end px-2" mat-header-cell>
*matHeaderCellDef
class="justify-content-end px-2"
mat-header-cell
mat-sort-header
>
<span class="d-none d-sm-block" i18n>Allocation</span> <span class="d-none d-sm-block" i18n>Allocation</span>
<span class="d-block d-sm-none" title="Allocation">%</span> <span class="d-block d-sm-none" title="Allocation">%</span>
</th> </th>
@ -61,6 +55,7 @@
<ng-container matColumnDef="expandedDetail"> <ng-container matColumnDef="expandedDetail">
<td <td
*matCellDef="let element" *matCellDef="let element"
class="p-0"
mat-cell mat-cell
[attr.colspan]="displayedColumns.length" [attr.colspan]="displayedColumns.length"
> >
@ -68,71 +63,61 @@
[@detailExpand]="element.expand ? 'expanded' : 'collapsed'" [@detailExpand]="element.expand ? 'expanded' : 'collapsed'"
class="allocationByParent" class="allocationByParent"
> >
<div class="holdingParents"> <div class="holding-parents-table">
<div class="holdingParentProportionChart d-flex">
@for (
parentHolding of element.parents?.slice(0, 10);
track parentHolding;
let i = $index
) {
<div
[class.cursor-pointer]="parentHolding?.symbol.length > 0"
[matTooltip]="parentHolding.name"
[style.background-color]="getColor(parentHolding?.symbol)"
[style.width.%]="100 * parentHolding?.allocationInPercentage"
(click)="onClickOpenPositionModal(parentHolding.position)"
>
{{ parentHolding?.symbol }}
</div>
}
</div>
<table mat-table [dataSource]="element.parents"> <table mat-table [dataSource]="element.parents">
<colgroup>
<col class="w-100" />
<col />
<col />
</colgroup>
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th *matHeaderCellDef mat-header-cell>Name</th>
<td *matCellDef="let parentHolding" mat-cell> <td *matCellDef="let parentHolding" mat-cell>
<span
class="colorBadge"
[style.background-color]="getColor(parentHolding?.symbol)"
></span>
{{ parentHolding?.name }} {{ parentHolding?.name }}
</td> </td>
</ng-container> <td *matFooterCellDef class="px-2" mat-footer-cell>
<ng-container matColumnDef="symbol"> <ng-container i18n>Name</ng-container>
<th *matHeaderCellDef mat-header-cell>Symbol</th>
<td *matCellDef="let parentHolding" mat-cell>
{{ parentHolding?.symbol }}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="valueInBaseCurrency"> <ng-container matColumnDef="valueInBaseCurrency">
<th *matHeaderCellDef mat-header-cell>Value</th>
<td *matCellDef="let parentHolding" mat-cell> <td *matCellDef="let parentHolding" mat-cell>
<div class="d-flex justify-content-end">
<gf-value <gf-value
[isCurrency]="true" [isCurrency]="true"
[locale]="locale" [locale]="locale"
[value]="parentHolding?.valueInBaseCurrency" [value]="parentHolding?.valueInBaseCurrency"
/> />
</div>
</td>
<td *matFooterCellDef class="px-2" mat-footer-cell>
<ng-container i18n>Value</ng-container>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="allocationInPercentage"> <ng-container matColumnDef="allocationInPercentage">
<th *matHeaderCellDef mat-header-cell>Allocation</th>
<td *matCellDef="let parentHolding" mat-cell> <td *matCellDef="let parentHolding" mat-cell>
<div class="d-flex justify-content-end">
<gf-value <gf-value
[isPercent]="true" [isPercent]="true"
[locale]="locale" [locale]="locale"
[value]="parentHolding?.allocationInPercentage" [value]="parentHolding?.allocationInPercentage"
/> />
</div>
</td>
<td *matFooterCellDef class="px-2" mat-footer-cell>
<span class="d-none d-sm-block" i18n>Allocation</span>
<span class="d-block d-sm-none">%</span>
</td> </td>
</ng-container> </ng-container>
<tr <tr
*matHeaderRowDef="displayedHoldingParentColumns" *matRowDef="let row; columns: displayedColumns"
mat-header-row mat-row
[ngClass]="{ 'cursor-pointer': row.position }"
(click)="onClickHolding(row.position)"
></tr> ></tr>
<tr <tr
*matRowDef="let row; columns: displayedHoldingParentColumns" *matFooterRowDef="displayedColumns"
mat-row mat-footer-row
[class.cursor-pointer]="row.position" style="visibility: hidden"
(click)="onClickOpenPositionModal(row.position)"
></tr> ></tr>
</table> </table>
</div> </div>
@ -144,8 +129,10 @@
<tr <tr
*matRowDef="let element; columns: displayedColumns" *matRowDef="let element; columns: displayedColumns"
mat-row mat-row
[class.cursor-pointer]="element.parents?.length > 0" [ngClass]="{
[class.expanded]="element.expand ?? false" 'cursor-pointer': element.parents?.length > 0,
expanded: element.expand ?? false
}"
(click)=" (click)="
element.expand ? (element.expand = false) : (element.expand = true) element.expand ? (element.expand = false) : (element.expand = true)
" "

65
libs/ui/src/lib/top-holdings/top-holdings.component.scss

@ -2,73 +2,32 @@
display: block; display: block;
.holdings-table { .holdings-table {
th { tr {
::ng-deep { &:not(.expanded) + tr.holding-detail td {
.mat-sort-header-container {
justify-content: inherit;
}
}
}
tr.holding-detail {
height: 0;
&:hover {
// Disable hover effect.
background-color: var(--mat-table-background-color-even) !important;
}
}
tr.expanded {
background-color: var(--mat-table-background-color-even);
}
tr:not(.expanded) + tr.holding-detail td {
border-bottom: 0; border-bottom: 0;
} }
.holdingParents { &.expanded {
padding: 1em 0; > td {
--table-padding: 0.5em; font-weight: bold;
.holdingParentProportionChart {
height: 2em;
div {
box-sizing: border-box;
line-height: 2em;
padding: 0 var(--table-padding);
overflow: hidden;
&:hover {
filter: brightness(0.95);
}
} }
} }
.colorBadge { &.holding-detail {
width: 0.75em; height: 0;
height: 0.75em;
border-radius: 100%;
display: inline-block;
} }
table, .holding-parents-table {
tr, --table-padding: 0.5em;
th,
td {
background-color: transparent;
}
tr { tr {
height: auto; height: auto;
&:hover {
background-color: var(--mat-table-background-color-hover);
}
}
th,
td { td {
padding: var(--table-padding); padding: var(--table-padding);
} }
} }
} }
} }
}
}

76
libs/ui/src/lib/top-holdings/top-holdings.component.ts

@ -27,29 +27,11 @@ import {
} from '@angular/core'; } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { get } from 'lodash';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
const {
blue,
cyan,
grape,
green,
indigo,
lime,
orange,
pink,
red,
teal,
violet,
yellow
} = require('open-color');
@Component({ @Component({
animations: [ animations: [
trigger('detailExpand', [ trigger('detailExpand', [
@ -67,9 +49,7 @@ const {
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,
MatPaginatorModule, MatPaginatorModule,
MatSortModule,
MatTableModule, MatTableModule,
MatTooltipModule,
NgxSkeletonLoaderModule NgxSkeletonLoaderModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@ -91,14 +71,9 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy {
}; };
} = {}; } = {};
@Output() proportionChartClicked = new EventEmitter<AssetProfileIdentifier>(); @Output() holdingClicked = new EventEmitter<AssetProfileIdentifier>();
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
private colorMap: {
[symbol: string]: string;
} = {};
public dataSource = new MatTableDataSource<HoldingWithParents>(); public dataSource = new MatTableDataSource<HoldingWithParents>();
public displayedColumns: string[] = [ public displayedColumns: string[] = [
@ -106,66 +81,25 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy {
'valueInBaseCurrency', 'valueInBaseCurrency',
'allocationInPercentage' 'allocationInPercentage'
]; ];
public displayedHoldingParentColumns: string[] = [
'name',
'symbol',
'valueInBaseCurrency',
'allocationInPercentage'
];
public isLoading = true; public isLoading = true;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
private colorPaletteIndex = 0;
private colorPalette = [
blue[5],
teal[5],
lime[5],
orange[5],
pink[5],
violet[5],
indigo[5],
cyan[5],
green[5],
yellow[5],
red[5],
grape[5]
];
public getColor(symbol) {
if (this.colorMap[symbol]) {
// Reuse color
return this.colorMap[symbol];
} else {
const color = this.colorPalette[this.colorPaletteIndex];
this.colorPaletteIndex =
this.colorPaletteIndex < this.colorPalette.length
? this.colorPaletteIndex + 1
: 0;
this.colorMap[symbol] = color;
return color;
}
}
public onClickOpenPositionModal(holding: AssetProfileIdentifier) {
try {
this.proportionChartClicked.emit(holding);
} catch {}
}
public ngOnChanges() { public ngOnChanges() {
this.isLoading = true; this.isLoading = true;
this.dataSource = new MatTableDataSource(this.topHoldings); this.dataSource = new MatTableDataSource(this.topHoldings);
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = get;
if (this.topHoldings) { if (this.topHoldings) {
this.isLoading = false; this.isLoading = false;
} }
} }
public onClickHolding(assetProfileIdentifier: AssetProfileIdentifier) {
this.holdingClicked.emit(assetProfileIdentifier);
}
public onShowAllHoldings() { public onShowAllHoldings() {
this.pageSize = Number.MAX_SAFE_INTEGER; this.pageSize = Number.MAX_SAFE_INTEGER;

Loading…
Cancel
Save