Browse Source

Feature/fetch user data on demand in user detail dialog

- Pass only userId when opening the user detail dialog from admin users
- Load user details inside the dialog via GET /api/v1/admin/user/:id
pull/5923/head
David Requeno 2 months ago
parent
commit
90ae908767
  1. 13
      apps/client/src/app/components/admin-users/admin-users.component.ts
  2. 4
      apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts
  3. 39
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
  4. 22
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html
  5. 6
      apps/client/src/app/services/admin.service.ts

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

@ -1,4 +1,3 @@
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { import {
getDateFnsLocale, getDateFnsLocale,
@ -56,6 +55,7 @@ import { NotificationService } from '../../core/notification/notification.servic
import { AdminService } from '../../services/admin.service'; import { AdminService } from '../../services/admin.service';
import { DataService } from '../../services/data.service'; import { DataService } from '../../services/data.service';
import { ImpersonationStorageService } from '../../services/impersonation-storage.service'; import { ImpersonationStorageService } from '../../services/impersonation-storage.service';
import { TokenStorageService } from '../../services/token-storage.service';
import { UserService } from '../../services/user/user.service'; import { UserService } from '../../services/user/user.service';
import { UserDetailDialogParams } from '../user-detail-dialog/interfaces/interfaces'; import { UserDetailDialogParams } from '../user-detail-dialog/interfaces/interfaces';
import { GfUserDetailDialogComponent } from '../user-detail-dialog/user-detail-dialog.component'; import { GfUserDetailDialogComponent } from '../user-detail-dialog/user-detail-dialog.component';
@ -283,22 +283,13 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
} }
private openUserDetailDialog(aUserId: string) { private openUserDetailDialog(aUserId: string) {
const userData = this.dataSource.data.find(({ id }) => {
return id === aUserId;
});
if (!userData) {
this.router.navigate(['.'], { relativeTo: this.route });
return;
}
const dialogRef = this.dialog.open< const dialogRef = this.dialog.open<
GfUserDetailDialogComponent, GfUserDetailDialogComponent,
UserDetailDialogParams UserDetailDialogParams
>(GfUserDetailDialogComponent, { >(GfUserDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
userData, userId: aUserId,
deviceType: this.deviceType, deviceType: this.deviceType,
hasPermissionForSubscription: this.hasPermissionForSubscription, hasPermissionForSubscription: this.hasPermissionForSubscription,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale

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

@ -1,8 +1,6 @@
import { AdminUsersResponse } from '@ghostfolio/common/interfaces';
export interface UserDetailDialogParams { export interface UserDetailDialogParams {
deviceType: string; deviceType: string;
hasPermissionForSubscription: boolean; hasPermissionForSubscription: boolean;
locale: string; locale: string;
userData: AdminUsersResponse['users'][0]; userId: string;
} }

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

@ -1,5 +1,4 @@
import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { AdminUserResponse } from '@ghostfolio/common/interfaces';
import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@ -7,14 +6,20 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
OnInit,
Inject, Inject,
OnDestroy OnDestroy
} from '@angular/core'; } from '@angular/core';
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 { Subject } from 'rxjs'; import { Subject, EMPTY } from 'rxjs';
import { catchError, finalize, takeUntil } from 'rxjs/operators';
import { NotificationService } from '../../core/notification/notification.service';
import { AdminService } from '../../services/admin.service';
import { GfDialogFooterComponent } from '../dialog-footer/dialog-footer.component';
import { GfDialogHeaderComponent } from '../dialog-header/dialog-header.component';
import { UserDetailDialogParams } from './interfaces/interfaces'; import { UserDetailDialogParams } from './interfaces/interfaces';
@Component({ @Component({
@ -33,14 +38,38 @@ import { UserDetailDialogParams } from './interfaces/interfaces';
styleUrls: ['./user-detail-dialog.component.scss'], styleUrls: ['./user-detail-dialog.component.scss'],
templateUrl: './user-detail-dialog.html' templateUrl: './user-detail-dialog.html'
}) })
export class GfUserDetailDialogComponent implements OnDestroy { export class GfUserDetailDialogComponent implements OnInit, OnDestroy {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public isLoading = true;
public user: AdminUserResponse;
public constructor( public constructor(
@Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams,
public dialogRef: MatDialogRef<GfUserDetailDialogComponent> public dialogRef: MatDialogRef<GfUserDetailDialogComponent>,
private adminService: AdminService,
private notificationService: NotificationService
) {} ) {}
public ngOnInit(): void {
this.adminService
.fetchUserById(this.data.userId)
.pipe(
takeUntil(this.unsubscribeSubject),
finalize(() => (this.isLoading = false)),
catchError(() => {
this.notificationService.alert({
title: $localize`User`,
message: $localize`Unable to load user`
});
this.dialogRef.close();
return EMPTY;
})
)
.subscribe((user) => {
this.user = user;
});
}
public onClose() { public onClose() {
this.dialogRef.close(); this.dialogRef.close();
} }

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

@ -6,11 +6,10 @@
<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">
@if (user) {
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value i18n size="medium" [value]="data.userData.id"> <gf-value i18n size="medium" [value]="user.id"> User ID </gf-value>
User ID
</gf-value>
</div> </div>
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value <gf-value
@ -18,7 +17,7 @@
size="medium" size="medium"
[isDate]="true" [isDate]="true"
[locale]="data.locale" [locale]="data.locale"
[value]="data.userData.createdAt" [value]="user.createdAt"
> >
Registration Date Registration Date
</gf-value> </gf-value>
@ -27,13 +26,11 @@
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value i18n size="medium" [value]="data.userData.role"> <gf-value i18n size="medium" [value]="user.role"> Role </gf-value>
Role
</gf-value>
</div> </div>
@if (data.hasPermissionForSubscription) { @if (data.hasPermissionForSubscription) {
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value i18n size="medium" [value]="data.userData.country"> <gf-value i18n size="medium" [value]="user.country">
Country Country
</gf-value> </gf-value>
</div> </div>
@ -46,7 +43,7 @@
i18n i18n
size="medium" size="medium"
[locale]="data.locale" [locale]="data.locale"
[value]="data.userData.accountCount" [value]="user.accountCount"
> >
Accounts Accounts
</gf-value> </gf-value>
@ -56,7 +53,7 @@
i18n i18n
size="medium" size="medium"
[locale]="data.locale" [locale]="data.locale"
[value]="data.userData.activityCount" [value]="user.activityCount"
> >
Activities Activities
</gf-value> </gf-value>
@ -71,7 +68,7 @@
size="medium" size="medium"
[locale]="data.locale" [locale]="data.locale"
[precision]="0" [precision]="0"
[value]="data.userData.engagement" [value]="user.engagement"
> >
Engagement per Day Engagement per Day
</gf-value> </gf-value>
@ -81,13 +78,14 @@
i18n i18n
size="medium" size="medium"
[locale]="data.locale" [locale]="data.locale"
[value]="data.userData.dailyApiRequests" [value]="user.dailyApiRequests"
> >
API Requests Today API Requests Today
</gf-value> </gf-value>
</div> </div>
</div> </div>
} }
}
</div> </div>
</div> </div>

6
apps/client/src/app/services/admin.service.ts

@ -157,6 +157,12 @@ export class AdminService {
return this.http.get<AdminUsersResponse>('/api/v1/admin/user', { params }); return this.http.get<AdminUsersResponse>('/api/v1/admin/user', { params });
} }
public fetchUserById(id: string) {
return this.http.get<
import('@ghostfolio/common/interfaces').AdminUserResponse
>(`/api/v1/admin/user/${id}`);
}
public gather7Days() { public gather7Days() {
return this.http.post<void>('/api/v1/admin/gather', {}); return this.http.post<void>('/api/v1/admin/gather', {});
} }

Loading…
Cancel
Save