Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
c69686651e | |||
93b6011ddc | |||
f567e25f27 | |||
5dc538bafb | |||
b4de06fcf0 | |||
27da0eb26e | |||
8ff80c10e5 |
14
CHANGELOG.md
14
CHANGELOG.md
@ -5,6 +5,20 @@ 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.22.0 - 2023-11-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the platform icon to the account selectors in the cash balance transfer from one to another account
|
||||||
|
- Added the platform icon to the account selector of the create or edit activity dialog
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Optimized the style of the carousel component on mobile for the testimonial section on the landing page
|
||||||
|
- Introduced action menus in the overview of the admin control panel
|
||||||
|
- Harmonized the name column in the historical market data table of the admin control panel
|
||||||
|
- Refactored the implementation of the data range functionality (`getRange()`) in the market data service
|
||||||
|
|
||||||
## 2.21.0 - 2023-11-09
|
## 2.21.0 - 2023-11-09
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -61,6 +61,7 @@ export const CurrentRateServiceMock = {
|
|||||||
for (const dataGatheringItem of dataGatheringItems) {
|
for (const dataGatheringItem of dataGatheringItems) {
|
||||||
values.push({
|
values.push({
|
||||||
date,
|
date,
|
||||||
|
dataSource: dataGatheringItem.dataSource,
|
||||||
marketPriceInBaseCurrency: mockGetValue(
|
marketPriceInBaseCurrency: mockGetValue(
|
||||||
dataGatheringItem.symbol,
|
dataGatheringItem.symbol,
|
||||||
date
|
date
|
||||||
@ -74,6 +75,7 @@ export const CurrentRateServiceMock = {
|
|||||||
for (const dataGatheringItem of dataGatheringItems) {
|
for (const dataGatheringItem of dataGatheringItems) {
|
||||||
values.push({
|
values.push({
|
||||||
date,
|
date,
|
||||||
|
dataSource: dataGatheringItem.dataSource,
|
||||||
marketPriceInBaseCurrency: mockGetValue(
|
marketPriceInBaseCurrency: mockGetValue(
|
||||||
dataGatheringItem.symbol,
|
dataGatheringItem.symbol,
|
||||||
date
|
date
|
||||||
|
@ -2,6 +2,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
|
|||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
import { DataSource, MarketData } from '@prisma/client';
|
import { DataSource, MarketData } from '@prisma/client';
|
||||||
|
|
||||||
import { CurrentRateService } from './current-rate.service';
|
import { CurrentRateService } from './current-rate.service';
|
||||||
@ -25,30 +26,30 @@ jest.mock('@ghostfolio/api/services/market-data/market-data.service', () => {
|
|||||||
getRange: ({
|
getRange: ({
|
||||||
dateRangeEnd,
|
dateRangeEnd,
|
||||||
dateRangeStart,
|
dateRangeStart,
|
||||||
symbols
|
uniqueAssets
|
||||||
}: {
|
}: {
|
||||||
dateRangeEnd: Date;
|
dateRangeEnd: Date;
|
||||||
dateRangeStart: Date;
|
dateRangeStart: Date;
|
||||||
symbols: string[];
|
uniqueAssets: UniqueAsset[];
|
||||||
}) => {
|
}) => {
|
||||||
return Promise.resolve<MarketData[]>([
|
return Promise.resolve<MarketData[]>([
|
||||||
{
|
{
|
||||||
createdAt: dateRangeStart,
|
createdAt: dateRangeStart,
|
||||||
dataSource: DataSource.YAHOO,
|
dataSource: uniqueAssets[0].dataSource,
|
||||||
date: dateRangeStart,
|
date: dateRangeStart,
|
||||||
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
|
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
|
||||||
marketPrice: 1841.823902,
|
marketPrice: 1841.823902,
|
||||||
state: 'CLOSE',
|
state: 'CLOSE',
|
||||||
symbol: symbols[0]
|
symbol: uniqueAssets[0].symbol
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createdAt: dateRangeEnd,
|
createdAt: dateRangeEnd,
|
||||||
dataSource: DataSource.YAHOO,
|
dataSource: uniqueAssets[0].dataSource,
|
||||||
date: dateRangeEnd,
|
date: dateRangeEnd,
|
||||||
id: '082d6893-df27-4c91-8a5d-092e84315b56',
|
id: '082d6893-df27-4c91-8a5d-092e84315b56',
|
||||||
marketPrice: 1847.839966,
|
marketPrice: 1847.839966,
|
||||||
state: 'CLOSE',
|
state: 'CLOSE',
|
||||||
symbol: symbols[0]
|
symbol: uniqueAssets[0].symbol
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -134,6 +135,7 @@ describe('CurrentRateService', () => {
|
|||||||
errors: [],
|
errors: [],
|
||||||
values: [
|
values: [
|
||||||
{
|
{
|
||||||
|
dataSource: 'YAHOO',
|
||||||
date: undefined,
|
date: undefined,
|
||||||
marketPriceInBaseCurrency: 1841.823902,
|
marketPriceInBaseCurrency: 1841.823902,
|
||||||
symbol: 'AMZN'
|
symbol: 'AMZN'
|
||||||
|
@ -2,7 +2,11 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
|
|||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
import { resetHours } from '@ghostfolio/common/helper';
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
import { DataProviderInfo, ResponseError } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
DataProviderInfo,
|
||||||
|
ResponseError,
|
||||||
|
UniqueAsset
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { isBefore, isToday } from 'date-fns';
|
import { isBefore, isToday } from 'date-fns';
|
||||||
import { flatten, isEmpty, uniqBy } from 'lodash';
|
import { flatten, isEmpty, uniqBy } from 'lodash';
|
||||||
@ -52,6 +56,7 @@ export class CurrentRateService {
|
|||||||
|
|
||||||
if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) {
|
if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) {
|
||||||
result.push({
|
result.push({
|
||||||
|
dataSource: dataGatheringItem.dataSource,
|
||||||
date: today,
|
date: today,
|
||||||
marketPriceInBaseCurrency:
|
marketPriceInBaseCurrency:
|
||||||
this.exchangeRateDataService.toCurrency(
|
this.exchangeRateDataService.toCurrency(
|
||||||
@ -75,27 +80,30 @@ export class CurrentRateService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const symbols = dataGatheringItems.map((dataGatheringItem) => {
|
const uniqueAssets: UniqueAsset[] = dataGatheringItems.map(
|
||||||
return dataGatheringItem.symbol;
|
({ dataSource, symbol }) => {
|
||||||
});
|
return { dataSource, symbol };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.marketDataService
|
this.marketDataService
|
||||||
.getRange({
|
.getRange({
|
||||||
dateQuery,
|
dateQuery,
|
||||||
symbols
|
uniqueAssets
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
return data.map((marketDataItem) => {
|
return data.map(({ dataSource, date, marketPrice, symbol }) => {
|
||||||
return {
|
return {
|
||||||
date: marketDataItem.date,
|
dataSource,
|
||||||
|
date,
|
||||||
|
symbol,
|
||||||
marketPriceInBaseCurrency:
|
marketPriceInBaseCurrency:
|
||||||
this.exchangeRateDataService.toCurrency(
|
this.exchangeRateDataService.toCurrency(
|
||||||
marketDataItem.marketPrice,
|
marketPrice,
|
||||||
currencies[marketDataItem.symbol],
|
currencies[symbol],
|
||||||
userCurrency
|
userCurrency
|
||||||
),
|
)
|
||||||
symbol: marketDataItem.symbol
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -112,7 +120,7 @@ export class CurrentRateService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!isEmpty(quoteErrors)) {
|
if (!isEmpty(quoteErrors)) {
|
||||||
for (const { symbol } of quoteErrors) {
|
for (const { dataSource, symbol } of quoteErrors) {
|
||||||
try {
|
try {
|
||||||
// If missing quote, fallback to the latest available historical market price
|
// If missing quote, fallback to the latest available historical market price
|
||||||
let value: GetValueObject = response.values.find((currentValue) => {
|
let value: GetValueObject = response.values.find((currentValue) => {
|
||||||
@ -121,6 +129,7 @@ export class CurrentRateService {
|
|||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = {
|
value = {
|
||||||
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
date: today,
|
date: today,
|
||||||
marketPriceInBaseCurrency: 0
|
marketPriceInBaseCurrency: 0
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export interface GetValueObject {
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
|
export interface GetValueObject extends UniqueAsset {
|
||||||
date: Date;
|
date: Date;
|
||||||
marketPriceInBaseCurrency: number;
|
marketPriceInBaseCurrency: number;
|
||||||
symbol: string;
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,12 @@ export class SymbolService {
|
|||||||
|
|
||||||
const marketData = await this.marketDataService.getRange({
|
const marketData = await this.marketDataService.getRange({
|
||||||
dateQuery: { gte: subDays(new Date(), days) },
|
dateQuery: { gte: subDays(new Date(), days) },
|
||||||
symbols: [dataGatheringItem.symbol]
|
uniqueAssets: [
|
||||||
|
{
|
||||||
|
dataSource: dataGatheringItem.dataSource,
|
||||||
|
symbol: dataGatheringItem.symbol
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
historicalData = marketData.map(({ date, marketPrice: value }) => {
|
historicalData = marketData.map(({ date, marketPrice: value }) => {
|
||||||
|
@ -127,7 +127,9 @@ export class UserService {
|
|||||||
updatedAt
|
updatedAt
|
||||||
} = await this.prismaService.user.findUnique({
|
} = await this.prismaService.user.findUnique({
|
||||||
include: {
|
include: {
|
||||||
Account: true,
|
Account: {
|
||||||
|
include: { Platform: true }
|
||||||
|
},
|
||||||
Analytics: true,
|
Analytics: true,
|
||||||
Settings: true,
|
Settings: true,
|
||||||
Subscription: true
|
Subscription: true
|
||||||
@ -250,8 +252,8 @@ export class UserService {
|
|||||||
currentPermissions.push(permissions.impersonateAllUsers);
|
currentPermissions.push(permissions.impersonateAllUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Account = sortBy(user.Account, (account) => {
|
user.Account = sortBy(user.Account, ({ name }) => {
|
||||||
return account.name;
|
return name.toLowerCase();
|
||||||
});
|
});
|
||||||
user.permissions = currentPermissions.sort();
|
user.permissions = currentPermissions.sort();
|
||||||
|
|
||||||
|
@ -66,6 +66,10 @@
|
|||||||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-altoo</loc>
|
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-altoo</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-basil-finance</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-beanvest</loc>
|
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-beanvest</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -126,6 +130,10 @@
|
|||||||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-kubera</loc>
|
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-kubera</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-magnifi</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-markets.sh</loc>
|
<loc>https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-markets.sh</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -368,6 +376,10 @@
|
|||||||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-altoo</loc>
|
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-altoo</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-basil-finance</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-beanvest</loc>
|
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-beanvest</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -428,6 +440,10 @@
|
|||||||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-kubera</loc>
|
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-kubera</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-magnifi</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-markets.sh</loc>
|
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-markets.sh</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -694,6 +710,10 @@
|
|||||||
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-altoo</loc>
|
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-altoo</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-basil-finance</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-beanvest</loc>
|
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-beanvest</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -754,6 +774,10 @@
|
|||||||
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-kubera</loc>
|
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-kubera</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-magnifi</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-markets.sh</loc>
|
<loc>https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-markets.sh</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -866,6 +890,10 @@
|
|||||||
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-altoo</loc>
|
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-altoo</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-basil-finance</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-beanvest</loc>
|
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-beanvest</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
@ -926,6 +954,10 @@
|
|||||||
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-kubera</loc>
|
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-kubera</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-magnifi</loc>
|
||||||
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-markets.sh</loc>
|
<loc>https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-markets.sh</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
@ -59,10 +59,10 @@ export class MarketDataService {
|
|||||||
|
|
||||||
public async getRange({
|
public async getRange({
|
||||||
dateQuery,
|
dateQuery,
|
||||||
symbols
|
uniqueAssets
|
||||||
}: {
|
}: {
|
||||||
dateQuery: DateQuery;
|
dateQuery: DateQuery;
|
||||||
symbols: string[];
|
uniqueAssets: UniqueAsset[];
|
||||||
}): Promise<MarketData[]> {
|
}): Promise<MarketData[]> {
|
||||||
return await this.prismaService.marketData.findMany({
|
return await this.prismaService.marketData.findMany({
|
||||||
orderBy: [
|
orderBy: [
|
||||||
@ -74,10 +74,17 @@ export class MarketDataService {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
where: {
|
where: {
|
||||||
date: dateQuery,
|
OR: uniqueAssets.map(({ dataSource, symbol }) => {
|
||||||
symbol: {
|
return {
|
||||||
in: symbols
|
AND: [
|
||||||
}
|
{
|
||||||
|
dataSource,
|
||||||
|
symbol,
|
||||||
|
date: dateQuery
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
|
|||||||
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { AssetSubClass, DataSource, Prisma } from '@prisma/client';
|
import { AssetSubClass, DataSource, Prisma } from '@prisma/client';
|
||||||
|
import { isUUID } from 'class-validator';
|
||||||
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 { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
|
||||||
@ -83,7 +84,7 @@ export class AdminMarketDataComponent
|
|||||||
public defaultDateFormat: string;
|
public defaultDateFormat: string;
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public displayedColumns = [
|
public displayedColumns = [
|
||||||
'symbol',
|
'nameWithSymbol',
|
||||||
'dataSource',
|
'dataSource',
|
||||||
'assetClass',
|
'assetClass',
|
||||||
'assetSubClass',
|
'assetSubClass',
|
||||||
@ -97,6 +98,7 @@ export class AdminMarketDataComponent
|
|||||||
];
|
];
|
||||||
public filters$ = new Subject<Filter[]>();
|
public filters$ = new Subject<Filter[]>();
|
||||||
public isLoading = false;
|
public isLoading = false;
|
||||||
|
public isUUID = isUUID;
|
||||||
public placeholder = '';
|
public placeholder = '';
|
||||||
public pageSize = DEFAULT_PAGE_SIZE;
|
public pageSize = DEFAULT_PAGE_SIZE;
|
||||||
public totalItems = 0;
|
public totalItems = 0;
|
||||||
|
@ -28,6 +28,24 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="nameWithSymbol">
|
||||||
|
<th
|
||||||
|
*matHeaderCellDef
|
||||||
|
class="px-1"
|
||||||
|
mat-header-cell
|
||||||
|
mat-sort-header="symbol"
|
||||||
|
>
|
||||||
|
<ng-container i18n>Name</ng-container>
|
||||||
|
</th>
|
||||||
|
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
|
||||||
|
<div class="text-truncate">{{ element.name }}</div>
|
||||||
|
<div *ngIf="!isUUID(element.symbol)">
|
||||||
|
<small class="text-muted">{{ element.symbol | gfSymbol }}</small>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="dataSource">
|
<ng-container matColumnDef="dataSource">
|
||||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||||
<ng-container i18n>Data Source</ng-container>
|
<ng-container i18n>Data Source</ng-container>
|
||||||
|
@ -6,6 +6,7 @@ import { MatPaginatorModule } from '@angular/material/paginator';
|
|||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
|
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/
|
|||||||
GfActivitiesFilterModule,
|
GfActivitiesFilterModule,
|
||||||
GfAssetProfileDialogModule,
|
GfAssetProfileDialogModule,
|
||||||
GfCreateAssetProfileDialogModule,
|
GfCreateAssetProfileDialogModule,
|
||||||
|
GfSymbolModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<div class="w-50">
|
<div class="w-50">
|
||||||
<table>
|
<table>
|
||||||
<tr *ngFor="let exchangeRate of exchangeRates">
|
<tr *ngFor="let exchangeRate of exchangeRates">
|
||||||
<td class="d-flex">
|
<td>
|
||||||
<gf-value
|
<gf-value
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="1"
|
[value]="1"
|
||||||
@ -46,8 +46,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="pl-1">{{ exchangeRate.label1 }}</td>
|
<td class="pl-1">{{ exchangeRate.label1 }}</td>
|
||||||
<td class="px-1">=</td>
|
<td class="px-1">=</td>
|
||||||
<td class="d-flex justify-content-end">
|
<td align="right">
|
||||||
<gf-value
|
<gf-value
|
||||||
|
class="d-inline-block"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[precision]="4"
|
[precision]="4"
|
||||||
[value]="exchangeRate.value"
|
[value]="exchangeRate.value"
|
||||||
@ -55,26 +56,50 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="pl-1">{{ exchangeRate.label2 }}</td>
|
<td class="pl-1">{{ exchangeRate.label2 }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<button
|
||||||
class="h-100 mx-1 no-min-width px-2"
|
class="mx-1 no-min-width px-2"
|
||||||
mat-button
|
mat-button
|
||||||
[queryParams]="{
|
[matMenuTriggerFor]="exchangeRateActionsMenu"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<ion-icon name="ellipsis-horizontal"></ion-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu
|
||||||
|
#exchangeRateActionsMenu="matMenu"
|
||||||
|
class="h-100 mx-1 no-min-width px-2"
|
||||||
|
xPosition="before"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
mat-menu-item
|
||||||
|
[queryParams]="{
|
||||||
assetProfileDialog: true,
|
assetProfileDialog: true,
|
||||||
dataSource: exchangeRate.dataSource,
|
dataSource: exchangeRate.dataSource,
|
||||||
symbol: exchangeRate.symbol
|
symbol: exchangeRate.symbol
|
||||||
}"
|
}"
|
||||||
[routerLink]="['/admin', 'market-data']"
|
[routerLink]="['/admin', 'market-data']"
|
||||||
>
|
>
|
||||||
<ion-icon name="create-outline"></ion-icon>
|
<span class="align-items-center d-flex">
|
||||||
</a>
|
<ion-icon
|
||||||
<button
|
class="mr-2"
|
||||||
*ngIf="customCurrencies.includes(exchangeRate.label2)"
|
name="create-outline"
|
||||||
class="h-100 mx-1 no-min-width px-2"
|
></ion-icon>
|
||||||
mat-button
|
<span i18n>Edit</span>
|
||||||
(click)="onDeleteCurrency(exchangeRate.label2)"
|
</span>
|
||||||
>
|
</a>
|
||||||
<ion-icon name="trash-outline"></ion-icon>
|
<button
|
||||||
</button>
|
*ngIf="customCurrencies.includes(exchangeRate.label2)"
|
||||||
|
mat-menu-item
|
||||||
|
(click)="onDeleteCurrency(exchangeRate.label2)"
|
||||||
|
>
|
||||||
|
<span class="align-items-center d-flex">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-2"
|
||||||
|
name="trash-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>Delete</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -149,17 +174,34 @@
|
|||||||
<table>
|
<table>
|
||||||
<tr *ngFor="let coupon of coupons">
|
<tr *ngFor="let coupon of coupons">
|
||||||
<td class="text-monospace">{{ coupon.code }}</td>
|
<td class="text-monospace">{{ coupon.code }}</td>
|
||||||
<td class="d-flex justify-content-end pl-2">
|
<td class="pl-2 text-right">{{ coupon.duration }}</td>
|
||||||
{{ coupon.duration }}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
class="h-100 mx-1 no-min-width px-2"
|
class="mx-1 no-min-width px-2"
|
||||||
mat-button
|
mat-button
|
||||||
(click)="onDeleteCoupon(coupon.code)"
|
[matMenuTriggerFor]="couponActionsMenu"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
>
|
>
|
||||||
<ion-icon name="trash-outline"></ion-icon>
|
<ion-icon name="ellipsis-horizontal"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<mat-menu
|
||||||
|
#couponActionsMenu="matMenu"
|
||||||
|
class="h-100 mx-1 no-min-width px-2"
|
||||||
|
xPosition="before"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
(click)="onDeleteCoupon(coupon.code)"
|
||||||
|
>
|
||||||
|
<span class="align-items-center d-flex">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-2"
|
||||||
|
name="trash-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>Delete</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -3,6 +3,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
@ -20,6 +21,7 @@ import { AdminOverviewComponent } from './admin-overview.component';
|
|||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
|
MatMenuModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatSlideToggleModule,
|
MatSlideToggleModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
|
@ -10,9 +10,17 @@
|
|||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>From</mat-label>
|
<mat-label i18n>From</mat-label>
|
||||||
<mat-select formControlName="fromAccount">
|
<mat-select formControlName="fromAccount">
|
||||||
<mat-option *ngFor="let account of accounts" [value]="account.id"
|
<mat-option *ngFor="let account of accounts" [value]="account.id">
|
||||||
>{{ account.name }}</mat-option
|
<div class="d-flex">
|
||||||
>
|
<gf-symbol-icon
|
||||||
|
*ngIf="account.Platform?.url"
|
||||||
|
class="mr-1"
|
||||||
|
[tooltip]="account.Platform?.name"
|
||||||
|
[url]="account.Platform?.url"
|
||||||
|
></gf-symbol-icon
|
||||||
|
><span>{{ account.name }}</span>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -20,9 +28,17 @@
|
|||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>To</mat-label>
|
<mat-label i18n>To</mat-label>
|
||||||
<mat-select formControlName="toAccount">
|
<mat-select formControlName="toAccount">
|
||||||
<mat-option *ngFor="let account of accounts" [value]="account.id"
|
<mat-option *ngFor="let account of accounts" [value]="account.id">
|
||||||
>{{ account.name }}</mat-option
|
<div class="d-flex">
|
||||||
>
|
<gf-symbol-icon
|
||||||
|
*ngIf="account.Platform?.url"
|
||||||
|
class="mr-1"
|
||||||
|
[tooltip]="account.Platform?.name"
|
||||||
|
[url]="account.Platform?.url"
|
||||||
|
></gf-symbol-icon
|
||||||
|
><span>{{ account.name }}</span>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
||||||
|
|
||||||
import { TransferBalanceDialog } from './transfer-balance-dialog.component';
|
import { TransferBalanceDialog } from './transfer-balance-dialog.component';
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ import { TransferBalanceDialog } from './transfer-balance-dialog.component';
|
|||||||
declarations: [TransferBalanceDialog],
|
declarations: [TransferBalanceDialog],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
GfSymbolIconModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
|
@ -327,7 +327,7 @@
|
|||||||
<div class="col-md-8 offset-md-2">
|
<div class="col-md-8 offset-md-2">
|
||||||
<gf-carousel [aria-label]="'Testimonials'">
|
<gf-carousel [aria-label]="'Testimonials'">
|
||||||
<div *ngFor="let testimonial of testimonials" gf-carousel-item>
|
<div *ngFor="let testimonial of testimonials" gf-carousel-item>
|
||||||
<div class="d-flex px-3">
|
<div class="d-flex px-4">
|
||||||
<gf-logo
|
<gf-logo
|
||||||
class="mr-3 mt-2 pt-1"
|
class="mr-3 mt-2 pt-1"
|
||||||
size="medium"
|
size="medium"
|
||||||
|
@ -72,9 +72,20 @@
|
|||||||
*ngIf="!activityForm.controls['accountId'].hasValidator(Validators.required)"
|
*ngIf="!activityForm.controls['accountId'].hasValidator(Validators.required)"
|
||||||
[value]="null"
|
[value]="null"
|
||||||
></mat-option>
|
></mat-option>
|
||||||
<mat-option *ngFor="let account of data.accounts" [value]="account.id"
|
<mat-option
|
||||||
>{{ account.name }}</mat-option
|
*ngFor="let account of data.accounts"
|
||||||
|
[value]="account.id"
|
||||||
>
|
>
|
||||||
|
<div class="d-flex">
|
||||||
|
<gf-symbol-icon
|
||||||
|
*ngIf="account.Platform?.url"
|
||||||
|
class="mr-1"
|
||||||
|
[tooltip]="account.Platform?.name"
|
||||||
|
[url]="account.Platform?.url"
|
||||||
|
></gf-symbol-icon
|
||||||
|
><span>{{ account.name }}</span>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
||||||
import { GfSymbolAutocompleteModule } from '@ghostfolio/ui/symbol-autocomplete/symbol-autocomplete.module';
|
import { GfSymbolAutocompleteModule } from '@ghostfolio/ui/symbol-autocomplete/symbol-autocomplete.module';
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ import { CreateOrUpdateActivityDialog } from './create-or-update-activity-dialog
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
GfSymbolAutocompleteModule,
|
GfSymbolAutocompleteModule,
|
||||||
|
GfSymbolIconModule,
|
||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
@ -2,6 +2,7 @@ import { Product } from '@ghostfolio/common/interfaces';
|
|||||||
|
|
||||||
import { AllvueSystemsPageComponent } from './products/allvue-systems-page.component';
|
import { AllvueSystemsPageComponent } from './products/allvue-systems-page.component';
|
||||||
import { AltooPageComponent } from './products/altoo-page.component';
|
import { AltooPageComponent } from './products/altoo-page.component';
|
||||||
|
import { BasilFinancePageComponent } from './products/basil-finance-page.component';
|
||||||
import { BeanvestPageComponent } from './products/beanvest-page.component';
|
import { BeanvestPageComponent } from './products/beanvest-page.component';
|
||||||
import { CapitallyPageComponent } from './products/capitally-page.component';
|
import { CapitallyPageComponent } from './products/capitally-page.component';
|
||||||
import { CapMonPageComponent } from './products/capmon-page.component';
|
import { CapMonPageComponent } from './products/capmon-page.component';
|
||||||
@ -18,6 +19,7 @@ import { GoSpatzPageComponent } from './products/gospatz-page.component';
|
|||||||
import { IntuitMintPageComponent } from './products/intuit-mint-page.component';
|
import { IntuitMintPageComponent } from './products/intuit-mint-page.component';
|
||||||
import { JustEtfPageComponent } from './products/justetf-page.component';
|
import { JustEtfPageComponent } from './products/justetf-page.component';
|
||||||
import { KuberaPageComponent } from './products/kubera-page.component';
|
import { KuberaPageComponent } from './products/kubera-page.component';
|
||||||
|
import { MagnifiPageComponent } from './products/magnifi-page.component';
|
||||||
import { MarketsShPageComponent } from './products/markets.sh-page.component';
|
import { MarketsShPageComponent } from './products/markets.sh-page.component';
|
||||||
import { MaybeFinancePageComponent } from './products/maybe-finance-page.component';
|
import { MaybeFinancePageComponent } from './products/maybe-finance-page.component';
|
||||||
import { MonarchMoneyPageComponent } from './products/monarch-money-page.component';
|
import { MonarchMoneyPageComponent } from './products/monarch-money-page.component';
|
||||||
@ -84,6 +86,15 @@ export const products: Product[] = [
|
|||||||
origin: $localize`Switzerland`,
|
origin: $localize`Switzerland`,
|
||||||
slogan: 'Simplicity for Complex Wealth'
|
slogan: 'Simplicity for Complex Wealth'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: BasilFinancePageComponent,
|
||||||
|
founded: 2022,
|
||||||
|
hasFreePlan: true,
|
||||||
|
hasSelfHostingAbility: false,
|
||||||
|
key: 'basil-finance',
|
||||||
|
name: 'Basil Finance',
|
||||||
|
slogan: 'The ultimate solution for tracking and managing your investments'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: BeanvestPageComponent,
|
component: BeanvestPageComponent,
|
||||||
founded: 2020,
|
founded: 2020,
|
||||||
@ -252,6 +263,17 @@ export const products: Product[] = [
|
|||||||
pricingPerYear: '$150',
|
pricingPerYear: '$150',
|
||||||
slogan: 'The Time Machine for your Net Worth'
|
slogan: 'The Time Machine for your Net Worth'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: MagnifiPageComponent,
|
||||||
|
founded: 2018,
|
||||||
|
hasFreePlan: false,
|
||||||
|
hasSelfHostingAbility: false,
|
||||||
|
key: 'magnifi',
|
||||||
|
name: 'Magnifi',
|
||||||
|
origin: $localize`United States`,
|
||||||
|
pricingPerYear: '$132',
|
||||||
|
slogan: 'AI Investing Assistant'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: MarketsShPageComponent,
|
component: MarketsShPageComponent,
|
||||||
founded: 2022,
|
founded: 2022,
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { products } from '../products';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
host: { class: 'page' },
|
||||||
|
imports: [CommonModule, MatButtonModule, RouterModule],
|
||||||
|
selector: 'gf-basil-finance-page',
|
||||||
|
standalone: true,
|
||||||
|
styleUrls: ['../product-page-template.scss'],
|
||||||
|
templateUrl: '../product-page-template.html'
|
||||||
|
})
|
||||||
|
export class BasilFinancePageComponent {
|
||||||
|
public product1 = products.find(({ key }) => {
|
||||||
|
return key === 'ghostfolio';
|
||||||
|
});
|
||||||
|
|
||||||
|
public product2 = products.find(({ key }) => {
|
||||||
|
return key === 'basil-finance';
|
||||||
|
});
|
||||||
|
|
||||||
|
public routerLinkAbout = ['/' + $localize`about`];
|
||||||
|
public routerLinkFeatures = ['/' + $localize`features`];
|
||||||
|
public routerLinkResourcesPersonalFinanceTools = [
|
||||||
|
'/' + $localize`resources`,
|
||||||
|
'personal-finance-tools'
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { products } from '../products';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
host: { class: 'page' },
|
||||||
|
imports: [CommonModule, MatButtonModule, RouterModule],
|
||||||
|
selector: 'gf-magnifi-page',
|
||||||
|
standalone: true,
|
||||||
|
styleUrls: ['../product-page-template.scss'],
|
||||||
|
templateUrl: '../product-page-template.html'
|
||||||
|
})
|
||||||
|
export class MagnifiPageComponent {
|
||||||
|
public product1 = products.find(({ key }) => {
|
||||||
|
return key === 'ghostfolio';
|
||||||
|
});
|
||||||
|
|
||||||
|
public product2 = products.find(({ key }) => {
|
||||||
|
return key === 'magnifi';
|
||||||
|
});
|
||||||
|
|
||||||
|
public routerLinkAbout = ['/' + $localize`about`];
|
||||||
|
public routerLinkFeatures = ['/' + $localize`features`];
|
||||||
|
public routerLinkResourcesPersonalFinanceTools = [
|
||||||
|
'/' + $localize`resources`,
|
||||||
|
'personal-finance-tools'
|
||||||
|
];
|
||||||
|
}
|
@ -2,8 +2,8 @@
|
|||||||
*ngIf="this.showPrevArrow"
|
*ngIf="this.showPrevArrow"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="previous"
|
aria-label="previous"
|
||||||
class="carousel-nav carousel-nav-prev no-min-width position-absolute"
|
class="carousel-nav carousel-nav-prev no-min-width position-absolute px-1"
|
||||||
mat-stroked-button
|
mat-button
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
(click)="previous()"
|
(click)="previous()"
|
||||||
>
|
>
|
||||||
@ -25,8 +25,8 @@
|
|||||||
*ngIf="this.showNextArrow"
|
*ngIf="this.showNextArrow"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="next"
|
aria-label="next"
|
||||||
class="carousel-nav carousel-nav-next no-min-width position-absolute"
|
class="carousel-nav carousel-nav-next no-min-width position-absolute px-1"
|
||||||
mat-stroked-button
|
mat-button
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
(click)="next()"
|
(click)="next()"
|
||||||
>
|
>
|
||||||
|
@ -12,13 +12,14 @@
|
|||||||
button {
|
button {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&.carousel-nav-prev {
|
&.carousel-nav-prev {
|
||||||
left: -50px;
|
left: -0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.carousel-nav-next {
|
&.carousel-nav-next {
|
||||||
right: -50px;
|
right: -0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.21.0",
|
"version": "2.22.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",
|
||||||
|
Reference in New Issue
Block a user