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