Browse Source

Align with account dialog host

pull/7472/head
Thomas Kaul 1 month ago
parent
commit
982324ec28
  1. 79
      apps/client/src/app/pages/portfolio/activities/activity-dialog-host/activity-dialog-host.component.ts

79
apps/client/src/app/pages/portfolio/activities/activity-dialog-host/activity-dialog-host.component.ts

@ -1,6 +1,8 @@
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos';
import { Activity, User } from '@ghostfolio/common/interfaces'; import { Activity, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
@ -16,8 +18,14 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Observable, of } from 'rxjs'; import { Observable, of, Subject } from 'rxjs';
import { map, switchMap } from 'rxjs/operators'; import {
distinctUntilChanged,
map,
switchMap,
takeUntil,
tap
} from 'rxjs/operators';
import { GfCreateOrUpdateActivityDialogComponent } from '../create-or-update-activity-dialog/create-or-update-activity-dialog.component'; import { GfCreateOrUpdateActivityDialogComponent } from '../create-or-update-activity-dialog/create-or-update-activity-dialog.component';
import { CreateOrUpdateActivityDialogParams } from '../create-or-update-activity-dialog/interfaces/interfaces'; import { CreateOrUpdateActivityDialogParams } from '../create-or-update-activity-dialog/interfaces/interfaces';
@ -31,31 +39,47 @@ import { ActivityDialogMode } from './types/activity-dialog-mode.type';
export class GfActivityDialogHostComponent implements OnDestroy, OnInit { export class GfActivityDialogHostComponent implements OnDestroy, OnInit {
private dialogRef: MatDialogRef<GfCreateOrUpdateActivityDialogComponent>; private dialogRef: MatDialogRef<GfCreateOrUpdateActivityDialogComponent>;
private readonly dialogClosed = new Subject<void>();
private readonly dataService = inject(DataService); private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService); private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog); private readonly dialog = inject(MatDialog);
private readonly impersonationStorageService = inject(
ImpersonationStorageService
);
private readonly route = inject(ActivatedRoute); private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly userService = inject(UserService); private readonly userService = inject(UserService);
public ngOnInit() { public ngOnInit() {
const mode = this.route.snapshot.data.mode as ActivityDialogMode; const mode = this.route.snapshot.data.mode as ActivityDialogMode;
const activityId = this.route.snapshot.paramMap.get('activityId');
// The router reuses this component when only the activity id changes, so
// the parameters are observed instead of read from the snapshot once
this.route.paramMap
.pipe(
map((paramMap) => {
return paramMap.get('activityId');
}),
distinctUntilChanged(),
tap(() => {
this.closeDialog();
}),
switchMap((activityId) => {
const activity$: Observable<Activity | undefined> = activityId const activity$: Observable<Activity | undefined> = activityId
? this.dataService.fetchActivity(activityId) ? this.dataService.fetchActivity(activityId)
: of(undefined); : of(undefined);
this.userService return this.userService.get().pipe(
.get()
.pipe(
switchMap((user) => { switchMap((user) => {
return activity$.pipe( return activity$.pipe(
map((activity) => { map((activity) => {
return { activity, user }; return { activity, user };
}) })
); );
})
);
}), }),
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
) )
@ -65,7 +89,11 @@ export class GfActivityDialogHostComponent implements OnDestroy, OnInit {
}, },
next: ({ activity, user }) => { next: ({ activity, user }) => {
if (mode === 'update') { if (mode === 'update') {
if (!activity) { if (
!activity ||
!hasPermission(user?.permissions, permissions.updateActivity) ||
this.isReadOnlyMode(user)
) {
this.navigateBack(); this.navigateBack();
return; return;
@ -82,6 +110,16 @@ export class GfActivityDialogHostComponent implements OnDestroy, OnInit {
return; return;
} }
// Cloning creates a new activity as well
if (
!hasPermission(user?.permissions, permissions.createActivity) ||
this.isReadOnlyMode(user)
) {
this.navigateBack();
return;
}
this.openDialog({ this.openDialog({
user, user,
activity: { activity: {
@ -105,6 +143,25 @@ export class GfActivityDialogHostComponent implements OnDestroy, OnInit {
// be closed explicitly when leaving the route (for example via the browser // be closed explicitly when leaving the route (for example via the browser
// navigation) // navigation)
this.dialogRef?.close(); this.dialogRef?.close();
this.dialogClosed.complete();
}
private closeDialog() {
// Tear down the subscription of the dialog which is about to be replaced,
// so that its result is not mistaken for the user closing it
this.dialogClosed.next();
this.dialogRef?.close();
}
private isReadOnlyMode(user: User) {
// Mirrors the conditions under which the activities page offers the
// creation and the modification of an activity
return (
!!this.impersonationStorageService.getId() ||
!!user?.settings?.isRestrictedView
);
} }
private navigateBack() { private navigateBack() {
@ -124,7 +181,7 @@ export class GfActivityDialogHostComponent implements OnDestroy, OnInit {
}) { }) {
const deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; const deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
this.dialogRef = this.dialog.open< const dialogRef = this.dialog.open<
GfCreateOrUpdateActivityDialogComponent, GfCreateOrUpdateActivityDialogComponent,
CreateOrUpdateActivityDialogParams CreateOrUpdateActivityDialogParams
>(GfCreateOrUpdateActivityDialogComponent, { >(GfCreateOrUpdateActivityDialogComponent, {
@ -137,9 +194,11 @@ export class GfActivityDialogHostComponent implements OnDestroy, OnInit {
width: deviceType === 'mobile' ? '100vw' : '50rem' width: deviceType === 'mobile' ? '100vw' : '50rem'
}); });
this.dialogRef this.dialogRef = dialogRef;
dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntil(this.dialogClosed), takeUntilDestroyed(this.destroyRef))
.subscribe((result: CreateOrderDto | UpdateOrderDto | null) => { .subscribe((result: CreateOrderDto | UpdateOrderDto | null) => {
if (!result) { if (!result) {
this.navigateBack(); this.navigateBack();

Loading…
Cancel
Save