Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
00a2b60eb5 | |||
fcbf2f1645 | |||
460266a501 | |||
9fe90273c7 | |||
4078229fe6 | |||
609c03f174 | |||
e7d4641d13 |
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 2.49.0 - 2024-02-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a button to apply the active filters in the assistant
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Moved the assistant from experimental to general availability
|
||||||
|
- Improved the usability by reloading the content with a logo click on the home page
|
||||||
|
- Upgraded `yahoo-finance2` from version `2.9.0` to `2.9.1`
|
||||||
|
|
||||||
## 2.48.1 - 2024-02-06
|
## 2.48.1 - 2024-02-06
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -280,6 +280,10 @@ Not sure what to work on? We have got some ideas. Please join the Ghostfolio [Sl
|
|||||||
|
|
||||||
If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio).
|
If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio).
|
||||||
|
|
||||||
|
## Analytics
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
© 2021 - 2024 [Ghostfolio](https://ghostfol.io)
|
© 2021 - 2024 [Ghostfolio](https://ghostfol.io)
|
||||||
|
@ -10,7 +10,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|||||||
import { Sort, SortDirection } from '@angular/material/sort';
|
import { Sort, SortDirection } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { downloadAsFile } from '@ghostfolio/common/helper';
|
import { downloadAsFile } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
@ -43,7 +42,6 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
public currency: string;
|
public currency: string;
|
||||||
public dataSource: MatTableDataSource<OrderWithAccount>;
|
public dataSource: MatTableDataSource<OrderWithAccount>;
|
||||||
public equity: number;
|
public equity: number;
|
||||||
public hasImpersonationId: boolean;
|
|
||||||
public hasPermissionToDeleteAccountBalance: boolean;
|
public hasPermissionToDeleteAccountBalance: boolean;
|
||||||
public historicalDataItems: HistoricalDataItem[];
|
public historicalDataItems: HistoricalDataItem[];
|
||||||
public holdings: PortfolioPosition[];
|
public holdings: PortfolioPosition[];
|
||||||
@ -65,7 +63,6 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
@Inject(MAT_DIALOG_DATA) public data: AccountDetailDialogParams,
|
@Inject(MAT_DIALOG_DATA) public data: AccountDetailDialogParams,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
public dialogRef: MatDialogRef<AccountDetailDialog>,
|
public dialogRef: MatDialogRef<AccountDetailDialog>,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
@ -136,13 +133,6 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.impersonationStorageService
|
|
||||||
.onChangeHasImpersonation()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((impersonationId) => {
|
|
||||||
this.hasImpersonationId = !!impersonationId;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.fetchAccountBalances();
|
this.fetchAccountBalances();
|
||||||
this.fetchActivities();
|
this.fetchActivities();
|
||||||
this.fetchPortfolioPerformance();
|
this.fetchPortfolioPerformance();
|
||||||
@ -165,17 +155,9 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onExport() {
|
public onExport() {
|
||||||
let activityIds = [];
|
let activityIds = this.dataSource.data.map(({ id }) => {
|
||||||
|
return id;
|
||||||
if (this.user?.settings?.isExperimentalFeatures === true) {
|
});
|
||||||
activityIds = this.dataSource.data.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
activityIds = this.activities.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchExport({ activityIds })
|
.fetchExport({ activityIds })
|
||||||
@ -215,36 +197,21 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
private fetchActivities() {
|
private fetchActivities() {
|
||||||
this.isLoadingActivities = true;
|
this.isLoadingActivities = true;
|
||||||
|
|
||||||
if (this.user?.settings?.isExperimentalFeatures === true) {
|
this.dataService
|
||||||
this.dataService
|
.fetchActivities({
|
||||||
.fetchActivities({
|
filters: [{ id: this.data.accountId, type: 'ACCOUNT' }],
|
||||||
filters: [{ id: this.data.accountId, type: 'ACCOUNT' }],
|
sortColumn: this.sortColumn,
|
||||||
sortColumn: this.sortColumn,
|
sortDirection: this.sortDirection
|
||||||
sortDirection: this.sortDirection
|
})
|
||||||
})
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.subscribe(({ activities, count }) => {
|
||||||
.subscribe(({ activities, count }) => {
|
this.dataSource = new MatTableDataSource(activities);
|
||||||
this.dataSource = new MatTableDataSource(activities);
|
this.totalItems = count;
|
||||||
this.totalItems = count;
|
|
||||||
|
|
||||||
this.isLoadingActivities = false;
|
this.isLoadingActivities = false;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.dataService
|
|
||||||
.fetchActivities({
|
|
||||||
filters: [{ id: this.data.accountId, type: 'ACCOUNT' }]
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ activities }) => {
|
|
||||||
this.activities = activities;
|
|
||||||
|
|
||||||
this.isLoadingActivities = false;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchPortfolioPerformance() {
|
private fetchPortfolioPerformance() {
|
||||||
@ -268,7 +235,8 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
return {
|
return {
|
||||||
date,
|
date,
|
||||||
value:
|
value:
|
||||||
this.hasImpersonationId || this.user.settings.isRestrictedView
|
this.data.hasImpersonationId ||
|
||||||
|
this.user.settings.isRestrictedView
|
||||||
? netWorthInPercentage
|
? netWorthInPercentage
|
||||||
: netWorth
|
: netWorth
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
class="h-100"
|
class="h-100"
|
||||||
[currency]="user?.settings?.baseCurrency"
|
[currency]="user?.settings?.baseCurrency"
|
||||||
[historicalDataItems]="historicalDataItems"
|
[historicalDataItems]="historicalDataItems"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="data.hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[isLoading]="isLoadingChart"
|
[isLoading]="isLoadingChart"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
/>
|
/>
|
||||||
@ -86,13 +86,12 @@
|
|||||||
<ion-icon name="swap-vertical-outline" />
|
<ion-icon name="swap-vertical-outline" />
|
||||||
<div class="d-none d-sm-block ml-2" i18n>Activities</div>
|
<div class="d-none d-sm-block ml-2" i18n>Activities</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<gf-activities-table-lazy
|
<gf-activities-table
|
||||||
*ngIf="user?.settings?.isExperimentalFeatures === true"
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="data.deviceType"
|
[deviceType]="data.deviceType"
|
||||||
[hasPermissionToCreateActivity]="false"
|
[hasPermissionToCreateActivity]="false"
|
||||||
[hasPermissionToExportActivities]="!hasImpersonationId && !user.settings.isRestrictedView"
|
[hasPermissionToExportActivities]="!data.hasImpersonationId && !user.settings.isRestrictedView"
|
||||||
[hasPermissionToFilter]="false"
|
[hasPermissionToFilter]="false"
|
||||||
[hasPermissionToOpenDetails]="false"
|
[hasPermissionToOpenDetails]="false"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -103,19 +102,6 @@
|
|||||||
(export)="onExport()"
|
(export)="onExport()"
|
||||||
(sortChanged)="onSortChanged($event)"
|
(sortChanged)="onSortChanged($event)"
|
||||||
/>
|
/>
|
||||||
<gf-activities-table
|
|
||||||
*ngIf="user?.settings?.isExperimentalFeatures !== true"
|
|
||||||
[activities]="activities"
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[deviceType]="data.deviceType"
|
|
||||||
[hasPermissionToCreateActivity]="false"
|
|
||||||
[hasPermissionToExportActivities]="true"
|
|
||||||
[hasPermissionToFilter]="false"
|
|
||||||
[hasPermissionToOpenDetails]="false"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[showActions]="false"
|
|
||||||
(export)="onExport()"
|
|
||||||
/>
|
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab>
|
<mat-tab>
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
@ -126,7 +112,7 @@
|
|||||||
[accountBalances]="accountBalances"
|
[accountBalances]="accountBalances"
|
||||||
[accountId]="data.accountId"
|
[accountId]="data.accountId"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[showActions]="!hasImpersonationId && hasPermissionToDeleteAccountBalance && !user.settings.isRestrictedView"
|
[showActions]="!data.hasImpersonationId && hasPermissionToDeleteAccountBalance && !user.settings.isRestrictedView"
|
||||||
(accountBalanceDeleted)="onDeleteAccountBalance($event)"
|
(accountBalanceDeleted)="onDeleteAccountBalance($event)"
|
||||||
/>
|
/>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
@ -8,7 +8,6 @@ import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-heade
|
|||||||
import { GfHoldingsTableModule } from '@ghostfolio/ui/holdings-table/holdings-table.module';
|
import { GfHoldingsTableModule } from '@ghostfolio/ui/holdings-table/holdings-table.module';
|
||||||
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
||||||
import { GfAccountBalancesModule } from '@ghostfolio/ui/account-balances/account-balances.module';
|
import { GfAccountBalancesModule } from '@ghostfolio/ui/account-balances/account-balances.module';
|
||||||
import { GfActivitiesTableLazyModule } from '@ghostfolio/ui/activities-table-lazy/activities-table-lazy.module';
|
|
||||||
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
@ -21,7 +20,6 @@ import { AccountDetailDialog } from './account-detail-dialog.component';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
GfAccountBalancesModule,
|
GfAccountBalancesModule,
|
||||||
GfActivitiesTableModule,
|
GfActivitiesTableModule,
|
||||||
GfActivitiesTableLazyModule,
|
|
||||||
GfDialogFooterModule,
|
GfDialogFooterModule,
|
||||||
GfDialogHeaderModule,
|
GfDialogHeaderModule,
|
||||||
GfHoldingsTableModule,
|
GfHoldingsTableModule,
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
mat-button
|
mat-button
|
||||||
[ngClass]="{ 'w-100': hasTabs }"
|
[ngClass]="{ 'w-100': hasTabs }"
|
||||||
[routerLink]="['/']"
|
[routerLink]="['/']"
|
||||||
|
(click)="onLogoClick()"
|
||||||
>
|
>
|
||||||
<gf-logo class="px-2" [label]="pageTitle" />
|
<gf-logo class="px-2" [label]="pageTitle" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span class="spacer"></span>
|
<span class="gf-spacer"></span>
|
||||||
<ul class="alig-items-center d-flex list-inline m-0 px-2">
|
<ul class="alig-items-center d-flex list-inline m-0 px-2">
|
||||||
<li class="list-inline-item">
|
<li class="list-inline-item">
|
||||||
<a
|
<a
|
||||||
@ -119,11 +120,7 @@
|
|||||||
[matMenuTriggerRestoreFocus]="false"
|
[matMenuTriggerRestoreFocus]="false"
|
||||||
(menuOpened)="onOpenAssistant()"
|
(menuOpened)="onOpenAssistant()"
|
||||||
>
|
>
|
||||||
@if (user?.settings?.isExperimentalFeatures) {
|
<ion-icon class="rotate-90" name="options-outline" />
|
||||||
<ion-icon class="rotate-90" name="options-outline" />
|
|
||||||
} @else {
|
|
||||||
<ion-icon name="search-outline" />
|
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
<mat-menu
|
<mat-menu
|
||||||
#assistantMenu="matMenu"
|
#assistantMenu="matMenu"
|
||||||
@ -324,7 +321,7 @@
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span class="spacer"></span>
|
<span class="gf-spacer"></span>
|
||||||
<ul class="alig-items-center d-flex list-inline m-0 px-2">
|
<ul class="alig-items-center d-flex list-inline m-0 px-2">
|
||||||
<li class="list-inline-item">
|
<li class="list-inline-item">
|
||||||
<a
|
<a
|
||||||
|
@ -38,10 +38,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
||||||
import { LoginWithAccessTokenDialog } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component';
|
import { LoginWithAccessTokenDialog } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component';
|
||||||
|
import { LayoutService } from '@ghostfolio/client/core/layout.service';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import {
|
import {
|
||||||
@ -89,6 +90,7 @@ export class HeaderComponent implements OnChanges {
|
|||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
|
private layoutService: LayoutService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private settingsStorageService: SettingsStorageService,
|
private settingsStorageService: SettingsStorageService,
|
||||||
private tokenStorageService: TokenStorageService,
|
private tokenStorageService: TokenStorageService,
|
||||||
@ -192,6 +194,12 @@ export class HeaderComponent implements OnChanges {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onLogoClick() {
|
||||||
|
if (this.currentRoute === 'home' || this.currentRoute === 'zen') {
|
||||||
|
this.layoutService.getShouldReloadSubject().next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public onMenuClosed() {
|
public onMenuClosed() {
|
||||||
this.isMenuOpen = false;
|
this.isMenuOpen = false;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
<div class="container justify-content-center p-3">
|
<div class="container justify-content-center p-3">
|
||||||
<div *ngIf="!user?.settings?.isExperimentalFeatures" class="mb-3 text-center">
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="user?.settings?.dateRange"
|
|
||||||
[isLoading]="positions === undefined"
|
|
||||||
[options]="dateRangeOptions"
|
|
||||||
(change)="onChangeDateRange($event.value)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
|
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
|
||||||
<mat-card appearance="outlined">
|
<mat-card appearance="outlined">
|
||||||
|
@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
|||||||
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
|
import { LayoutService } from '@ghostfolio/client/core/layout.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import {
|
||||||
LineChartItem,
|
LineChartItem,
|
||||||
@ -43,6 +44,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
|
private layoutService: LayoutService,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
@ -73,6 +75,12 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.layoutService.shouldReloadContent$
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.update();
|
||||||
|
});
|
||||||
|
|
||||||
this.showDetails =
|
this.showDetails =
|
||||||
!this.user.settings.isRestrictedView &&
|
!this.user.settings.isRestrictedView &&
|
||||||
this.user.settings.viewMode !== 'ZEN';
|
this.user.settings.viewMode !== 'ZEN';
|
||||||
|
@ -96,17 +96,6 @@
|
|||||||
[showDetails]="showDetails"
|
[showDetails]="showDetails"
|
||||||
[unit]="unit"
|
[unit]="unit"
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
*ngIf="showDetails && !user?.settings?.isExperimentalFeatures"
|
|
||||||
class="text-center"
|
|
||||||
>
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="user?.settings?.dateRange"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[options]="dateRangeOptions"
|
|
||||||
(change)="onChangeDateRange($event.value)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -3,7 +3,6 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module';
|
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module';
|
||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
|
||||||
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
||||||
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ import { HomeOverviewComponent } from './home-overview.component';
|
|||||||
GfLineChartModule,
|
GfLineChartModule,
|
||||||
GfNoTransactionsInfoModule,
|
GfNoTransactionsInfoModule,
|
||||||
GfPortfolioPerformanceModule,
|
GfPortfolioPerformanceModule,
|
||||||
GfToggleModule,
|
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
|
@ -268,17 +268,9 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onExport() {
|
public onExport() {
|
||||||
let activityIds = [];
|
let activityIds = this.dataSource.data.map(({ id }) => {
|
||||||
|
return id;
|
||||||
if (this.user?.settings?.isExperimentalFeatures === true) {
|
});
|
||||||
activityIds = this.dataSource.data.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
activityIds = this.activities.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchExport({ activityIds })
|
.fetchExport({ activityIds })
|
||||||
|
@ -249,13 +249,12 @@
|
|||||||
<div class="row" [ngClass]="{ 'd-none': !activities?.length }">
|
<div class="row" [ngClass]="{ 'd-none': !activities?.length }">
|
||||||
<div class="col mb-3">
|
<div class="col mb-3">
|
||||||
<div class="h5 mb-0" i18n>Activities</div>
|
<div class="h5 mb-0" i18n>Activities</div>
|
||||||
<gf-activities-table-lazy
|
<gf-activities-table
|
||||||
*ngIf="user?.settings?.isExperimentalFeatures === true"
|
|
||||||
[baseCurrency]="data.baseCurrency"
|
[baseCurrency]="data.baseCurrency"
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="data.deviceType"
|
[deviceType]="data.deviceType"
|
||||||
[hasPermissionToCreateActivity]="false"
|
[hasPermissionToCreateActivity]="false"
|
||||||
[hasPermissionToExportActivities]="!hasImpersonationId && !user.settings.isRestrictedView"
|
[hasPermissionToExportActivities]="!data.hasImpersonationId && !user.settings.isRestrictedView"
|
||||||
[hasPermissionToFilter]="false"
|
[hasPermissionToFilter]="false"
|
||||||
[hasPermissionToOpenDetails]="false"
|
[hasPermissionToOpenDetails]="false"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
@ -267,20 +266,6 @@
|
|||||||
[totalItems]="totalItems"
|
[totalItems]="totalItems"
|
||||||
(export)="onExport()"
|
(export)="onExport()"
|
||||||
/>
|
/>
|
||||||
<gf-activities-table
|
|
||||||
*ngIf="user?.settings?.isExperimentalFeatures !== true"
|
|
||||||
[activities]="activities"
|
|
||||||
[baseCurrency]="data.baseCurrency"
|
|
||||||
[deviceType]="data.deviceType"
|
|
||||||
[hasPermissionToCreateActivity]="false"
|
|
||||||
[hasPermissionToExportActivities]="true"
|
|
||||||
[hasPermissionToFilter]="false"
|
|
||||||
[hasPermissionToOpenDetails]="false"
|
|
||||||
[locale]="data.locale"
|
|
||||||
[showActions]="false"
|
|
||||||
[showNameColumn]="false"
|
|
||||||
(export)="onExport()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import { MatChipsModule } from '@angular/material/chips';
|
|||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
|
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
|
||||||
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
|
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
|
||||||
import { GfActivitiesTableLazyModule } from '@ghostfolio/ui/activities-table-lazy/activities-table-lazy.module';
|
|
||||||
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
||||||
import { GfDataProviderCreditsModule } from '@ghostfolio/ui/data-provider-credits/data-provider-credits.module';
|
import { GfDataProviderCreditsModule } from '@ghostfolio/ui/data-provider-credits/data-provider-credits.module';
|
||||||
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
||||||
@ -20,7 +19,6 @@ import { PositionDetailDialog } from './position-detail-dialog.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfActivitiesTableModule,
|
GfActivitiesTableModule,
|
||||||
GfActivitiesTableLazyModule,
|
|
||||||
GfDataProviderCreditsModule,
|
GfDataProviderCreditsModule,
|
||||||
GfDialogFooterModule,
|
GfDialogFooterModule,
|
||||||
GfDialogHeaderModule,
|
GfDialogHeaderModule,
|
||||||
|
19
apps/client/src/app/core/layout.service.ts
Normal file
19
apps/client/src/app/core/layout.service.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LayoutService {
|
||||||
|
public shouldReloadContent$: Observable<void>;
|
||||||
|
|
||||||
|
private shouldReloadSubject = new Subject<void>();
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.shouldReloadContent$ = this.shouldReloadSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getShouldReloadSubject() {
|
||||||
|
return this.shouldReloadSubject;
|
||||||
|
}
|
||||||
|
}
|
@ -121,43 +121,25 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchActivities() {
|
public fetchActivities() {
|
||||||
if (this.user?.settings?.isExperimentalFeatures === true) {
|
this.dataService
|
||||||
this.dataService
|
.fetchActivities({
|
||||||
.fetchActivities({
|
filters: this.userService.getFilters(),
|
||||||
filters: this.userService.getFilters(),
|
skip: this.pageIndex * this.pageSize,
|
||||||
skip: this.pageIndex * this.pageSize,
|
sortColumn: this.sortColumn,
|
||||||
sortColumn: this.sortColumn,
|
sortDirection: this.sortDirection,
|
||||||
sortDirection: this.sortDirection,
|
take: this.pageSize
|
||||||
take: this.pageSize
|
})
|
||||||
})
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.subscribe(({ activities, count }) => {
|
||||||
.subscribe(({ activities, count }) => {
|
this.dataSource = new MatTableDataSource(activities);
|
||||||
this.dataSource = new MatTableDataSource(activities);
|
this.totalItems = count;
|
||||||
this.totalItems = count;
|
|
||||||
|
|
||||||
if (this.hasPermissionToCreateActivity && this.totalItems <= 0) {
|
if (this.hasPermissionToCreateActivity && this.totalItems <= 0) {
|
||||||
this.router.navigate([], { queryParams: { createDialog: true } });
|
this.router.navigate([], { queryParams: { createDialog: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.dataService
|
|
||||||
.fetchActivities({})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ activities }) => {
|
|
||||||
this.activities = activities;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.hasPermissionToCreateActivity &&
|
|
||||||
this.activities?.length <= 0
|
|
||||||
) {
|
|
||||||
this.router.navigate([], { queryParams: { createDialog: true } });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangePage(page: PageEvent) {
|
public onChangePage(page: PageEvent) {
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Activities</h1>
|
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Activities</h1>
|
||||||
<gf-activities-table-lazy
|
<gf-activities-table
|
||||||
*ngIf="user?.settings?.isExperimentalFeatures === true"
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
@ -27,24 +26,6 @@
|
|||||||
(pageChanged)="onChangePage($event)"
|
(pageChanged)="onChangePage($event)"
|
||||||
(sortChanged)="onSortChanged($event)"
|
(sortChanged)="onSortChanged($event)"
|
||||||
/>
|
/>
|
||||||
<gf-activities-table
|
|
||||||
*ngIf="user?.settings?.isExperimentalFeatures !== true"
|
|
||||||
[activities]="activities"
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[deviceType]="deviceType"
|
|
||||||
[hasPermissionToCreateActivity]="hasPermissionToCreateActivity"
|
|
||||||
[hasPermissionToExportActivities]="!hasImpersonationId"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[showActions]="!hasImpersonationId && hasPermissionToDeleteActivity && !user.settings.isRestrictedView"
|
|
||||||
(activityDeleted)="onDeleteActivity($event)"
|
|
||||||
(activityToClone)="onCloneActivity($event)"
|
|
||||||
(activityToUpdate)="onUpdateActivity($event)"
|
|
||||||
(deleteAllActivities)="onDeleteAllActivities()"
|
|
||||||
(export)="onExport($event)"
|
|
||||||
(exportDrafts)="onExportDrafts($event)"
|
|
||||||
(import)="onImport()"
|
|
||||||
(importDividends)="onImportDividends()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service';
|
import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service';
|
||||||
import { GfActivitiesTableLazyModule } from '@ghostfolio/ui/activities-table-lazy/activities-table-lazy.module';
|
|
||||||
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
||||||
|
|
||||||
import { ActivitiesPageRoutingModule } from './activities-page-routing.module';
|
import { ActivitiesPageRoutingModule } from './activities-page-routing.module';
|
||||||
@ -18,7 +17,6 @@ import { GfImportActivitiesDialogModule } from './import-activities-dialog/impor
|
|||||||
ActivitiesPageRoutingModule,
|
ActivitiesPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfActivitiesTableModule,
|
GfActivitiesTableModule,
|
||||||
GfActivitiesTableLazyModule,
|
|
||||||
GfCreateOrUpdateActivityDialogModule,
|
GfCreateOrUpdateActivityDialogModule,
|
||||||
GfImportActivitiesDialogModule,
|
GfImportActivitiesDialogModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
@ -116,8 +116,8 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="pt-3">
|
<div class="pt-3">
|
||||||
<ng-container *ngIf="errorMessages?.length === 0; else errorMessage">
|
<ng-container *ngIf="errorMessages?.length === 0; else errorMessage">
|
||||||
<gf-activities-table-lazy
|
<gf-activities-table
|
||||||
*ngIf="importStep === 1 && data?.user?.settings?.isExperimentalFeatures === true"
|
*ngIf="importStep === 1"
|
||||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="data?.deviceType"
|
[deviceType]="data?.deviceType"
|
||||||
@ -137,23 +137,6 @@
|
|||||||
[totalItems]="totalItems"
|
[totalItems]="totalItems"
|
||||||
(selectedActivities)="updateSelection($event)"
|
(selectedActivities)="updateSelection($event)"
|
||||||
/>
|
/>
|
||||||
<gf-activities-table
|
|
||||||
*ngIf="importStep === 1 && data?.user?.settings?.isExperimentalFeatures !== true"
|
|
||||||
[activities]="activities"
|
|
||||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
|
||||||
[deviceType]="data?.deviceType"
|
|
||||||
[hasPermissionToCreateActivity]="false"
|
|
||||||
[hasPermissionToExportActivities]="false"
|
|
||||||
[hasPermissionToFilter]="false"
|
|
||||||
[hasPermissionToOpenDetails]="false"
|
|
||||||
[locale]="data?.user?.settings?.locale"
|
|
||||||
[pageSize]="maxSafeInteger"
|
|
||||||
[showActions]="false"
|
|
||||||
[showCheckbox]="true"
|
|
||||||
[showFooter]="false"
|
|
||||||
[showSymbolColumn]="false"
|
|
||||||
(selectedActivities)="updateSelection($event)"
|
|
||||||
/>
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<button mat-button (click)="onReset(stepper)">
|
<button mat-button (click)="onReset(stepper)">
|
||||||
<ng-container i18n>Back</ng-container>
|
<ng-container i18n>Back</ng-container>
|
||||||
|
@ -13,7 +13,6 @@ import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-heade
|
|||||||
import { GfFileDropModule } from '@ghostfolio/client/directives/file-drop/file-drop.module';
|
import { GfFileDropModule } from '@ghostfolio/client/directives/file-drop/file-drop.module';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
|
||||||
import { GfActivitiesTableLazyModule } from '@ghostfolio/ui/activities-table-lazy/activities-table-lazy.module';
|
|
||||||
|
|
||||||
import { ImportActivitiesDialog } from './import-activities-dialog.component';
|
import { ImportActivitiesDialog } from './import-activities-dialog.component';
|
||||||
|
|
||||||
@ -23,7 +22,6 @@ import { ImportActivitiesDialog } from './import-activities-dialog.component';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
GfActivitiesTableModule,
|
GfActivitiesTableModule,
|
||||||
GfActivitiesTableLazyModule,
|
|
||||||
GfDialogFooterModule,
|
GfDialogFooterModule,
|
||||||
GfDialogHeaderModule,
|
GfDialogHeaderModule,
|
||||||
GfFileDropModule,
|
GfFileDropModule,
|
||||||
|
@ -11,7 +11,6 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import { prettifySymbol } from '@ghostfolio/common/helper';
|
import { prettifySymbol } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
Filter,
|
|
||||||
PortfolioDetails,
|
PortfolioDetails,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
UniqueAsset,
|
UniqueAsset,
|
||||||
@ -24,7 +23,7 @@ import { Account, AssetClass, DataSource, Platform } from '@prisma/client';
|
|||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-allocations-page',
|
selector: 'gf-allocations-page',
|
||||||
@ -38,8 +37,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
public activeFilters: Filter[] = [];
|
|
||||||
public allFilters: Filter[];
|
|
||||||
public continents: {
|
public continents: {
|
||||||
[code: string]: { name: string; value: number };
|
[code: string]: { name: string; value: number };
|
||||||
};
|
};
|
||||||
@ -47,7 +44,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
[code: string]: { name: string; value: number };
|
[code: string]: { name: string; value: number };
|
||||||
};
|
};
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public filters$ = new Subject<Filter[]>();
|
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public isLoading = false;
|
public isLoading = false;
|
||||||
public markets: {
|
public markets: {
|
||||||
@ -60,7 +56,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
public placeholder = '';
|
|
||||||
public platforms: {
|
public platforms: {
|
||||||
[id: string]: Pick<Platform, 'name'> & {
|
[id: string]: Pick<Platform, 'name'> & {
|
||||||
id: string;
|
id: string;
|
||||||
@ -135,98 +130,34 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.hasImpersonationId = !!impersonationId;
|
this.hasImpersonationId = !!impersonationId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filters$
|
|
||||||
.pipe(
|
|
||||||
distinctUntilChanged(),
|
|
||||||
switchMap((filters) => {
|
|
||||||
this.isLoading = true;
|
|
||||||
this.activeFilters = filters;
|
|
||||||
this.placeholder =
|
|
||||||
this.activeFilters.length <= 0
|
|
||||||
? $localize`Filter by account or tag...`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
this.initialize();
|
|
||||||
|
|
||||||
return this.fetchPortfolioDetails();
|
|
||||||
}),
|
|
||||||
takeUntil(this.unsubscribeSubject)
|
|
||||||
)
|
|
||||||
.subscribe((portfolioDetails) => {
|
|
||||||
this.initialize();
|
|
||||||
|
|
||||||
this.portfolioDetails = portfolioDetails;
|
|
||||||
|
|
||||||
this.initializeAllocationsData();
|
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
if (state?.user) {
|
if (state?.user) {
|
||||||
this.user = state.user;
|
this.user = state.user;
|
||||||
|
|
||||||
const accountFilters: Filter[] = this.user.accounts.map(
|
|
||||||
({ id, name }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: name,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const assetClassFilters: Filter[] = [];
|
|
||||||
for (const assetClass of Object.keys(AssetClass)) {
|
|
||||||
assetClassFilters.push({
|
|
||||||
id: assetClass,
|
|
||||||
label: translate(assetClass),
|
|
||||||
type: 'ASSET_CLASS'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: translate(name),
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.allFilters = [
|
|
||||||
...accountFilters,
|
|
||||||
...assetClassFilters,
|
|
||||||
...tagFilters
|
|
||||||
];
|
|
||||||
|
|
||||||
this.worldMapChartFormat =
|
this.worldMapChartFormat =
|
||||||
this.hasImpersonationId || this.user.settings.isRestrictedView
|
this.hasImpersonationId || this.user.settings.isRestrictedView
|
||||||
? `{0}%`
|
? `{0}%`
|
||||||
: `{0} ${this.user?.settings?.baseCurrency}`;
|
: `{0} ${this.user?.settings?.baseCurrency}`;
|
||||||
|
|
||||||
if (this.user?.settings?.isExperimentalFeatures === true) {
|
this.isLoading = true;
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
this.fetchPortfolioDetails()
|
this.fetchPortfolioDetails()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((portfolioDetails) => {
|
.subscribe((portfolioDetails) => {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
this.portfolioDetails = portfolioDetails;
|
this.portfolioDetails = portfolioDetails;
|
||||||
|
|
||||||
this.initializeAllocationsData();
|
this.initializeAllocationsData();
|
||||||
|
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
@ -273,10 +204,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
private fetchPortfolioDetails() {
|
private fetchPortfolioDetails() {
|
||||||
return this.dataService.fetchPortfolioDetails({
|
return this.dataService.fetchPortfolioDetails({
|
||||||
filters:
|
filters: this.userService.getFilters()
|
||||||
this.activeFilters.length > 0
|
|
||||||
? this.activeFilters
|
|
||||||
: this.userService.getFilters()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,6 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Allocations</h1>
|
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Allocations</h1>
|
||||||
@if (!user?.settings?.isExperimentalFeatures) {
|
|
||||||
<gf-activities-filter
|
|
||||||
[allFilters]="allFilters"
|
|
||||||
[isLoading]="isLoading"
|
|
||||||
[placeholder]="placeholder"
|
|
||||||
(valueChanged)="filters$.next($event)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -4,7 +4,6 @@ import { MatCardModule } from '@angular/material/card';
|
|||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
|
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
|
||||||
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
|
|
||||||
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
|
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
|
||||||
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
|
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
@ -17,7 +16,6 @@ import { AllocationsPageComponent } from './allocations-page.component';
|
|||||||
imports: [
|
imports: [
|
||||||
AllocationsPageRoutingModule,
|
AllocationsPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfActivitiesFilterModule,
|
|
||||||
GfPortfolioProportionChartModule,
|
GfPortfolioProportionChartModule,
|
||||||
GfPremiumIndicatorModule,
|
GfPremiumIndicatorModule,
|
||||||
GfWorldMapChartModule,
|
GfWorldMapChartModule,
|
||||||
|
@ -8,7 +8,6 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
|||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import {
|
||||||
Filter,
|
|
||||||
HistoricalDataItem,
|
HistoricalDataItem,
|
||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
@ -17,14 +16,14 @@ import {
|
|||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
import { GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { AssetClass, DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { isNumber, sortBy } from 'lodash';
|
import { isNumber, sortBy } from 'lodash';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-analysis-page',
|
selector: 'gf-analysis-page',
|
||||||
@ -32,8 +31,6 @@ import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
|
|||||||
templateUrl: './analysis-page.html'
|
templateUrl: './analysis-page.html'
|
||||||
})
|
})
|
||||||
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||||
public activeFilters: Filter[] = [];
|
|
||||||
public allFilters: Filter[];
|
|
||||||
public benchmarkDataItems: HistoricalDataItem[] = [];
|
public benchmarkDataItems: HistoricalDataItem[] = [];
|
||||||
public benchmarks: Partial<SymbolProfile>[];
|
public benchmarks: Partial<SymbolProfile>[];
|
||||||
public bottom3: Position[];
|
public bottom3: Position[];
|
||||||
@ -42,7 +39,6 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public dividendsByGroup: InvestmentItem[];
|
public dividendsByGroup: InvestmentItem[];
|
||||||
public dividendTimelineDataLabel = $localize`Dividend`;
|
public dividendTimelineDataLabel = $localize`Dividend`;
|
||||||
public filters$ = new Subject<Filter[]>();
|
|
||||||
public firstOrderDate: Date;
|
public firstOrderDate: Date;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public investments: InvestmentItem[];
|
public investments: InvestmentItem[];
|
||||||
@ -58,7 +54,6 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
public performance: PortfolioPerformance;
|
public performance: PortfolioPerformance;
|
||||||
public performanceDataItems: HistoricalDataItem[];
|
public performanceDataItems: HistoricalDataItem[];
|
||||||
public performanceDataItemsInPercentage: HistoricalDataItem[];
|
public performanceDataItemsInPercentage: HistoricalDataItem[];
|
||||||
public placeholder = '';
|
|
||||||
public portfolioEvolutionDataLabel = $localize`Investment`;
|
public portfolioEvolutionDataLabel = $localize`Investment`;
|
||||||
public streaks: PortfolioInvestments['streaks'];
|
public streaks: PortfolioInvestments['streaks'];
|
||||||
public top3: Position[];
|
public top3: Position[];
|
||||||
@ -118,61 +113,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.hasImpersonationId = !!impersonationId;
|
this.hasImpersonationId = !!impersonationId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filters$
|
|
||||||
.pipe(
|
|
||||||
distinctUntilChanged(),
|
|
||||||
map((filters) => {
|
|
||||||
this.activeFilters = filters;
|
|
||||||
this.placeholder =
|
|
||||||
this.activeFilters.length <= 0
|
|
||||||
? $localize`Filter by account or tag...`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
this.update();
|
|
||||||
}),
|
|
||||||
takeUntil(this.unsubscribeSubject)
|
|
||||||
)
|
|
||||||
.subscribe(() => {});
|
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
if (state?.user) {
|
if (state?.user) {
|
||||||
this.user = state.user;
|
this.user = state.user;
|
||||||
|
|
||||||
const accountFilters: Filter[] = this.user.accounts.map(
|
|
||||||
({ id, name }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: name,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const assetClassFilters: Filter[] = [];
|
|
||||||
for (const assetClass of Object.keys(AssetClass)) {
|
|
||||||
assetClassFilters.push({
|
|
||||||
id: assetClass,
|
|
||||||
label: translate(assetClass),
|
|
||||||
type: 'ASSET_CLASS'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: translate(name),
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.allFilters = [
|
|
||||||
...accountFilters,
|
|
||||||
...assetClassFilters,
|
|
||||||
...tagFilters
|
|
||||||
];
|
|
||||||
|
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -196,24 +142,6 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeDateRange(dateRange: DateRange) {
|
|
||||||
this.dataService
|
|
||||||
.putUserSetting({ dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.userService.remove();
|
|
||||||
|
|
||||||
this.userService
|
|
||||||
.get()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((user) => {
|
|
||||||
this.user = user;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangeGroupBy(aMode: GroupBy) {
|
public onChangeGroupBy(aMode: GroupBy) {
|
||||||
this.mode = aMode;
|
this.mode = aMode;
|
||||||
this.fetchDividendsAndInvestments();
|
this.fetchDividendsAndInvestments();
|
||||||
@ -227,10 +155,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
private fetchDividendsAndInvestments() {
|
private fetchDividendsAndInvestments() {
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchDividends({
|
.fetchDividends({
|
||||||
filters:
|
filters: this.userService.getFilters(),
|
||||||
this.activeFilters.length > 0
|
|
||||||
? this.activeFilters
|
|
||||||
: this.userService.getFilters(),
|
|
||||||
groupBy: this.mode,
|
groupBy: this.mode,
|
||||||
range: this.user?.settings?.dateRange
|
range: this.user?.settings?.dateRange
|
||||||
})
|
})
|
||||||
@ -243,10 +168,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchInvestments({
|
.fetchInvestments({
|
||||||
filters:
|
filters: this.userService.getFilters(),
|
||||||
this.activeFilters.length > 0
|
|
||||||
? this.activeFilters
|
|
||||||
: this.userService.getFilters(),
|
|
||||||
groupBy: this.mode,
|
groupBy: this.mode,
|
||||||
range: this.user?.settings?.dateRange
|
range: this.user?.settings?.dateRange
|
||||||
})
|
})
|
||||||
@ -321,10 +243,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioPerformance({
|
.fetchPortfolioPerformance({
|
||||||
filters:
|
filters: this.userService.getFilters(),
|
||||||
this.activeFilters.length > 0
|
|
||||||
? this.activeFilters
|
|
||||||
: this.userService.getFilters(),
|
|
||||||
range: this.user?.settings?.dateRange
|
range: this.user?.settings?.dateRange
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -370,10 +289,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPositions({
|
.fetchPositions({
|
||||||
filters:
|
filters: this.userService.getFilters(),
|
||||||
this.activeFilters.length > 0
|
|
||||||
? this.activeFilters
|
|
||||||
: this.userService.getFilters(),
|
|
||||||
range: this.user?.settings?.dateRange
|
range: this.user?.settings?.dateRange
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
@ -1,21 +1,5 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Analysis</h1>
|
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Analysis</h1>
|
||||||
@if (!user?.settings?.isExperimentalFeatures) {
|
|
||||||
<div class="my-4 text-center">
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="user?.settings?.dateRange"
|
|
||||||
[isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart"
|
|
||||||
[options]="dateRangeOptions"
|
|
||||||
(change)="onChangeDateRange($event.value)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<gf-activities-filter
|
|
||||||
[allFilters]="allFilters"
|
|
||||||
[isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart"
|
|
||||||
[placeholder]="placeholder"
|
|
||||||
(valueChanged)="filters$.next($event)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<gf-benchmark-comparator
|
<gf-benchmark-comparator
|
||||||
|
@ -7,17 +7,15 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
|||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import {
|
||||||
Filter,
|
|
||||||
PortfolioDetails,
|
PortfolioDetails,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { DataSource } from '@prisma/client';
|
||||||
import { AssetClass, DataSource } from '@prisma/client';
|
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-holdings-page',
|
selector: 'gf-holdings-page',
|
||||||
@ -25,15 +23,11 @@ import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
|
|||||||
templateUrl: './holdings-page.html'
|
templateUrl: './holdings-page.html'
|
||||||
})
|
})
|
||||||
export class HoldingsPageComponent implements OnDestroy, OnInit {
|
export class HoldingsPageComponent implements OnDestroy, OnInit {
|
||||||
public activeFilters: Filter[] = [];
|
|
||||||
public allFilters: Filter[];
|
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public filters$ = new Subject<Filter[]>();
|
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToCreateOrder: boolean;
|
public hasPermissionToCreateOrder: boolean;
|
||||||
public holdings: PortfolioPosition[];
|
public holdings: PortfolioPosition[];
|
||||||
public isLoading = false;
|
public isLoading = false;
|
||||||
public placeholder = '';
|
|
||||||
public portfolioDetails: PortfolioDetails;
|
public portfolioDetails: PortfolioDetails;
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -75,31 +69,6 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.hasImpersonationId = !!impersonationId;
|
this.hasImpersonationId = !!impersonationId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filters$
|
|
||||||
.pipe(
|
|
||||||
distinctUntilChanged(),
|
|
||||||
switchMap((filters) => {
|
|
||||||
this.isLoading = true;
|
|
||||||
this.activeFilters = filters;
|
|
||||||
this.placeholder =
|
|
||||||
this.activeFilters.length <= 0
|
|
||||||
? $localize`Filter by account or tag...`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return this.fetchPortfolioDetails();
|
|
||||||
}),
|
|
||||||
takeUntil(this.unsubscribeSubject)
|
|
||||||
)
|
|
||||||
.subscribe((portfolioDetails) => {
|
|
||||||
this.portfolioDetails = portfolioDetails;
|
|
||||||
|
|
||||||
this.initialize();
|
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
@ -111,52 +80,17 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
|
|||||||
permissions.createOrder
|
permissions.createOrder
|
||||||
);
|
);
|
||||||
|
|
||||||
const accountFilters: Filter[] = this.user.accounts.map(
|
this.holdings = undefined;
|
||||||
({ id, name }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: name,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const assetClassFilters: Filter[] = [];
|
this.fetchPortfolioDetails()
|
||||||
for (const assetClass of Object.keys(AssetClass)) {
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
assetClassFilters.push({
|
.subscribe((portfolioDetails) => {
|
||||||
id: assetClass,
|
this.portfolioDetails = portfolioDetails;
|
||||||
label: translate(assetClass),
|
|
||||||
type: 'ASSET_CLASS'
|
this.initialize();
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: translate(name),
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.allFilters = [
|
|
||||||
...accountFilters,
|
|
||||||
...assetClassFilters,
|
|
||||||
...tagFilters
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.user?.settings?.isExperimentalFeatures === true) {
|
|
||||||
this.holdings = undefined;
|
|
||||||
|
|
||||||
this.fetchPortfolioDetails()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((portfolioDetails) => {
|
|
||||||
this.portfolioDetails = portfolioDetails;
|
|
||||||
|
|
||||||
this.initialize();
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
@ -170,10 +104,7 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
private fetchPortfolioDetails() {
|
private fetchPortfolioDetails() {
|
||||||
return this.dataService.fetchPortfolioDetails({
|
return this.dataService.fetchPortfolioDetails({
|
||||||
filters:
|
filters: this.userService.getFilters()
|
||||||
this.activeFilters.length > 0
|
|
||||||
? this.activeFilters
|
|
||||||
: this.userService.getFilters()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,6 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Holdings</h1>
|
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Holdings</h1>
|
||||||
@if (!user?.settings?.isExperimentalFeatures) {
|
|
||||||
<gf-activities-filter
|
|
||||||
[allFilters]="allFilters"
|
|
||||||
[isLoading]="isLoading"
|
|
||||||
[placeholder]="placeholder"
|
|
||||||
(valueChanged)="filters$.next($event)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
|
|
||||||
import { GfHoldingsTableModule } from '@ghostfolio/ui/holdings-table/holdings-table.module';
|
import { GfHoldingsTableModule } from '@ghostfolio/ui/holdings-table/holdings-table.module';
|
||||||
|
|
||||||
import { HoldingsPageRoutingModule } from './holdings-page-routing.module';
|
import { HoldingsPageRoutingModule } from './holdings-page-routing.module';
|
||||||
@ -11,7 +10,6 @@ import { HoldingsPageComponent } from './holdings-page.component';
|
|||||||
declarations: [HoldingsPageComponent],
|
declarations: [HoldingsPageComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfActivitiesFilterModule,
|
|
||||||
GfHoldingsTableModule,
|
GfHoldingsTableModule,
|
||||||
HoldingsPageRoutingModule,
|
HoldingsPageRoutingModule,
|
||||||
MatButtonModule
|
MatButtonModule
|
||||||
|
@ -50,27 +50,25 @@ export class UserService extends ObservableStore<UserStoreState> {
|
|||||||
const filters: Filter[] = [];
|
const filters: Filter[] = [];
|
||||||
const user = this.getState().user;
|
const user = this.getState().user;
|
||||||
|
|
||||||
if (user?.settings?.isExperimentalFeatures === true) {
|
if (user.settings['filters.accounts']) {
|
||||||
if (user.settings['filters.accounts']) {
|
filters.push({
|
||||||
filters.push({
|
id: user.settings['filters.accounts'][0],
|
||||||
id: user.settings['filters.accounts'][0],
|
type: 'ACCOUNT'
|
||||||
type: 'ACCOUNT'
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (user.settings['filters.assetClasses']) {
|
if (user.settings['filters.assetClasses']) {
|
||||||
filters.push({
|
filters.push({
|
||||||
id: user.settings['filters.assetClasses'][0],
|
id: user.settings['filters.assetClasses'][0],
|
||||||
type: 'ASSET_CLASS'
|
type: 'ASSET_CLASS'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.settings['filters.tags']) {
|
if (user.settings['filters.tags']) {
|
||||||
filters.push({
|
filters.push({
|
||||||
id: user.settings['filters.tags'][0],
|
id: user.settings['filters.tags'][0],
|
||||||
type: 'TAG'
|
type: 'TAG'
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -379,6 +379,10 @@ ngx-skeleton-loader {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gf-spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.gf-table {
|
.gf-table {
|
||||||
@include gf-table;
|
@include gf-table;
|
||||||
}
|
}
|
||||||
|
0
git-hooks/pre-commit
Executable file → Normal file
0
git-hooks/pre-commit
Executable file → Normal file
@ -1,490 +0,0 @@
|
|||||||
<div *ngIf="hasPermissionToCreateActivity" class="d-flex justify-content-end">
|
|
||||||
<button
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-stroked-button
|
|
||||||
(click)="onImport()"
|
|
||||||
>
|
|
||||||
<ion-icon class="mr-2" name="cloud-upload-outline" />
|
|
||||||
<ng-container i18n>Import Activities</ng-container>...
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToExportActivities"
|
|
||||||
class="mx-1 no-min-width px-2"
|
|
||||||
mat-stroked-button
|
|
||||||
[matMenuTriggerFor]="activitiesMenu"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<ion-icon name="ellipsis-vertical" />
|
|
||||||
</button>
|
|
||||||
<mat-menu #activitiesMenu="matMenu" xPosition="before">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="dataSource?.data.length === 0"
|
|
||||||
(click)="onImportDividends()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="color-wand-outline" />
|
|
||||||
<ng-container i18n>Import Dividends</ng-container>...
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToExportActivities"
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="dataSource?.data.length === 0"
|
|
||||||
(click)="onExport()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="cloud-download-outline" />
|
|
||||||
<span i18n>Export Activities</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToExportActivities"
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="!hasDrafts"
|
|
||||||
(click)="onExportDrafts()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="calendar-clear-outline" />
|
|
||||||
<span i18n>Export Drafts as ICS</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-menu-item
|
|
||||||
(click)="onDeleteAllActivities()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="trash-outline" />
|
|
||||||
<span i18n>Delete all Activities</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activities">
|
|
||||||
<table
|
|
||||||
class="gf-table w-100"
|
|
||||||
mat-table
|
|
||||||
matSort
|
|
||||||
[dataSource]="dataSource"
|
|
||||||
[matSortActive]="sortColumn"
|
|
||||||
[matSortDirection]="sortDirection"
|
|
||||||
[matSortDisabled]="sortDisabled"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="select" sticky>
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
|
||||||
<mat-checkbox
|
|
||||||
color="primary"
|
|
||||||
[checked]="
|
|
||||||
areAllRowsSelected() && !hasErrors && selectedRows.hasValue()
|
|
||||||
"
|
|
||||||
[disabled]="hasErrors"
|
|
||||||
[indeterminate]="selectedRows.hasValue() && !areAllRowsSelected()"
|
|
||||||
(change)="$event ? toggleAllRows() : null"
|
|
||||||
></mat-checkbox>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<mat-checkbox
|
|
||||||
color="primary"
|
|
||||||
[checked]="element.error ? false : selectedRows.isSelected(element)"
|
|
||||||
[disabled]="element.error"
|
|
||||||
(change)="$event ? selectedRows.toggle(element) : null"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
></mat-checkbox>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="importStatus">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
|
||||||
<ng-container i18n></ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<div
|
|
||||||
*ngIf="element.error"
|
|
||||||
class="d-flex"
|
|
||||||
matTooltipPosition="above"
|
|
||||||
[matTooltip]="element.error.message"
|
|
||||||
>
|
|
||||||
<ion-icon class="text-danger" name="alert-circle-outline" />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="icon">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
|
|
||||||
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
|
||||||
<gf-symbol-icon
|
|
||||||
[dataSource]="element.SymbolProfile?.dataSource"
|
|
||||||
[symbol]="element.SymbolProfile?.symbol"
|
|
||||||
[tooltip]="element.SymbolProfile?.name"
|
|
||||||
/>
|
|
||||||
<div>{{ element.dataSource }}</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="nameWithSymbol">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
|
||||||
<ng-container i18n>Name</ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<div class="align-items-center d-flex line-height-1">
|
|
||||||
<div>
|
|
||||||
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
|
||||||
<span
|
|
||||||
*ngIf="element.isDraft"
|
|
||||||
class="badge badge-secondary ml-1"
|
|
||||||
i18n
|
|
||||||
>Draft</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!isUUID(element.SymbolProfile?.symbol)">
|
|
||||||
<small class="text-muted">{{
|
|
||||||
element.SymbolProfile?.symbol | gfSymbol
|
|
||||||
}}</small>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="type">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
|
||||||
<ng-container i18n>Type</ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<gf-activity-type [activityType]="element.type" />
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="date">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
|
||||||
<ng-container i18n>Date</ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<div class="d-flex">
|
|
||||||
{{ element.date | date: defaultDateFormat }}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="quantity">
|
|
||||||
<th
|
|
||||||
*matHeaderCellDef
|
|
||||||
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
||||||
mat-header-cell
|
|
||||||
mat-sort-header
|
|
||||||
>
|
|
||||||
<ng-container i18n>Quantity</ng-container>
|
|
||||||
</th>
|
|
||||||
<td
|
|
||||||
*matCellDef="let element"
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : element.quantity"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="unitPrice">
|
|
||||||
<th
|
|
||||||
*matHeaderCellDef
|
|
||||||
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
||||||
mat-header-cell
|
|
||||||
mat-sort-header
|
|
||||||
>
|
|
||||||
<ng-container i18n>Unit Price</ng-container>
|
|
||||||
</th>
|
|
||||||
<td
|
|
||||||
*matCellDef="let element"
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : element.unitPrice"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="fee">
|
|
||||||
<th
|
|
||||||
*matHeaderCellDef
|
|
||||||
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
||||||
mat-header-cell
|
|
||||||
mat-sort-header
|
|
||||||
>
|
|
||||||
<ng-container i18n>Fee</ng-container>
|
|
||||||
</th>
|
|
||||||
<td
|
|
||||||
*matCellDef="let element"
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : element.fee"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="value">
|
|
||||||
<th
|
|
||||||
*matHeaderCellDef
|
|
||||||
class="d-none d-lg-table-cell justify-content-end px-1"
|
|
||||||
mat-header-cell
|
|
||||||
>
|
|
||||||
<ng-container i18n>Value</ng-container>
|
|
||||||
</th>
|
|
||||||
<td
|
|
||||||
*matCellDef="let element"
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : element.value"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="currency">
|
|
||||||
<th *matHeaderCellDef class="d-none d-lg-table-cell px-1" mat-header-cell>
|
|
||||||
<ng-container i18n>Currency</ng-container>
|
|
||||||
</th>
|
|
||||||
<td
|
|
||||||
*matCellDef="let element"
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
{{ element.SymbolProfile?.currency }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="valueInBaseCurrency">
|
|
||||||
<th
|
|
||||||
*matHeaderCellDef
|
|
||||||
class="d-lg-none d-xl-none justify-content-end px-1"
|
|
||||||
mat-header-cell
|
|
||||||
>
|
|
||||||
<ng-container i18n>Value</ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="d-lg-none d-xl-none px-1" mat-cell>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : element.valueInBaseCurrency"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="account">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
|
||||||
<span class="d-none d-lg-block" i18n>Account</span>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<div class="d-flex">
|
|
||||||
<gf-symbol-icon
|
|
||||||
*ngIf="element.Account?.Platform?.url"
|
|
||||||
class="mr-1"
|
|
||||||
[tooltip]="element.Account?.Platform?.name"
|
|
||||||
[url]="element.Account?.Platform?.url"
|
|
||||||
/>
|
|
||||||
<span class="d-none d-lg-block">{{ element.Account?.name }}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="comment">
|
|
||||||
<th
|
|
||||||
*matHeaderCellDef
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-header-cell
|
|
||||||
></th>
|
|
||||||
<td
|
|
||||||
*matCellDef="let element"
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
*ngIf="element.comment"
|
|
||||||
class="mx-1 no-min-width px-2"
|
|
||||||
mat-button
|
|
||||||
title="Note"
|
|
||||||
(click)="onOpenComment(element.comment); $event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<ion-icon name="document-text-outline" />
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="actions" stickyEnd>
|
|
||||||
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
|
||||||
<button
|
|
||||||
*ngIf="
|
|
||||||
!hasPermissionToCreateActivity && hasPermissionToExportActivities
|
|
||||||
"
|
|
||||||
class="mx-1 no-min-width px-2"
|
|
||||||
mat-button
|
|
||||||
[matMenuTriggerFor]="activitiesMenu"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<ion-icon name="ellipsis-vertical" />
|
|
||||||
</button>
|
|
||||||
<mat-menu #activitiesMenu="matMenu" xPosition="before">
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToCreateActivity"
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-menu-item
|
|
||||||
(click)="onImport()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="cloud-upload-outline" />
|
|
||||||
<ng-container i18n>Import Activities</ng-container>...
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToCreateActivity"
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="dataSource?.data.length === 0"
|
|
||||||
(click)="onImportDividends()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="color-wand-outline" />
|
|
||||||
<ng-container i18n>Import Dividends</ng-container>...
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToExportActivities"
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="dataSource?.data.length === 0"
|
|
||||||
(click)="onExport()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="cloud-download-outline" />
|
|
||||||
<span i18n>Export Activities</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="hasPermissionToExportActivities"
|
|
||||||
class="align-items-center d-flex"
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="!hasDrafts"
|
|
||||||
(click)="onExportDrafts()"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="calendar-clear-outline" />
|
|
||||||
<span i18n>Export Drafts as ICS</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
|
||||||
<button
|
|
||||||
*ngIf="showActions"
|
|
||||||
class="mx-1 no-min-width px-2"
|
|
||||||
mat-button
|
|
||||||
[matMenuTriggerFor]="activityMenu"
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<ion-icon name="ellipsis-horizontal" />
|
|
||||||
</button>
|
|
||||||
<mat-menu #activityMenu="matMenu" xPosition="before">
|
|
||||||
<button mat-menu-item (click)="onUpdateActivity(element)">
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="create-outline" />
|
|
||||||
<span i18n>Edit</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item (click)="onCloneActivity(element)">
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="copy-outline" />
|
|
||||||
<span i18n>Clone</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="!element.isDraft"
|
|
||||||
(click)="onExportDraft(element.id)"
|
|
||||||
>
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="calendar-clear-outline" />
|
|
||||||
<span i18n>Export Draft as ICS</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item (click)="onDeleteActivity(element.id)">
|
|
||||||
<span class="align-items-center d-flex">
|
|
||||||
<ion-icon class="mr-2" name="trash-outline" />
|
|
||||||
<span i18n>Delete</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
|
||||||
<tr
|
|
||||||
*matRowDef="let row; columns: displayedColumns"
|
|
||||||
mat-row
|
|
||||||
[ngClass]="{
|
|
||||||
'cursor-pointer':
|
|
||||||
hasPermissionToOpenDetails &&
|
|
||||||
!row.isDraft &&
|
|
||||||
row.type !== 'FEE' &&
|
|
||||||
row.type !== 'INTEREST' &&
|
|
||||||
row.type !== 'ITEM' &&
|
|
||||||
row.type !== 'LIABILITY'
|
|
||||||
}"
|
|
||||||
(click)="onClickActivity(row)"
|
|
||||||
></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ngx-skeleton-loader
|
|
||||||
*ngIf="isLoading"
|
|
||||||
animation="pulse"
|
|
||||||
class="px-4 py-3"
|
|
||||||
[theme]="{
|
|
||||||
height: '1.5rem',
|
|
||||||
width: '100%'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<mat-paginator
|
|
||||||
[length]="totalItems"
|
|
||||||
[ngClass]="{
|
|
||||||
'd-none': (isLoading && !totalItems) || totalItems <= pageSize
|
|
||||||
}"
|
|
||||||
[pageIndex]="pageIndex"
|
|
||||||
[pageSize]="pageSize"
|
|
||||||
[showFirstLastButtons]="true"
|
|
||||||
(page)="onChangePage($event)"
|
|
||||||
></mat-paginator>
|
|
||||||
|
|
||||||
<div
|
|
||||||
*ngIf="
|
|
||||||
dataSource?.data.length === 0 && hasPermissionToCreateActivity && !isLoading
|
|
||||||
"
|
|
||||||
class="p-3 text-center"
|
|
||||||
>
|
|
||||||
<gf-no-transactions-info-indicator [hasBorder]="false" />
|
|
||||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||||||
@import 'apps/client/src/styles/ghostfolio-style';
|
|
||||||
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
.activities {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,241 +0,0 @@
|
|||||||
import { SelectionModel } from '@angular/cdk/collections';
|
|
||||||
import {
|
|
||||||
AfterViewInit,
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
|
||||||
import { MatSort, Sort, SortDirection } from '@angular/material/sort';
|
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
|
||||||
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
|
|
||||||
import { getDateFormatString } from '@ghostfolio/common/helper';
|
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
|
||||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
|
||||||
import { isUUID } from 'class-validator';
|
|
||||||
import { endOfToday, isAfter } from 'date-fns';
|
|
||||||
import { Subject, Subscription, takeUntil } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
selector: 'gf-activities-table-lazy',
|
|
||||||
styleUrls: ['./activities-table-lazy.component.scss'],
|
|
||||||
templateUrl: './activities-table-lazy.component.html'
|
|
||||||
})
|
|
||||||
export class ActivitiesTableLazyComponent
|
|
||||||
implements AfterViewInit, OnChanges, OnDestroy, OnInit
|
|
||||||
{
|
|
||||||
@Input() baseCurrency: string;
|
|
||||||
@Input() dataSource: MatTableDataSource<Activity>;
|
|
||||||
@Input() deviceType: string;
|
|
||||||
@Input() hasPermissionToCreateActivity: boolean;
|
|
||||||
@Input() hasPermissionToExportActivities: boolean;
|
|
||||||
@Input() hasPermissionToOpenDetails = true;
|
|
||||||
@Input() locale: string;
|
|
||||||
@Input() pageIndex: number;
|
|
||||||
@Input() pageSize = DEFAULT_PAGE_SIZE;
|
|
||||||
@Input() showActions = true;
|
|
||||||
@Input() showCheckbox = false;
|
|
||||||
@Input() showFooter = true;
|
|
||||||
@Input() showNameColumn = true;
|
|
||||||
@Input() sortColumn: string;
|
|
||||||
@Input() sortDirection: SortDirection;
|
|
||||||
@Input() sortDisabled = false;
|
|
||||||
@Input() totalItems = Number.MAX_SAFE_INTEGER;
|
|
||||||
|
|
||||||
@Output() activityDeleted = new EventEmitter<string>();
|
|
||||||
@Output() activityToClone = new EventEmitter<OrderWithAccount>();
|
|
||||||
@Output() activityToUpdate = new EventEmitter<OrderWithAccount>();
|
|
||||||
@Output() deleteAllActivities = new EventEmitter<void>();
|
|
||||||
@Output() export = new EventEmitter<void>();
|
|
||||||
@Output() exportDrafts = new EventEmitter<string[]>();
|
|
||||||
@Output() import = new EventEmitter<void>();
|
|
||||||
@Output() importDividends = new EventEmitter<UniqueAsset>();
|
|
||||||
@Output() pageChanged = new EventEmitter<PageEvent>();
|
|
||||||
@Output() selectedActivities = new EventEmitter<Activity[]>();
|
|
||||||
@Output() sortChanged = new EventEmitter<Sort>();
|
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
|
||||||
|
|
||||||
public defaultDateFormat: string;
|
|
||||||
public displayedColumns = [];
|
|
||||||
public endOfToday = endOfToday();
|
|
||||||
public hasDrafts = false;
|
|
||||||
public hasErrors = false;
|
|
||||||
public isAfter = isAfter;
|
|
||||||
public isLoading = true;
|
|
||||||
public isUUID = isUUID;
|
|
||||||
public routeQueryParams: Subscription;
|
|
||||||
public selectedRows = new SelectionModel<Activity>(true, []);
|
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
|
||||||
|
|
||||||
public constructor(private router: Router) {}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
if (this.showCheckbox) {
|
|
||||||
this.toggleAllRows();
|
|
||||||
this.selectedRows.changed
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((selectedRows) => {
|
|
||||||
this.selectedActivities.emit(selectedRows.source.selected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngAfterViewInit() {
|
|
||||||
this.sort.sortChange.subscribe((value: Sort) => {
|
|
||||||
this.sortChanged.emit(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public areAllRowsSelected() {
|
|
||||||
const numSelectedRows = this.selectedRows.selected.length;
|
|
||||||
const numTotalRows = this.dataSource.data.length;
|
|
||||||
return numSelectedRows === numTotalRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnChanges() {
|
|
||||||
this.defaultDateFormat = getDateFormatString(this.locale);
|
|
||||||
|
|
||||||
this.displayedColumns = [
|
|
||||||
'select',
|
|
||||||
'importStatus',
|
|
||||||
'icon',
|
|
||||||
'nameWithSymbol',
|
|
||||||
'type',
|
|
||||||
'date',
|
|
||||||
'quantity',
|
|
||||||
'unitPrice',
|
|
||||||
'fee',
|
|
||||||
'value',
|
|
||||||
'currency',
|
|
||||||
'valueInBaseCurrency',
|
|
||||||
'account',
|
|
||||||
'comment',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!this.showCheckbox) {
|
|
||||||
this.displayedColumns = this.displayedColumns.filter((column) => {
|
|
||||||
return column !== 'importStatus' && column !== 'select';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.showNameColumn) {
|
|
||||||
this.displayedColumns = this.displayedColumns.filter((column) => {
|
|
||||||
return column !== 'nameWithSymbol';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dataSource) {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangePage(page: PageEvent) {
|
|
||||||
this.pageChanged.emit(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onClickActivity(activity: Activity) {
|
|
||||||
if (this.showCheckbox) {
|
|
||||||
if (!activity.error) {
|
|
||||||
this.selectedRows.toggle(activity);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
this.hasPermissionToOpenDetails &&
|
|
||||||
!activity.isDraft &&
|
|
||||||
activity.type !== 'FEE' &&
|
|
||||||
activity.type !== 'INTEREST' &&
|
|
||||||
activity.type !== 'ITEM' &&
|
|
||||||
activity.type !== 'LIABILITY'
|
|
||||||
) {
|
|
||||||
this.onOpenPositionDialog({
|
|
||||||
dataSource: activity.SymbolProfile.dataSource,
|
|
||||||
symbol: activity.SymbolProfile.symbol
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onCloneActivity(aActivity: OrderWithAccount) {
|
|
||||||
this.activityToClone.emit(aActivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onDeleteActivity(aId: string) {
|
|
||||||
const confirmation = confirm(
|
|
||||||
$localize`Do you really want to delete this activity?`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (confirmation) {
|
|
||||||
this.activityDeleted.emit(aId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onExport() {
|
|
||||||
this.export.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onExportDraft(aActivityId: string) {
|
|
||||||
this.exportDrafts.emit([aActivityId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onExportDrafts() {
|
|
||||||
this.exportDrafts.emit(
|
|
||||||
this.dataSource.filteredData
|
|
||||||
.filter((activity) => {
|
|
||||||
return activity.isDraft;
|
|
||||||
})
|
|
||||||
.map((activity) => {
|
|
||||||
return activity.id;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onDeleteAllActivities() {
|
|
||||||
this.deleteAllActivities.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onImport() {
|
|
||||||
this.import.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onImportDividends() {
|
|
||||||
this.importDividends.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onOpenComment(aComment: string) {
|
|
||||||
alert(aComment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset): void {
|
|
||||||
this.router.navigate([], {
|
|
||||||
queryParams: { dataSource, symbol, positionDetailDialog: true }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onUpdateActivity(aActivity: OrderWithAccount) {
|
|
||||||
this.activityToUpdate.emit(aActivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleAllRows() {
|
|
||||||
this.areAllRowsSelected()
|
|
||||||
? this.selectedRows.clear()
|
|
||||||
: this.dataSource.data.forEach((row) => this.selectedRows.select(row));
|
|
||||||
|
|
||||||
this.selectedActivities.emit(this.selectedRows.selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
|
||||||
this.unsubscribeSubject.next();
|
|
||||||
this.unsubscribeSubject.complete();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
|
||||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
|
||||||
import { MatTableModule } from '@angular/material/table';
|
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
|
||||||
import { GfActivityTypeModule } from '@ghostfolio/ui/activity-type';
|
|
||||||
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
|
||||||
|
|
||||||
import { ActivitiesTableLazyComponent } from './activities-table-lazy.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [ActivitiesTableLazyComponent],
|
|
||||||
exports: [ActivitiesTableLazyComponent],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
GfActivityTypeModule,
|
|
||||||
GfNoTransactionsInfoModule,
|
|
||||||
GfSymbolIconModule,
|
|
||||||
GfSymbolModule,
|
|
||||||
GfValueModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatMenuModule,
|
|
||||||
MatPaginatorModule,
|
|
||||||
MatSortModule,
|
|
||||||
MatTableModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
NgxSkeletonLoaderModule,
|
|
||||||
RouterModule
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
||||||
})
|
|
||||||
export class GfActivitiesTableLazyModule {}
|
|
@ -1,11 +1,3 @@
|
|||||||
<gf-activities-filter
|
|
||||||
[allFilters]="allFilters"
|
|
||||||
[isLoading]="isLoading"
|
|
||||||
[ngClass]="{ 'd-none': !hasPermissionToFilter }"
|
|
||||||
[placeholder]="placeholder"
|
|
||||||
(valueChanged)="filters$.next($event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div *ngIf="hasPermissionToCreateActivity" class="d-flex justify-content-end">
|
<div *ngIf="hasPermissionToCreateActivity" class="d-flex justify-content-end">
|
||||||
<button
|
<button
|
||||||
class="align-items-center d-flex"
|
class="align-items-center d-flex"
|
||||||
@ -27,7 +19,7 @@
|
|||||||
<mat-menu #activitiesMenu="matMenu" xPosition="before">
|
<mat-menu #activitiesMenu="matMenu" xPosition="before">
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="dataSource.data.length === 0"
|
[disabled]="dataSource?.data.length === 0"
|
||||||
(click)="onImportDividends()"
|
(click)="onImportDividends()"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
@ -39,7 +31,7 @@
|
|||||||
*ngIf="hasPermissionToExportActivities"
|
*ngIf="hasPermissionToExportActivities"
|
||||||
class="align-items-center d-flex"
|
class="align-items-center d-flex"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="dataSource.data.length === 0"
|
[disabled]="dataSource?.data.length === 0"
|
||||||
(click)="onExport()"
|
(click)="onExport()"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
@ -77,11 +69,12 @@
|
|||||||
class="gf-table w-100"
|
class="gf-table w-100"
|
||||||
mat-table
|
mat-table
|
||||||
matSort
|
matSort
|
||||||
matSortActive="date"
|
|
||||||
matSortDirection="desc"
|
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
|
[matSortActive]="sortColumn"
|
||||||
|
[matSortDirection]="sortDirection"
|
||||||
|
[matSortDisabled]="sortDisabled"
|
||||||
>
|
>
|
||||||
<ng-container matColumnDef="select">
|
<ng-container matColumnDef="select" sticky>
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -102,7 +95,6 @@
|
|||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
></mat-checkbox>
|
></mat-checkbox>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="importStatus">
|
<ng-container matColumnDef="importStatus">
|
||||||
@ -119,67 +111,26 @@
|
|||||||
<ion-icon class="text-danger" name="alert-circle-outline" />
|
<ion-icon class="text-danger" name="alert-circle-outline" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="count">
|
<ng-container matColumnDef="icon">
|
||||||
<th
|
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
|
||||||
*matHeaderCellDef
|
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
||||||
class="d-none d-lg-table-cell px-1 text-right"
|
<gf-symbol-icon
|
||||||
i18n
|
[dataSource]="element.SymbolProfile?.dataSource"
|
||||||
mat-header-cell
|
[symbol]="element.SymbolProfile?.symbol"
|
||||||
></th>
|
[tooltip]="element.SymbolProfile?.name"
|
||||||
<td
|
/>
|
||||||
*matCellDef="let element; let i = index"
|
<div>{{ element.dataSource }}</div>
|
||||||
class="d-none d-lg-table-cell px-1 text-right"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
dataSource.data.length > pageSize
|
|
||||||
? dataSource.data.length - pageSize * pageIndex - i
|
|
||||||
: dataSource.data.length - i
|
|
||||||
}}
|
|
||||||
</td>
|
</td>
|
||||||
<td
|
|
||||||
*matFooterCellDef
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-footer-cell
|
|
||||||
></td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="date">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
|
||||||
<ng-container i18n>Date</ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<div class="d-flex">
|
|
||||||
{{ element.date | date: defaultDateFormat }}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>Total</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="type">
|
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
|
||||||
<ng-container i18n>Type</ng-container>
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
|
||||||
<gf-activity-type [activityType]="element.type" />
|
|
||||||
</td>
|
|
||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="nameWithSymbol">
|
<ng-container matColumnDef="nameWithSymbol">
|
||||||
<th
|
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
||||||
*matHeaderCellDef
|
|
||||||
class="px-1"
|
|
||||||
mat-header-cell
|
|
||||||
mat-sort-header="SymbolProfile.symbol"
|
|
||||||
>
|
|
||||||
<ng-container i18n>Name</ng-container>
|
<ng-container i18n>Name</ng-container>
|
||||||
</th>
|
</th>
|
||||||
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
<div class="d-flex align-items-center">
|
<div class="align-items-center d-flex line-height-1">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
||||||
<span
|
<span
|
||||||
@ -196,27 +147,25 @@
|
|||||||
}}</small>
|
}}</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="currency">
|
<ng-container matColumnDef="type">
|
||||||
<th
|
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||||
*matHeaderCellDef
|
<ng-container i18n>Type</ng-container>
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-header-cell
|
|
||||||
mat-sort-header="SymbolProfile.currency"
|
|
||||||
>
|
|
||||||
<ng-container i18n>Currency</ng-container>
|
|
||||||
</th>
|
</th>
|
||||||
<td
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
*matCellDef="let element"
|
<gf-activity-type [activityType]="element.type" />
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-cell
|
|
||||||
>
|
|
||||||
{{ element.SymbolProfile?.currency }}
|
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
</ng-container>
|
||||||
{{ baseCurrency }}
|
|
||||||
|
<ng-container matColumnDef="date">
|
||||||
|
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||||
|
<ng-container i18n>Date</ng-container>
|
||||||
|
</th>
|
||||||
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
|
<div class="d-flex">
|
||||||
|
{{ element.date | date: defaultDateFormat }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -242,11 +191,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
|
||||||
*matFooterCellDef
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-footer-cell
|
|
||||||
></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="unitPrice">
|
<ng-container matColumnDef="unitPrice">
|
||||||
@ -271,11 +215,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
|
||||||
*matFooterCellDef
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-footer-cell
|
|
||||||
></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="fee">
|
<ng-container matColumnDef="fee">
|
||||||
@ -300,15 +239,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : totalFees"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="value">
|
<ng-container matColumnDef="value">
|
||||||
@ -316,7 +246,6 @@
|
|||||||
*matHeaderCellDef
|
*matHeaderCellDef
|
||||||
class="d-none d-lg-table-cell justify-content-end px-1"
|
class="d-none d-lg-table-cell justify-content-end px-1"
|
||||||
mat-header-cell
|
mat-header-cell
|
||||||
mat-sort-header
|
|
||||||
>
|
>
|
||||||
<ng-container i18n>Value</ng-container>
|
<ng-container i18n>Value</ng-container>
|
||||||
</th>
|
</th>
|
||||||
@ -333,16 +262,18 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
</ng-container>
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
<ng-container matColumnDef="currency">
|
||||||
*ngIf="totalValue !== null"
|
<th *matHeaderCellDef class="d-none d-lg-table-cell px-1" mat-header-cell>
|
||||||
[isAbsolute]="true"
|
<ng-container i18n>Currency</ng-container>
|
||||||
[isCurrency]="true"
|
</th>
|
||||||
[locale]="locale"
|
<td
|
||||||
[value]="isLoading ? undefined : totalValue"
|
*matCellDef="let element"
|
||||||
/>
|
class="d-none d-lg-table-cell px-1"
|
||||||
</div>
|
mat-cell
|
||||||
|
>
|
||||||
|
{{ element.SymbolProfile?.currency }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -351,7 +282,6 @@
|
|||||||
*matHeaderCellDef
|
*matHeaderCellDef
|
||||||
class="d-lg-none d-xl-none justify-content-end px-1"
|
class="d-lg-none d-xl-none justify-content-end px-1"
|
||||||
mat-header-cell
|
mat-header-cell
|
||||||
mat-sort-header
|
|
||||||
>
|
>
|
||||||
<ng-container i18n>Value</ng-container>
|
<ng-container i18n>Value</ng-container>
|
||||||
</th>
|
</th>
|
||||||
@ -364,26 +294,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="d-lg-none d-xl-none px-1" mat-footer-cell>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
*ngIf="totalValue !== null"
|
|
||||||
[isAbsolute]="true"
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : totalValue"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="account">
|
<ng-container matColumnDef="account">
|
||||||
<th
|
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
||||||
*matHeaderCellDef
|
|
||||||
class="px-1"
|
|
||||||
mat-header-cell
|
|
||||||
mat-sort-header="Account.name"
|
|
||||||
>
|
|
||||||
<span class="d-none d-lg-block" i18n>Account</span>
|
<span class="d-none d-lg-block" i18n>Account</span>
|
||||||
</th>
|
</th>
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
@ -397,7 +311,6 @@
|
|||||||
<span class="d-none d-lg-block">{{ element.Account?.name }}</span>
|
<span class="d-none d-lg-block">{{ element.Account?.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="comment">
|
<ng-container matColumnDef="comment">
|
||||||
@ -421,11 +334,6 @@
|
|||||||
<ion-icon name="document-text-outline" />
|
<ion-icon name="document-text-outline" />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td
|
|
||||||
*matFooterCellDef
|
|
||||||
class="d-none d-lg-table-cell px-1"
|
|
||||||
mat-footer-cell
|
|
||||||
></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions" stickyEnd>
|
<ng-container matColumnDef="actions" stickyEnd>
|
||||||
@ -456,7 +364,7 @@
|
|||||||
<button
|
<button
|
||||||
*ngIf="hasPermissionToCreateActivity"
|
*ngIf="hasPermissionToCreateActivity"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="dataSource.data.length === 0"
|
[disabled]="dataSource?.data.length === 0"
|
||||||
(click)="onImportDividends()"
|
(click)="onImportDividends()"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
@ -468,7 +376,7 @@
|
|||||||
*ngIf="hasPermissionToExportActivities"
|
*ngIf="hasPermissionToExportActivities"
|
||||||
class="align-items-center d-flex"
|
class="align-items-center d-flex"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="dataSource.data.length === 0"
|
[disabled]="dataSource?.data.length === 0"
|
||||||
(click)="onExport()"
|
(click)="onExport()"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
@ -531,7 +439,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||||
@ -549,28 +456,9 @@
|
|||||||
}"
|
}"
|
||||||
(click)="onClickActivity(row)"
|
(click)="onClickActivity(row)"
|
||||||
></tr>
|
></tr>
|
||||||
<tr
|
|
||||||
*matFooterRowDef="displayedColumns"
|
|
||||||
mat-footer-row
|
|
||||||
[ngClass]="{
|
|
||||||
'd-none':
|
|
||||||
isLoading || dataSource.data.length === 0 || showFooter === false
|
|
||||||
}"
|
|
||||||
></tr>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-paginator
|
|
||||||
[ngClass]="{
|
|
||||||
'd-none':
|
|
||||||
(isLoading && dataSource.data.length === 0) ||
|
|
||||||
dataSource.data.length <= pageSize
|
|
||||||
}"
|
|
||||||
[pageSize]="pageSize"
|
|
||||||
[showFirstLastButtons]="true"
|
|
||||||
(page)="onChangePage($event)"
|
|
||||||
></mat-paginator>
|
|
||||||
|
|
||||||
<ngx-skeleton-loader
|
<ngx-skeleton-loader
|
||||||
*ngIf="isLoading"
|
*ngIf="isLoading"
|
||||||
animation="pulse"
|
animation="pulse"
|
||||||
@ -581,9 +469,20 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<mat-paginator
|
||||||
|
[length]="totalItems"
|
||||||
|
[ngClass]="{
|
||||||
|
'd-none': (isLoading && !totalItems) || totalItems <= pageSize
|
||||||
|
}"
|
||||||
|
[pageIndex]="pageIndex"
|
||||||
|
[pageSize]="pageSize"
|
||||||
|
[showFirstLastButtons]="true"
|
||||||
|
(page)="onChangePage($event)"
|
||||||
|
></mat-paginator>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="
|
*ngIf="
|
||||||
dataSource.data.length === 0 && hasPermissionToCreateActivity && !isLoading
|
dataSource?.data.length === 0 && hasPermissionToCreateActivity && !isLoading
|
||||||
"
|
"
|
||||||
class="p-3 text-center"
|
class="p-3 text-center"
|
||||||
>
|
>
|
||||||
|
@ -5,15 +5,5 @@
|
|||||||
|
|
||||||
.activities {
|
.activities {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
.mat-mdc-table {
|
|
||||||
th {
|
|
||||||
::ng-deep {
|
|
||||||
.mat-sort-header-container {
|
|
||||||
justify-content: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { SelectionModel } from '@angular/cdk/collections';
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
import {
|
import {
|
||||||
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@ -11,20 +12,17 @@ import {
|
|||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||||
import { MatSort } from '@angular/material/sort';
|
import { MatSort, Sort, SortDirection } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
|
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
|
||||||
import { getDateFormatString } from '@ghostfolio/common/helper';
|
import { getDateFormatString } from '@ghostfolio/common/helper';
|
||||||
import { Filter, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
|
||||||
import Big from 'big.js';
|
|
||||||
import { isUUID } from 'class-validator';
|
import { isUUID } from 'class-validator';
|
||||||
import { endOfToday, format, isAfter } from 'date-fns';
|
import { endOfToday, isAfter } from 'date-fns';
|
||||||
import { get, isNumber } from 'lodash';
|
import { Subject, Subscription, takeUntil } from 'rxjs';
|
||||||
import { Subject, Subscription, distinctUntilChanged, takeUntil } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@ -32,63 +30,56 @@ import { Subject, Subscription, distinctUntilChanged, takeUntil } from 'rxjs';
|
|||||||
styleUrls: ['./activities-table.component.scss'],
|
styleUrls: ['./activities-table.component.scss'],
|
||||||
templateUrl: './activities-table.component.html'
|
templateUrl: './activities-table.component.html'
|
||||||
})
|
})
|
||||||
export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
export class ActivitiesTableComponent
|
||||||
@Input() activities: Activity[];
|
implements AfterViewInit, OnChanges, OnDestroy, OnInit
|
||||||
|
{
|
||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
|
@Input() dataSource: MatTableDataSource<Activity>;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() hasPermissionToCreateActivity: boolean;
|
@Input() hasPermissionToCreateActivity: boolean;
|
||||||
@Input() hasPermissionToExportActivities: boolean;
|
@Input() hasPermissionToExportActivities: boolean;
|
||||||
@Input() hasPermissionToFilter = true;
|
|
||||||
@Input() hasPermissionToOpenDetails = true;
|
@Input() hasPermissionToOpenDetails = true;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
|
@Input() pageIndex: number;
|
||||||
@Input() pageSize = DEFAULT_PAGE_SIZE;
|
@Input() pageSize = DEFAULT_PAGE_SIZE;
|
||||||
@Input() showActions = true;
|
@Input() showActions = true;
|
||||||
@Input() showCheckbox = false;
|
@Input() showCheckbox = false;
|
||||||
@Input() showFooter = true;
|
@Input() showFooter = true;
|
||||||
@Input() showNameColumn = true;
|
@Input() showNameColumn = true;
|
||||||
|
@Input() sortColumn: string;
|
||||||
|
@Input() sortDirection: SortDirection;
|
||||||
|
@Input() sortDisabled = false;
|
||||||
|
@Input() totalItems = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
@Output() activityDeleted = new EventEmitter<string>();
|
@Output() activityDeleted = new EventEmitter<string>();
|
||||||
@Output() activityToClone = new EventEmitter<OrderWithAccount>();
|
@Output() activityToClone = new EventEmitter<OrderWithAccount>();
|
||||||
@Output() activityToUpdate = new EventEmitter<OrderWithAccount>();
|
@Output() activityToUpdate = new EventEmitter<OrderWithAccount>();
|
||||||
@Output() deleteAllActivities = new EventEmitter<void>();
|
@Output() deleteAllActivities = new EventEmitter<void>();
|
||||||
@Output() export = new EventEmitter<string[]>();
|
@Output() export = new EventEmitter<void>();
|
||||||
@Output() exportDrafts = new EventEmitter<string[]>();
|
@Output() exportDrafts = new EventEmitter<string[]>();
|
||||||
@Output() import = new EventEmitter<void>();
|
@Output() import = new EventEmitter<void>();
|
||||||
@Output() importDividends = new EventEmitter<UniqueAsset>();
|
@Output() importDividends = new EventEmitter<UniqueAsset>();
|
||||||
|
@Output() pageChanged = new EventEmitter<PageEvent>();
|
||||||
@Output() selectedActivities = new EventEmitter<Activity[]>();
|
@Output() selectedActivities = new EventEmitter<Activity[]>();
|
||||||
|
@Output() sortChanged = new EventEmitter<Sort>();
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
public allFilters: Filter[];
|
|
||||||
public dataSource: MatTableDataSource<Activity> = new MatTableDataSource();
|
|
||||||
public defaultDateFormat: string;
|
public defaultDateFormat: string;
|
||||||
public displayedColumns = [];
|
public displayedColumns = [];
|
||||||
public endOfToday = endOfToday();
|
public endOfToday = endOfToday();
|
||||||
public filters$ = new Subject<Filter[]>();
|
|
||||||
public hasDrafts = false;
|
public hasDrafts = false;
|
||||||
public hasErrors = false;
|
public hasErrors = false;
|
||||||
public isAfter = isAfter;
|
public isAfter = isAfter;
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
public isUUID = isUUID;
|
public isUUID = isUUID;
|
||||||
public pageIndex = 0;
|
|
||||||
public placeholder = '';
|
|
||||||
public routeQueryParams: Subscription;
|
public routeQueryParams: Subscription;
|
||||||
public searchKeywords: string[] = [];
|
|
||||||
public selectedRows = new SelectionModel<Activity>(true, []);
|
public selectedRows = new SelectionModel<Activity>(true, []);
|
||||||
public totalFees: number;
|
|
||||||
public totalValue: number;
|
|
||||||
|
|
||||||
private readonly SEARCH_STRING_SEPARATOR = ',';
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
public constructor(private router: Router) {
|
public constructor(private router: Router) {}
|
||||||
this.filters$
|
|
||||||
.pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((filters) => {
|
|
||||||
this.updateFilters(filters);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
if (this.showCheckbox) {
|
if (this.showCheckbox) {
|
||||||
@ -101,6 +92,12 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ngAfterViewInit() {
|
||||||
|
this.sort.sortChange.subscribe((value: Sort) => {
|
||||||
|
this.sortChanged.emit(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public areAllRowsSelected() {
|
public areAllRowsSelected() {
|
||||||
const numSelectedRows = this.selectedRows.selected.length;
|
const numSelectedRows = this.selectedRows.selected.length;
|
||||||
const numTotalRows = this.dataSource.data.length;
|
const numTotalRows = this.dataSource.data.length;
|
||||||
@ -108,13 +105,15 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ngOnChanges() {
|
public ngOnChanges() {
|
||||||
|
this.defaultDateFormat = getDateFormatString(this.locale);
|
||||||
|
|
||||||
this.displayedColumns = [
|
this.displayedColumns = [
|
||||||
'select',
|
'select',
|
||||||
'importStatus',
|
'importStatus',
|
||||||
'count',
|
'icon',
|
||||||
'date',
|
|
||||||
'type',
|
|
||||||
'nameWithSymbol',
|
'nameWithSymbol',
|
||||||
|
'type',
|
||||||
|
'date',
|
||||||
'quantity',
|
'quantity',
|
||||||
'unitPrice',
|
'unitPrice',
|
||||||
'fee',
|
'fee',
|
||||||
@ -126,11 +125,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
'actions'
|
'actions'
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.showCheckbox) {
|
if (!this.showCheckbox) {
|
||||||
this.displayedColumns = this.displayedColumns.filter((column) => {
|
|
||||||
return column !== 'count';
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.displayedColumns = this.displayedColumns.filter((column) => {
|
this.displayedColumns = this.displayedColumns.filter((column) => {
|
||||||
return column !== 'importStatus' && column !== 'select';
|
return column !== 'importStatus' && column !== 'select';
|
||||||
});
|
});
|
||||||
@ -142,60 +137,13 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.defaultDateFormat = getDateFormatString(this.locale);
|
if (this.dataSource) {
|
||||||
|
this.isLoading = false;
|
||||||
if (this.activities) {
|
|
||||||
this.activities = this.activities.map((activity) => {
|
|
||||||
return {
|
|
||||||
...activity,
|
|
||||||
error: activity.error
|
|
||||||
? {
|
|
||||||
...activity.error,
|
|
||||||
message: translate(
|
|
||||||
`IMPORT_ACTIVITY_ERROR_${activity.error.code}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.allFilters = this.getSearchableFieldValues(this.activities);
|
|
||||||
|
|
||||||
this.dataSource = new MatTableDataSource(this.activities);
|
|
||||||
this.dataSource.filterPredicate = (data, filter) => {
|
|
||||||
const filterableLabels = this.getFilterableValues(data).map(
|
|
||||||
({ label }) => {
|
|
||||||
return label.toLowerCase();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let includes = true;
|
|
||||||
for (const singleFilter of filter.split(this.SEARCH_STRING_SEPARATOR)) {
|
|
||||||
includes =
|
|
||||||
includes &&
|
|
||||||
filterableLabels.includes(singleFilter.trim().toLowerCase());
|
|
||||||
}
|
|
||||||
return includes;
|
|
||||||
};
|
|
||||||
this.dataSource.paginator = this.paginator;
|
|
||||||
this.dataSource.sort = this.sort;
|
|
||||||
this.dataSource.sortingDataAccessor = get;
|
|
||||||
|
|
||||||
this.updateFilters();
|
|
||||||
|
|
||||||
this.hasErrors = this.activities.some(({ error }) => {
|
|
||||||
return !!error;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.hasErrors = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangePage(page: PageEvent) {
|
public onChangePage(page: PageEvent) {
|
||||||
this.pageIndex = page.pageIndex;
|
this.pageChanged.emit(page);
|
||||||
|
|
||||||
this.totalFees = this.getTotalFees();
|
|
||||||
this.totalValue = this.getTotalValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onClickActivity(activity: Activity) {
|
public onClickActivity(activity: Activity) {
|
||||||
@ -233,15 +181,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onExport() {
|
public onExport() {
|
||||||
if (this.searchKeywords.length > 0) {
|
this.export.emit();
|
||||||
this.export.emit(
|
|
||||||
this.dataSource.filteredData.map((activity) => {
|
|
||||||
return activity.id;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.export.emit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onExportDraft(aActivityId: string) {
|
public onExportDraft(aActivityId: string) {
|
||||||
@ -298,145 +238,4 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFilterableValues(
|
|
||||||
activity: OrderWithAccount,
|
|
||||||
fieldValueMap: { [id: string]: Filter } = {}
|
|
||||||
): Filter[] {
|
|
||||||
if (activity.Account?.id) {
|
|
||||||
fieldValueMap[activity.Account.id] = {
|
|
||||||
id: activity.Account.id,
|
|
||||||
label: activity.Account.name,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity.SymbolProfile?.currency) {
|
|
||||||
fieldValueMap[activity.SymbolProfile.currency] = {
|
|
||||||
id: activity.SymbolProfile.currency,
|
|
||||||
label: activity.SymbolProfile.currency,
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
activity.SymbolProfile?.symbol &&
|
|
||||||
!isUUID(activity.SymbolProfile.symbol)
|
|
||||||
) {
|
|
||||||
fieldValueMap[activity.SymbolProfile.symbol] = {
|
|
||||||
id: activity.SymbolProfile.symbol,
|
|
||||||
label: activity.SymbolProfile.symbol,
|
|
||||||
type: 'SYMBOL'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldValueMap[activity.type] = {
|
|
||||||
id: activity.type,
|
|
||||||
label: activity.type,
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
|
|
||||||
fieldValueMap[format(new Date(activity.date), 'yyyy')] = {
|
|
||||||
id: format(new Date(activity.date), 'yyyy'),
|
|
||||||
label: format(new Date(activity.date), 'yyyy'),
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
|
|
||||||
return Object.values(fieldValueMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPaginatedData() {
|
|
||||||
if (this.dataSource.data.length > this.pageSize) {
|
|
||||||
const sortedData = this.dataSource.sortData(
|
|
||||||
this.dataSource.filteredData,
|
|
||||||
this.dataSource.sort
|
|
||||||
);
|
|
||||||
|
|
||||||
return sortedData.slice(
|
|
||||||
this.pageIndex * this.pageSize,
|
|
||||||
(this.pageIndex + 1) * this.pageSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.dataSource.filteredData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSearchableFieldValues(activities: OrderWithAccount[]): Filter[] {
|
|
||||||
const fieldValueMap: { [id: string]: Filter } = {};
|
|
||||||
|
|
||||||
for (const activity of activities) {
|
|
||||||
this.getFilterableValues(activity, fieldValueMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.values(fieldValueMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTotalFees() {
|
|
||||||
let totalFees = new Big(0);
|
|
||||||
const paginatedData = this.getPaginatedData();
|
|
||||||
for (const activity of paginatedData) {
|
|
||||||
if (isNumber(activity.feeInBaseCurrency)) {
|
|
||||||
totalFees = totalFees.plus(activity.feeInBaseCurrency);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalFees.toNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTotalValue() {
|
|
||||||
const paginatedData = this.getPaginatedData();
|
|
||||||
let totalValue = new Big(0);
|
|
||||||
|
|
||||||
for (const { type, valueInBaseCurrency } of paginatedData) {
|
|
||||||
if (isNumber(valueInBaseCurrency)) {
|
|
||||||
if (type === 'BUY' || type === 'ITEM') {
|
|
||||||
totalValue = totalValue.plus(valueInBaseCurrency);
|
|
||||||
} else if (
|
|
||||||
type === 'DIVIDEND' ||
|
|
||||||
type === 'FEE' ||
|
|
||||||
type === 'INTEREST' ||
|
|
||||||
type === 'LIABILITY' ||
|
|
||||||
type === 'SELL'
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalValue.toNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateFilters(filters: Filter[] = []) {
|
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
this.dataSource.filter = filters
|
|
||||||
.map((filter) => {
|
|
||||||
return filter.label;
|
|
||||||
})
|
|
||||||
.join(this.SEARCH_STRING_SEPARATOR);
|
|
||||||
|
|
||||||
const lowercaseSearchKeywords = filters.map((filter) => {
|
|
||||||
return filter.label.trim().toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.placeholder =
|
|
||||||
lowercaseSearchKeywords.length <= 0
|
|
||||||
? $localize`Filter by account, currency, symbol or type...`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
this.searchKeywords = filters.map((filter) => {
|
|
||||||
return filter.label;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.hasDrafts = this.dataSource.filteredData.some((activity) => {
|
|
||||||
return activity.isDraft === true;
|
|
||||||
});
|
|
||||||
this.totalFees = this.getTotalFees();
|
|
||||||
this.totalValue = this.getTotalValue();
|
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
|
|
||||||
import { GfActivityTypeModule } from '@ghostfolio/ui/activity-type';
|
import { GfActivityTypeModule } from '@ghostfolio/ui/activity-type';
|
||||||
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
@ -23,7 +22,6 @@ import { ActivitiesTableComponent } from './activities-table.component';
|
|||||||
exports: [ActivitiesTableComponent],
|
exports: [ActivitiesTableComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfActivitiesFilterModule,
|
|
||||||
GfActivityTypeModule,
|
GfActivityTypeModule,
|
||||||
GfNoTransactionsInfoModule,
|
GfNoTransactionsInfoModule,
|
||||||
GfSymbolIconModule,
|
GfSymbolIconModule,
|
||||||
|
@ -22,7 +22,7 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
|||||||
import { Filter, User } from '@ghostfolio/common/interfaces';
|
import { Filter, User } from '@ghostfolio/common/interfaces';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { Account, AssetClass, Tag } from '@prisma/client';
|
import { Account, AssetClass } from '@prisma/client';
|
||||||
import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs';
|
import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
@ -158,25 +158,6 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterForm.valueChanges
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ account, assetClass, tag }) => {
|
|
||||||
this.filtersChanged.emit([
|
|
||||||
{
|
|
||||||
id: account,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: assetClass,
|
|
||||||
type: 'ASSET_CLASS'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: tag,
|
|
||||||
type: 'TAG'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.searchFormControl.valueChanges
|
this.searchFormControl.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
map((searchTerm) => {
|
map((searchTerm) => {
|
||||||
@ -260,6 +241,25 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onApplyFilters() {
|
||||||
|
this.filtersChanged.emit([
|
||||||
|
{
|
||||||
|
id: this.filterForm.get('account').value,
|
||||||
|
type: 'ACCOUNT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: this.filterForm.get('assetClass').value,
|
||||||
|
type: 'ASSET_CLASS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: this.filterForm.get('tag').value,
|
||||||
|
type: 'TAG'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.onCloseAssistant();
|
||||||
|
}
|
||||||
|
|
||||||
public onChangeDateRange(dateRangeString: string) {
|
public onChangeDateRange(dateRangeString: string) {
|
||||||
this.dateRangeChanged.emit(dateRangeString as DateRange);
|
this.dateRangeChanged.emit(dateRangeString as DateRange);
|
||||||
}
|
}
|
||||||
|
@ -87,11 +87,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form [formGroup]="filterForm">
|
<form [formGroup]="filterForm">
|
||||||
<div
|
<ng-container *ngIf="!(isLoading || searchFormControl.value)">
|
||||||
*ngIf="!(isLoading || searchFormControl.value) && user?.settings?.isExperimentalFeatures"
|
<div class="date-range-selector-container p-3">
|
||||||
class="filter-container p-3"
|
|
||||||
>
|
|
||||||
<div class="mb-3">
|
|
||||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||||
<mat-label i18n>Date Range</mat-label>
|
<mat-label i18n>Date Range</mat-label>
|
||||||
<mat-select
|
<mat-select
|
||||||
@ -104,62 +101,72 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="p-3">
|
||||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
<div class="mb-3">
|
||||||
<mat-label i18n>Accounts</mat-label>
|
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||||
<mat-select formControlName="account">
|
<mat-label i18n>Accounts</mat-label>
|
||||||
<mat-option [value]="null"></mat-option>
|
<mat-select formControlName="account">
|
||||||
@for (account of accounts; track account.id) {
|
<mat-option [value]="null"></mat-option>
|
||||||
<mat-option [value]="account.id">
|
@for (account of accounts; track account.id) {
|
||||||
<div class="d-flex">
|
<mat-option [value]="account.id">
|
||||||
<gf-symbol-icon
|
<div class="d-flex">
|
||||||
*ngIf="account.Platform?.url"
|
<gf-symbol-icon
|
||||||
class="mr-1"
|
*ngIf="account.Platform?.url"
|
||||||
[tooltip]="account.Platform?.name"
|
class="mr-1"
|
||||||
[url]="account.Platform?.url"
|
[tooltip]="account.Platform?.name"
|
||||||
/><span>{{ account.name }}</span>
|
[url]="account.Platform?.url"
|
||||||
</div>
|
/><span>{{ account.name }}</span>
|
||||||
</mat-option>
|
</div>
|
||||||
}
|
</mat-option>
|
||||||
</mat-select>
|
}
|
||||||
</mat-form-field>
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||||
|
<mat-label i18n>Tags</mat-label>
|
||||||
|
<mat-select formControlName="tag">
|
||||||
|
<mat-option [value]="null"></mat-option>
|
||||||
|
@for (tag of tags; track tag.id) {
|
||||||
|
<mat-option [value]="tag.id">{{ tag.label }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||||
|
<mat-label i18n>Asset Classes</mat-label>
|
||||||
|
<mat-select formControlName="assetClass">
|
||||||
|
<mat-option [value]="null"></mat-option>
|
||||||
|
@for (assetClass of assetClasses; track assetClass.id) {
|
||||||
|
<mat-option [value]="assetClass.id"
|
||||||
|
>{{ assetClass.label }}</mat-option
|
||||||
|
>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex w-100">
|
||||||
|
<button
|
||||||
|
i18n
|
||||||
|
mat-button
|
||||||
|
[disabled]="!hasFilter(filterForm.value)"
|
||||||
|
(click)="onResetFilters()"
|
||||||
|
>
|
||||||
|
Reset Filters
|
||||||
|
</button>
|
||||||
|
<span class="gf-spacer"></span>
|
||||||
|
<button
|
||||||
|
color="primary"
|
||||||
|
i18n
|
||||||
|
mat-flat-button
|
||||||
|
[disabled]="!filterForm.dirty"
|
||||||
|
(click)="onApplyFilters()"
|
||||||
|
>
|
||||||
|
Apply Filters
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
</ng-container>
|
||||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
|
||||||
<mat-label i18n>Tags</mat-label>
|
|
||||||
<mat-select formControlName="tag">
|
|
||||||
<mat-option [value]="null"></mat-option>
|
|
||||||
@for (tag of tags; track tag.id) {
|
|
||||||
<mat-option [value]="tag.id">{{ tag.label }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
|
||||||
<mat-label i18n>Asset Classes</mat-label>
|
|
||||||
<mat-select formControlName="assetClass">
|
|
||||||
<mat-option [value]="null"></mat-option>
|
|
||||||
@for (assetClass of assetClasses; track assetClass.id) {
|
|
||||||
<mat-option [value]="assetClass.id"
|
|
||||||
>{{ assetClass.label }}</mat-option
|
|
||||||
>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="w-100"
|
|
||||||
color="primary"
|
|
||||||
i18n
|
|
||||||
mat-flat-button
|
|
||||||
[disabled]="!hasFilter(filterForm.value)"
|
|
||||||
(click)="onResetFilters()"
|
|
||||||
>
|
|
||||||
Reset Filters
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
.filter-container {
|
.date-range-selector-container {
|
||||||
.mat-mdc-tab-group {
|
border-bottom: 1px solid rgba(var(--dark-dividers));
|
||||||
max-height: 20vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
label {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-container {
|
.result-container {
|
||||||
@ -35,6 +27,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
|
.date-range-selector-container {
|
||||||
|
border-color: rgba(var(--light-dividers));
|
||||||
|
}
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
border-color: rgba(var(--light-dividers));
|
border-color: rgba(var(--light-dividers));
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.48.1",
|
"version": "2.49.0",
|
||||||
"homepage": "https://ghostfol.io",
|
"homepage": "https://ghostfol.io",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||||
@ -133,7 +133,7 @@
|
|||||||
"svgmap": "2.6.0",
|
"svgmap": "2.6.0",
|
||||||
"twitter-api-v2": "1.14.2",
|
"twitter-api-v2": "1.14.2",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"yahoo-finance2": "2.9.0",
|
"yahoo-finance2": "2.9.1",
|
||||||
"zone.js": "0.14.2"
|
"zone.js": "0.14.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -20184,10 +20184,10 @@ y18n@^5.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||||
|
|
||||||
yahoo-finance2@2.9.0:
|
yahoo-finance2@2.9.1:
|
||||||
version "2.9.0"
|
version "2.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.9.0.tgz#7842580de36606197f7d64897dd2e5e55b9371d3"
|
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.9.1.tgz#43e22465403f48c688ff8e762f3894aac8014d70"
|
||||||
integrity sha512-Q1UhB5uA0Uj2bBcSDqsZLt0tCxoHwrWCuvu4NMUgioyN8dlpq8ppbdKhZlzTD9ipIyKSgqG5TT7IlwB1x6eHZA==
|
integrity sha512-s+i5arE6+zUwHRJnze4EsU5aCTmsMFKFeBc9sMzSceDOjH+BSeEZG9twMYtWlSCjKbWLCmUEUCxtH1fvcq+f6Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/tough-cookie" "^4.0.2"
|
"@types/tough-cookie" "^4.0.2"
|
||||||
ajv "8.10.0"
|
ajv "8.10.0"
|
||||||
|
Reference in New Issue
Block a user