mirror of https://github.com/ghostfolio/ghostfolio
Thomas Kaul
3 years ago
committed by
GitHub
27 changed files with 438 additions and 57 deletions
@ -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,37 @@ |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<div class="col"> |
|||
<table class="gf-table w-100"> |
|||
<thead> |
|||
<tr class="mat-header-row"> |
|||
<th class="mat-header-cell px-1 py-2 text-right" i18n>#</th> |
|||
<th class="mat-header-cell px-1 py-2" i18n>Symbol</th> |
|||
<th class="mat-header-cell px-1 py-2" i18n>First transaction</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<ng-container *ngFor="let item of marketData; let i = index"> |
|||
<tr |
|||
class="cursor-pointer mat-row" |
|||
(click)="setCurrentSymbol(item.symbol)" |
|||
> |
|||
<td class="mat-cell px-1 py-2 text-right">{{ i + 1 }}</td> |
|||
<td class="mat-cell px-1 py-2">{{ item.symbol }}</td> |
|||
<td class="mat-cell px-1 py-2"> |
|||
{{ (item.date | date: defaultDateFormat) ?? '' }} |
|||
</td> |
|||
</tr> |
|||
<tr *ngIf="currentSymbol === item.symbol" class="mat-row"> |
|||
<td></td> |
|||
<td colspan="2"> |
|||
<gf-admin-market-data-detail |
|||
[marketData]="marketDataDetails" |
|||
></gf-admin-market-data-detail> |
|||
</td> |
|||
</tr> |
|||
</ng-container> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
</div> |
@ -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