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 @@
Activities
[];
accounts: AccountWithPlatform[];
activitiesCount: number;
+ activityTypes: ActivityType[];
dateOfFirstActivity: Date;
id: string;
permissions: string[];
diff --git a/libs/common/src/lib/types/user-with-settings.type.ts b/libs/common/src/lib/types/user-with-settings.type.ts
index 3773766b8..4c4a85a45 100644
--- a/libs/common/src/lib/types/user-with-settings.type.ts
+++ b/libs/common/src/lib/types/user-with-settings.type.ts
@@ -6,7 +6,7 @@ import { Account, Settings, User } from '@prisma/client';
// TODO: Compare with User interface
export type UserWithSettings = User & {
accounts: Account[];
- activityCount: number;
+ activitiesCount: number;
dataProviderGhostfolioDailyRequests: number;
permissions?: string[];
settings: Settings & { settings: UserSettings };
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html
index 20c6e34b7..9bfd44b9d 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.html
+++ b/libs/ui/src/lib/activities-table/activities-table.component.html
@@ -2,14 +2,11 @@
class="align-items-center d-flex justify-content-end justify-content-lg-between"
>
- @if (hasPermissionToFilterByType) {
+ @if (hasPermissionToFilterByType && activityTypeOptions().length > 1) {
Type
- @for (
- activityType of activityTypesTranslationMap | keyvalue: sortByValue;
- track activityType.key
- ) {
+ @for (activityType of activityTypeOptions(); track activityType.key) {
{{ activityType.value }}
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts
index c5a0e58c2..b83555be1 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts
+++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts
@@ -483,6 +483,7 @@ export const Actions: Story = {
export const Toolbar: Story = {
args: {
dataSource,
+ activityTypes: ['BUY', 'DIVIDEND', 'SELL'],
baseCurrency: 'USD',
deviceType: 'desktop',
hasActivities: true,
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts
index b88eb0a29..acf17c933 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.ts
+++ b/libs/ui/src/lib/activities-table/activities-table.component.ts
@@ -141,7 +141,6 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
- public activityTypesTranslationMap = new Map();
public hasDrafts = false;
public hasErrors = false;
public isDraftActivity = isDraftActivity;
@@ -149,6 +148,7 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
public selectedRows = new SelectionModel(true, []);
public typesFilter = new FormControl([]);
+ public readonly activityTypes = input([]);
public readonly dataSource = input.required<
MatTableDataSource | undefined
>();
@@ -175,6 +175,16 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
return routerLinks;
});
+ protected readonly activityTypeOptions = computed(() => {
+ return (this.activityTypes() ?? [])
+ .map((activityType) => {
+ return { key: activityType, value: translate(activityType) };
+ })
+ .sort((a, b) => {
+ return a.value.localeCompare(b.value);
+ });
+ });
+
protected readonly displayedColumns = computed(() => {
let columns = [
'select',
@@ -222,13 +232,6 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
private readonly notificationService = inject(NotificationService);
public constructor(private destroyRef: DestroyRef) {
- for (const type of Object.keys(ActivityType) as ActivityType[]) {
- this.activityTypesTranslationMap.set(
- ActivityType[type],
- translate(ActivityType[type])
- );
- }
-
addIcons({
alertCircleOutline,
calendarClearOutline,
@@ -386,13 +389,6 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
});
}
- public sortByValue(
- a: { key: ActivityType; value: string },
- b: { key: ActivityType; value: string }
- ) {
- return a.value.localeCompare(b.value);
- }
-
public toggleAllRows() {
if (this.areAllRowsSelected()) {
this.selectedRows.clear();
diff --git a/prisma/migrations/20260822000000_updated_indexes_of_order/migration.sql b/prisma/migrations/20260822000000_updated_indexes_of_order/migration.sql
new file mode 100644
index 000000000..b9eb5ee55
--- /dev/null
+++ b/prisma/migrations/20260822000000_updated_indexes_of_order/migration.sql
@@ -0,0 +1,17 @@
+-- DropIndex
+DROP INDEX "Order_symbolProfileId_idx";
+
+-- DropIndex
+DROP INDEX "Order_type_idx";
+
+-- DropIndex
+DROP INDEX "Order_userId_idx";
+
+-- CreateIndex
+CREATE INDEX "Order_symbolProfileId_date_idx" ON "Order"("symbolProfileId", "date");
+
+-- CreateIndex
+CREATE INDEX "Order_userId_date_idx" ON "Order"("userId", "date");
+
+-- CreateIndex
+CREATE INDEX "Order_userId_type_date_idx" ON "Order"("userId", "type", "date");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 41f1353c9..06ba23563 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -195,9 +195,9 @@ model Order {
@@index([accountId])
@@index([date])
- @@index([symbolProfileId])
- @@index([type])
- @@index([userId])
+ @@index([symbolProfileId, date])
+ @@index([userId, date])
+ @@index([userId, type, date])
}
model Platform {