Feature/add market data tab to admin control panel (#497)
* Add market data tab * Update changelogpull/498/head
parent
fcf07a0fd1
commit
6c07759eb7
@ -0,0 +1,11 @@
|
||||
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MarketDataService } from './market-data.service';
|
||||
|
||||
@Module({
|
||||
exports: [MarketDataService],
|
||||
imports: [PrismaModule],
|
||||
providers: [MarketDataService]
|
||||
})
|
||||
export class MarketDataModule {}
|
@ -0,0 +1,18 @@
|
||||
<div>
|
||||
<div *ngFor="let itemByMonth of marketDataByMonth | keyvalue" class="d-flex">
|
||||
<div>{{ itemByMonth.key }}</div>
|
||||
<div class="align-items-center d-flex flex-grow-1 justify-content-end">
|
||||
<div
|
||||
*ngFor="let dayItem of days; let i = index"
|
||||
class="day"
|
||||
[title]="
|
||||
(marketDataByMonth[itemByMonth.key][i + 1]?.date
|
||||
| date: defaultDateFormat) ?? ''
|
||||
"
|
||||
[ngClass]="{
|
||||
available: marketDataByMonth[itemByMonth.key][i + 1]?.day == i + 1
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,16 @@
|
||||
@import '~apps/client/src/styles/ghostfolio-style';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
.day {
|
||||
background-color: var(--danger);
|
||||
height: 0.5rem;
|
||||
margin-right: 0.25rem;
|
||||
width: 0.5rem;
|
||||
|
||||
&.available {
|
||||
background-color: var(--success);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||
import { MarketData } from '@prisma/client';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'gf-admin-market-data-detail',
|
||||
styleUrls: ['./admin-market-data-detail.component.scss'],
|
||||
templateUrl: './admin-market-data-detail.component.html'
|
||||
})
|
||||
export class AdminMarketDataDetailComponent implements OnChanges, OnInit {
|
||||
@Input() marketData: MarketData[];
|
||||
|
||||
public days = Array(31);
|
||||
public defaultDateFormat = DEFAULT_DATE_FORMAT;
|
||||
public marketDataByMonth: {
|
||||
[yearMonth: string]: { [day: string]: MarketData & { day: number } };
|
||||
} = {};
|
||||
|
||||
public constructor() {}
|
||||
|
||||
public ngOnInit() {}
|
||||
|
||||
public ngOnChanges() {
|
||||
this.marketDataByMonth = {};
|
||||
|
||||
for (const marketDataItem of this.marketData) {
|
||||
const currentDay = parseInt(format(marketDataItem.date, 'd'), 10);
|
||||
const key = format(marketDataItem.date, 'yyyy-MM');
|
||||
|
||||
if (!this.marketDataByMonth[key]) {
|
||||
this.marketDataByMonth[key] = {};
|
||||
}
|
||||
|
||||
this.marketDataByMonth[key][currentDay] = {
|
||||
...marketDataItem,
|
||||
day: currentDay
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
|
||||
import { AdminMarketDataDetailComponent } from './admin-market-data-detail.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AdminMarketDataDetailComponent],
|
||||
exports: [AdminMarketDataDetailComponent],
|
||||
imports: [CommonModule],
|
||||
providers: [],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GfAdminMarketDataDetailModule {}
|
@ -0,0 +1,82 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
||||
import { MarketData } from '@prisma/client';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'gf-admin-market-data',
|
||||
styleUrls: ['./admin-market-data.scss'],
|
||||
templateUrl: './admin-market-data.html'
|
||||
})
|
||||
export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
||||
public currentSymbol: string;
|
||||
public defaultDateFormat = DEFAULT_DATE_FORMAT;
|
||||
public marketData: AdminMarketDataItem[] = [];
|
||||
public marketDataDetails: MarketData[] = [];
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private dataService: DataService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Initializes the controller
|
||||
*/
|
||||
public ngOnInit() {
|
||||
this.fetchAdminMarketData();
|
||||
}
|
||||
|
||||
public setCurrentSymbol(aSymbol: string) {
|
||||
this.marketDataDetails = [];
|
||||
|
||||
if (this.currentSymbol === aSymbol) {
|
||||
this.currentSymbol = '';
|
||||
} else {
|
||||
this.currentSymbol = aSymbol;
|
||||
|
||||
this.fetchAdminMarketDataBySymbol(this.currentSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
|
||||
private fetchAdminMarketData() {
|
||||
this.dataService
|
||||
.fetchAdminMarketData()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketData }) => {
|
||||
this.marketData = marketData;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private fetchAdminMarketDataBySymbol(aSymbol: string) {
|
||||
this.dataService
|
||||
.fetchAdminMarketDataBySymbol(aSymbol)
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketData }) => {
|
||||
this.marketDataDetails = marketData;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
|
||||
|
||||
import { AdminMarketDataComponent } from './admin-market-data.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AdminMarketDataComponent],
|
||||
imports: [CommonModule, GfAdminMarketDataDetailModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GfAdminMarketDataModule {}
|
@ -0,0 +1,5 @@
|
||||
@import '~apps/client/src/styles/ghostfolio-style';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { MarketData } from '@prisma/client';
|
||||
|
||||
export interface AdminMarketDataDetails {
|
||||
marketData: MarketData[];
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export interface AdminMarketData {
|
||||
marketData: AdminMarketDataItem[];
|
||||
}
|
||||
|
||||
export interface AdminMarketDataItem {
|
||||
symbol: string;
|
||||
}
|
Loading…
Reference in new issue