Feature/clone or edit activity from holding detail dialog (#3644)
* Clone or edit activity from holding detail dialog * Update changelog
This commit is contained in:
parent
4410040a14
commit
dc1948016f
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added support to clone an activity from the holding detail dialog (experimental)
|
||||
- Added support to edit an activity from the holding detail dialog (experimental)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the caching of the benchmarks in the markets overview by returning cached data and recalculating in the background when it expires
|
||||
|
@ -36,7 +36,7 @@ import { parseISO } from 'date-fns';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import { CreateOrderDto } from './create-order.dto';
|
||||
import { Activities } from './interfaces/activities.interface';
|
||||
import { Activities, Activity } from './interfaces/activities.interface';
|
||||
import { OrderService } from './order.service';
|
||||
import { UpdateOrderDto } from './update-order.dto';
|
||||
|
||||
@ -140,6 +140,38 @@ export class OrderController {
|
||||
return { activities, count };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||
public async getOrderById(
|
||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
|
||||
@Param('id') id: string
|
||||
): Promise<Activity> {
|
||||
const impersonationUserId =
|
||||
await this.impersonationService.validateImpersonationId(impersonationId);
|
||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||
|
||||
const { activities } = await this.orderService.getOrders({
|
||||
userCurrency,
|
||||
userId: impersonationUserId || this.request.user.id,
|
||||
withExcludedAccounts: true
|
||||
});
|
||||
|
||||
const activity = activities.find((activity) => {
|
||||
return activity.id === id;
|
||||
});
|
||||
|
||||
if (!activity) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.NOT_FOUND),
|
||||
StatusCodes.NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
@HasPermission(permissions.createOrder)
|
||||
@Post()
|
||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||
|
@ -255,6 +255,10 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
colorScheme: this.user?.settings?.colorScheme,
|
||||
deviceType: this.deviceType,
|
||||
hasImpersonationId: this.hasImpersonationId,
|
||||
hasPermissionToCreateOrder:
|
||||
!this.hasImpersonationId &&
|
||||
hasPermission(this.user?.permissions, permissions.createOrder) &&
|
||||
!this.user?.settings?.isRestrictedView,
|
||||
hasPermissionToReportDataGlitch: hasPermission(
|
||||
this.user?.permissions,
|
||||
permissions.reportDataGlitch
|
||||
@ -262,7 +266,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
hasPermissionToUpdateOrder:
|
||||
!this.hasImpersonationId &&
|
||||
hasPermission(this.user?.permissions, permissions.updateOrder) &&
|
||||
!user?.settings?.isRestrictedView,
|
||||
!this.user?.settings?.isRestrictedView,
|
||||
locale: this.user?.settings?.locale
|
||||
},
|
||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
||||
|
@ -48,6 +48,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { SortDirection } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { Router } from '@angular/router';
|
||||
import { Account, Tag } from '@prisma/client';
|
||||
import { format, isSameMonth, isToday, parseISO } from 'date-fns';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
@ -141,6 +142,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
||||
public dialogRef: MatDialogRef<GfHoldingDetailDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: HoldingDetailDialogParams,
|
||||
private formBuilder: FormBuilder,
|
||||
private router: Router,
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
@ -424,6 +426,14 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
||||
this.tagInput.nativeElement.value = '';
|
||||
}
|
||||
|
||||
public onCloneActivity(aActivity: Activity) {
|
||||
this.router.navigate(['/portfolio', 'activities'], {
|
||||
queryParams: { activityId: aActivity.id, createDialog: true }
|
||||
});
|
||||
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public onClose() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
@ -456,6 +466,14 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
public onUpdateActivity(aActivity: Activity) {
|
||||
this.router.navigate(['/portfolio', 'activities'], {
|
||||
queryParams: { activityId: aActivity.id, editDialog: true }
|
||||
});
|
||||
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
|
@ -346,12 +346,19 @@
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data.locale"
|
||||
[showActions]="false"
|
||||
[showActions]="
|
||||
!data.hasImpersonationId &&
|
||||
data.hasPermissionToCreateOrder &&
|
||||
user?.settings?.isExperimentalFeatures &&
|
||||
!user?.settings?.isRestrictedView
|
||||
"
|
||||
[showNameColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(activityToClone)="onCloneActivity($event)"
|
||||
(activityToUpdate)="onUpdateActivity($event)"
|
||||
(export)="onExport()"
|
||||
/>
|
||||
</mat-tab>
|
||||
|
@ -8,6 +8,7 @@ export interface HoldingDetailDialogParams {
|
||||
dataSource: DataSource;
|
||||
deviceType: string;
|
||||
hasImpersonationId: boolean;
|
||||
hasPermissionToCreateOrder: boolean;
|
||||
hasPermissionToReportDataGlitch: boolean;
|
||||
hasPermissionToUpdateOrder: boolean;
|
||||
locale: string;
|
||||
|
@ -16,7 +16,6 @@ import { PageEvent } from '@angular/material/paginator';
|
||||
import { Sort, SortDirection } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Order as OrderModel } from '@prisma/client';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
@ -63,14 +62,24 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((params) => {
|
||||
if (params['createDialog']) {
|
||||
this.openCreateActivityDialog();
|
||||
if (params['activityId']) {
|
||||
this.dataService
|
||||
.fetchActivity(params['activityId'])
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((activity) => {
|
||||
this.openCreateActivityDialog(activity);
|
||||
});
|
||||
} else {
|
||||
this.openCreateActivityDialog();
|
||||
}
|
||||
} else if (params['editDialog']) {
|
||||
if (this.dataSource && params['activityId']) {
|
||||
const activity = this.dataSource.data.find(({ id }) => {
|
||||
return id === params['activityId'];
|
||||
});
|
||||
|
||||
this.openUpdateActivityDialog(activity);
|
||||
if (params['activityId']) {
|
||||
this.dataService
|
||||
.fetchActivity(params['activityId'])
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((activity) => {
|
||||
this.openUpdateActivityDialog(activity);
|
||||
});
|
||||
} else {
|
||||
this.router.navigate(['.'], { relativeTo: this.route });
|
||||
}
|
||||
@ -242,7 +251,7 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
||||
this.fetchActivities();
|
||||
}
|
||||
|
||||
public onUpdateActivity(aActivity: OrderModel) {
|
||||
public onUpdateActivity(aActivity: Activity) {
|
||||
this.router.navigate([], {
|
||||
queryParams: { activityId: aActivity.id, editDialog: true }
|
||||
});
|
||||
|
@ -4,7 +4,10 @@ import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto
|
||||
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
|
||||
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||
import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
Activities,
|
||||
Activity
|
||||
} from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||
import { PortfolioHoldingDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-holding-detail.interface';
|
||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||
@ -212,6 +215,17 @@ export class DataService {
|
||||
);
|
||||
}
|
||||
|
||||
public fetchActivity(aActivityId: string) {
|
||||
return this.http.get<Activity>(`/api/v1/order/${aActivityId}`).pipe(
|
||||
map((activity) => {
|
||||
activity.createdAt = parseISO(<string>(<unknown>activity.createdAt));
|
||||
activity.date = parseISO(<string>(<unknown>activity.date));
|
||||
|
||||
return activity;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public fetchDividends({
|
||||
filters,
|
||||
groupBy = 'month',
|
||||
|
Loading…
x
Reference in New Issue
Block a user