Migrated users table in Admin Control to mat-table (#2469)

* Migrated users table in Admin Control to mat-table

* Update changelog
pull/2453/head^2
LIYANMUBARAK 1 year ago committed by GitHub
parent 73ac4b4197
commit c9878c9050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a chart to the account detail dialog - Added a chart to the account detail dialog
### Changed
- Changed the users table in the admin control panel to an `@angular/material` data table
## 2.12.0 - 2023-10-17 ## 2.12.0 - 2023-10-17
### Added ### Added

@ -1,4 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
@ -20,13 +21,15 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-users.html' templateUrl: './admin-users.html'
}) })
export class AdminUsersComponent implements OnDestroy, OnInit { export class AdminUsersComponent implements OnDestroy, OnInit {
public dataSource: MatTableDataSource<AdminData['users'][0]> =
new MatTableDataSource();
public defaultDateFormat: string; public defaultDateFormat: string;
public displayedColumns: string[] = [];
public getEmojiFlag = getEmojiFlag; public getEmojiFlag = getEmojiFlag;
public hasPermissionForSubscription: boolean; public hasPermissionForSubscription: boolean;
public hasPermissionToImpersonateAllUsers: boolean; public hasPermissionToImpersonateAllUsers: boolean;
public info: InfoItem; public info: InfoItem;
public user: User; public user: User;
public users: AdminData['users'];
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -44,6 +47,29 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
permissions.enableSubscription permissions.enableSubscription
); );
if (this.hasPermissionForSubscription) {
this.displayedColumns = [
'index',
'user',
'country',
'registration',
'accounts',
'activities',
'engagementPerDay',
'lastRequest',
'actions'
];
} else {
this.displayedColumns = [
'index',
'user',
'registration',
'accounts',
'activities',
'actions'
];
}
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
@ -118,7 +144,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
.fetchAdminData() .fetchAdminData()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ users }) => { .subscribe(({ users }) => {
this.users = users; this.dataSource = new MatTableDataSource(users);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

@ -2,136 +2,232 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="users"> <div class="users">
<table class="gf-table"> <table class="gf-table" mat-table [dataSource]="dataSource">
<thead> <ng-container matColumnDef="index">
<tr class="mat-mdc-header-row"> <th
<th class="mat-mdc-header-cell px-1 py-2 text-right">#</th> *matHeaderCellDef
<th class="mat-mdc-header-cell px-1 py-2" i18n>User</th> class="mat-mdc-header-cell px-1 py-2 text-right"
<th mat-header-cell
*ngIf="hasPermissionForSubscription" >
class="mat-mdc-header-cell px-1 py-2" #
> </th>
<ng-container i18n>Country</ng-container> <td
</th> *matCellDef="let element; let i=index"
<th class="mat-mdc-header-cell px-1 py-2"> class="mat-mdc-cell px-1 py-2 text-right"
<ng-container i18n>Registration</ng-container> mat-cell
</th> >
<th class="mat-mdc-header-cell px-1 py-2 text-right"> {{ i + 1 }}
<ng-container i18n>Accounts</ng-container> </td>
</th> </ng-container>
<th class="mat-mdc-header-cell px-1 py-2 text-right">
<ng-container i18n>Activities</ng-container> <ng-container matColumnDef="user">
</th> <th
<th *matHeaderCellDef
*ngIf="hasPermissionForSubscription" class="mat-mdc-header-cell px-1 py-2"
class="mat-mdc-header-cell px-1 py-2 text-right" i18n
> mat-header-cell
<ng-container i18n>Engagement per Day</ng-container> >
</th> User
<th </th>
*ngIf="hasPermissionForSubscription" <td
class="mat-mdc-header-cell px-1 py-2" *matCellDef="let element"
i18n class="mat-mdc-cell px-1 py-2"
> mat-cell
Last Request >
</th> <div class="d-flex align-items-center">
<th class="mat-mdc-header-cell px-1 py-2"></th> <span class="d-none d-sm-inline-block text-monospace"
</tr> >{{ element.id }}</span
</thead> >
<tbody> <span class="d-inline-block d-sm-none text-monospace"
<tr >{{ (element.id | slice:0:5) + '...' }}</span
*ngFor="let userItem of users; let i = index"
class="mat-mdc-row"
>
<td class="mat-mdc-cell px-1 py-2 text-right">{{ i + 1 }}</td>
<td class="mat-mdc-cell px-1 py-2">
<div class="d-flex align-items-center">
<span class="d-none d-sm-inline-block text-monospace"
>{{ userItem.id }}</span
>
<span class="d-inline-block d-sm-none text-monospace"
>{{ (userItem.id | slice:0:5) + '...' }}</span
>
<gf-premium-indicator
*ngIf="userItem?.subscription?.type === 'Premium'"
class="ml-1"
[enableLink]="false"
[title]="'Expires ' + formatDistanceToNow(userItem.subscription.expiresAt) + ' (' + (userItem.subscription.expiresAt | date: defaultDateFormat) + ')'"
></gf-premium-indicator>
</div>
</td>
<td
*ngIf="hasPermissionForSubscription"
class="mat-mdc-cell px-1 py-2"
>
<span class="h5" [title]="userItem.country"
>{{ getEmojiFlag(userItem.country) }}</span
> >
</td> <gf-premium-indicator
<td class="mat-mdc-cell px-1 py-2"> *ngIf="element?.subscription?.type === 'Premium'"
{{ formatDistanceToNow(userItem.createdAt) }} class="ml-1"
</td> [enableLink]="false"
<td class="mat-mdc-cell px-1 py-2 text-right"> [title]="'Expires ' + formatDistanceToNow(element.subscription.expiresAt) + ' (' + (element.subscription.expiresAt | date: defaultDateFormat) + ')'"
<gf-value ></gf-premium-indicator>
class="d-inline-block justify-content-end" </div>
[locale]="user?.settings?.locale" </td>
[value]="userItem.accountCount" </ng-container>
></gf-value>
</td> <ng-container
<td class="mat-mdc-cell px-1 py-2 text-right"> *ngIf="hasPermissionForSubscription"
<gf-value matColumnDef="country"
class="d-inline-block justify-content-end" >
[locale]="user?.settings?.locale" <th
[value]="userItem.transactionCount" *matHeaderCellDef
></gf-value> class="mat-mdc-header-cell px-1 py-2"
</td> mat-header-cell
<td >
*ngIf="hasPermissionForSubscription" <ng-container i18n>Country</ng-container>
class="mat-mdc-cell px-1 py-2 text-right" </th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2"
mat-cell
>
<span class="h5" [title]="element.country"
>{{ getEmojiFlag(element.country) }}</span
> >
<gf-value </td>
class="d-inline-block justify-content-end" </ng-container>
[locale]="user?.settings?.locale"
[precision]="0" <ng-container matColumnDef="registration">
[value]="userItem.engagement" <th
></gf-value> *matHeaderCellDef
</td> class="mat-mdc-header-cell px-1 py-2"
<td mat-header-cell
*ngIf="hasPermissionForSubscription" >
class="mat-mdc-cell px-1 py-2" <ng-container i18n>Registration</ng-container>
</th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2"
mat-cell
>
{{ formatDistanceToNow(element.createdAt) }}
</td>
</ng-container>
<ng-container matColumnDef="accounts">
<th
*matHeaderCellDef
class="mat-mdc-header-cell px-1 py-2 text-right"
mat-header-cell
>
<ng-container i18n>Accounts</ng-container>
</th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2 text-right"
mat-cell
>
<gf-value
class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale"
[value]="element.accountCount"
></gf-value>
</td>
</ng-container>
<ng-container matColumnDef="activities">
<th
*matHeaderCellDef
class="mat-mdc-header-cell px-1 py-2 text-right"
mat-header-cell
>
<ng-container i18n>Activities</ng-container>
</th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2 text-right"
mat-cell
>
<gf-value
class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale"
[value]="element.transactionCount"
></gf-value>
</td>
</ng-container>
<ng-container
*ngIf="hasPermissionForSubscription"
matColumnDef="engagementPerDay"
>
<th
*matHeaderCellDef
class="mat-mdc-header-cell px-1 py-2 text-right"
mat-header-cell
>
<ng-container i18n>Engagement per Day</ng-container>
</th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2 text-right"
mat-cell
>
<gf-value
class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale"
[precision]="0"
[value]="element.engagement"
></gf-value>
</td>
</ng-container>
<ng-container
*ngIf="hasPermissionForSubscription"
matColumnDef="lastRequest"
>
<th
*matHeaderCellDef
class="mat-mdc-header-cell px-1 py-2"
i18n
mat-header-cell
>
Last Request
</th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2"
mat-cell
>
{{ formatDistanceToNow(element.lastActivity) }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th
*matHeaderCellDef
class="mat-mdc-header-cell px-1 py-2"
mat-header-cell
></th>
<td
*matCellDef="let element"
class="mat-mdc-cell px-1 py-2"
mat-cell
>
<button
class="mx-1 no-min-width px-2"
mat-button
[matMenuTriggerFor]="userMenu"
(click)="$event.stopPropagation()"
> >
{{ formatDistanceToNow(userItem.lastActivity) }} <ion-icon name="ellipsis-horizontal"></ion-icon>
</td> </button>
<td class="mat-mdc-cell px-1 py-2"> <mat-menu #userMenu="matMenu" xPosition="before">
<button
*ngIf="hasPermissionToImpersonateAllUsers"
mat-menu-item
(click)="onImpersonateUser(element.id)"
>
<ion-icon class="mr-2" name="contract-outline"></ion-icon>
<span i18n>Impersonate User</span>
</button>
<button <button
class="mx-1 no-min-width px-2" mat-menu-item
mat-button [disabled]="element.id === user?.id"
[matMenuTriggerFor]="userMenu" (click)="onDeleteUser(element.id)"
(click)="$event.stopPropagation()"
> >
<ion-icon name="ellipsis-horizontal"></ion-icon> <ion-icon class="mr-2" name="trash-outline"></ion-icon>
<span i18n>Delete User</span>
</button> </button>
<mat-menu #userMenu="matMenu" xPosition="before"> </mat-menu>
<button </td>
*ngIf="hasPermissionToImpersonateAllUsers" </ng-container>
mat-menu-item
(click)="onImpersonateUser(userItem.id)" <tr
> *matHeaderRowDef="displayedColumns"
<ion-icon class="mr-2" name="contract-outline"></ion-icon> class="mat-mdc-header-row"
<span i18n>Impersonate User</span> mat-header-row
</button> ></tr>
<button <tr
mat-menu-item *matRowDef="let row; columns: displayedColumns"
[disabled]="userItem.id === user?.id" class="mat-mdc-row"
(click)="onDeleteUser(userItem.id)" mat-row
> ></tr>
<ion-icon class="mr-2" name="trash-outline"></ion-icon>
<span i18n>Delete User</span>
</button>
</mat-menu>
</td>
</tr>
</tbody>
</table> </table>
</div> </div>
</div> </div>

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatTableModule } from '@angular/material/table';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
import { GfValueModule } from '@ghostfolio/ui/value'; import { GfValueModule } from '@ghostfolio/ui/value';
@ -15,7 +16,8 @@ import { AdminUsersComponent } from './admin-users.component';
GfPremiumIndicatorModule, GfPremiumIndicatorModule,
GfValueModule, GfValueModule,
MatButtonModule, MatButtonModule,
MatMenuModule MatMenuModule,
MatTableModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })

Loading…
Cancel
Save