From bf39fd89d23b7236b38bb62c4607bd210e5e5e07 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 20 Jun 2026 09:56:47 +0200 Subject: [PATCH] Task/extend user detail dialog of admin control with subscriptions (#7078) Extend user detail dialog with subscriptions --- apps/api/src/app/admin/admin.service.ts | 18 ++++ .../user-detail-dialog.component.ts | 41 +++++++- .../user-detail-dialog.html | 96 +++++++++++++++++++ .../lib/interfaces/admin-user.interface.ts | 1 + 4 files changed, 154 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 46ab25e96..f08fb6a37 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -240,6 +240,24 @@ export class AdminService { throw new NotFoundException(`User with ID ${id} not found`); } + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + const subscriptions = await this.prismaService.subscription.findMany({ + orderBy: { + expiresAt: 'desc' + }, + where: { + userId: id + } + }); + + user.subscriptions = subscriptions.map((subscription) => { + return { + ...subscription, + price: subscription.price ?? 0 + }; + }); + } + return user; } diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts index 05001a6bb..7b008786d 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts @@ -1,5 +1,6 @@ +import { getSum } from '@ghostfolio/common/helper'; import { AdminUserResponse } from '@ghostfolio/common/interfaces'; -import { AdminService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { @@ -16,7 +17,11 @@ import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { IonIcon } from '@ionic/angular/standalone'; +import { Subscription } from '@prisma/client'; +import { Big } from 'big.js'; +import { differenceInDays } from 'date-fns'; import { addIcons } from 'ionicons'; import { ellipsisVertical } from 'ionicons/icons'; import { EMPTY } from 'rxjs'; @@ -35,7 +40,8 @@ import { IonIcon, MatButtonModule, MatDialogModule, - MatMenuModule + MatMenuModule, + MatTableModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-user-detail-dialog', @@ -43,18 +49,29 @@ import { templateUrl: './user-detail-dialog.html' }) export class GfUserDetailDialogComponent implements OnInit { + public baseCurrency: string; + public subscriptionsDataSource = new MatTableDataSource(); + public subscriptionsDisplayedColumns = [ + 'createdAt', + 'type', + 'price', + 'expiresAt' + ]; public user: AdminUserResponse; public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, + private dataService: DataService, private destroyRef: DestroyRef, public dialogRef: MatDialogRef< GfUserDetailDialogComponent, UserDetailDialogResult > ) { + this.baseCurrency = this.dataService.fetchInfo().baseCurrency; + addIcons({ ellipsisVertical }); @@ -74,6 +91,8 @@ export class GfUserDetailDialogComponent implements OnInit { .subscribe((user) => { this.user = user; + this.subscriptionsDataSource.data = this.user.subscriptions ?? []; + this.changeDetectorRef.markForCheck(); }); } @@ -85,6 +104,24 @@ export class GfUserDetailDialogComponent implements OnInit { }); } + public getSum() { + return getSum( + this.subscriptionsDataSource.data.map(({ price }) => { + return new Big(price); + }) + ).toNumber(); + } + + public getType({ createdAt, expiresAt, price }: Subscription) { + if (price) { + return $localize`Paid`; + } + + return differenceInDays(expiresAt, createdAt) <= 90 + ? $localize`Trial` + : $localize`Coupon`; + } + public onClose() { this.dialogRef.close(); } diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html index caf1679eb..df935c780 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -124,6 +124,102 @@ > + + @if (subscriptionsDataSource.data.length > 0) { +
+
+

Subscriptions

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Created + + + + Total + + Type + + {{ getType(element) }} + + Price + + + + + + Expires + + +
+
+
+
+ } } diff --git a/libs/common/src/lib/interfaces/admin-user.interface.ts b/libs/common/src/lib/interfaces/admin-user.interface.ts index 4cb02b16e..a7cda68a8 100644 --- a/libs/common/src/lib/interfaces/admin-user.interface.ts +++ b/libs/common/src/lib/interfaces/admin-user.interface.ts @@ -12,4 +12,5 @@ export interface AdminUser { provider: Provider; role: Role; subscription?: Subscription; + subscriptions?: Subscription[]; }