Feature/extract historical market data editor to reusable component (#4080)

* Extract historical market data editor to reusable component

* Update changelog
pull/4091/head^2
Amandee Ellawala 3 weeks ago committed by GitHub
parent c85a1be3cf
commit 11d5f36c31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added pagination to the users table of the admin control panel - Added pagination to the users table of the admin control panel
### Changed
- Extracted the historical market data editor to a reusable component
## 2.125.0 - 2024-11-30 ## 2.125.0 - 2024-11-30
### Changed ### Changed

@ -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 {}

@ -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 {}

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

@ -1,15 +1,17 @@
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,
LineChartItem,
User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
@ -23,7 +25,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,7 +32,6 @@ 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';
@ -75,11 +75,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 marketDataItems: 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 +98,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 +111,16 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
} }
public initialize() { public initialize() {
this.historicalDataItems = undefined;
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,
@ -121,10 +133,19 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
this.assetProfileClass = translate(this.assetProfile?.assetClass); this.assetProfileClass = translate(this.assetProfile?.assetClass);
this.assetProfileSubClass = translate(this.assetProfile?.assetSubClass); this.assetProfileSubClass = translate(this.assetProfile?.assetSubClass);
this.countries = {}; this.countries = {};
this.isBenchmark = this.benchmarks.some(({ id }) => { this.isBenchmark = this.benchmarks.some(({ id }) => {
return id === this.assetProfile.id; return id === this.assetProfile.id;
}); });
this.marketDataDetails = marketData;
this.historicalDataItems = marketData.map(({ date, marketPrice }) => {
return {
date: format(date, DATE_FORMAT),
value: marketPrice
};
});
this.marketDataItems = marketData;
this.sectors = {}; this.sectors = {};
if (this.assetProfile?.countries?.length > 0) { if (this.assetProfile?.countries?.length > 0) {
@ -200,47 +221,6 @@ 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();

@ -68,50 +68,28 @@
</div> </div>
<div class="flex-grow-1" mat-dialog-content> <div class="flex-grow-1" mat-dialog-content>
<gf-admin-market-data-detail <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-historical-market-data-editor
class="mb-3" class="mb-3"
[currency]="assetProfile?.currency" [currency]="assetProfile?.currency"
[dataSource]="data.dataSource" [dataSource]="data.dataSource"
[dateOfFirstActivity]="assetProfile?.dateOfFirstActivity" [dateOfFirstActivity]="assetProfile?.dateOfFirstActivity"
[locale]="data.locale" [locale]="data.locale"
[marketData]="marketDataDetails" [marketData]="marketDataItems"
[symbol]="data.symbol" [symbol]="data.symbol"
[user]="user"
(marketDataChanged)="onMarketDataChanged($event)" (marketDataChanged)="onMarketDataChanged($event)"
/> />
<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"

@ -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 { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector'; import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector';
import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor';
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';
@ -24,9 +25,10 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
GfAdminMarketDataDetailModule,
GfAssetProfileIconComponent, GfAssetProfileIconComponent,
GfCurrencySelectorComponent, GfCurrencySelectorComponent,
GfHistoricalMarketDataEditorComponent,
GfLineChartComponent,
GfPortfolioProportionChartComponent, GfPortfolioProportionChartComponent,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,

@ -1,34 +1,58 @@
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 { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces';
@Component({ @Component({
host: { class: 'h-100' },
selector: 'gf-market-data-detail-dialog',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['./market-data-detail-dialog.scss'], host: { class: 'h-100' },
templateUrl: 'market-data-detail-dialog.html' imports: [
CommonModule,
FormsModule,
MatButtonModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule
],
selector: 'gf-historical-market-data-editor-dialog',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
standalone: true,
styleUrls: ['./historical-market-data-editor-dialog.scss'],
templateUrl: 'historical-market-data-editor-dialog.html'
}) })
export class MarketDataDetailDialog implements OnDestroy { export class GfHistoricalMarketDataEditorDialogComponent implements OnDestroy {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: MarketDataDetailDialogParams, @Inject(MAT_DIALOG_DATA)
public data: HistoricalMarketDataEditorDialogParams,
private dateAdapter: DateAdapter<any>, private dateAdapter: DateAdapter<any>,
public dialogRef: MatDialogRef<MarketDataDetailDialog>, public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>,
@Inject(MAT_DATE_LOCALE) private locale: string @Inject(MAT_DATE_LOCALE) private locale: string
) {} ) {}

@ -2,7 +2,7 @@ import { User } from '@ghostfolio/common/interfaces';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export interface MarketDataDetailDialogParams { export interface HistoricalMarketDataEditorDialogParams {
currency: string; currency: string;
dataSource: DataSource; dataSource: DataSource;
dateString: string; dateString: string;

@ -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>

@ -2,10 +2,6 @@
display: block; display: block;
font-size: 0.9rem; font-size: 0.9rem;
gf-line-chart {
aspect-ratio: 16/9;
}
.date { .date {
font-feature-settings: 'tnum'; font-feature-settings: 'tnum';
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;

@ -1,4 +1,5 @@
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,
@ -6,15 +7,22 @@ import {
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces'; import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { CommonModule } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
EventEmitter, EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
OnInit,
Output Output
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { 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,
@ -29,55 +37,70 @@ import {
parseISO parseISO
} from 'date-fns'; } from 'date-fns';
import { first, last } from 'lodash'; import { first, last } from 'lodash';
import ms from 'ms';
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 { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component';
import { MarketDataDetailDialog } from './market-data-detail-dialog/market-data-detail-dialog.component'; import { HistoricalMarketDataEditorDialogParams } from './historical-market-data-editor-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-admin-market-data-detail', imports: [CommonModule, MatButtonModule, MatInputModule, ReactiveFormsModule],
styleUrls: ['./admin-market-data-detail.component.scss'], selector: 'gf-historical-market-data-editor',
templateUrl: './admin-market-data-detail.component.html' standalone: true,
styleUrls: ['./historical-market-data-editor.component.scss'],
templateUrl: './historical-market-data-editor.component.html'
}) })
export class AdminMarketDataDetailComponent implements OnChanges { export class GfHistoricalMarketDataEditorComponent
implements OnChanges, OnDestroy, 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>();
public days = Array(31); public days = Array(31);
public defaultDateFormat: string; public defaultDateFormat: string;
public deviceType: string; public deviceType: string;
public historicalDataForm = this.formBuilder.group({
historicalData: this.formBuilder.group({
csvString: ''
})
});
public historicalDataItems: LineChartItem[]; public historicalDataItems: LineChartItem[];
public marketDataByMonth: { public marketDataByMonth: {
[yearMonth: string]: { [yearMonth: string]: {
[day: string]: Pick<MarketData, 'date' | 'marketPrice'> & { day: number }; [day: string]: Pick<MarketData, 'date' | 'marketPrice'> & { day: number };
}; };
} = {}; } = {};
public user: User;
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,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private userService: UserService private formBuilder: FormBuilder,
private snackBar: MatSnackBar
) { ) {
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,29 +200,84 @@ 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(
data: { GfHistoricalMarketDataEditorDialogComponent,
marketPrice, {
currency: this.currency, data: {
dataSource: this.dataSource, marketPrice,
dateString: `${yearMonth}-${day}`, currency: this.currency,
symbol: this.symbol, dataSource: this.dataSource,
user: this.user dateString: `${yearMonth}-${day}`,
} as MarketDataDetailDialogParams, symbol: this.symbol,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', user: this.user
width: this.deviceType === 'mobile' ? '100vw' : '50rem' } as HistoricalMarketDataEditorDialogParams,
}); height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}
);
dialogRef dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ withRefresh } = { withRefresh: false }) => { .subscribe(({ withRefresh } = { withRefresh: false }) => {
this.marketDataChanged.next(withRefresh); this.marketDataChanged.emit(withRefresh);
}); });
} }
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.dataSource,
marketData: {
marketData
},
symbol: this.symbol
})
.pipe(
catchError(({ error, message }) => {
this.snackBar.open(`${error}: ${message[0]}`, undefined, {
duration: ms('3 seconds')
});
return EMPTY;
}),
takeUntil(this.unsubscribeSubject)
)
.subscribe(() => {
this.initializeHistoricalDataForm();
this.marketDataChanged.emit(true);
});
} catch {
this.snackBar.open(
$localize`Oops! Could not parse historical data.`,
undefined,
{ duration: ms('3 seconds') }
);
}
}
public ngOnDestroy() { public ngOnDestroy() {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private initializeHistoricalDataForm() {
this.historicalDataForm.setValue({
historicalData: {
csvString:
GfHistoricalMarketDataEditorComponent.HISTORICAL_DATA_TEMPLATE
}
});
}
} }

@ -0,0 +1 @@
export * from './historical-market-data-editor.component';
Loading…
Cancel
Save