|
|
@ -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,15 @@ 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 { EMPTY, Observable, of, Subject } from 'rxjs'; |
|
|
import { map, switchMap } from 'rxjs/operators'; |
|
|
import { |
|
|
|
|
|
catchError, |
|
|
|
|
|
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,72 +40,102 @@ 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'); |
|
|
|
|
|
|
|
|
|
|
|
const activity$: Observable<Activity | undefined> = activityId |
|
|
|
|
|
? this.dataService.fetchActivity(activityId) |
|
|
|
|
|
: of(undefined); |
|
|
|
|
|
|
|
|
|
|
|
this.userService |
|
|
// The router reuses this component when only the activity id changes, so
|
|
|
.get() |
|
|
// the parameters are observed instead of read from the snapshot once
|
|
|
|
|
|
this.route.paramMap |
|
|
.pipe( |
|
|
.pipe( |
|
|
switchMap((user) => { |
|
|
map((paramMap) => { |
|
|
return activity$.pipe( |
|
|
return paramMap.get('activityId'); |
|
|
map((activity) => { |
|
|
}), |
|
|
return { activity, user }; |
|
|
distinctUntilChanged(), |
|
|
|
|
|
tap(() => { |
|
|
|
|
|
this.closeDialog(); |
|
|
|
|
|
}), |
|
|
|
|
|
switchMap((activityId) => { |
|
|
|
|
|
const activity$: Observable<Activity | undefined> = activityId |
|
|
|
|
|
? this.dataService.fetchActivity(activityId) |
|
|
|
|
|
: of(undefined); |
|
|
|
|
|
|
|
|
|
|
|
return this.userService.get().pipe( |
|
|
|
|
|
switchMap((user) => { |
|
|
|
|
|
return activity$.pipe( |
|
|
|
|
|
map((activity) => { |
|
|
|
|
|
return { activity, user }; |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
}), |
|
|
|
|
|
catchError(() => { |
|
|
|
|
|
this.navigateBack(); |
|
|
|
|
|
|
|
|
|
|
|
return EMPTY; |
|
|
}) |
|
|
}) |
|
|
); |
|
|
); |
|
|
}), |
|
|
}), |
|
|
takeUntilDestroyed(this.destroyRef) |
|
|
takeUntilDestroyed(this.destroyRef) |
|
|
) |
|
|
) |
|
|
.subscribe({ |
|
|
.subscribe(({ activity, user }) => { |
|
|
error: () => { |
|
|
if (mode === 'update') { |
|
|
this.navigateBack(); |
|
|
if ( |
|
|
}, |
|
|
!activity || |
|
|
next: ({ activity, user }) => { |
|
|
!hasPermission(user?.permissions, permissions.updateActivity) || |
|
|
if (mode === 'update') { |
|
|
this.isReadOnlyMode(user) |
|
|
if (!activity) { |
|
|
) { |
|
|
this.navigateBack(); |
|
|
this.navigateBack(); |
|
|
|
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.openDialog({ activity, user, isUpdate: true }); |
|
|
|
|
|
|
|
|
|
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (mode === 'clone' && !activity) { |
|
|
this.openDialog({ activity, user, isUpdate: true }); |
|
|
this.navigateBack(); |
|
|
|
|
|
|
|
|
|
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (mode === 'clone' && !activity) { |
|
|
|
|
|
this.navigateBack(); |
|
|
|
|
|
|
|
|
this.openDialog({ |
|
|
return; |
|
|
user, |
|
|
|
|
|
activity: { |
|
|
|
|
|
...activity, |
|
|
|
|
|
accountId: activity?.accountId, |
|
|
|
|
|
assetProfile: activity?.assetProfile ?? null, |
|
|
|
|
|
date: new Date(), |
|
|
|
|
|
fee: 0, |
|
|
|
|
|
id: null, |
|
|
|
|
|
type: activity?.type ?? 'BUY', |
|
|
|
|
|
unitPrice: null |
|
|
|
|
|
}, |
|
|
|
|
|
isUpdate: false |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Cloning creates a new activity as well
|
|
|
|
|
|
if ( |
|
|
|
|
|
!hasPermission(user?.permissions, permissions.createActivity) || |
|
|
|
|
|
this.isReadOnlyMode(user) |
|
|
|
|
|
) { |
|
|
|
|
|
this.navigateBack(); |
|
|
|
|
|
|
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.openDialog({ |
|
|
|
|
|
user, |
|
|
|
|
|
activity: { |
|
|
|
|
|
...activity, |
|
|
|
|
|
accountId: activity?.accountId, |
|
|
|
|
|
assetProfile: activity?.assetProfile ?? null, |
|
|
|
|
|
date: new Date(), |
|
|
|
|
|
fee: 0, |
|
|
|
|
|
id: null, |
|
|
|
|
|
type: activity?.type ?? 'BUY', |
|
|
|
|
|
unitPrice: null |
|
|
|
|
|
}, |
|
|
|
|
|
isUpdate: false |
|
|
|
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -105,6 +144,23 @@ 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) { |
|
|
|
|
|
return ( |
|
|
|
|
|
!!this.impersonationStorageService.getId() || |
|
|
|
|
|
!!user?.settings?.isRestrictedView |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private navigateBack() { |
|
|
private navigateBack() { |
|
|
@ -124,7 +180,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 +193,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(); |
|
|
|