From 4da8658eecaa29100c955a6ea36acab64f83e1a6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 22 Aug 2026 11:35:00 +0200 Subject: [PATCH] Task/improve type filter of activities table component to only list activity types in use (#7602) * Limit type filter in activities table component to used types * Update changelog --- CHANGELOG.md | 2 ++ .../app/portfolio/portfolio.service.spec.ts | 2 +- apps/api/src/app/user/user.service.ts | 32 ++++++++++++------- .../holding-detail-dialog.component.ts | 14 +++++--- .../portfolio/activities/activities-page.html | 1 + .../src/lib/interfaces/user.interface.ts | 3 +- .../src/lib/types/user-with-settings.type.ts | 2 +- .../activities-table.component.html | 7 ++-- .../activities-table.component.stories.ts | 1 + .../activities-table.component.ts | 26 +++++++-------- .../migration.sql | 17 ++++++++++ prisma/schema.prisma | 6 ++-- 12 files changed, 72 insertions(+), 41 deletions(-) create mode 100644 prisma/migrations/20260822000000_updated_indexes_of_order/migration.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b4eacd7..5cd20a1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the type filter of the activities table on the activities page to only list the activity types in use (experimental) - Improved the permission selector with icons in the create or update access dialog - Extracted the access level icon to a reusable component - Disabled the telemetry in the _Storybook_ setup +- Improved the indexes of the order database table - Upgraded the `Node.js` engine from version `>=22.18.0` to `>=22.22.3` (`package.json`) ### Fixed diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index 970b13734..7cc256cf6 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -247,7 +247,7 @@ describe('PortfolioService', () => { jest.spyOn(userService, 'user').mockResolvedValue({ accounts: [], - activityCount: 0, + activitiesCount: 0, dataProviderGhostfolioDailyRequests: 0, id: userDummyData.id, settings: { diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 0a3a3ca76..c8220a824 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -129,8 +129,7 @@ export class UserService { const [ access, accounts, - activitiesCount, - firstActivity, + activitiesGroupedByType, impersonationUser, tagsForUser ] = await Promise.all([ @@ -150,13 +149,10 @@ export class UserService { userId: impersonationUserId || user.id } }), - this.prismaService.order.count({ - where: { userId: impersonationUserId || user.id } - }), - this.prismaService.order.findFirst({ - orderBy: { - date: 'asc' - }, + this.prismaService.order.groupBy({ + _min: { date: true }, + by: ['type'], + orderBy: { _min: { date: 'asc' } }, where: { userId: impersonationUserId || user.id } }), impersonationUserId @@ -165,6 +161,19 @@ export class UserService { this.tagService.getTagsForUser(impersonationUserId || user.id) ]); + const activitiesCount = impersonationUserId + ? (impersonationUser?.activitiesCount ?? 0) + : (user.activitiesCount ?? 0); + + const activityTypes = activitiesGroupedByType.map(({ type }) => { + return type; + }); + + // The groupBy is ordered by the minimum date, thus the first group + // carries the date of the first activity + const dateOfFirstActivity = + activitiesGroupedByType[0]?._min.date ?? new Date(); + const resolvedUserSettings = resolveUserSettings({ impersonationUserSettings: impersonationUser?.settings ?.settings as UserSettings, @@ -209,6 +218,8 @@ export class UserService { return { activitiesCount, + activityTypes, + dateOfFirstActivity, id, permissions, referralPartners, @@ -226,7 +237,6 @@ export class UserService { accounts: accounts.sort((a, b) => { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); }), - dateOfFirstActivity: firstActivity?.date ?? new Date(), settings: { ...resolvedUserSettings, baseCurrency: resolvedUserSettings.baseCurrency ?? DEFAULT_CURRENCY, @@ -324,6 +334,7 @@ export class UserService { const user: UserWithSettings = { accessToken, accounts, + activitiesCount, authChallenge, createdAt, id, @@ -332,7 +343,6 @@ export class UserService { settings: settings as UserWithSettings['settings'], thirdPartyId, updatedAt, - activityCount: analytics?.activityCount, dataProviderGhostfolioDailyRequests: analytics?.dataProviderGhostfolioDailyRequests ?? 0 }; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 22585efc6..33a0695aa 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -649,11 +649,9 @@ export class GfHoldingDetailDialogComponent implements OnInit { .postActivity(activity) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { - this.router.navigate( - internalRoutes.portfolio.subRoutes.activities.routerLink - ); + this.userService.get(true).subscribe(); - this.dialogRef.close(); + this.navigateToActivities(); }); } @@ -729,4 +727,12 @@ export class GfHoldingDetailDialogComponent implements OnInit { { id: this.data.symbol, type: 'SYMBOL' } ]; } + + private navigateToActivities() { + this.router.navigate( + internalRoutes.portfolio.subRoutes.activities.routerLink + ); + + this.dialogRef.close(); + } } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.html b/apps/client/src/app/pages/portfolio/activities/activities-page.html index e1c213183..102f58a31 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.html +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -3,6 +3,7 @@