Feature/improve empty state of proportion chart (#811)
* Improve empty state * Update changelog
This commit is contained in:
parent
6575440877
commit
957200854c
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved the empty state of the portfolio proportion chart component
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed an issue with dates in the value component
|
- Fixed an issue with dates in the value component
|
||||||
|
@ -106,16 +106,6 @@ export class PortfolioController {
|
|||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range
|
@Query('range') range
|
||||||
): Promise<PortfolioDetails & { hasError: boolean }> {
|
): Promise<PortfolioDetails & { hasError: boolean }> {
|
||||||
if (
|
|
||||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
|
||||||
this.request.user.subscription.type === 'Basic'
|
|
||||||
) {
|
|
||||||
throw new HttpException(
|
|
||||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
|
||||||
StatusCodes.FORBIDDEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
const { accounts, holdings, hasErrors } =
|
const { accounts, holdings, hasErrors } =
|
||||||
@ -162,7 +152,11 @@ export class PortfolioController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { accounts, hasError, holdings };
|
const isBasicUser =
|
||||||
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||||
|
this.request.user.subscription.type === 'Basic';
|
||||||
|
|
||||||
|
return { accounts, hasError, holdings: isBasicUser ? {} : holdings };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('investments')
|
@Get('investments')
|
||||||
|
@ -147,13 +147,6 @@ export class UserService {
|
|||||||
user.subscription = this.subscriptionService.getSubscription(
|
user.subscription = this.subscriptionService.getSubscription(
|
||||||
userFromDatabase?.Subscription
|
userFromDatabase?.Subscription
|
||||||
);
|
);
|
||||||
|
|
||||||
if (user.subscription.type === SubscriptionType.Basic) {
|
|
||||||
user.permissions = user.permissions.filter((permission) => {
|
|
||||||
return permission !== permissions.updateViewMode;
|
|
||||||
});
|
|
||||||
user.Settings.viewMode = ViewMode.ZEN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
@ -123,17 +123,6 @@
|
|||||||
}"
|
}"
|
||||||
></ngx-skeleton-loader>
|
></ngx-skeleton-loader>
|
||||||
|
|
||||||
<div
|
|
||||||
*ngIf="
|
|
||||||
dataSource.data.length === 0 && hasPermissionToCreateOrder && !isLoading
|
|
||||||
"
|
|
||||||
class="p-3 text-center"
|
|
||||||
>
|
|
||||||
<gf-no-transactions-info-indicator
|
|
||||||
[hasBorder]="false"
|
|
||||||
></gf-no-transactions-info-indicator>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="dataSource.data.length > pageSize && !isLoading"
|
*ngIf="dataSource.data.length > pageSize && !isLoading"
|
||||||
class="my-3 text-center"
|
class="my-3 text-center"
|
||||||
|
@ -27,7 +27,6 @@ import { Subject, Subscription } from 'rxjs';
|
|||||||
export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
|
export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() hasPermissionToCreateOrder: boolean;
|
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@Input() positions: PortfolioPosition[];
|
@Input() positions: PortfolioPosition[];
|
||||||
|
|
||||||
|
@ -139,11 +139,6 @@
|
|||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="align-items-center d-flex pr-1 pt-1 w-50" i18n>
|
<div class="align-items-center d-flex pr-1 pt-1 w-50" i18n>
|
||||||
View Mode
|
View Mode
|
||||||
<ion-icon
|
|
||||||
*ngIf="!hasPermissionToUpdateViewMode"
|
|
||||||
class="mx-1 text-muted"
|
|
||||||
name="diamond-outline"
|
|
||||||
></ion-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-1 w-50">
|
<div class="pl-1 w-50">
|
||||||
<div class="align-items-center d-flex overflow-hidden">
|
<div class="align-items-center d-flex overflow-hidden">
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
UniqueAsset,
|
UniqueAsset,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
|
||||||
import { Market, ToggleOption } from '@ghostfolio/common/types';
|
import { Market, ToggleOption } from '@ghostfolio/common/types';
|
||||||
import { Account, AssetClass, DataSource } from '@prisma/client';
|
import { Account, AssetClass, DataSource } from '@prisma/client';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
@ -41,7 +40,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
};
|
};
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToCreateOrder: boolean;
|
|
||||||
public markets: {
|
public markets: {
|
||||||
[key in Market]: { name: string; value: number };
|
[key in Market]: { name: string; value: number };
|
||||||
};
|
};
|
||||||
@ -139,11 +137,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
if (state?.user) {
|
if (state?.user) {
|
||||||
this.user = state.user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToCreateOrder = hasPermission(
|
|
||||||
this.user.permissions,
|
|
||||||
permissions.createOrder
|
|
||||||
);
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -30,33 +30,14 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
>By Asset Class</mat-card-title
|
><span i18n>By Currency</span
|
||||||
>
|
><ion-icon
|
||||||
<gf-toggle
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
[defaultValue]="period"
|
class="ml-1 text-muted"
|
||||||
[isLoading]="false"
|
name="diamond-outline"
|
||||||
[options]="periodOptions"
|
></ion-icon
|
||||||
(change)="onChangePeriod($event.value)"
|
></mat-card-title>
|
||||||
></gf-toggle>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-portfolio-proportion-chart
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
|
||||||
[keys]="['assetClass', 'assetSubClass']"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[positions]="positions"
|
|
||||||
></gf-portfolio-proportion-chart>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<mat-card class="mb-3">
|
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
|
||||||
<mat-card-title class="text-truncate" i18n
|
|
||||||
>By Currency</mat-card-title
|
|
||||||
>
|
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
[defaultValue]="period"
|
[defaultValue]="period"
|
||||||
[isLoading]="false"
|
[isLoading]="false"
|
||||||
@ -75,10 +56,46 @@
|
|||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
|
><span i18n>By Asset Class</span
|
||||||
|
><ion-icon
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon
|
||||||
|
></mat-card-title>
|
||||||
|
<gf-toggle
|
||||||
|
[defaultValue]="period"
|
||||||
|
[isLoading]="false"
|
||||||
|
[options]="periodOptions"
|
||||||
|
(change)="onChangePeriod($event.value)"
|
||||||
|
></gf-toggle>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-portfolio-proportion-chart
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
|
[keys]="['assetClass', 'assetSubClass']"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[positions]="positions"
|
||||||
|
></gf-portfolio-proportion-chart>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
<div class="col-md-12 allocations-by-symbol">
|
<div class="col-md-12 allocations-by-symbol">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n>By Symbol</mat-card-title>
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
|
><span i18n>By Symbol</span
|
||||||
|
><ion-icon
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon
|
||||||
|
></mat-card-title>
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
[defaultValue]="period"
|
[defaultValue]="period"
|
||||||
[isLoading]="false"
|
[isLoading]="false"
|
||||||
@ -104,7 +121,14 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n>By Sector</mat-card-title>
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
|
><span i18n>By Sector</span
|
||||||
|
><ion-icon
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon
|
||||||
|
></mat-card-title>
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
[defaultValue]="period"
|
[defaultValue]="period"
|
||||||
[isLoading]="false"
|
[isLoading]="false"
|
||||||
@ -127,9 +151,14 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
>By Continent</mat-card-title
|
><span i18n>By Continent</span
|
||||||
>
|
><ion-icon
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon
|
||||||
|
></mat-card-title>
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
[defaultValue]="period"
|
[defaultValue]="period"
|
||||||
[isLoading]="false"
|
[isLoading]="false"
|
||||||
@ -151,7 +180,14 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n>By Country</mat-card-title>
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
|
><span i18n>By Country</span
|
||||||
|
><ion-icon
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon
|
||||||
|
></mat-card-title>
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
[defaultValue]="period"
|
[defaultValue]="period"
|
||||||
[isLoading]="false"
|
[isLoading]="false"
|
||||||
@ -176,7 +212,14 @@
|
|||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n>Regions</mat-card-title>
|
<mat-card-title class="align-items-center d-flex text-truncate"
|
||||||
|
><span i18n>Regions</span
|
||||||
|
><ion-icon
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon
|
||||||
|
></mat-card-title>
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
[defaultValue]="period"
|
[defaultValue]="period"
|
||||||
[isLoading]="false"
|
[isLoading]="false"
|
||||||
@ -225,7 +268,6 @@
|
|||||||
<gf-positions-table
|
<gf-positions-table
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[positions]="positionsArray"
|
[positions]="positionsArray"
|
||||||
></gf-positions-table>
|
></gf-positions-table>
|
||||||
|
@ -246,6 +246,12 @@ export class PortfolioProportionChartComponent
|
|||||||
labels = labelSubCategory.concat(labels);
|
labels = labelSubCategory.concat(labels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (datasets[0]?.data?.length === 0 || datasets[0]?.data?.[0] === 0) {
|
||||||
|
labels = [''];
|
||||||
|
datasets[0].backgroundColor = [this.colorMap[UNKNOWN_KEY]];
|
||||||
|
datasets[0].data[0] = Number.MAX_SAFE_INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
datasets,
|
datasets,
|
||||||
labels
|
labels
|
||||||
@ -323,7 +329,9 @@ export class PortfolioProportionChartComponent
|
|||||||
|
|
||||||
const percentage = (context.parsed * 100) / sum;
|
const percentage = (context.parsed * 100) / sum;
|
||||||
|
|
||||||
if (this.isInPercent) {
|
if (<number>context.raw === Number.MAX_SAFE_INTEGER) {
|
||||||
|
return 'No data available';
|
||||||
|
} else if (this.isInPercent) {
|
||||||
return [`${name ?? symbol}`, `${percentage.toFixed(2)}%`];
|
return [`${name ?? symbol}`, `${percentage.toFixed(2)}%`];
|
||||||
} else {
|
} else {
|
||||||
const value = <number>context.raw;
|
const value = <number>context.raw;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user