From e27f258ac69e9d3134933b6701a24139503cb9fe Mon Sep 17 00:00:00 2001 From: Karel De Smet Date: Tue, 3 Feb 2026 20:23:35 +0100 Subject: [PATCH] Task/extend user detail dialog by actions menu (#6256) * Extend user detail dialog * Update changelog --- CHANGELOG.md | 1 + .../admin-users/admin-users.component.ts | 58 +++++++++++-------- .../interfaces/interfaces.ts | 1 + .../user-detail-dialog.component.ts | 25 ++++++-- .../user-detail-dialog.html | 40 +++++++++---- 5 files changed, 84 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a565308b..30cc42eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the ability to fetch top holdings for ETF and mutual fund assets from _Yahoo Finance_ - Added support for the impersonation mode in the endpoint `GET api/v1/account/:id/balances` +- Added an action menu to the user detail dialog in the users section of the admin control panel ### Changed diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 2ae3b1a57..d479f2037 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -57,7 +57,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { switchMap, takeUntil, tap } from 'rxjs/operators'; @Component({ imports: [ @@ -139,8 +139,25 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } - this.route.paramMap - .pipe(takeUntil(this.unsubscribeSubject)) + this.userService.stateChanged + .pipe( + takeUntil(this.unsubscribeSubject), + tap((state) => { + if (state?.user) { + this.user = state.user; + + this.defaultDateFormat = getDateFormatString( + this.user.settings.locale + ); + + this.hasPermissionToImpersonateAllUsers = hasPermission( + this.user.permissions, + permissions.impersonateAllUsers + ); + } + }), + switchMap(() => this.route.paramMap) + ) .subscribe((params) => { const userId = params.get('userId'); @@ -149,23 +166,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } }); - this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((state) => { - if (state?.user) { - this.user = state.user; - - this.defaultDateFormat = getDateFormatString( - this.user.settings.locale - ); - - this.hasPermissionToImpersonateAllUsers = hasPermission( - this.user.permissions, - permissions.impersonateAllUsers - ); - } - }); - addIcons({ contractOutline, ellipsisHorizontal, @@ -208,10 +208,13 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { .deleteUser(aId) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { - this.fetchUsers(); + this.router.navigate(['..'], { relativeTo: this.route }); }); }, confirmType: ConfirmationDialogType.Warn, + discardFn: () => { + this.router.navigate(['..'], { relativeTo: this.route }); + }, title: $localize`Do you really want to delete this user?` }); } @@ -293,6 +296,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { >(GfUserDetailDialogComponent, { autoFocus: false, data: { + currentUserId: this.user?.id, deviceType: this.deviceType, hasPermissionForSubscription: this.hasPermissionForSubscription, locale: this.user?.settings?.locale, @@ -305,10 +309,14 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { dialogRef .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(() => { - this.router.navigate( - internalRoutes.adminControl.subRoutes.users.routerLink - ); + .subscribe((data) => { + if (data?.action === 'delete' && data?.userId) { + this.onDeleteUser(data.userId); + } else { + this.router.navigate( + internalRoutes.adminControl.subRoutes.users.routerLink + ); + } }); } } diff --git a/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts index b922e7a54..ed46e8a02 100644 --- a/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts @@ -1,4 +1,5 @@ export interface UserDetailDialogParams { + currentUserId: string; deviceType: string; hasPermissionForSubscription: boolean; locale: string; 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 cdf977058..6f7f4ead6 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,6 +1,4 @@ import { AdminUserResponse } from '@ghostfolio/common/interfaces'; -import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; -import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { AdminService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -16,6 +14,10 @@ import { 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 { IonIcon } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { ellipsisVertical } from 'ionicons/icons'; import { EMPTY, Subject } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; @@ -25,11 +27,11 @@ import { UserDetailDialogParams } from './interfaces/interfaces'; changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'd-flex flex-column h-100' }, imports: [ - GfDialogFooterComponent, - GfDialogHeaderComponent, GfValueComponent, + IonIcon, MatButtonModule, - MatDialogModule + MatDialogModule, + MatMenuModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-user-detail-dialog', @@ -46,7 +48,11 @@ export class GfUserDetailDialogComponent implements OnDestroy, OnInit { private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, public dialogRef: MatDialogRef - ) {} + ) { + addIcons({ + ellipsisVertical + }); + } public ngOnInit() { this.adminService @@ -66,6 +72,13 @@ export class GfUserDetailDialogComponent implements OnDestroy, OnInit { }); } + public deleteUser() { + this.dialogRef.close({ + action: 'delete', + userId: this.data.userId + }); + } + 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 60f6a2585..570dcf4d6 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 @@ -1,9 +1,28 @@ - - +
+ + + + +
@@ -103,7 +122,8 @@
- +
+ +