Feature/extract historical market data editor to reusable component (#4080)
* Extract historical market data editor to reusable component * Update changelog
This commit is contained in:
parent
c85a1be3cf
commit
11d5f36c31
@ -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
|
||||
|
||||
### Changed
|
||||
|
||||
- Extracted the historical market data editor to a reusable component
|
||||
|
||||
## 2.125.0 - 2024-11-30
|
||||
|
||||
### 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 {
|
||||
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 { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
|
||||
import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
|
||||
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
|
||||
import { AdminService } from '@ghostfolio/client/services/admin.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 { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
AdminMarketDataDetails,
|
||||
AssetProfileIdentifier
|
||||
AssetProfileIdentifier,
|
||||
LineChartItem,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
|
||||
@ -23,7 +25,6 @@ import {
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, FormControl, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import {
|
||||
AssetClass,
|
||||
AssetSubClass,
|
||||
@ -31,7 +32,6 @@ import {
|
||||
SymbolProfile
|
||||
} from '@prisma/client';
|
||||
import { format } from 'date-fns';
|
||||
import { parse as csvToJson } from 'papaparse';
|
||||
import { EMPTY, Subject } from 'rxjs';
|
||||
import { catchError, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -75,11 +75,13 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
};
|
||||
public currencies: string[] = [];
|
||||
public ghostfolioScraperApiSymbolPrefix = ghostfolioScraperApiSymbolPrefix;
|
||||
public historicalDataItems: LineChartItem[];
|
||||
public isBenchmark = false;
|
||||
public marketDataDetails: MarketData[] = [];
|
||||
public marketDataItems: MarketData[] = [];
|
||||
public sectors: {
|
||||
[name: string]: { name: string; value: number };
|
||||
};
|
||||
public user: User;
|
||||
|
||||
private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format(
|
||||
new Date(),
|
||||
@ -96,7 +98,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
public dialogRef: MatDialogRef<AssetProfileDialog>,
|
||||
private formBuilder: FormBuilder,
|
||||
private notificationService: NotificationService,
|
||||
private snackBar: MatSnackBar
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
public ngOnInit() {
|
||||
@ -109,6 +111,16 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
this.historicalDataItems = undefined;
|
||||
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
}
|
||||
});
|
||||
|
||||
this.adminService
|
||||
.fetchAdminMarketDataBySymbol({
|
||||
dataSource: this.data.dataSource,
|
||||
@ -121,10 +133,19 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
this.assetProfileClass = translate(this.assetProfile?.assetClass);
|
||||
this.assetProfileSubClass = translate(this.assetProfile?.assetSubClass);
|
||||
this.countries = {};
|
||||
|
||||
this.isBenchmark = this.benchmarks.some(({ 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 = {};
|
||||
|
||||
if (this.assetProfile?.countries?.length > 0) {
|
||||
@ -200,47 +221,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
.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) {
|
||||
if (withRefresh) {
|
||||
this.initialize();
|
||||
|
@ -68,50 +68,28 @@
|
||||
</div>
|
||||
|
||||
<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"
|
||||
[currency]="assetProfile?.currency"
|
||||
[dataSource]="data.dataSource"
|
||||
[dateOfFirstActivity]="assetProfile?.dateOfFirstActivity"
|
||||
[locale]="data.locale"
|
||||
[marketData]="marketDataDetails"
|
||||
[marketData]="marketDataItems"
|
||||
[symbol]="data.symbol"
|
||||
[user]="user"
|
||||
(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="col-6 mb-3">
|
||||
<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 { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
|
||||
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 { GfValueComponent } from '@ghostfolio/ui/value';
|
||||
|
||||
@ -24,9 +25,10 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
GfAdminMarketDataDetailModule,
|
||||
GfAssetProfileIconComponent,
|
||||
GfCurrencySelectorComponent,
|
||||
GfHistoricalMarketDataEditorComponent,
|
||||
GfLineChartComponent,
|
||||
GfPortfolioProportionChartComponent,
|
||||
GfValueComponent,
|
||||
MatButtonModule,
|
||||
|
@ -1,34 +1,58 @@
|
||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
Inject,
|
||||
OnDestroy
|
||||
} 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 { 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 { MarketDataDetailDialogParams } from './interfaces/interfaces';
|
||||
import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
host: { class: 'h-100' },
|
||||
selector: 'gf-market-data-detail-dialog',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrls: ['./market-data-detail-dialog.scss'],
|
||||
templateUrl: 'market-data-detail-dialog.html'
|
||||
host: { class: 'h-100' },
|
||||
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>();
|
||||
|
||||
public constructor(
|
||||
private adminService: AdminService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
@Inject(MAT_DIALOG_DATA) public data: MarketDataDetailDialogParams,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: HistoricalMarketDataEditorDialogParams,
|
||||
private dateAdapter: DateAdapter<any>,
|
||||
public dialogRef: MatDialogRef<MarketDataDetailDialog>,
|
||||
public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>,
|
||||
@Inject(MAT_DATE_LOCALE) private locale: string
|
||||
) {}
|
||||
|
@ -2,7 +2,7 @@ import { User } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { DataSource } from '@prisma/client';
|
||||
|
||||
export interface MarketDataDetailDialogParams {
|
||||
export interface HistoricalMarketDataEditorDialogParams {
|
||||
currency: string;
|
||||
dataSource: DataSource;
|
||||
dateString: string;
|
@ -1,14 +1,4 @@
|
||||
<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) {
|
||||
<div class="d-flex">
|
||||
<div class="date px-1 text-nowrap">{{ itemByMonth.key }}</div>
|
||||
@ -43,4 +33,42 @@
|
||||
</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>
|
@ -2,10 +2,6 @@
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
|
||||
gf-line-chart {
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-feature-settings: 'tnum';
|
||||
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 {
|
||||
DATE_FORMAT,
|
||||
getDateFormatString,
|
||||
@ -6,15 +7,22 @@ import {
|
||||
} from '@ghostfolio/common/helper';
|
||||
import { LineChartItem, User } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
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 {
|
||||
addDays,
|
||||
@ -29,55 +37,70 @@ import {
|
||||
parseISO
|
||||
} from 'date-fns';
|
||||
import { first, last } from 'lodash';
|
||||
import ms from 'ms';
|
||||
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 { MarketDataDetailDialog } from './market-data-detail-dialog/market-data-detail-dialog.component';
|
||||
import { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component';
|
||||
import { HistoricalMarketDataEditorDialogParams } from './historical-market-data-editor-dialog/interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'gf-admin-market-data-detail',
|
||||
styleUrls: ['./admin-market-data-detail.component.scss'],
|
||||
templateUrl: './admin-market-data-detail.component.html'
|
||||
imports: [CommonModule, MatButtonModule, MatInputModule, ReactiveFormsModule],
|
||||
selector: 'gf-historical-market-data-editor',
|
||||
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() dataSource: DataSource;
|
||||
@Input() dateOfFirstActivity: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() marketData: MarketData[];
|
||||
@Input() symbol: string;
|
||||
@Input() user: User;
|
||||
|
||||
@Output() marketDataChanged = new EventEmitter<boolean>();
|
||||
|
||||
public days = Array(31);
|
||||
public defaultDateFormat: string;
|
||||
public deviceType: string;
|
||||
public historicalDataForm = this.formBuilder.group({
|
||||
historicalData: this.formBuilder.group({
|
||||
csvString: ''
|
||||
})
|
||||
});
|
||||
public historicalDataItems: LineChartItem[];
|
||||
public marketDataByMonth: {
|
||||
[yearMonth: string]: {
|
||||
[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>();
|
||||
|
||||
public constructor(
|
||||
private adminService: AdminService,
|
||||
private deviceService: DeviceDetectorService,
|
||||
private dialog: MatDialog,
|
||||
private userService: UserService
|
||||
private formBuilder: FormBuilder,
|
||||
private snackBar: MatSnackBar
|
||||
) {
|
||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||
}
|
||||
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
}
|
||||
});
|
||||
public ngOnInit() {
|
||||
this.initializeHistoricalDataForm();
|
||||
}
|
||||
|
||||
public ngOnChanges() {
|
||||
@ -177,29 +200,84 @@ export class AdminMarketDataDetailComponent implements OnChanges {
|
||||
}) {
|
||||
const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice;
|
||||
|
||||
const dialogRef = this.dialog.open(MarketDataDetailDialog, {
|
||||
data: {
|
||||
marketPrice,
|
||||
currency: this.currency,
|
||||
dataSource: this.dataSource,
|
||||
dateString: `${yearMonth}-${day}`,
|
||||
symbol: this.symbol,
|
||||
user: this.user
|
||||
} as MarketDataDetailDialogParams,
|
||||
height: this.deviceType === 'mobile' ? '98vh' : '80vh',
|
||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||
});
|
||||
const dialogRef = this.dialog.open(
|
||||
GfHistoricalMarketDataEditorDialogComponent,
|
||||
{
|
||||
data: {
|
||||
marketPrice,
|
||||
currency: this.currency,
|
||||
dataSource: this.dataSource,
|
||||
dateString: `${yearMonth}-${day}`,
|
||||
symbol: this.symbol,
|
||||
user: this.user
|
||||
} as HistoricalMarketDataEditorDialogParams,
|
||||
height: this.deviceType === 'mobile' ? '98vh' : '80vh',
|
||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||
}
|
||||
);
|
||||
|
||||
dialogRef
|
||||
.afterClosed()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.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() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
|
||||
private initializeHistoricalDataForm() {
|
||||
this.historicalDataForm.setValue({
|
||||
historicalData: {
|
||||
csvString:
|
||||
GfHistoricalMarketDataEditorComponent.HISTORICAL_DATA_TEMPLATE
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
1
libs/ui/src/lib/historical-market-data-editor/index.ts
Normal file
1
libs/ui/src/lib/historical-market-data-editor/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './historical-market-data-editor.component';
|
Loading…
x
Reference in New Issue
Block a user