Browse Source

Merge ae9ec2fcf4 into 2520d0d961

pull/6256/merge
Karel De Smet 6 hours ago
committed by GitHub
parent
commit
f004e7dbf0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 55
      apps/client/src/app/components/admin-users/admin-users.component.ts
  3. 1
      apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts
  4. 25
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
  5. 40
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html

1
CHANGELOG.md

@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Upgraded `lodash` from version `4.17.21` to `4.17.23` - Upgraded `lodash` from version `4.17.21` to `4.17.23`
- Upgraded `Nx` from version `22.3.3` to `22.4.1` - Upgraded `Nx` from version `22.3.3` to `22.4.1`
- Upgraded `prettier` from version `3.8.0` to `3.8.1` - Upgraded `prettier` from version `3.8.0` to `3.8.1`
- Extended the user detail dialog with an actions menu
## 2.233.0 - 2026-01-23 ## 2.233.0 - 2026-01-23

55
apps/client/src/app/components/admin-users/admin-users.component.ts

@ -57,7 +57,7 @@ import {
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { switchMap, takeUntil, tap } from 'rxjs/operators';
@Component({ @Component({
imports: [ imports: [
@ -139,8 +139,25 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
]; ];
} }
this.route.paramMap this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .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) => { .subscribe((params) => {
const userId = params.get('userId'); 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({ addIcons({
contractOutline, contractOutline,
ellipsisHorizontal, ellipsisHorizontal,
@ -208,7 +208,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
.deleteUser(aId) .deleteUser(aId)
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
this.fetchUsers(); this.router.navigate(['..'], { relativeTo: this.route });
}); });
}, },
confirmType: ConfirmationDialogType.Warn, confirmType: ConfirmationDialogType.Warn,
@ -293,6 +293,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
>(GfUserDetailDialogComponent, { >(GfUserDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
currentUserId: this.user?.id,
deviceType: this.deviceType, deviceType: this.deviceType,
hasPermissionForSubscription: this.hasPermissionForSubscription, hasPermissionForSubscription: this.hasPermissionForSubscription,
locale: this.user?.settings?.locale, locale: this.user?.settings?.locale,
@ -305,10 +306,14 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
dialogRef dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe((data) => {
this.router.navigate( if (data?.action === 'delete' && data?.userId) {
internalRoutes.adminControl.subRoutes.users.routerLink this.onDeleteUser(data.userId);
); } else {
this.router.navigate(
internalRoutes.adminControl.subRoutes.users.routerLink
);
}
}); });
} }
} }

1
apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts

@ -1,4 +1,5 @@
export interface UserDetailDialogParams { export interface UserDetailDialogParams {
currentUserId: string;
deviceType: string; deviceType: string;
hasPermissionForSubscription: boolean; hasPermissionForSubscription: boolean;
locale: string; locale: string;

25
apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts

@ -1,6 +1,4 @@
import { AdminUserResponse } from '@ghostfolio/common/interfaces'; 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 { AdminService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
@ -16,6 +14,10 @@ import {
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatDialogModule } 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 { EMPTY, Subject } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators'; import { catchError, takeUntil } from 'rxjs/operators';
@ -25,11 +27,11 @@ import { UserDetailDialogParams } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column h-100' }, host: { class: 'd-flex flex-column h-100' },
imports: [ imports: [
GfDialogFooterComponent,
GfDialogHeaderComponent,
GfValueComponent, GfValueComponent,
IonIcon,
MatButtonModule, MatButtonModule,
MatDialogModule MatDialogModule,
MatMenuModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-user-detail-dialog', selector: 'gf-user-detail-dialog',
@ -46,7 +48,11 @@ export class GfUserDetailDialogComponent implements OnDestroy, OnInit {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams,
public dialogRef: MatDialogRef<GfUserDetailDialogComponent> public dialogRef: MatDialogRef<GfUserDetailDialogComponent>
) {} ) {
addIcons({
ellipsisVertical
});
}
public ngOnInit() { public ngOnInit() {
this.adminService 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() { public onClose() {
this.dialogRef.close(); this.dialogRef.close();
} }

40
apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html

@ -1,9 +1,28 @@
<gf-dialog-header <div class="d-flex justify-content-end">
position="center" <button
[deviceType]="data.deviceType" class="mx-1 no-min-width px-2"
(closeButtonClicked)="onClose()" mat-button
/> type="button"
[matMenuTriggerFor]="userDetailActionsMenu"
(click)="$event.stopPropagation()"
>
<ion-icon name="ellipsis-vertical" />
</button>
<mat-menu
#userDetailActionsMenu="matMenu"
class="no-max-width"
xPosition="before"
>
<button
mat-menu-item
type="button"
[disabled]="this.data.currentUserId === this.data.userId"
(click)="deleteUser()"
>
<ng-container i18n>Delete</ng-container>
</button>
</mat-menu>
</div>
<div class="flex-grow-1" mat-dialog-content> <div class="flex-grow-1" mat-dialog-content>
<div class="container p-0"> <div class="container p-0">
<div class="mb-3 row"> <div class="mb-3 row">
@ -103,7 +122,8 @@
</div> </div>
</div> </div>
<gf-dialog-footer <div class="justify-content-end" mat-dialog-actions>
[deviceType]="data.deviceType" <button mat-button type="button" (click)="onClose()">
(closeButtonClicked)="onClose()" <ng-container i18n>Close</ng-container>
/> </button>
</div>

Loading…
Cancel
Save