diff --git a/CHANGELOG.md b/CHANGELOG.md index 9688c4ac42..e5c182608e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added a _Storybook_ story for the investment chart component + ### Changed +- Moved the investment chart component to `@ghostfolio/ui` - Improved the matching of assets in the import of activities with _Ghostfolio AI_ to prefer the holdings of the user (experimental) - Extended the holdings table by the ISIN in the _Copy AI prompt to clipboard_ actions on the analysis page and in the tool to get the portfolio of the server of the Model Context Protocol (MCP) (experimental) - Hardened the validation of the date in the endpoint to create an account balance @@ -18,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed an exception in the authentication interceptor when the time zone of the browser is not available +- Fixed the missing `userId` in the unique constraint of the `AccountBalance` database schema ## 3.86.0 - 2026-10-13 diff --git a/apps/api/src/app/account-balance/account-balance.service.ts b/apps/api/src/app/account-balance/account-balance.service.ts index 56975cdf08..afa4386e01 100644 --- a/apps/api/src/app/account-balance/account-balance.service.ts +++ b/apps/api/src/app/account-balance/account-balance.service.ts @@ -68,9 +68,9 @@ export class AccountBalanceService { value: balance }, where: { - userId, - accountId_date: { + accountId_userId_date: { accountId, + userId, date: resetHours(parseISO(date)) } } diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index 9a1dfe9438..f6fe52a8bd 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -1,4 +1,3 @@ -import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_DATE_RANGE, @@ -23,6 +22,7 @@ import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table'; import { translate } from '@ghostfolio/ui/i18n'; +import { GfInvestmentChartComponent } from '@ghostfolio/ui/investment-chart'; import { DataService } from '@ghostfolio/ui/services'; import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.html b/apps/client/src/app/components/user-account-membership/user-account-membership.html index ae23dc9860..f18da13ed9 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.html +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -50,7 +50,7 @@ } }
- @if (!user?.subscription?.expiresAt) { + @if (!user?.subscription?.subscribedAt) { { user.settings.retirementDate = parseISO(user.settings.retirementDate); } + if (user.subscription?.expiresAt) { + user.subscription.expiresAt = parseISO(user.subscription.expiresAt); + } + + if (user.subscription?.subscribedAt) { + user.subscription.subscribedAt = parseISO( + user.subscription.subscribedAt + ); + } + this.setState({ user }, UserStoreActions.GetUser); if ( diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 48f69fd403..615ed09214 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -31,13 +31,6 @@ export const secondaryColorRgb = { b: 207 }; -export const warnColorHex = '#dc3545'; -export const warnColorRgb = { - r: 220, - g: 53, - b: 69 -}; - export const AI_ACTIVITIES_FILE_MAXIMUM_SIZE = 10 * 1024 * 1024; export const AI_ACTIVITIES_TIMEOUT = ms('2 minutes'); export const AI_MAX_ACTIVITIES = 25; diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index ff0f6646f5..aa079c1219 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -27,6 +27,7 @@ export interface User { subscription: { expiresAt?: Date; offer: SubscriptionOffer; + subscribedAt?: Date; type: SubscriptionType; }; tags: (Tag & { isUsed: boolean })[]; diff --git a/libs/ui/src/lib/investment-chart/index.ts b/libs/ui/src/lib/investment-chart/index.ts new file mode 100644 index 0000000000..3929e41867 --- /dev/null +++ b/libs/ui/src/lib/investment-chart/index.ts @@ -0,0 +1 @@ +export * from './investment-chart.component'; diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.html b/libs/ui/src/lib/investment-chart/investment-chart.component.html similarity index 100% rename from apps/client/src/app/components/investment-chart/investment-chart.component.html rename to libs/ui/src/lib/investment-chart/investment-chart.component.html diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.scss b/libs/ui/src/lib/investment-chart/investment-chart.component.scss similarity index 100% rename from apps/client/src/app/components/investment-chart/investment-chart.component.scss rename to libs/ui/src/lib/investment-chart/investment-chart.component.scss diff --git a/libs/ui/src/lib/investment-chart/investment-chart.component.stories.ts b/libs/ui/src/lib/investment-chart/investment-chart.component.stories.ts new file mode 100644 index 0000000000..225857c7da --- /dev/null +++ b/libs/ui/src/lib/investment-chart/investment-chart.component.stories.ts @@ -0,0 +1,75 @@ +import { moduleMetadata } from '@storybook/angular'; +import type { Meta, StoryObj } from '@storybook/angular'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { GfInvestmentChartComponent } from './investment-chart.component'; + +export default { + title: 'Investment Chart', + component: GfInvestmentChartComponent, + decorators: [ + moduleMetadata({ + imports: [NgxSkeletonLoaderModule] + }) + ] +} as Meta; + +type Story = StoryObj; + +export const PerformanceByYear: Story = { + args: { + benchmarkDataItems: [ + { + date: '2015-01-01', + investment: -0.02783300898051616 + }, + { + date: '2016-01-01', + investment: 0.2955008663975502 + }, + { + date: '2017-01-01', + investment: 0.5581973009289353 + }, + { + date: '2018-01-01', + investment: -0.27056776062551113 + }, + { + date: '2019-01-01', + investment: 0.27802802888773426 + }, + { + date: '2020-01-01', + investment: 0.11264960377555408 + }, + { + date: '2021-01-01', + investment: 0.31365756351078433 + }, + { + date: '2022-01-01', + investment: -0.23865494728984496 + }, + { + date: '2023-01-01', + investment: 0.07752911954819477 + }, + { + date: '2024-01-01', + investment: 0.24854314905668837 + }, + { + date: '2025-01-01', + investment: 0.05351048565996891 + }, + { + date: '2026-01-01', + investment: 0.1037188655512382 + } + ], + benchmarkDataLabel: 'Net Performance', + groupBy: 'year', + isInPercentage: true + } +}; diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/libs/ui/src/lib/investment-chart/investment-chart.component.ts similarity index 99% rename from apps/client/src/app/components/investment-chart/investment-chart.component.ts rename to libs/ui/src/lib/investment-chart/investment-chart.component.ts index 8b74a74d4c..86af956d45 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/libs/ui/src/lib/investment-chart/investment-chart.component.ts @@ -12,11 +12,6 @@ import { getLocale, parseDate } from '@ghostfolio/common/helper'; import { LineChartItem } from '@ghostfolio/common/interfaces'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { ColorScheme, GroupBy } from '@ghostfolio/common/types'; -import { - getTimeSeriesTooltipOptions, - onPrefersColorSchemeChange, - registerChartConfiguration -} from '@ghostfolio/ui/chart'; import { ChangeDetectionStrategy, @@ -47,6 +42,12 @@ import { type AnnotationOptions } from 'chartjs-plugin-annotation'; import { isFuture } from 'date-fns'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { + getTimeSeriesTooltipOptions, + onPrefersColorSchemeChange, + registerChartConfiguration +} from '../chart'; + @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxSkeletonLoaderModule], diff --git a/prisma/migrations/20261014000000_added_user_id_to_unique_constraint_of_account_balance/migration.sql b/prisma/migrations/20261014000000_added_user_id_to_unique_constraint_of_account_balance/migration.sql new file mode 100644 index 0000000000..40c76e5816 --- /dev/null +++ b/prisma/migrations/20261014000000_added_user_id_to_unique_constraint_of_account_balance/migration.sql @@ -0,0 +1,5 @@ +-- DropIndex +DROP INDEX "AccountBalance_accountId_date_key"; + +-- CreateIndex +CREATE UNIQUE INDEX "AccountBalance_accountId_userId_date_key" ON "AccountBalance"("accountId", "userId", "date"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e21fc10e8c..17a2d4e39d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,7 +65,7 @@ model AccountBalance { userId String value Float - @@unique([accountId, date]) + @@unique([accountId, userId, date]) @@index([accountId]) @@index([date]) }