Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror
This commit is contained in:
commit
099896f46b
10
CHANGELOG.md
10
CHANGELOG.md
@ -5,7 +5,15 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
## 2.105.0 - 2024-08-21
|
||||
|
||||
### Added
|
||||
|
||||
- Added support to deactivate rules in the _X-ray_ section (experimental)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the language localization for German (`de`)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -12,11 +12,8 @@ export class RulesService {
|
||||
aRules: Rule<T>[],
|
||||
aUserSettings: UserSettings
|
||||
) {
|
||||
return aRules
|
||||
.filter((rule) => {
|
||||
return rule.getSettings(aUserSettings)?.isActive;
|
||||
})
|
||||
.map((rule) => {
|
||||
return aRules.map((rule) => {
|
||||
if (rule.getSettings(aUserSettings)?.isActive) {
|
||||
const { evaluation, value } = rule.evaluate(
|
||||
rule.getSettings(aUserSettings)
|
||||
);
|
||||
@ -24,9 +21,17 @@ export class RulesService {
|
||||
return {
|
||||
evaluation,
|
||||
value,
|
||||
isActive: true,
|
||||
key: rule.getKey(),
|
||||
name: rule.getName()
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
isActive: false,
|
||||
key: rule.getKey(),
|
||||
name: rule.getName()
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import type {
|
||||
ColorScheme,
|
||||
DateRange,
|
||||
HoldingsViewMode,
|
||||
ViewMode
|
||||
ViewMode,
|
||||
XRayRulesSettings
|
||||
} from '@ghostfolio/common/types';
|
||||
|
||||
import {
|
||||
@ -102,4 +103,7 @@ export class UpdateUserSettingDto {
|
||||
@IsIn(<ViewMode[]>['DEFAULT', 'ZEN'])
|
||||
@IsOptional()
|
||||
viewMode?: ViewMode;
|
||||
|
||||
@IsOptional()
|
||||
xRayRules?: XRayRulesSettings;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import { JwtService } from '@nestjs/jwt';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { User as UserModel } from '@prisma/client';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
import { size } from 'lodash';
|
||||
import { merge, size } from 'lodash';
|
||||
|
||||
import { DeleteOwnUserDto } from './delete-own-user.dto';
|
||||
import { UserItem } from './interfaces/user-item.interface';
|
||||
@ -144,10 +144,11 @@ export class UserController {
|
||||
);
|
||||
}
|
||||
|
||||
const userSettings: UserSettings = {
|
||||
...(<UserSettings>this.request.user.Settings.settings),
|
||||
...data
|
||||
};
|
||||
const userSettings: UserSettings = merge(
|
||||
{},
|
||||
<UserSettings>this.request.user.Settings.settings,
|
||||
data
|
||||
);
|
||||
|
||||
for (const key in userSettings) {
|
||||
if (userSettings[key] === false || userSettings[key] === null) {
|
||||
|
@ -197,6 +197,18 @@ export class UserService {
|
||||
(user.Settings.settings as UserSettings).viewMode = 'DEFAULT';
|
||||
}
|
||||
|
||||
// Set default values for X-ray rules
|
||||
if (!(user.Settings.settings as UserSettings).xRayRules) {
|
||||
(user.Settings.settings as UserSettings).xRayRules = {
|
||||
AccountClusterRiskCurrentInvestment: { isActive: true },
|
||||
AccountClusterRiskSingleAccount: { isActive: true },
|
||||
CurrencyClusterRiskBaseCurrencyCurrentInvestment: { isActive: true },
|
||||
CurrencyClusterRiskCurrentInvestment: { isActive: true },
|
||||
EmergencyFundSetup: { isActive: true },
|
||||
FeeRatioInitialInvestment: { isActive: true }
|
||||
};
|
||||
}
|
||||
|
||||
let currentPermissions = getPermissions(user.role);
|
||||
|
||||
if (!(user.Settings.settings as UserSettings).isExperimentalFeatures) {
|
||||
|
@ -79,7 +79,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||
public getSettings(aUserSettings: UserSettings): Settings {
|
||||
return {
|
||||
baseCurrency: aUserSettings.baseCurrency,
|
||||
isActive: true,
|
||||
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
|
||||
thresholdMax: 0.5
|
||||
};
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
|
||||
|
||||
public getSettings(aUserSettings: UserSettings): RuleSettings {
|
||||
return {
|
||||
isActive: true
|
||||
isActive: aUserSettings.xRayRules[this.getKey()].isActive
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
|
||||
public getSettings(aUserSettings: UserSettings): Settings {
|
||||
return {
|
||||
baseCurrency: aUserSettings.baseCurrency,
|
||||
isActive: true
|
||||
isActive: aUserSettings.xRayRules[this.getKey()].isActive
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||
public getSettings(aUserSettings: UserSettings): Settings {
|
||||
return {
|
||||
baseCurrency: aUserSettings.baseCurrency,
|
||||
isActive: true,
|
||||
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
|
||||
thresholdMax: 0.5
|
||||
};
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export class EmergencyFundSetup extends Rule<Settings> {
|
||||
public getSettings(aUserSettings: UserSettings): Settings {
|
||||
return {
|
||||
baseCurrency: aUserSettings.baseCurrency,
|
||||
isActive: true,
|
||||
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
|
||||
thresholdMin: 0
|
||||
};
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
|
||||
public getSettings(aUserSettings: UserSettings): Settings {
|
||||
return {
|
||||
baseCurrency: aUserSettings.baseCurrency,
|
||||
isActive: true,
|
||||
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
|
||||
thresholdMax: 0.01
|
||||
};
|
||||
}
|
||||
|
@ -14,12 +14,17 @@
|
||||
} @else {
|
||||
<div
|
||||
class="align-items-center d-flex icon-container mr-2 px-2"
|
||||
[ngClass]="{ okay: rule?.value === true, warn: rule?.value === false }"
|
||||
[ngClass]="{
|
||||
okay: rule?.value === true,
|
||||
warn: rule?.value === false
|
||||
}"
|
||||
>
|
||||
@if (rule?.value === true) {
|
||||
<ion-icon name="checkmark-circle-outline" />
|
||||
} @else {
|
||||
} @else if (rule?.isActive === true) {
|
||||
<ion-icon name="warning-outline" />
|
||||
} @else {
|
||||
<ion-icon class="text-muted" name="remove-circle-outline" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@ -46,6 +51,27 @@
|
||||
<div class="h6 my-1">{{ rule?.name }}</div>
|
||||
<div class="evaluation">{{ rule?.evaluation }}</div>
|
||||
</div>
|
||||
<div>
|
||||
@if (hasPermissionToUpdateUserSettings) {
|
||||
<button
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
[matMenuTriggerFor]="rulesMenu"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<ion-icon name="ellipsis-horizontal" />
|
||||
</button>
|
||||
<mat-menu #rulesMenu="matMenu" xPosition="before">
|
||||
<button mat-menu-item (click)="onUpdateRule(rule)">
|
||||
@if (rule?.isActive) {
|
||||
<ng-container i18n>Deactivate</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>Activate</ng-container>
|
||||
}
|
||||
</button>
|
||||
</mat-menu>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
||||
import { PortfolioReportRule } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
@ -14,10 +17,23 @@ import {
|
||||
styleUrls: ['./rule.component.scss']
|
||||
})
|
||||
export class RuleComponent implements OnInit {
|
||||
@Input() hasPermissionToUpdateUserSettings: boolean;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() rule: PortfolioReportRule;
|
||||
|
||||
@Output() ruleUpdated = new EventEmitter<UpdateUserSettingDto>();
|
||||
|
||||
public constructor() {}
|
||||
|
||||
public ngOnInit() {}
|
||||
|
||||
public onUpdateRule(rule: PortfolioReportRule) {
|
||||
const settings: UpdateUserSettingDto = {
|
||||
xRayRules: {
|
||||
[rule.key]: { isActive: !rule.isActive }
|
||||
}
|
||||
};
|
||||
|
||||
this.ruleUpdated.emit(settings);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { RuleComponent } from './rule.component';
|
||||
@ -7,7 +9,12 @@ import { RuleComponent } from './rule.component';
|
||||
@NgModule({
|
||||
declarations: [RuleComponent],
|
||||
exports: [RuleComponent],
|
||||
imports: [CommonModule, NgxSkeletonLoaderModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
MatMenuModule,
|
||||
NgxSkeletonLoaderModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GfRuleModule {}
|
||||
|
@ -1,20 +1,19 @@
|
||||
<div class="container p-0">
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
@if (hasPermissionToCreateOrder && rules === null) {
|
||||
<mat-card appearance="outlined" class="my-2 text-center">
|
||||
<mat-card-content>
|
||||
<gf-no-transactions-info-indicator [hasBorder]="false" />
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
|
||||
@if (rules?.length === 0) {
|
||||
@if (isLoading) {
|
||||
<gf-rule [isLoading]="true" />
|
||||
}
|
||||
|
||||
@if (rules !== null && rules !== undefined) {
|
||||
@for (rule of rules; track rule) {
|
||||
<gf-rule [rule]="rule" />
|
||||
@for (rule of rules; track rule.key) {
|
||||
<gf-rule
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
hasPermissionToUpdateUserSettings
|
||||
"
|
||||
[rule]="rule"
|
||||
(ruleUpdated)="onRuleUpdated($event)"
|
||||
/>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
||||
import { PortfolioReportRule } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-rules',
|
||||
@ -9,8 +16,15 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
styleUrls: ['./rules.component.scss']
|
||||
})
|
||||
export class RulesComponent {
|
||||
@Input() hasPermissionToCreateOrder: boolean;
|
||||
@Input() hasPermissionToUpdateUserSettings: boolean;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() rules: PortfolioReportRule[];
|
||||
|
||||
@Output() rulesUpdated = new EventEmitter<UpdateUserSettingDto>();
|
||||
|
||||
public constructor() {}
|
||||
|
||||
public onRuleUpdated(event: UpdateUserSettingDto) {
|
||||
this.rulesUpdated.emit(event);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { GfRuleModule } from '@ghostfolio/client/components/rule/rule.module';
|
||||
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
@ -11,13 +10,7 @@ import { RulesComponent } from './rules.component';
|
||||
@NgModule({
|
||||
declarations: [RulesComponent],
|
||||
exports: [RulesComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfNoTransactionsInfoComponent,
|
||||
GfRuleModule,
|
||||
MatButtonModule,
|
||||
MatCardModule
|
||||
],
|
||||
imports: [CommonModule, GfRuleModule, MatButtonModule, MatCardModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GfRulesModule {}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
||||
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 { PortfolioReportRule, User } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
PortfolioReport,
|
||||
PortfolioReportRule,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
@ -23,9 +28,10 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
public feeRules: PortfolioReportRule[];
|
||||
public fireWealth: Big;
|
||||
public hasImpersonationId: boolean;
|
||||
public hasPermissionToCreateOrder: boolean;
|
||||
public hasPermissionToUpdateUserSettings: boolean;
|
||||
public inactiveRules: PortfolioReportRule[];
|
||||
public isLoading = false;
|
||||
public isLoadingPortfolioReport = false;
|
||||
public user: User;
|
||||
public withdrawalRatePerMonth: Big;
|
||||
public withdrawalRatePerYear: Big;
|
||||
@ -64,21 +70,6 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
|
||||
this.dataService
|
||||
.fetchPortfolioReport()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((portfolioReport) => {
|
||||
this.accountClusterRiskRules =
|
||||
portfolioReport.rules['accountClusterRisk'] || null;
|
||||
this.currencyClusterRiskRules =
|
||||
portfolioReport.rules['currencyClusterRisk'] || null;
|
||||
this.emergencyFundRules =
|
||||
portfolioReport.rules['emergencyFund'] || null;
|
||||
this.feeRules = portfolioReport.rules['fees'] || null;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
|
||||
this.impersonationStorageService
|
||||
.onChangeHasImpersonation()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
@ -92,11 +83,6 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.hasPermissionToCreateOrder = hasPermission(
|
||||
this.user.permissions,
|
||||
permissions.createOrder
|
||||
);
|
||||
|
||||
this.hasPermissionToUpdateUserSettings =
|
||||
this.user.subscription?.type === 'Basic'
|
||||
? false
|
||||
@ -108,6 +94,8 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this.initializePortfolioReport();
|
||||
}
|
||||
|
||||
public onAnnualInterestRateChange(annualInterestRate: number) {
|
||||
@ -149,6 +137,17 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public onRulesUpdated(event: UpdateUserSettingDto) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.dataService
|
||||
.putUserSetting(event)
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(() => {
|
||||
this.initializePortfolioReport();
|
||||
});
|
||||
}
|
||||
|
||||
public onSavingsRateChange(savingsRate: number) {
|
||||
this.dataService
|
||||
.putUserSetting({ savingsRate })
|
||||
@ -192,4 +191,59 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
|
||||
private initializePortfolioReport() {
|
||||
this.isLoadingPortfolioReport = true;
|
||||
|
||||
this.dataService
|
||||
.fetchPortfolioReport()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((portfolioReport) => {
|
||||
this.inactiveRules = this.mergeInactiveRules(portfolioReport);
|
||||
|
||||
this.accountClusterRiskRules =
|
||||
portfolioReport.rules['accountClusterRisk']?.filter(
|
||||
({ isActive }) => {
|
||||
return isActive;
|
||||
}
|
||||
) ?? null;
|
||||
|
||||
this.currencyClusterRiskRules =
|
||||
portfolioReport.rules['currencyClusterRisk']?.filter(
|
||||
({ isActive }) => {
|
||||
return isActive;
|
||||
}
|
||||
) ?? null;
|
||||
|
||||
this.emergencyFundRules =
|
||||
portfolioReport.rules['emergencyFund']?.filter(({ isActive }) => {
|
||||
return isActive;
|
||||
}) ?? null;
|
||||
|
||||
this.feeRules =
|
||||
portfolioReport.rules['fees']?.filter(({ isActive }) => {
|
||||
return isActive;
|
||||
}) ?? null;
|
||||
|
||||
this.isLoadingPortfolioReport = false;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private mergeInactiveRules(report: PortfolioReport): PortfolioReportRule[] {
|
||||
let inactiveRules: PortfolioReportRule[] = [];
|
||||
|
||||
for (const category in report.rules) {
|
||||
const rulesArray = report.rules[category];
|
||||
|
||||
inactiveRules = inactiveRules.concat(
|
||||
rulesArray.filter(({ isActive }) => {
|
||||
return !isActive;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return inactiveRules;
|
||||
}
|
||||
}
|
||||
|
@ -125,8 +125,14 @@
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[isLoading]="isLoadingPortfolioReport"
|
||||
[rules]="emergencyFundRules"
|
||||
(rulesUpdated)="onRulesUpdated($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
@ -137,8 +143,14 @@
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[isLoading]="isLoadingPortfolioReport"
|
||||
[rules]="currencyClusterRiskRules"
|
||||
(rulesUpdated)="onRulesUpdated($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
@ -149,11 +161,17 @@
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[isLoading]="isLoadingPortfolioReport"
|
||||
[rules]="accountClusterRiskRules"
|
||||
(rulesUpdated)="onRulesUpdated($event)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<h4 class="align-items-center d-flex m-0">
|
||||
<span i18n>Fees</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
@ -161,10 +179,31 @@
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[isLoading]="isLoadingPortfolioReport"
|
||||
[rules]="feeRules"
|
||||
(rulesUpdated)="onRulesUpdated($event)"
|
||||
/>
|
||||
</div>
|
||||
@if (inactiveRules?.length > 0) {
|
||||
<div>
|
||||
<h4 class="m-0" i18n>Inactive</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[isLoading]="isLoadingPortfolioReport"
|
||||
[rules]="inactiveRules"
|
||||
(rulesUpdated)="onRulesUpdated($event)"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
export interface PortfolioReportRule {
|
||||
evaluation: string;
|
||||
evaluation?: string;
|
||||
isActive: boolean;
|
||||
key: string;
|
||||
name: string;
|
||||
value: boolean;
|
||||
value?: boolean;
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ import {
|
||||
ColorScheme,
|
||||
DateRange,
|
||||
HoldingsViewMode,
|
||||
ViewMode
|
||||
ViewMode,
|
||||
XRayRulesSettings
|
||||
} from '@ghostfolio/common/types';
|
||||
|
||||
export interface UserSettings {
|
||||
@ -23,4 +24,5 @@ export interface UserSettings {
|
||||
retirementDate?: string;
|
||||
savingsRate?: number;
|
||||
viewMode?: ViewMode;
|
||||
xRayRules?: XRayRulesSettings;
|
||||
}
|
||||
|
@ -668,5 +668,15 @@ export const personalFinanceTools: Product[] = [
|
||||
origin: 'United States',
|
||||
pricingPerYear: '$99',
|
||||
slogan: 'Change Your Relationship With Money'
|
||||
},
|
||||
{
|
||||
founded: 2019,
|
||||
hasFreePlan: false,
|
||||
hasSelfHostingAbility: false,
|
||||
key: 'ziggma',
|
||||
name: 'Ziggma',
|
||||
origin: 'United States',
|
||||
pricingPerYear: '$90',
|
||||
slogan: 'Your solution for investing success'
|
||||
}
|
||||
];
|
||||
|
@ -19,6 +19,7 @@ import type { SubscriptionOffer } from './subscription-offer.type';
|
||||
import type { ToggleOption } from './toggle-option.type';
|
||||
import type { UserWithSettings } from './user-with-settings.type';
|
||||
import type { ViewMode } from './view-mode.type';
|
||||
import type { XRayRulesSettings } from './x-ray-rules-settings.type';
|
||||
|
||||
export type {
|
||||
AccessType,
|
||||
@ -41,5 +42,6 @@ export type {
|
||||
SubscriptionOffer,
|
||||
ToggleOption,
|
||||
UserWithSettings,
|
||||
ViewMode
|
||||
ViewMode,
|
||||
XRayRulesSettings
|
||||
};
|
||||
|
12
libs/common/src/lib/types/x-ray-rules-settings.type.ts
Normal file
12
libs/common/src/lib/types/x-ray-rules-settings.type.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export type XRayRulesSettings = {
|
||||
AccountClusterRiskCurrentInvestment?: RuleSettings;
|
||||
AccountClusterRiskSingleAccount?: RuleSettings;
|
||||
CurrencyClusterRiskBaseCurrencyCurrentInvestment?: RuleSettings;
|
||||
CurrencyClusterRiskCurrentInvestment?: RuleSettings;
|
||||
EmergencyFundSetup?: RuleSettings;
|
||||
FeeRatioInitialInvestment?: RuleSettings;
|
||||
};
|
||||
|
||||
interface RuleSettings {
|
||||
isActive: boolean;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "2.104.1",
|
||||
"version": "2.105.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||
|
Loading…
x
Reference in New Issue
Block a user