Feature/render tags in dialogs (#864)

* Render tags

* Update changelog
This commit is contained in:
Thomas Kaul 2022-04-25 22:37:34 +02:00 committed by GitHub
parent c61a415fb2
commit b7bbc029ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 73 additions and 20 deletions

View File

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added the tags to the create or edit transaction dialog
- Added the tags to the position detail dialog
### Changed ### Changed
- Changed the date to UTC in the data gathering service - Changed the date to UTC in the data gathering service

View File

@ -202,7 +202,8 @@ export class OrderService {
} }
}, },
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
SymbolProfile: true SymbolProfile: true,
tags: true
}, },
orderBy: { date: 'asc' } orderBy: { date: 'asc' }
}) })

View File

@ -1,5 +1,6 @@
import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface';
import { OrderWithAccount } from '@ghostfolio/common/types'; import { OrderWithAccount } from '@ghostfolio/common/types';
import { Tag } from '@prisma/client';
export interface PortfolioPositionDetail { export interface PortfolioPositionDetail {
averagePrice: number; averagePrice: number;
@ -16,6 +17,7 @@ export interface PortfolioPositionDetail {
orders: OrderWithAccount[]; orders: OrderWithAccount[];
quantity: number; quantity: number;
SymbolProfile: EnhancedSymbolProfile; SymbolProfile: EnhancedSymbolProfile;
tags: Tag[];
transactionCount: number; transactionCount: number;
value: number; value: number;
} }

View File

@ -46,7 +46,12 @@ import type {
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { AssetClass, DataSource, Type as TypeOfOrder } from '@prisma/client'; import {
AssetClass,
DataSource,
Tag,
Type as TypeOfOrder
} from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
import { import {
differenceInDays, differenceInDays,
@ -62,7 +67,7 @@ import {
subDays, subDays,
subYears subYears
} from 'date-fns'; } from 'date-fns';
import { isEmpty, sortBy } from 'lodash'; import { isEmpty, sortBy, uniqBy } from 'lodash';
import { import {
HistoricalDataContainer, HistoricalDataContainer,
@ -476,8 +481,11 @@ export class PortfolioService {
); );
}); });
let tags: Tag[] = [];
if (orders.length <= 0) { if (orders.length <= 0) {
return { return {
tags,
averagePrice: undefined, averagePrice: undefined,
firstBuyDate: undefined, firstBuyDate: undefined,
grossPerformance: undefined, grossPerformance: undefined,
@ -504,6 +512,8 @@ export class PortfolioService {
const portfolioOrders: PortfolioOrder[] = orders const portfolioOrders: PortfolioOrder[] = orders
.filter((order) => { .filter((order) => {
tags = tags.concat(order.tags);
return order.type === 'BUY' || order.type === 'SELL'; return order.type === 'BUY' || order.type === 'SELL';
}) })
.map((order) => ({ .map((order) => ({
@ -518,6 +528,8 @@ export class PortfolioService {
unitPrice: new Big(order.unitPrice) unitPrice: new Big(order.unitPrice)
})); }));
tags = uniqBy(tags, 'id');
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currency: positionCurrency, currency: positionCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
@ -626,6 +638,7 @@ export class PortfolioService {
netPerformance, netPerformance,
orders, orders,
SymbolProfile, SymbolProfile,
tags,
transactionCount, transactionCount,
averagePrice: averagePrice.toNumber(), averagePrice: averagePrice.toNumber(),
grossPerformancePercent: grossPerformancePercent:
@ -682,6 +695,7 @@ export class PortfolioService {
minPrice, minPrice,
orders, orders,
SymbolProfile, SymbolProfile,
tags,
averagePrice: 0, averagePrice: 0,
firstBuyDate: undefined, firstBuyDate: undefined,
grossPerformance: undefined, grossPerformance: undefined,

View File

@ -11,7 +11,7 @@ import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import { OrderWithAccount } from '@ghostfolio/common/types'; import { OrderWithAccount } from '@ghostfolio/common/types';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface'; import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { SymbolProfile } from '@prisma/client'; import { SymbolProfile, Tag } from '@prisma/client';
import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -48,6 +48,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
[name: string]: { name: string; value: number }; [name: string]: { name: string; value: number };
}; };
public SymbolProfile: SymbolProfile; public SymbolProfile: SymbolProfile;
public tags: Tag[];
public transactionCount: number; public transactionCount: number;
public value: number; public value: number;
@ -83,6 +84,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
orders, orders,
quantity, quantity,
SymbolProfile, SymbolProfile,
tags,
transactionCount, transactionCount,
value value
}) => { }) => {
@ -115,6 +117,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.quantity = quantity; this.quantity = quantity;
this.sectors = {}; this.sectors = {};
this.SymbolProfile = SymbolProfile; this.SymbolProfile = SymbolProfile;
this.tags = tags;
this.transactionCount = transactionCount; this.transactionCount = transactionCount;
this.value = value; this.value = value;

View File

@ -194,21 +194,31 @@
</div> </div>
</div> </div>
<gf-activities-table <div class="mb-3">
*ngIf="orders?.length > 0" <div class="h4 mb-0" i18n>Activities</div>
[activities]="orders" <gf-activities-table
[baseCurrency]="data.baseCurrency" *ngIf="orders?.length > 0"
[deviceType]="data.deviceType" [activities]="orders"
[hasPermissionToCreateActivity]="false" [baseCurrency]="data.baseCurrency"
[hasPermissionToExportActivities]="!hasImpersonationId" [deviceType]="data.deviceType"
[hasPermissionToFilter]="false" [hasPermissionToCreateActivity]="false"
[hasPermissionToImportActivities]="false" [hasPermissionToExportActivities]="!hasImpersonationId"
[hasPermissionToOpenDetails]="false" [hasPermissionToFilter]="false"
[locale]="data.locale" [hasPermissionToImportActivities]="false"
[showActions]="false" [hasPermissionToOpenDetails]="false"
[showSymbolColumn]="false" [locale]="data.locale"
(export)="onExport()" [showActions]="false"
></gf-activities-table> [showSymbolColumn]="false"
(export)="onExport()"
></gf-activities-table>
</div>
<div *ngIf="tags?.length > 0">
<div class="h4" i18n>Tags</div>
<mat-chip-list>
<mat-chip *ngFor="let tag of tags">{{ tag.name }}</mat-chip>
</mat-chip-list>
</div>
</div> </div>
<gf-dialog-footer <gf-dialog-footer

View File

@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common'; 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 { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module'; import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
@ -24,6 +25,7 @@ import { PositionDetailDialog } from './position-detail-dialog.component';
GfPortfolioProportionChartModule, GfPortfolioProportionChartModule,
GfValueModule, GfValueModule,
MatButtonModule, MatButtonModule,
MatChipsModule,
MatDialogModule, MatDialogModule,
NgxSkeletonLoaderModule NgxSkeletonLoaderModule
], ],

View File

@ -86,6 +86,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
}, },
Validators.required Validators.required
], ],
tags: [this.data.activity?.tags],
type: [undefined, Validators.required], // Set after value changes subscription type: [undefined, Validators.required], // Set after value changes subscription
unitPrice: [this.data.activity?.unitPrice, Validators.required] unitPrice: [this.data.activity?.unitPrice, Validators.required]
}); });

View File

@ -134,6 +134,18 @@
> >
</mat-form-field> </mat-form-field>
</div> </div>
<div
[ngClass]="{ 'd-none': activityForm.controls['tags']?.value?.length <= 0 }"
>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Tags</mat-label>
<mat-chip-list>
<mat-chip *ngFor="let tag of activityForm.controls['tags']?.value">
{{ tag.name }}
</mat-chip>
</mat-chip-list>
</mat-form-field>
</div>
</div> </div>
<div class="d-flex" mat-dialog-actions> <div class="d-flex" mat-dialog-actions>
<gf-value <gf-value

View File

@ -3,6 +3,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
@ -24,6 +25,7 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
FormsModule, FormsModule,
MatAutocompleteModule, MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatChipsModule,
MatDatepickerModule, MatDatepickerModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,

View File

@ -1,8 +1,9 @@
import { Account, Order, Platform, SymbolProfile } from '@prisma/client'; import { Account, Order, Platform, SymbolProfile, Tag } from '@prisma/client';
type AccountWithPlatform = Account & { Platform?: Platform }; type AccountWithPlatform = Account & { Platform?: Platform };
export type OrderWithAccount = Order & { export type OrderWithAccount = Order & {
Account?: AccountWithPlatform; Account?: AccountWithPlatform;
SymbolProfile?: SymbolProfile; SymbolProfile?: SymbolProfile;
tags?: Tag[];
}; };