Feature/extend asset profile details dialog (#1469)
* Extend asset profile details dialog * Update changelog
This commit is contained in:
parent
4b74be50da
commit
3611684f17
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- Extended the asset profile details dialog in the admin control panel
|
||||
|
||||
## 1.214.0 - 19.11.2022
|
||||
|
||||
### Added
|
||||
|
@ -147,7 +147,7 @@ export class AdminService {
|
||||
countriesCount,
|
||||
marketDataItemCount,
|
||||
sectorsCount,
|
||||
activityCount: symbolProfile._count.Order,
|
||||
activitiesCount: symbolProfile._count.Order,
|
||||
assetClass: symbolProfile.assetClass,
|
||||
assetSubClass: symbolProfile.assetSubClass,
|
||||
dataSource: symbolProfile.dataSource,
|
||||
@ -165,8 +165,14 @@ export class AdminService {
|
||||
dataSource,
|
||||
symbol
|
||||
}: UniqueAsset): Promise<AdminMarketDataDetails> {
|
||||
return {
|
||||
marketData: await this.marketDataService.marketDataItems({
|
||||
const [[assetProfile], marketData] = await Promise.all([
|
||||
this.symbolProfileService.getSymbolProfiles([
|
||||
{
|
||||
dataSource,
|
||||
symbol
|
||||
}
|
||||
]),
|
||||
this.marketDataService.marketDataItems({
|
||||
orderBy: {
|
||||
date: 'asc'
|
||||
},
|
||||
@ -175,6 +181,11 @@ export class AdminService {
|
||||
symbol
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
assetProfile,
|
||||
marketData
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,12 @@ export class SymbolProfileService {
|
||||
): Promise<EnhancedSymbolProfile[]> {
|
||||
return this.prismaService.symbolProfile
|
||||
.findMany({
|
||||
include: { SymbolProfileOverrides: true },
|
||||
include: {
|
||||
_count: {
|
||||
select: { Order: true }
|
||||
},
|
||||
SymbolProfileOverrides: true
|
||||
},
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
@ -69,7 +74,12 @@ export class SymbolProfileService {
|
||||
): Promise<EnhancedSymbolProfile[]> {
|
||||
return this.prismaService.symbolProfile
|
||||
.findMany({
|
||||
include: { SymbolProfileOverrides: true },
|
||||
include: {
|
||||
_count: {
|
||||
select: { Order: true }
|
||||
},
|
||||
SymbolProfileOverrides: true
|
||||
},
|
||||
where: {
|
||||
id: {
|
||||
in: symbolProfileIds.map((symbolProfileId) => {
|
||||
@ -89,7 +99,12 @@ export class SymbolProfileService {
|
||||
): Promise<EnhancedSymbolProfile[]> {
|
||||
return this.prismaService.symbolProfile
|
||||
.findMany({
|
||||
include: { SymbolProfileOverrides: true },
|
||||
include: {
|
||||
_count: {
|
||||
select: { Order: true }
|
||||
},
|
||||
SymbolProfileOverrides: true
|
||||
},
|
||||
where: {
|
||||
symbol: {
|
||||
in: symbols
|
||||
@ -101,12 +116,14 @@ export class SymbolProfileService {
|
||||
|
||||
private getSymbols(
|
||||
symbolProfiles: (SymbolProfile & {
|
||||
_count: { Order: number };
|
||||
SymbolProfileOverrides: SymbolProfileOverrides;
|
||||
})[]
|
||||
): EnhancedSymbolProfile[] {
|
||||
return symbolProfiles.map((symbolProfile) => {
|
||||
const item = {
|
||||
...symbolProfile,
|
||||
activitiesCount: 0,
|
||||
countries: this.getCountries(
|
||||
symbolProfile?.countries as unknown as Prisma.JsonArray
|
||||
),
|
||||
@ -115,6 +132,9 @@ export class SymbolProfileService {
|
||||
symbolMapping: this.getSymbolMapping(symbolProfile)
|
||||
};
|
||||
|
||||
item.activitiesCount = symbolProfile._count.Order;
|
||||
delete item._count;
|
||||
|
||||
if (item.SymbolProfileOverrides) {
|
||||
item.assetClass =
|
||||
item.SymbolProfileOverrides.assetClass ?? item.assetClass;
|
||||
|
@ -13,6 +13,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { MaterialCssVarsModule } from 'angular-material-css-vars';
|
||||
import { MarkdownModule } from 'ngx-markdown';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
@ -27,7 +28,6 @@ import { GfHeaderModule } from './components/header/header.module';
|
||||
import { authInterceptorProviders } from './core/auth.interceptor';
|
||||
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
|
||||
import { LanguageService } from './core/language.service';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
|
||||
export function NgxStripeFactory(): string {
|
||||
return environment.stripePublicKey;
|
||||
|
@ -63,10 +63,10 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
||||
'assetClass',
|
||||
'assetSubClass',
|
||||
'date',
|
||||
'activityCount',
|
||||
'activitiesCount',
|
||||
'marketDataItemCount',
|
||||
'countriesCount',
|
||||
'sectorsCount',
|
||||
'countriesCount',
|
||||
'actions'
|
||||
];
|
||||
public filters$ = new Subject<Filter[]>();
|
||||
|
@ -64,12 +64,12 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="activityCount">
|
||||
<ng-container matColumnDef="activitiesCount">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||
<ng-container i18n>Activity Count</ng-container>
|
||||
<ng-container i18n>Activities Count</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
|
||||
{{ element.activityCount }}
|
||||
{{ element.activitiesCount }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@ -82,15 +82,6 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="countriesCount">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||
<ng-container i18n>Countries Count</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
|
||||
{{ element.countriesCount }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="sectorsCount">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||
<ng-container i18n>Sectors Count</ng-container>
|
||||
@ -100,6 +91,15 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="countriesCount">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||
<ng-container i18n>Countries Count</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
|
||||
{{ element.countriesCount }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
||||
<button
|
||||
@ -146,7 +146,7 @@
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="element.activityCount !== 0"
|
||||
[disabled]="element.activitiesCount !== 0"
|
||||
(click)="onDeleteProfileData({dataSource: element.dataSource, symbol: element.symbol})"
|
||||
>
|
||||
<ng-container i18n>Delete</ng-container>
|
||||
|
@ -8,7 +8,10 @@ import {
|
||||
} from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
EnhancedSymbolProfile,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { MarketData } from '@prisma/client';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
@ -23,7 +26,14 @@ import { AssetProfileDialogParams } from './interfaces/interfaces';
|
||||
styleUrls: ['./asset-profile-dialog.component.scss']
|
||||
})
|
||||
export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
public assetProfile: EnhancedSymbolProfile;
|
||||
public countries: {
|
||||
[code: string]: { name: string; value: number };
|
||||
};
|
||||
public marketDataDetails: MarketData[] = [];
|
||||
public sectors: {
|
||||
[name: string]: { name: string; value: number };
|
||||
};
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
@ -57,8 +67,29 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
this.adminService
|
||||
.fetchAdminMarketDataBySymbol({ dataSource, symbol })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketData }) => {
|
||||
.subscribe(({ assetProfile, marketData }) => {
|
||||
this.assetProfile = assetProfile;
|
||||
this.countries = {};
|
||||
this.marketDataDetails = marketData;
|
||||
this.sectors = {};
|
||||
|
||||
if (assetProfile?.countries?.length > 0) {
|
||||
for (const country of assetProfile.countries) {
|
||||
this.countries[country.code] = {
|
||||
name: country.name,
|
||||
value: country.weight
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (assetProfile?.sectors?.length > 0) {
|
||||
for (const sector of assetProfile.sectors) {
|
||||
this.sectors[sector.name] = {
|
||||
name: sector.name,
|
||||
value: sector.weight
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
|
@ -2,12 +2,13 @@
|
||||
mat-dialog-title
|
||||
position="center"
|
||||
[deviceType]="data.deviceType"
|
||||
[title]="data.symbol"
|
||||
[title]="assetProfile?.name ?? data.symbol"
|
||||
(closeButtonClicked)="onClose()"
|
||||
></gf-dialog-header>
|
||||
|
||||
<div class="flex-grow-1" mat-dialog-content>
|
||||
<gf-admin-market-data-detail
|
||||
class="mb-3"
|
||||
[dataSource]="data.dataSource"
|
||||
[dateOfFirstActivity]="data.dateOfFirstActivity"
|
||||
[locale]="data.locale"
|
||||
@ -15,6 +16,93 @@
|
||||
[symbol]="data.symbol"
|
||||
(marketDataChanged)="onMarketDataChanged($event)"
|
||||
></gf-admin-market-data-detail>
|
||||
<div class="row">
|
||||
<div class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[isDate]="data.dateOfFirstActivity ? true : false"
|
||||
[locale]="data.locale"
|
||||
[value]="data.dateOfFirstActivity ?? '-'"
|
||||
>First Buy Date</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.activitiesCount ?? 0"
|
||||
>Transactions</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[hidden]="!assetProfile?.assetClass"
|
||||
[value]="assetProfile?.assetClass"
|
||||
>Asset Class</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[hidden]="!assetProfile?.assetSubClass"
|
||||
[value]="assetProfile?.assetSubClass"
|
||||
>Asset Sub Class</gf-value
|
||||
>
|
||||
</div>
|
||||
<ng-container
|
||||
*ngIf="assetProfile?.countries?.length > 0 || assetProfile?.sectors?.length > 0"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="assetProfile?.countries?.length === 1 && assetProfile?.sectors?.length === 1; else charts"
|
||||
>
|
||||
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #charts>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
></gf-portfolio-proportion-chart>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
></gf-portfolio-proportion-chart>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<gf-dialog-footer
|
||||
|
@ -5,6 +5,8 @@ import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
|
||||
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
|
||||
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
|
||||
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
|
||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||
|
||||
import { AssetProfileDialog } from './asset-profile-dialog.component';
|
||||
|
||||
@ -15,6 +17,8 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
|
||||
GfAdminMarketDataDetailModule,
|
||||
GfDialogFooterModule,
|
||||
GfDialogHeaderModule,
|
||||
GfPortfolioProportionChartModule,
|
||||
GfValueModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule
|
||||
],
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { MarketData } from '@prisma/client';
|
||||
|
||||
import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface';
|
||||
|
||||
export interface AdminMarketDataDetails {
|
||||
assetProfile: EnhancedSymbolProfile;
|
||||
marketData: MarketData[];
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { ScraperConfiguration } from './scraper-configuration.interface';
|
||||
import { Sector } from './sector.interface';
|
||||
|
||||
export interface EnhancedSymbolProfile {
|
||||
activitiesCount: number;
|
||||
assetClass: AssetClass;
|
||||
assetSubClass: AssetSubClass;
|
||||
countries: Country[];
|
||||
|
Loading…
x
Reference in New Issue
Block a user