Browse Source

Task/extend user detail dialog of admin control with subscriptions (#7078)

Extend user detail dialog with subscriptions
pull/6999/head
Thomas Kaul 2 days ago
committed by GitHub
parent
commit
bf39fd89d2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      apps/api/src/app/admin/admin.service.ts
  2. 41
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
  3. 96
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html
  4. 1
      libs/common/src/lib/interfaces/admin-user.interface.ts

18
apps/api/src/app/admin/admin.service.ts

@ -240,6 +240,24 @@ export class AdminService {
throw new NotFoundException(`User with ID ${id} not found`); throw new NotFoundException(`User with ID ${id} not found`);
} }
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
const subscriptions = await this.prismaService.subscription.findMany({
orderBy: {
expiresAt: 'desc'
},
where: {
userId: id
}
});
user.subscriptions = subscriptions.map((subscription) => {
return {
...subscription,
price: subscription.price ?? 0
};
});
}
return user; return user;
} }

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

@ -1,5 +1,6 @@
import { getSum } from '@ghostfolio/common/helper';
import { AdminUserResponse } from '@ghostfolio/common/interfaces'; import { AdminUserResponse } from '@ghostfolio/common/interfaces';
import { AdminService } from '@ghostfolio/ui/services'; import { AdminService, DataService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
import { import {
@ -16,7 +17,11 @@ 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 { MatMenuModule } from '@angular/material/menu';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { Subscription } from '@prisma/client';
import { Big } from 'big.js';
import { differenceInDays } from 'date-fns';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { ellipsisVertical } from 'ionicons/icons'; import { ellipsisVertical } from 'ionicons/icons';
import { EMPTY } from 'rxjs'; import { EMPTY } from 'rxjs';
@ -35,7 +40,8 @@ import {
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,
MatMenuModule MatMenuModule,
MatTableModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-user-detail-dialog', selector: 'gf-user-detail-dialog',
@ -43,18 +49,29 @@ import {
templateUrl: './user-detail-dialog.html' templateUrl: './user-detail-dialog.html'
}) })
export class GfUserDetailDialogComponent implements OnInit { export class GfUserDetailDialogComponent implements OnInit {
public baseCurrency: string;
public subscriptionsDataSource = new MatTableDataSource<Subscription>();
public subscriptionsDisplayedColumns = [
'createdAt',
'type',
'price',
'expiresAt'
];
public user: AdminUserResponse; public user: AdminUserResponse;
public constructor( public constructor(
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams,
private dataService: DataService,
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
public dialogRef: MatDialogRef< public dialogRef: MatDialogRef<
GfUserDetailDialogComponent, GfUserDetailDialogComponent,
UserDetailDialogResult UserDetailDialogResult
> >
) { ) {
this.baseCurrency = this.dataService.fetchInfo().baseCurrency;
addIcons({ addIcons({
ellipsisVertical ellipsisVertical
}); });
@ -74,6 +91,8 @@ export class GfUserDetailDialogComponent implements OnInit {
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
this.subscriptionsDataSource.data = this.user.subscriptions ?? [];
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
@ -85,6 +104,24 @@ export class GfUserDetailDialogComponent implements OnInit {
}); });
} }
public getSum() {
return getSum(
this.subscriptionsDataSource.data.map(({ price }) => {
return new Big(price);
})
).toNumber();
}
public getType({ createdAt, expiresAt, price }: Subscription) {
if (price) {
return $localize`Paid`;
}
return differenceInDays(expiresAt, createdAt) <= 90
? $localize`Trial`
: $localize`Coupon`;
}
public onClose() { public onClose() {
this.dialogRef.close(); this.dialogRef.close();
} }

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

@ -124,6 +124,102 @@
> >
</div> </div>
</div> </div>
@if (subscriptionsDataSource.data.length > 0) {
<div class="row">
<div class="col">
<h2 class="h6" i18n>Subscriptions</h2>
<div class="overflow-x-auto">
<table
class="gf-table w-100"
mat-table
[dataSource]="subscriptionsDataSource"
>
<ng-container matColumnDef="createdAt">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Created</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<gf-value
[isDate]="true"
[locale]="data.locale"
[value]="element.createdAt"
/>
</td>
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>
Total
</td>
</ng-container>
<ng-container matColumnDef="type">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Type</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ getType(element) }}
</td>
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
</ng-container>
<ng-container matColumnDef="price">
<th *matHeaderCellDef class="px-1 text-right" mat-header-cell>
<ng-container i18n>Price</ng-container>
</th>
<td
*matCellDef="let element"
class="px-1 text-right"
mat-cell
>
<gf-value
class="d-inline-block justify-content-end"
[isCurrency]="true"
[locale]="data.locale"
[unit]="baseCurrency"
[value]="element.price"
/>
</td>
<td *matFooterCellDef class="px-1 text-right" mat-footer-cell>
<gf-value
class="d-inline-block justify-content-end"
[isCurrency]="true"
[locale]="data.locale"
[unit]="baseCurrency"
[value]="getSum()"
/>
</td>
</ng-container>
<ng-container matColumnDef="expiresAt">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Expires</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<gf-value
[isDate]="true"
[locale]="data.locale"
[value]="element.expiresAt"
/>
</td>
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
</ng-container>
<tr
*matHeaderRowDef="subscriptionsDisplayedColumns"
mat-header-row
></tr>
<tr
*matRowDef="let row; columns: subscriptionsDisplayedColumns"
mat-row
></tr>
<tr
*matFooterRowDef="subscriptionsDisplayedColumns"
mat-footer-row
></tr>
</table>
</div>
</div>
</div>
}
} }
</div> </div>
</div> </div>

1
libs/common/src/lib/interfaces/admin-user.interface.ts

@ -12,4 +12,5 @@ export interface AdminUser {
provider: Provider; provider: Provider;
role: Role; role: Role;
subscription?: Subscription; subscription?: Subscription;
subscriptions?: Subscription[];
} }

Loading…
Cancel
Save