Browse Source

Feature/Extract historical market data editor to @ghostfolio/ui (#3845)

pull/4080/head
Amandee Ellawala 9 months ago
committed by Thomas Kaul
parent
commit
62f06dc0b7
  1. 15
      apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts
  2. 26
      apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts
  3. 2
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  4. 3
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss
  5. 77
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  6. 46
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  7. 6
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts
  8. 48
      libs/ui/src/lib/admin-market-data-detail/admin-market-data-detail.component.html
  9. 0
      libs/ui/src/lib/admin-market-data-detail/admin-market-data-detail.component.scss
  10. 104
      libs/ui/src/lib/admin-market-data-detail/admin-market-data-detail.component.ts
  11. 1
      libs/ui/src/lib/admin-market-data-detail/index.ts
  12. 0
      libs/ui/src/lib/admin-market-data-detail/interfaces/interfaces.ts
  13. 0
      libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts
  14. 29
      libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts
  15. 0
      libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html
  16. 0
      libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss

15
apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts

@ -1,15 +0,0 @@
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AdminMarketDataDetailComponent } from './admin-market-data-detail.component';
import { GfMarketDataDetailDialogModule } from './market-data-detail-dialog/market-data-detail-dialog.module';
@NgModule({
declarations: [AdminMarketDataDetailComponent],
exports: [AdminMarketDataDetailComponent],
imports: [CommonModule, GfLineChartComponent, GfMarketDataDetailDialogModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfAdminMarketDataDetailModule {}

26
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts

@ -1,26 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MarketDataDetailDialog } from './market-data-detail-dialog.component';
@NgModule({
declarations: [MarketDataDetailDialog],
imports: [
CommonModule,
FormsModule,
MatButtonModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfMarketDataDetailDialogModule {}

2
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -14,6 +14,7 @@ import {
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { AssetProfileDialogParams } from '@ghostfolio/ui/admin-market-data-detail/interfaces/interfaces';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
@ -39,7 +40,6 @@ import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
import { AdminMarketDataService } from './admin-market-data.service'; import { AdminMarketDataService } from './admin-market-data.service';
import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component'; import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component';
import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces';
import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component'; import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component';
import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/interfaces/interfaces'; import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/interfaces/interfaces';

3
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss

@ -4,4 +4,7 @@
.mat-mdc-dialog-content { .mat-mdc-dialog-content {
max-height: unset; max-height: unset;
} }
gf-line-chart {
aspect-ratio: 16/9;
}
} }

77
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -1,16 +1,19 @@
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
AdminMarketDataDetails, AdminMarketDataDetails,
AssetProfileIdentifier AssetProfileIdentifier,
User,
LineChartItem
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { AssetProfileDialogParams } from '@ghostfolio/ui/admin-market-data-detail/interfaces/interfaces';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { import {
@ -23,7 +26,6 @@ import {
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormControl, Validators } from '@angular/forms'; import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { import {
AssetClass, AssetClass,
AssetSubClass, AssetSubClass,
@ -31,12 +33,9 @@ import {
SymbolProfile SymbolProfile
} from '@prisma/client'; } from '@prisma/client';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { parse as csvToJson } from 'papaparse';
import { EMPTY, Subject } from 'rxjs'; import { EMPTY, Subject } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators'; import { catchError, takeUntil } from 'rxjs/operators';
import { AssetProfileDialogParams } from './interfaces/interfaces';
@Component({ @Component({
host: { class: 'd-flex flex-column h-100' }, host: { class: 'd-flex flex-column h-100' },
selector: 'gf-asset-profile-dialog', selector: 'gf-asset-profile-dialog',
@ -75,11 +74,13 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
}; };
public currencies: string[] = []; public currencies: string[] = [];
public ghostfolioScraperApiSymbolPrefix = ghostfolioScraperApiSymbolPrefix; public ghostfolioScraperApiSymbolPrefix = ghostfolioScraperApiSymbolPrefix;
public historicalDataItems: LineChartItem[];
public isBenchmark = false; public isBenchmark = false;
public marketDataDetails: MarketData[] = []; public marketDataDetails: MarketData[] = [];
public sectors: { public sectors: {
[name: string]: { name: string; value: number }; [name: string]: { name: string; value: number };
}; };
public user: User;
private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format(
new Date(), new Date(),
@ -96,7 +97,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
public dialogRef: MatDialogRef<AssetProfileDialog>, public dialogRef: MatDialogRef<AssetProfileDialog>,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private notificationService: NotificationService, private notificationService: NotificationService,
private snackBar: MatSnackBar private userService: UserService
) {} ) {}
public ngOnInit() { public ngOnInit() {
@ -109,6 +110,13 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
} }
public initialize() { public initialize() {
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
}
});
this.adminService this.adminService
.fetchAdminMarketDataBySymbol({ .fetchAdminMarketDataBySymbol({
dataSource: this.data.dataSource, dataSource: this.data.dataSource,
@ -125,6 +133,16 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
return id === this.assetProfile.id; return id === this.assetProfile.id;
}); });
this.marketDataDetails = marketData; this.marketDataDetails = marketData;
this.historicalDataItems = this.marketDataDetails.map(
({ date, marketPrice }) => {
return {
date: format(date, DATE_FORMAT),
value: marketPrice
};
}
);
this.sectors = {}; this.sectors = {};
if (this.assetProfile?.countries?.length > 0) { if (this.assetProfile?.countries?.length > 0) {
@ -200,53 +218,16 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
.subscribe(); .subscribe();
} }
public onImportHistoricalData() {
try {
const marketData = csvToJson(
this.assetProfileForm.controls['historicalData'].controls['csvString']
.value,
{
dynamicTyping: true,
header: true,
skipEmptyLines: true
}
).data as UpdateMarketDataDto[];
this.adminService
.postMarketData({
dataSource: this.data.dataSource,
marketData: {
marketData
},
symbol: this.data.symbol
})
.pipe(
catchError(({ error, message }) => {
this.snackBar.open(`${error}: ${message[0]}`, undefined, {
duration: 3000
});
return EMPTY;
}),
takeUntil(this.unsubscribeSubject)
)
.subscribe(() => {
this.initialize();
});
} catch {
this.snackBar.open(
$localize`Oops! Could not parse historical data.`,
undefined,
{ duration: 3000 }
);
}
}
public onMarketDataChanged(withRefresh: boolean = false) { public onMarketDataChanged(withRefresh: boolean = false) {
if (withRefresh) { if (withRefresh) {
this.initialize(); this.initialize();
} }
} }
public onImportHistoricalDataChanged() {
this.initialize();
}
public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
this.dataService this.dataService
.postBenchmark({ dataSource, symbol }) .postBenchmark({ dataSource, symbol })

46
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -68,6 +68,17 @@
</div> </div>
<div class="flex-grow-1" mat-dialog-content> <div class="flex-grow-1" mat-dialog-content>
<p>Line</p>
<gf-line-chart
class="mb-4"
[colorScheme]="user?.settings?.colorScheme"
[historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="data.locale"
[showXAxis]="true"
[showYAxis]="true"
[symbol]="data.symbol"
/>
<gf-admin-market-data-detail <gf-admin-market-data-detail
class="mb-3" class="mb-3"
[currency]="assetProfile?.currency" [currency]="assetProfile?.currency"
@ -76,42 +87,11 @@
[locale]="data.locale" [locale]="data.locale"
[marketData]="marketDataDetails" [marketData]="marketDataDetails"
[symbol]="data.symbol" [symbol]="data.symbol"
[user]="user"
(marketDataChanged)="onMarketDataChanged($event)" (marketDataChanged)="onMarketDataChanged($event)"
(updateHistoricalData)="onImportHistoricalDataChanged()"
/> />
<div class="mt-3" formGroupName="historicalData">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label>
<ng-container i18n>Historical Data</ng-container> (CSV)
</mat-label>
<textarea
cdkAutosizeMaxRows="5"
cdkTextareaAutosize
formControlName="csvString"
matInput
type="text"
(keyup.enter)="$event.stopPropagation()"
></textarea>
</mat-form-field>
</div>
<div class="d-flex justify-content-end mt-2">
<button
color="accent"
mat-flat-button
type="button"
[disabled]="
!assetProfileForm.controls['historicalData']?.controls['csvString']
.touched ||
assetProfileForm.controls['historicalData']?.controls['csvString']
?.value === ''
"
(click)="onImportHistoricalData()"
>
<ng-container i18n>Import</ng-container>
</button>
</div>
<div class="row"> <div class="row">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value i18n size="medium" [value]="assetProfile?.symbol" <gf-value i18n size="medium" [value]="assetProfile?.symbol"

6
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts

@ -1,7 +1,8 @@
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component'; import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
import { AdminMarketDataDetailComponent } from '@ghostfolio/ui/admin-market-data-detail';
import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector'; import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
@ -22,11 +23,12 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
@NgModule({ @NgModule({
declarations: [AssetProfileDialog], declarations: [AssetProfileDialog],
imports: [ imports: [
AdminMarketDataDetailComponent,
CommonModule, CommonModule,
FormsModule, FormsModule,
GfAdminMarketDataDetailModule,
GfAssetProfileIconComponent, GfAssetProfileIconComponent,
GfCurrencySelectorComponent, GfCurrencySelectorComponent,
GfLineChartComponent,
GfPortfolioProportionChartComponent, GfPortfolioProportionChartComponent,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,

48
apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html → libs/ui/src/lib/admin-market-data-detail/admin-market-data-detail.component.html

@ -1,14 +1,4 @@
<div> <div>
<gf-line-chart
class="mb-4"
[colorScheme]="user?.settings?.colorScheme"
[historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="locale"
[showXAxis]="true"
[showYAxis]="true"
[symbol]="symbol"
/>
@for (itemByMonth of marketDataByMonth | keyvalue; track itemByMonth) { @for (itemByMonth of marketDataByMonth | keyvalue; track itemByMonth) {
<div class="d-flex"> <div class="d-flex">
<div class="date px-1 text-nowrap">{{ itemByMonth.key }}</div> <div class="date px-1 text-nowrap">{{ itemByMonth.key }}</div>
@ -43,4 +33,42 @@
</div> </div>
</div> </div>
} }
<form
class="d-flex flex-column h-100"
[formGroup]="historicalDataForm"
(ngSubmit)="onImportHistoricalData()"
>
<div class="mt-3" formGroupName="historicalData">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label>
<ng-container i18n>Historical Data</ng-container> (CSV)
</mat-label>
<textarea
cdkAutosizeMaxRows="5"
cdkTextareaAutosize
formControlName="csvString"
matInput
type="text"
(keyup.enter)="$event.stopPropagation()"
></textarea>
</mat-form-field>
</div>
<div class="d-flex justify-content-end mt-2">
<button
color="accent"
mat-flat-button
type="button"
[disabled]="
!historicalDataForm.controls['historicalData']?.controls['csvString']
.touched ||
historicalDataForm.controls['historicalData']?.controls['csvString']
?.value === ''
"
(click)="onImportHistoricalData()"
>
<ng-container i18n>Import</ng-container>
</button>
</div>
</form>
</div> </div>

0
apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss → libs/ui/src/lib/admin-market-data-detail/admin-market-data-detail.component.scss

104
apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts → libs/ui/src/lib/admin-market-data-detail/admin-market-data-detail.component.ts

@ -1,20 +1,29 @@
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { import {
DATE_FORMAT, DATE_FORMAT,
getDateFormatString, getDateFormatString,
getLocale getLocale
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces'; import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { AssetProfileDialogParams } from '@ghostfolio/ui/admin-market-data-detail/interfaces/interfaces';
import { CommonModule } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
EventEmitter, EventEmitter,
Inject,
Input, Input,
OnChanges, OnChanges,
OnInit,
Output Output
} from '@angular/core'; } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBar } from '@angular/material/snack-bar';
import { DataSource, MarketData } from '@prisma/client'; import { DataSource, MarketData } from '@prisma/client';
import { import {
addDays, addDays,
@ -30,26 +39,32 @@ import {
} from 'date-fns'; } from 'date-fns';
import { first, last } from 'lodash'; import { first, last } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, takeUntil } from 'rxjs'; import { parse as csvToJson } from 'papaparse';
import { EMPTY, Subject, takeUntil } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { MarketDataDetailDialogParams } from './market-data-detail-dialog/interfaces/interfaces'; import { MarketDataDetailDialogParams } from './market-data-detail-dialog/interfaces/interfaces';
import { MarketDataDetailDialog } from './market-data-detail-dialog/market-data-detail-dialog.component'; import { MarketDataDetailDialogComponent } from './market-data-detail-dialog/market-data-detail-dialog.component';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, ReactiveFormsModule, MatButtonModule, MatInputModule],
selector: 'gf-admin-market-data-detail', selector: 'gf-admin-market-data-detail',
standalone: true,
styleUrls: ['./admin-market-data-detail.component.scss'], styleUrls: ['./admin-market-data-detail.component.scss'],
templateUrl: './admin-market-data-detail.component.html' templateUrl: './admin-market-data-detail.component.html'
}) })
export class AdminMarketDataDetailComponent implements OnChanges { export class AdminMarketDataDetailComponent implements OnChanges, OnInit {
@Input() currency: string; @Input() currency: string;
@Input() dataSource: DataSource; @Input() dataSource: DataSource;
@Input() dateOfFirstActivity: string; @Input() dateOfFirstActivity: string;
@Input() locale = getLocale(); @Input() locale = getLocale();
@Input() marketData: MarketData[]; @Input() marketData: MarketData[];
@Input() symbol: string; @Input() symbol: string;
@Input() user: User;
@Output() marketDataChanged = new EventEmitter<boolean>(); @Output() marketDataChanged = new EventEmitter<boolean>();
@Output() updateHistoricalData = new EventEmitter();
public days = Array(31); public days = Array(31);
public defaultDateFormat: string; public defaultDateFormat: string;
@ -60,24 +75,33 @@ export class AdminMarketDataDetailComponent implements OnChanges {
[day: string]: Pick<MarketData, 'date' | 'marketPrice'> & { day: number }; [day: string]: Pick<MarketData, 'date' | 'marketPrice'> & { day: number };
}; };
} = {}; } = {};
public user: User;
public historicalDataForm = this.formBuilder.group({
historicalData: this.formBuilder.group({
csvString: ''
})
});
private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format(
new Date(),
DATE_FORMAT
)};123.45`;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private adminService: AdminService,
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private userService: UserService private snackBar: MatSnackBar,
private formBuilder: FormBuilder
) { ) {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
}
this.userService.stateChanged public ngOnInit() {
.pipe(takeUntil(this.unsubscribeSubject)) this.initializeHistoricalDataForm();
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
}
});
} }
public ngOnChanges() { public ngOnChanges() {
@ -177,7 +201,7 @@ export class AdminMarketDataDetailComponent implements OnChanges {
}) { }) {
const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice; const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice;
const dialogRef = this.dialog.open(MarketDataDetailDialog, { const dialogRef = this.dialog.open(MarketDataDetailDialogComponent, {
data: { data: {
marketPrice, marketPrice,
currency: this.currency, currency: this.currency,
@ -198,6 +222,56 @@ export class AdminMarketDataDetailComponent implements OnChanges {
}); });
} }
public onImportHistoricalData() {
try {
const marketData = csvToJson(
this.historicalDataForm.controls['historicalData'].controls['csvString']
.value,
{
dynamicTyping: true,
header: true,
skipEmptyLines: true
}
).data as UpdateMarketDataDto[];
this.adminService
.postMarketData({
dataSource: this.data.dataSource,
marketData: {
marketData
},
symbol: this.data.symbol
})
.pipe(
catchError(({ error, message }) => {
this.snackBar.open(`${error}: ${message[0]}`, undefined, {
duration: 3000
});
return EMPTY;
}),
takeUntil(this.unsubscribeSubject)
)
.subscribe(() => {
this.updateHistoricalData.emit();
this.initializeHistoricalDataForm();
});
} catch {
this.snackBar.open(
$localize`Oops! Could not parse historical data.`,
undefined,
{ duration: 3000 }
);
}
}
public initializeHistoricalDataForm() {
this.historicalDataForm.setValue({
historicalData: {
csvString: AdminMarketDataDetailComponent.HISTORICAL_DATA_TEMPLATE
}
});
}
public ngOnDestroy() { public ngOnDestroy() {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();

1
libs/ui/src/lib/admin-market-data-detail/index.ts

@ -0,0 +1 @@
export * from './admin-market-data-detail.component';

0
apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts → libs/ui/src/lib/admin-market-data-detail/interfaces/interfaces.ts

0
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts → libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts

29
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts → libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts

@ -1,26 +1,49 @@
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { CommonModule } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA,
Inject, Inject,
OnDestroy OnDestroy
} from '@angular/core'; } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatDatepickerModule } from '@angular/material/datepicker';
import {
MAT_DIALOG_DATA,
MatDialogModule,
MatDialogRef
} from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
import { MarketDataDetailDialogParams } from './interfaces/interfaces'; import { MarketDataDetailDialogParams } from './interfaces/interfaces';
@Component({ @Component({
host: { class: 'h-100' }, host: { class: 'h-100' },
imports: [
CommonModule,
FormsModule,
MatButtonModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule
],
selector: 'gf-market-data-detail-dialog', selector: 'gf-market-data-detail-dialog',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
standalone: true,
styleUrls: ['./market-data-detail-dialog.scss'], styleUrls: ['./market-data-detail-dialog.scss'],
templateUrl: 'market-data-detail-dialog.html' templateUrl: 'market-data-detail-dialog.html'
}) })
export class MarketDataDetailDialog implements OnDestroy { export class MarketDataDetailDialogComponent implements OnDestroy {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
@ -28,7 +51,7 @@ export class MarketDataDetailDialog implements OnDestroy {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: MarketDataDetailDialogParams, @Inject(MAT_DIALOG_DATA) public data: MarketDataDetailDialogParams,
private dateAdapter: DateAdapter<any>, private dateAdapter: DateAdapter<any>,
public dialogRef: MatDialogRef<MarketDataDetailDialog>, public dialogRef: MatDialogRef<MarketDataDetailDialogComponent>,
@Inject(MAT_DATE_LOCALE) private locale: string @Inject(MAT_DATE_LOCALE) private locale: string
) {} ) {}

0
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html → libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html

0
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss → libs/ui/src/lib/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss

Loading…
Cancel
Save