Browse Source

Merge branch 'main' into feature/migrate-blog-page-to-standalone

pull/5742/head
H_S 3 weeks ago
committed by GitHub
parent
commit
7a7da5a1ca
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      CHANGELOG.md
  2. 60
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  3. 188
      apps/client/src/app/app.component.html
  4. 18
      apps/client/src/app/app.component.scss
  5. 35
      apps/client/src/app/app.component.ts
  6. 7
      apps/client/src/app/app.module.ts
  7. 181
      apps/client/src/app/components/footer/footer.component.html
  8. 16
      apps/client/src/app/components/footer/footer.component.scss
  9. 74
      apps/client/src/app/components/footer/footer.component.ts
  10. 2
      apps/client/src/index.html
  11. 3
      libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts
  12. 62
      libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts
  13. 293
      libs/ui/src/lib/mocks/holdings.ts
  14. 351
      libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts

9
CHANGELOG.md

@ -10,6 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Refactored the blog page component to standalone
### Added
- Added a _Storybook_ story for the holdings table component
### Changed
- Disabled the zoom functionality in the _Progressive Web App_ (PWA)
- Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service
- Extracted the footer to a component
## 2.208.0 - 2025-10-11

60
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -12,6 +12,7 @@ import {
IDataProviderHistoricalResponse,
IDataProviderResponse
} from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import {
DEFAULT_CURRENCY,
REPLACE_NAME_PARTS
@ -49,7 +50,8 @@ export class FinancialModelingPrepService implements DataProviderInterface {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly cryptocurrencyService: CryptocurrencyService
private readonly cryptocurrencyService: CryptocurrencyService,
private readonly prismaService: PrismaService
) {
this.apiKey = this.configurationService.get(
'API_KEY_FINANCIAL_MODELING_PREP'
@ -220,7 +222,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
public getDataProviderInfo(): DataProviderInfo {
return {
dataSource: DataSource.FINANCIAL_MODELING_PREP,
dataSource: this.getName(),
isPremium: true,
name: 'Financial Modeling Prep',
url: 'https://financialmodelingprep.com/developer/docs'
@ -359,25 +361,41 @@ export class FinancialModelingPrepService implements DataProviderInterface {
[symbol: string]: Pick<SymbolProfile, 'currency'>;
} = {};
const quotes = await fetch(
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
).then((res) => res.json());
const [assetProfileResolutions, quotes] = await Promise.all([
this.prismaService.assetProfileResolution.findMany({
where: {
dataSourceTarget: this.getDataProviderInfo().dataSource,
symbolTarget: { in: symbols }
}
}),
fetch(
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
).then((res) => res.json())
]);
await Promise.all(
quotes.map(({ symbol }) => {
return this.getAssetProfile({
requestTimeout,
symbol
}).then((assetProfile) => {
if (assetProfile?.currency) {
currencyBySymbolMap[symbol] = { currency: assetProfile.currency };
}
});
})
);
if (assetProfileResolutions.length === symbols.length) {
for (const { currency, symbolTarget } of assetProfileResolutions) {
currencyBySymbolMap[symbolTarget] = { currency };
}
} else {
await Promise.all(
quotes.map(({ symbol }) => {
return this.getAssetProfile({
requestTimeout,
symbol
}).then((assetProfile) => {
if (assetProfile?.currency) {
currencyBySymbolMap[symbol] = {
currency: assetProfile.currency
};
}
});
})
);
}
for (const { price, symbol } of quotes) {
let marketState: MarketState = 'delayed';
@ -394,7 +412,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
marketState,
currency: currencyBySymbolMap[symbol]?.currency,
dataProviderInfo: this.getDataProviderInfo(),
dataSource: DataSource.FINANCIAL_MODELING_PREP,
dataSource: this.getDataProviderInfo().dataSource,
marketPrice: price
};
}

188
apps/client/src/app/app.component.html

@ -11,8 +11,8 @@
>
<span i18n>You are using the Live Demo.</span>
<span class="a ml-2 p-1" i18n>Create Account</span>
</div></a
>
</div>
</a>
}
@if (!canCreateAccount && user?.systemMessage) {
<div
@ -47,187 +47,7 @@
</main>
@if (showFooter) {
<footer class="justify-content-center overflow-hidden py-4 w-100">
<div class="container">
<div class="mb-3 row">
<div class="col-sm">
<a [routerLink]="['/']"><gf-logo /></a>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Personal Finance</div>
<ul class="list-unstyled">
@if (hasPermissionToAccessFearAndGreedIndex) {
<li>
<a i18n [routerLink]="routerLinkMarkets">Markets</a>
</li>
}
<li><a i18n [routerLink]="routerLinkResources">Resources</a></li>
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2">Ghostfolio</div>
<ul class="list-unstyled">
<li><a i18n [routerLink]="routerLinkAbout">About</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkBlog">Blog</a>
</li>
}
<li>
<a i18n [routerLink]="routerLinkAboutChangelog">Changelog</a>
</li>
<li><a i18n [routerLink]="routerLinkFeatures">Features</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkFaq"
>Frequently Asked Questions (FAQ)</a
>
</li>
}
@if (!hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutLicense">License</a>
</li>
}
@if (hasPermissionForStatistics) {
<li>
<a [routerLink]="routerLinkOpenStartup">Open Startup</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkPricing">Pricing</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutPrivacyPolicy"
>Privacy Policy</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutTermsOfService"
>Terms of Service</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a
class="align-items-baseline d-flex"
href="https://status.ghostfol.io"
target="_blank"
title="Ghostfolio Status"
>Status<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
}
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Community</div>
<ul class="list-unstyled">
<li>
<a
class="align-items-baseline d-flex"
href="https://github.com/ghostfolio/ghostfolio"
target="_blank"
title="Find Ghostfolio on GitHub"
>GitHub<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://linkedin.com/company/ghostfolio"
target="_blank"
title="Follow Ghostfolio on LinkedIn"
>LinkedIn<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
target="_blank"
title="Join the Ghostfolio Slack community"
>Slack<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://x.com/ghostfolio_"
target="_blank"
title="Follow Ghostfolio on X (formerly Twitter)"
>X (formerly Twitter)<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>&nbsp;</li>
<!--
<li>
<a href="../ca" title="Ghostfolio en català">Català</a>
</li>
-->
<li>
<a href="../zh" title="Ghostfolio in Chinese">Chinese</a>
</li>
<li>
<a href="../de" title="Ghostfolio in Deutsch">Deutsch</a>
</li>
<li>
<a href="../en" title="Ghostfolio in English">English</a>
</li>
<li>
<a href="../es" title="Ghostfolio in Español">Español</a>
</li>
<li>
<a href="../fr" title="Ghostfolio en Français">Français</a>
</li>
<li>
<a href="../it" title="Ghostfolio in Italiano">Italiano</a>
</li>
<li>
<a href="../nl" title="Ghostfolio in Nederlands">Nederlands</a>
</li>
<li>
<a href="../pl" title="Ghostfolio in Polski">Polski</a>
</li>
<li>
<a href="../pt" title="Ghostfolio in Português">Português</a>
</li>
<li>
<a href="../tr" title="Ghostfolio in Türkçe">Türkçe</a>
</li>
<!--
<li>
<a href="../uk" title="Ghostfolio in Українська">Українська</a>
</li>
-->
</ul>
</div>
</div>
<div class="mb-2 row text-center">
<div class="col">
© 2021 - {{ currentYear }}
<a href="https://ghostfol.io">Ghostfolio</a>
</div>
</div>
<div class="row text-center text-muted">
<div class="col">
<small class="d-block" i18n
>The risk of loss in trading can be substantial. It is not advisable
to invest money you may need in the short term.</small
>
</div>
</div>
</div>
<div class="container d-none d-md-block mt-5">
<div class="row justify-content-center">
<div class="font-weight-bold line-height-1 logotype">Ghostfolio</div>
</div>
</div>
<footer class="justify-content-center overflow-hidden w-100">
<gf-footer class="py-4" [info]="info" [user]="user" />
</footer>
}

18
apps/client/src/app/app.component.scss

@ -34,18 +34,6 @@
}
}
footer {
background-color: rgba(var(--palette-foreground-text), 0.05);
font-size: 90%;
.logotype {
font-size: 13vw;
letter-spacing: -0.03em;
margin-bottom: -5svw;
opacity: 0.05;
}
}
header {
height: var(--mat-toolbar-standard-height);
}
@ -54,9 +42,3 @@
min-height: calc(100svh - var(--mat-toolbar-standard-height));
}
}
:host-context(.theme-dark) {
footer {
background-color: rgba(var(--palette-foreground-text-dark), 0.05);
}
}

35
apps/client/src/app/app.component.ts

@ -52,36 +52,16 @@ export class AppComponent implements OnDestroy, OnInit {
public canCreateAccount: boolean;
public currentRoute: string;
public currentSubRoute: string;
public currentYear = new Date().getFullYear();
public deviceType: string;
public hasImpersonationId: boolean;
public hasInfoMessage: boolean;
public hasPermissionForStatistics: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public hasPermissionToChangeDateRange: boolean;
public hasPermissionToChangeFilters: boolean;
public hasPromotion = false;
public hasTabs = false;
public info: InfoItem;
public pageTitle: string;
public routerLinkAbout = publicRoutes.about.routerLink;
public routerLinkAboutChangelog =
publicRoutes.about.subRoutes.changelog.routerLink;
public routerLinkAboutLicense =
publicRoutes.about.subRoutes.license.routerLink;
public routerLinkAboutPrivacyPolicy =
publicRoutes.about.subRoutes.privacyPolicy.routerLink;
public routerLinkAboutTermsOfService =
publicRoutes.about.subRoutes.termsOfService.routerLink;
public routerLinkBlog = publicRoutes.blog.routerLink;
public routerLinkFaq = publicRoutes.faq.routerLink;
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkMarkets = publicRoutes.markets.routerLink;
public routerLinkOpenStartup = publicRoutes.openStartup.routerLink;
public routerLinkPricing = publicRoutes.pricing.routerLink;
public routerLinkRegister = publicRoutes.register.routerLink;
public routerLinkResources = publicRoutes.resources.routerLink;
public showFooter = false;
public user: User;
@ -126,21 +106,6 @@ export class AppComponent implements OnDestroy, OnInit {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.info = this.dataService.fetchInfo();
this.hasPermissionForSubscription = hasPermission(
this.info?.globalPermissions,
permissions.enableSubscription
);
this.hasPermissionForStatistics = hasPermission(
this.info?.globalPermissions,
permissions.enableStatistics
);
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
this.hasPromotion =
!!this.info?.subscriptionOffer?.coupon ||
!!this.info?.subscriptionOffer?.durationExtension;

7
apps/client/src/app/app.module.ts

@ -1,5 +1,3 @@
import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { Platform } from '@angular/cdk/platform';
import {
provideHttpClient,
@ -20,7 +18,6 @@ import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ServiceWorkerModule } from '@angular/service-worker';
import { provideIonicAngular } from '@ionic/angular/standalone';
import { IonIcon } from '@ionic/angular/standalone';
import { provideMarkdown } from 'ngx-markdown';
import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader';
import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe';
@ -30,6 +27,7 @@ import { CustomDateAdapter } from './adapter/custom-date-adapter';
import { DateFormats } from './adapter/date-formats';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { GfFooterComponent } from './components/footer/footer.component';
import { GfHeaderComponent } from './components/header/header.component';
import { authInterceptorProviders } from './core/auth.interceptor';
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
@ -47,10 +45,9 @@ export function NgxStripeFactory(): string {
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
GfFooterComponent,
GfHeaderComponent,
GfLogoComponent,
GfNotificationModule,
IonIcon,
MatAutocompleteModule,
MatChipsModule,
MatNativeDateModule,

181
apps/client/src/app/components/footer/footer.component.html

@ -0,0 +1,181 @@
<div class="container">
<div class="mb-3 row">
<div class="col-sm">
<a [routerLink]="['/']"><gf-logo /></a>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Personal Finance</div>
<ul class="list-unstyled">
@if (hasPermissionToAccessFearAndGreedIndex) {
<li>
<a i18n [routerLink]="routerLinkMarkets">Markets</a>
</li>
}
<li><a i18n [routerLink]="routerLinkResources">Resources</a></li>
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2">Ghostfolio</div>
<ul class="list-unstyled">
<li><a i18n [routerLink]="routerLinkAbout">About</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkBlog">Blog</a>
</li>
}
<li>
<a i18n [routerLink]="routerLinkAboutChangelog">Changelog</a>
</li>
<li><a i18n [routerLink]="routerLinkFeatures">Features</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkFaq"
>Frequently Asked Questions (FAQ)</a
>
</li>
}
@if (!hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutLicense">License</a>
</li>
}
@if (hasPermissionForStatistics) {
<li>
<a [routerLink]="routerLinkOpenStartup">Open Startup</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkPricing">Pricing</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutPrivacyPolicy"
>Privacy Policy</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutTermsOfService"
>Terms of Service</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a
class="align-items-baseline d-flex"
href="https://status.ghostfol.io"
target="_blank"
title="Ghostfolio Status"
>Status<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
}
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Community</div>
<ul class="list-unstyled">
<li>
<a
class="align-items-baseline d-flex"
href="https://github.com/ghostfolio/ghostfolio"
target="_blank"
title="Find Ghostfolio on GitHub"
>GitHub<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://linkedin.com/company/ghostfolio"
target="_blank"
title="Follow Ghostfolio on LinkedIn"
>LinkedIn<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
target="_blank"
title="Join the Ghostfolio Slack community"
>Slack<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://x.com/ghostfolio_"
target="_blank"
title="Follow Ghostfolio on X (formerly Twitter)"
>X (formerly Twitter)<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>&nbsp;</li>
<!--
<li>
<a href="../ca" title="Ghostfolio en català">Català</a>
</li>
-->
<li>
<a href="../zh" title="Ghostfolio in Chinese">Chinese</a>
</li>
<li>
<a href="../de" title="Ghostfolio in Deutsch">Deutsch</a>
</li>
<li>
<a href="../en" title="Ghostfolio in English">English</a>
</li>
<li>
<a href="../es" title="Ghostfolio in Español">Español</a>
</li>
<li>
<a href="../fr" title="Ghostfolio en Français">Français</a>
</li>
<li>
<a href="../it" title="Ghostfolio in Italiano">Italiano</a>
</li>
<li>
<a href="../nl" title="Ghostfolio in Nederlands">Nederlands</a>
</li>
<li>
<a href="../pl" title="Ghostfolio in Polski">Polski</a>
</li>
<li>
<a href="../pt" title="Ghostfolio in Português">Português</a>
</li>
<li>
<a href="../tr" title="Ghostfolio in Türkçe">Türkçe</a>
</li>
<!--
<li>
<a href="../uk" title="Ghostfolio in Українська">Українська</a>
</li>
-->
</ul>
</div>
</div>
<div class="mb-2 row text-center">
<div class="col">
© 2021 - {{ currentYear }}
<a href="https://ghostfol.io">Ghostfolio</a>
</div>
</div>
<div class="row text-center text-muted">
<div class="col">
<small class="d-block" i18n
>The risk of loss in trading can be substantial. It is not advisable to
invest money you may need in the short term.</small
>
</div>
</div>
</div>
<div class="container d-none d-md-block mt-5">
<div class="row justify-content-center">
<div class="font-weight-bold line-height-1 logotype">Ghostfolio</div>
</div>
</div>

16
apps/client/src/app/components/footer/footer.component.scss

@ -0,0 +1,16 @@
:host {
background-color: rgba(var(--palette-foreground-text), 0.05);
display: block;
font-size: 90%;
.logotype {
font-size: 13vw;
letter-spacing: -0.03em;
margin-bottom: -5svw;
opacity: 0.05;
}
}
:host-context(.theme-dark) {
background-color: rgba(var(--palette-foreground-text-dark), 0.05);
}

74
apps/client/src/app/components/footer/footer.component.ts

@ -0,0 +1,74 @@
import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
Input,
OnChanges
} from '@angular/core';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { openOutline } from 'ionicons/icons';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, GfLogoComponent, IonIcon, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-footer',
styleUrls: ['./footer.component.scss'],
templateUrl: './footer.component.html'
})
export class GfFooterComponent implements OnChanges {
@Input() public info: InfoItem;
@Input() public user: User;
public currentYear = new Date().getFullYear();
public hasPermissionForStatistics: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public routerLinkAbout = publicRoutes.about.routerLink;
public routerLinkAboutChangelog =
publicRoutes.about.subRoutes.changelog.routerLink;
public routerLinkAboutLicense =
publicRoutes.about.subRoutes.license.routerLink;
public routerLinkAboutPrivacyPolicy =
publicRoutes.about.subRoutes.privacyPolicy.routerLink;
public routerLinkAboutTermsOfService =
publicRoutes.about.subRoutes.termsOfService.routerLink;
public routerLinkBlog = publicRoutes.blog.routerLink;
public routerLinkFaq = publicRoutes.faq.routerLink;
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkMarkets = publicRoutes.markets.routerLink;
public routerLinkOpenStartup = publicRoutes.openStartup.routerLink;
public routerLinkPricing = publicRoutes.pricing.routerLink;
public routerLinkResources = publicRoutes.resources.routerLink;
public constructor() {
addIcons({
openOutline
});
}
public ngOnChanges() {
this.hasPermissionForStatistics = hasPermission(
this.info?.globalPermissions,
permissions.enableStatistics
);
this.hasPermissionForSubscription = hasPermission(
this.info?.globalPermissions,
permissions.enableSubscription
);
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
}
}

2
apps/client/src/index.html

@ -13,7 +13,7 @@
<meta content="${rootUrl}/${featureGraphicPath}" name="twitter:image" />
<meta content="${title}" name="twitter:title" />
<meta
content="initial-scale=1, viewport-fit=cover, width=device-width"
content="initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, width=device-width"
name="viewport"
/>
<meta content="#FFFFFF" name="theme-color" />

3
libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts

@ -112,6 +112,7 @@ export const Loading: Story = {
accounts: undefined,
baseCurrency: 'USD',
deviceType: 'desktop',
hasPermissionToOpenDetails: false,
locale: 'en-US',
showActions: false,
showAllocationInPercentage: false,
@ -128,6 +129,7 @@ export const Default: Story = {
accounts,
baseCurrency: 'USD',
deviceType: 'desktop',
hasPermissionToOpenDetails: false,
locale: 'en-US',
showActions: false,
showAllocationInPercentage: false,
@ -147,6 +149,7 @@ export const WithoutFooter: Story = {
accounts,
baseCurrency: 'USD',
deviceType: 'desktop',
hasPermissionToOpenDetails: false,
locale: 'en-US',
showActions: false,
showAllocationInPercentage: false,

62
libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts

@ -0,0 +1,62 @@
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfEntityLogoComponent } from '../entity-logo';
import { holdings } from '../mocks/holdings';
import { GfValueComponent } from '../value';
import { GfHoldingsTableComponent } from './holdings-table.component';
export default {
title: 'Holdings Table',
component: GfHoldingsTableComponent,
decorators: [
moduleMetadata({
imports: [
CommonModule,
GfEntityLogoComponent,
GfValueComponent,
MatButtonModule,
MatDialogModule,
MatPaginatorModule,
MatSortModule,
MatTableModule,
NgxSkeletonLoaderModule
]
})
]
} as Meta<GfHoldingsTableComponent>;
type Story = StoryObj<GfHoldingsTableComponent>;
export const Loading: Story = {
args: {
holdings: undefined,
baseCurrency: 'USD',
deviceType: 'desktop',
hasPermissionToOpenDetails: false,
hasPermissionToShowQuantities: true,
hasPermissionToShowValues: true,
locale: 'en-US',
pageSize: Number.MAX_SAFE_INTEGER
}
};
export const Default: Story = {
args: {
holdings,
baseCurrency: 'USD',
deviceType: 'desktop',
hasPermissionToOpenDetails: false,
hasPermissionToShowQuantities: true,
hasPermissionToShowValues: true,
locale: 'en-US',
pageSize: Number.MAX_SAFE_INTEGER
}
};

293
libs/ui/src/lib/mocks/holdings.ts

@ -0,0 +1,293 @@
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
export const holdings: PortfolioPosition[] = [
{
allocationInPercentage: 0.042990776363386086,
assetClass: 'EQUITY' as any,
assetClassLabel: 'Equity',
assetSubClass: 'STOCK' as any,
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
weight: 1,
continent: 'North America',
name: 'United States'
}
],
currency: 'USD',
dataSource: 'YAHOO' as any,
dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 3856,
grossPerformancePercent: 0.46047289228564603,
grossPerformancePercentWithCurrencyEffect: 0.46047289228564603,
grossPerformanceWithCurrencyEffect: 3856,
holdings: [],
investment: 8374,
marketPrice: 244.6,
name: 'Apple Inc',
netPerformance: 3855,
netPerformancePercent: 0.460353475041796,
netPerformancePercentWithCurrencyEffect: 0.036440677966101696,
netPerformanceWithCurrencyEffect: 430,
quantity: 50,
sectors: [
{
name: 'Technology',
weight: 1
}
],
symbol: 'AAPL',
tags: [],
transactionCount: 1,
url: 'https://www.apple.com',
valueInBaseCurrency: 12230
},
{
allocationInPercentage: 0.02377401948293552,
assetClass: 'EQUITY' as any,
assetClassLabel: 'Equity',
assetSubClass: 'STOCK' as any,
assetSubClassLabel: 'Stock',
countries: [
{
code: 'DE',
weight: 1,
continent: 'Europe',
name: 'Germany'
}
],
currency: 'EUR',
dataSource: 'YAHOO' as any,
dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'),
dividend: 192,
grossPerformance: 2226.700251889169,
grossPerformancePercent: 0.49083842309827874,
grossPerformancePercentWithCurrencyEffect: 0.29306136948826367,
grossPerformanceWithCurrencyEffect: 1532.8272791336772,
holdings: [],
investment: 4536.523929471033,
marketPrice: 322.2,
name: 'Allianz SE',
netPerformance: 2222.2921914357685,
netPerformancePercent: 0.48986674069961134,
netPerformancePercentWithCurrencyEffect: 0.034489367670592026,
netPerformanceWithCurrencyEffect: 225.48257403052068,
quantity: 20,
sectors: [
{
name: 'Financial Services',
weight: 1
}
],
symbol: 'ALV.DE',
tags: [],
transactionCount: 2,
url: 'https://www.allianz.com',
valueInBaseCurrency: 6763.224181360202
},
{
allocationInPercentage: 0.08038536990007467,
assetClass: 'EQUITY' as any,
assetClassLabel: 'Equity',
assetSubClass: 'STOCK' as any,
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
weight: 1,
continent: 'North America',
name: 'United States'
}
],
currency: 'USD',
dataSource: 'YAHOO' as any,
dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 12758.05,
grossPerformancePercent: 1.2619300787837724,
grossPerformancePercentWithCurrencyEffect: 1.2619300787837724,
grossPerformanceWithCurrencyEffect: 12758.05,
holdings: [],
investment: 10109.95,
marketPrice: 228.68,
name: 'Amazon.com, Inc.',
netPerformance: 12677.26,
netPerformancePercent: 1.253938941339967,
netPerformancePercentWithCurrencyEffect: -0.037866008722316276,
netPerformanceWithCurrencyEffect: -899.99926757812,
quantity: 100,
sectors: [
{
name: 'Consumer Discretionary',
weight: 1
}
],
symbol: 'AMZN',
tags: [],
transactionCount: 1,
url: 'https://www.aboutamazon.com',
valueInBaseCurrency: 22868
},
{
allocationInPercentage: 0.19216416482928922,
assetClass: 'LIQUIDITY' as any,
assetClassLabel: 'Liquidity',
assetSubClass: 'CRYPTOCURRENCY' as any,
assetSubClassLabel: 'Cryptocurrency',
countries: [],
currency: 'USD',
dataSource: 'COINGECKO' as any,
dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'),
dividend: 0,
grossPerformance: 52666.7898248,
grossPerformancePercent: 26.333394912400003,
grossPerformancePercentWithCurrencyEffect: 26.333394912400003,
grossPerformanceWithCurrencyEffect: 52666.7898248,
holdings: [],
investment: 1999.9999999999998,
marketPrice: 97364,
name: 'Bitcoin',
netPerformance: 52636.8898248,
netPerformancePercent: 26.3184449124,
netPerformancePercentWithCurrencyEffect: -0.04760906442310894,
netPerformanceWithCurrencyEffect: -2732.737808972287,
quantity: 0.5614682,
sectors: [],
symbol: 'bitcoin',
tags: [],
transactionCount: 1,
url: null,
valueInBaseCurrency: 54666.7898248
},
{
allocationInPercentage: 0.04307127421937313,
assetClass: 'EQUITY' as any,
assetClassLabel: 'Equity',
assetSubClass: 'STOCK' as any,
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
weight: 1,
continent: 'North America',
name: 'United States'
}
],
currency: 'USD',
dataSource: 'YAHOO' as any,
dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'),
dividend: 0,
grossPerformance: 5065.5,
grossPerformancePercent: 0.7047750229568411,
grossPerformancePercentWithCurrencyEffect: 0.7047750229568411,
grossPerformanceWithCurrencyEffect: 5065.5,
holdings: [],
investment: 7187.4,
marketPrice: 408.43,
name: 'Microsoft Corporation',
netPerformance: 5065.5,
netPerformancePercent: 0.7047750229568411,
netPerformancePercentWithCurrencyEffect: -0.015973588391056275,
netPerformanceWithCurrencyEffect: -198.899926757814,
quantity: 30,
sectors: [
{
name: 'Technology',
weight: 1
}
],
symbol: 'MSFT',
tags: [],
transactionCount: 1,
url: 'https://www.microsoft.com',
valueInBaseCurrency: 12252.9
},
{
allocationInPercentage: 0.18762679306394897,
assetClass: 'EQUITY' as any,
assetClassLabel: 'Equity',
assetSubClass: 'STOCK' as any,
assetSubClassLabel: 'Stock',
countries: [
{
code: 'US',
weight: 1,
continent: 'North America',
name: 'United States'
}
],
currency: 'USD',
dataSource: 'YAHOO' as any,
dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'),
dividend: 0,
grossPerformance: 51227.500000005,
grossPerformancePercent: 23.843379101756675,
grossPerformancePercentWithCurrencyEffect: 23.843379101756675,
grossPerformanceWithCurrencyEffect: 51227.500000005,
holdings: [],
investment: 2148.499999995,
marketPrice: 355.84,
name: 'Tesla, Inc.',
netPerformance: 51197.500000005,
netPerformancePercent: 23.829415871596066,
netPerformancePercentWithCurrencyEffect: -0.12051410125545206,
netPerformanceWithCurrencyEffect: -7314.00091552734,
quantity: 150,
sectors: [
{
name: 'Consumer Discretionary',
weight: 1
}
],
symbol: 'TSLA',
tags: [],
transactionCount: 1,
url: 'https://www.tesla.com',
valueInBaseCurrency: 53376
},
{
allocationInPercentage: 0.053051250766657634,
assetClass: 'EQUITY' as any,
assetClassLabel: 'Equity',
assetSubClass: 'ETF' as any,
assetSubClassLabel: 'ETF',
countries: [
{
code: 'US',
weight: 1,
continent: 'North America',
name: 'United States'
}
],
currency: 'USD',
dataSource: 'YAHOO' as any,
dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 6845.8,
grossPerformancePercent: 1.0164758094605268,
grossPerformancePercentWithCurrencyEffect: 1.0164758094605268,
grossPerformanceWithCurrencyEffect: 6845.8,
holdings: [],
investment: 8246.2,
marketPrice: 301.84,
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
netPerformance: 6746.3,
netPerformancePercent: 1.0017018833976383,
netPerformancePercentWithCurrencyEffect: 0.01085061564051406,
netPerformanceWithCurrencyEffect: 161.99969482422,
quantity: 50,
sectors: [
{
name: 'Equity',
weight: 1
}
],
symbol: 'VTI',
tags: [],
transactionCount: 5,
url: 'https://www.vanguard.com',
valueInBaseCurrency: 15092
}
];

351
libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts

@ -4,6 +4,7 @@ import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { holdings } from '../mocks/holdings';
import { GfTreemapChartComponent } from './treemap-chart.component';
export default {
@ -34,359 +35,11 @@ type Story = StoryObj<GfTreemapChartComponent>;
export const Default: Story = {
args: {
holdings,
baseCurrency: 'USD',
colorScheme: 'LIGHT',
cursor: undefined,
dateRange: 'mtd',
holdings: [
{
allocationInPercentage: 0.042990776363386086,
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
countries: [],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 3856,
grossPerformancePercent: 0.46047289228564603,
grossPerformancePercentWithCurrencyEffect: 0.46047289228564603,
grossPerformanceWithCurrencyEffect: 3856,
holdings: [],
investment: 8374,
marketPrice: 244.6,
name: 'Apple Inc',
netPerformance: 3855,
netPerformancePercent: 0.460353475041796,
netPerformancePercentWithCurrencyEffect: 0.036440677966101696,
netPerformanceWithCurrencyEffect: 430,
quantity: 50,
sectors: [],
symbol: 'AAPL',
tags: [],
transactionCount: 1,
url: 'https://www.apple.com',
valueInBaseCurrency: 12230
},
{
allocationInPercentage: 0.02377401948293552,
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
countries: [],
currency: 'EUR',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'),
dividend: 192,
grossPerformance: 2226.700251889169,
grossPerformancePercent: 0.49083842309827874,
grossPerformancePercentWithCurrencyEffect: 0.29306136948826367,
grossPerformanceWithCurrencyEffect: 1532.8272791336772,
holdings: [],
investment: 4536.523929471033,
marketPrice: 322.2,
name: 'Allianz SE',
netPerformance: 2222.2921914357685,
netPerformancePercent: 0.48986674069961134,
netPerformancePercentWithCurrencyEffect: 0.034489367670592026,
netPerformanceWithCurrencyEffect: 225.48257403052068,
quantity: 20,
sectors: [],
symbol: 'ALV.DE',
tags: [],
transactionCount: 2,
url: 'https://www.allianz.com',
valueInBaseCurrency: 6763.224181360202
},
{
allocationInPercentage: 0.08038536990007467,
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
countries: [],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 12758.05,
grossPerformancePercent: 1.2619300787837724,
grossPerformancePercentWithCurrencyEffect: 1.2619300787837724,
grossPerformanceWithCurrencyEffect: 12758.05,
holdings: [],
investment: 10109.95,
marketPrice: 228.68,
name: 'Amazon.com, Inc.',
netPerformance: 12677.26,
netPerformancePercent: 1.253938941339967,
netPerformancePercentWithCurrencyEffect: -0.037866008722316276,
netPerformanceWithCurrencyEffect: -899.99926757812,
quantity: 100,
sectors: [],
symbol: 'AMZN',
tags: [],
transactionCount: 1,
url: 'https://www.aboutamazon.com',
valueInBaseCurrency: 22868
},
{
allocationInPercentage: 0.19216416482928922,
assetClass: 'LIQUIDITY',
assetSubClass: 'CRYPTOCURRENCY',
countries: [],
currency: 'USD',
dataSource: 'COINGECKO',
dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'),
dividend: 0,
grossPerformance: 52666.7898248,
grossPerformancePercent: 26.333394912400003,
grossPerformancePercentWithCurrencyEffect: 26.333394912400003,
grossPerformanceWithCurrencyEffect: 52666.7898248,
holdings: [],
investment: 1999.9999999999998,
marketPrice: 97364,
name: 'Bitcoin',
netPerformance: 52636.8898248,
netPerformancePercent: 26.3184449124,
netPerformancePercentWithCurrencyEffect: -0.04760906442310894,
netPerformanceWithCurrencyEffect: -2732.737808972287,
quantity: 0.5614682,
sectors: [],
symbol: 'bitcoin',
tags: [],
transactionCount: 1,
url: null,
valueInBaseCurrency: 54666.7898248
},
{
allocationInPercentage: 0.007378652850073097,
assetClass: 'FIXED_INCOME',
assetSubClass: 'BOND',
countries: [],
currency: 'EUR',
dataSource: 'MANUAL',
dateOfFirstActivity: new Date('2021-02-01T00:00:00.000Z'),
dividend: 11.45,
grossPerformance: 0,
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: -0.1247202380342517,
grossPerformanceWithCurrencyEffect: -258.2576430160448,
holdings: [],
investment: 2099.0764063811926,
marketPrice: 1,
name: 'Bondora Go & Grow',
netPerformance: 0,
netPerformancePercent: 0,
netPerformancePercentWithCurrencyEffect: 0.009445843828715519,
netPerformanceWithCurrencyEffect: 19.6420125363184,
quantity: 2000,
sectors: [],
symbol: 'BONDORA_GO_AND_GROW',
tags: [],
transactionCount: 5,
url: null,
valueInBaseCurrency: 2099.0764063811926
},
{
allocationInPercentage: 0.07787531695543741,
assetClass: 'EQUITY',
assetSubClass: 'ETF',
countries: [],
currency: 'CHF',
dataSource: 'MANUAL',
dateOfFirstActivity: new Date('2021-04-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 4550.843985045582,
grossPerformancePercent: 0.3631417324494093,
grossPerformancePercentWithCurrencyEffect: 0.42037247857285137,
grossPerformanceWithCurrencyEffect: 5107.057936556927,
holdings: [],
investment: 17603.097090932337,
marketPrice: 188.22,
name: 'frankly Extreme 95 Index',
netPerformance: 4550.843985045582,
netPerformancePercent: 0.3631417324494093,
netPerformancePercentWithCurrencyEffect: 0.026190604904358043,
netPerformanceWithCurrencyEffect: 565.4165171873152,
quantity: 105.87328656807,
sectors: [],
symbol: 'FRANKLY95P',
tags: [],
transactionCount: 6,
url: 'https://www.frankly.ch',
valueInBaseCurrency: 22153.941075977917
},
{
allocationInPercentage: 0.04307127421937313,
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
countries: [],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'),
dividend: 0,
grossPerformance: 5065.5,
grossPerformancePercent: 0.7047750229568411,
grossPerformancePercentWithCurrencyEffect: 0.7047750229568411,
grossPerformanceWithCurrencyEffect: 5065.5,
holdings: [],
investment: 7187.4,
marketPrice: 408.43,
name: 'Microsoft Corporation',
netPerformance: 5065.5,
netPerformancePercent: 0.7047750229568411,
netPerformancePercentWithCurrencyEffect: -0.015973588391056275,
netPerformanceWithCurrencyEffect: -198.899926757814,
quantity: 30,
sectors: [],
symbol: 'MSFT',
tags: [],
transactionCount: 1,
url: 'https://www.microsoft.com',
valueInBaseCurrency: 12252.9
},
{
allocationInPercentage: 0.18762679306394897,
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
countries: [],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'),
dividend: 0,
grossPerformance: 51227.500000005,
grossPerformancePercent: 23.843379101756675,
grossPerformancePercentWithCurrencyEffect: 23.843379101756675,
grossPerformanceWithCurrencyEffect: 51227.500000005,
holdings: [],
investment: 2148.499999995,
marketPrice: 355.84,
name: 'Tesla, Inc.',
netPerformance: 51197.500000005,
netPerformancePercent: 23.829415871596066,
netPerformancePercentWithCurrencyEffect: -0.12051410125545206,
netPerformanceWithCurrencyEffect: -7314.00091552734,
quantity: 150,
sectors: [],
symbol: 'TSLA',
tags: [],
transactionCount: 1,
url: 'https://www.tesla.com',
valueInBaseCurrency: 53376
},
{
allocationInPercentage: 0.053051250766657634,
assetClass: 'EQUITY',
assetSubClass: 'ETF',
countries: [],
currency: 'USD',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 6845.8,
grossPerformancePercent: 1.0164758094605268,
grossPerformancePercentWithCurrencyEffect: 1.0164758094605268,
grossPerformanceWithCurrencyEffect: 6845.8,
holdings: [],
investment: 8246.2,
marketPrice: 301.84,
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
netPerformance: 6746.3,
netPerformancePercent: 1.0017018833976383,
netPerformancePercentWithCurrencyEffect: 0.01085061564051406,
netPerformanceWithCurrencyEffect: 161.99969482422,
quantity: 50,
sectors: [],
symbol: 'VTI',
tags: [],
transactionCount: 5,
url: 'https://www.vanguard.com',
valueInBaseCurrency: 15092
},
{
allocationInPercentage: 0.0836576192450555,
assetClass: 'EQUITY',
assetSubClass: 'ETF',
countries: [],
currency: 'CHF',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2018-03-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 6462.42356864925,
grossPerformancePercent: 0.5463044783973836,
grossPerformancePercentWithCurrencyEffect: 0.6282343505275325,
grossPerformanceWithCurrencyEffect: 7121.935580698947,
holdings: [],
investment: 17336.464702612564,
marketPrice: 129.74,
name: 'Vanguard FTSE All-World UCITS ETF',
netPerformance: 6373.040578098944,
netPerformancePercent: 0.5387484388540966,
netPerformancePercentWithCurrencyEffect: 0.008409682389650015,
netPerformanceWithCurrencyEffect: 198.47200506226807,
quantity: 165,
sectors: [],
symbol: 'VWRL.SW',
tags: [],
transactionCount: 5,
url: 'https://www.vanguard.com',
valueInBaseCurrency: 23798.888271261814
},
{
allocationInPercentage: 0.03265192235898284,
assetClass: 'EQUITY',
assetSubClass: 'ETF',
countries: [],
currency: 'EUR',
dataSource: 'YAHOO',
dateOfFirstActivity: new Date('2021-08-19T00:00:00.000Z'),
dividend: 0,
grossPerformance: 3112.7991183879094,
grossPerformancePercent: 0.5040147846036197,
grossPerformancePercentWithCurrencyEffect: 0.3516875105542396,
grossPerformanceWithCurrencyEffect: 2416.799201046856,
holdings: [],
investment: 6176.007556675063,
marketPrice: 118.005,
name: 'Xtrackers MSCI World UCITS ETF 1C',
netPerformance: 3081.4179261125105,
netPerformancePercent: 0.4989336392216841,
netPerformancePercentWithCurrencyEffect: 0.006460676966633529,
netPerformanceWithCurrencyEffect: 59.626750161726044,
quantity: 75,
sectors: [],
symbol: 'XDWD.DE',
tags: [],
transactionCount: 1,
url: null,
valueInBaseCurrency: 9288.806675062973
},
{
allocationInPercentage: 0.17537283996478595,
assetClass: 'LIQUIDITY',
assetSubClass: 'CASH',
countries: [],
currency: 'USD',
dataSource: 'MANUAL',
dateOfFirstActivity: new Date('2021-04-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 0,
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: 0,
holdings: [],
investment: 49890,
marketPrice: 0,
name: 'USD',
netPerformance: 0,
netPerformancePercent: 0,
netPerformancePercentWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0,
quantity: 0,
sectors: [],
symbol: 'USD',
tags: [],
transactionCount: 0,
valueInBaseCurrency: 49890
}
],
locale: 'en-US'
}
};

Loading…
Cancel
Save