Browse Source

Feature/extend holding detail dialog by historical market data editor (#4281)

* Extend holding detail dialog by historical market data editor

* Update changelog
pull/4315/head
Amandee Ellawala 1 month ago
committed by GitHub
parent
commit
ab379f9abf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 5
      apps/api/src/app/user/user.service.ts
  3. 48
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  4. 21
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
  5. 1
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html

1
CHANGELOG.md

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Japan)
- Extended the tags selector component by a `readonly` attribute
- Extended the tags selector component to support creating custom tags
- Extended the holding detail dialog by the historical market data editor (experimental)
- Added global styles to the _Storybook_ setup
### Changed

5
apps/api/src/app/user/user.service.ts

@ -346,7 +346,10 @@ export class UserService {
currentPermissions,
permissions.accessHoldingsChart,
permissions.createAccess,
permissions.readAiPrompt
permissions.createMarketDataOfOwnAssetProfile,
permissions.readAiPrompt,
permissions.readMarketDataOfOwnAssetProfile,
permissions.updateMarketDataOfOwnAssetProfile
);
// Reset benchmark

48
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts

@ -13,8 +13,10 @@ import {
LineChartItem,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-credits';
import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor';
import { translate } from '@ghostfolio/ui/i18n';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
@ -44,7 +46,7 @@ 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 { Account, MarketData, Tag } from '@prisma/client';
import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs';
@ -62,6 +64,7 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces';
GfDataProviderCreditsComponent,
GfDialogFooterModule,
GfDialogHeaderModule,
GfHistoricalMarketDataEditorComponent,
GfLineChartComponent,
GfPortfolioProportionChartComponent,
GfTagsSelectorComponent,
@ -95,9 +98,11 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public dividendYieldPercentWithCurrencyEffect: number;
public feeInBaseCurrency: number;
public firstBuyDate: string;
public hasPermissionToReadMarketDataOfOwnAssetProfile: boolean;
public historicalDataItems: LineChartItem[];
public investment: number;
public investmentPrecision = 2;
public marketDataItems: MarketData[] = [];
public marketPrice: number;
public maxPrice: number;
public minPrice: number;
@ -231,6 +236,14 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
this.feeInBaseCurrency = feeInBaseCurrency;
this.firstBuyDate = firstBuyDate;
this.hasPermissionToReadMarketDataOfOwnAssetProfile =
hasPermission(
this.user?.permissions,
permissions.readMarketDataOfOwnAssetProfile
) &&
SymbolProfile?.dataSource === 'MANUAL' &&
SymbolProfile?.userId === this.user?.id;
this.historicalDataItems = historicalData.map(
({ averagePrice, date, marketPrice }) => {
this.benchmarkDataItems.push({
@ -393,6 +406,10 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
}
);
if (this.hasPermissionToReadMarketDataOfOwnAssetProfile) {
this.fetchMarketData();
}
this.changeDetectorRef.markForCheck();
}
);
@ -448,6 +465,12 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
});
}
public onMarketDataChanged(withRefresh = false) {
if (withRefresh) {
this.fetchMarketData();
}
}
public onTagsChanged(tags: Tag[]) {
this.activityForm.get('tags').setValue(tags);
}
@ -464,4 +487,27 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private fetchMarketData() {
this.dataService
.fetchMarketDataBySymbol({
dataSource: this.data.dataSource,
symbol: this.data.symbol
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketData }) => {
this.marketDataItems = marketData;
this.historicalDataItems = this.marketDataItems.map(
({ date, marketPrice }) => {
return {
date: format(date, DATE_FORMAT),
value: marketPrice
};
}
);
this.changeDetectorRef.markForCheck();
});
}
}

21
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html

@ -364,6 +364,27 @@
[showValueInBaseCurrency]="false"
/>
</mat-tab>
@if (
hasPermissionToReadMarketDataOfOwnAssetProfile &&
user?.settings?.isExperimentalFeatures
) {
<mat-tab>
<ng-template mat-tab-label>
<ion-icon name="server-outline" />
<div class="d-none d-sm-block ml-2" i18n>Market Data</div>
</ng-template>
<gf-historical-market-data-editor
[currency]="SymbolProfile?.currency"
[dataSource]="SymbolProfile?.dataSource"
[dateOfFirstActivity]="firstBuyDate"
[locale]="data.locale"
[marketData]="marketDataItems"
[symbol]="SymbolProfile?.symbol"
[user]="user"
(marketDataChanged)="onMarketDataChanged($event)"
/>
</mat-tab>
}
</mat-tab-group>
<gf-tags-selector

1
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html

@ -48,6 +48,7 @@
cdkTextareaAutosize
formControlName="csvString"
matInput
rows="2"
type="text"
(keyup.enter)="$event.stopPropagation()"
></textarea>

Loading…
Cancel
Save