Setup allocations page and endpoint (#859)
* Setup tagging system * Update changelog
This commit is contained in:
parent
ea89ca5734
commit
bad9d17c44
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a tagging system for activities
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Extracted the activities table filter to a dedicated component
|
- Extracted the activities table filter to a dedicated component
|
||||||
|
@ -6,6 +6,7 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d
|
|||||||
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||||
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
|
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
|
||||||
|
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
@ -26,7 +27,8 @@ import { InfoService } from './info.service';
|
|||||||
PrismaModule,
|
PrismaModule,
|
||||||
PropertyModule,
|
PropertyModule,
|
||||||
RedisCacheModule,
|
RedisCacheModule,
|
||||||
SymbolProfileModule
|
SymbolProfileModule,
|
||||||
|
TagModule
|
||||||
],
|
],
|
||||||
providers: [InfoService]
|
providers: [InfoService]
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.se
|
|||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
||||||
import {
|
import {
|
||||||
DEMO_USER_ID,
|
DEMO_USER_ID,
|
||||||
PROPERTY_IS_READ_ONLY_MODE,
|
PROPERTY_IS_READ_ONLY_MODE,
|
||||||
@ -33,7 +34,8 @@ export class InfoService {
|
|||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
private readonly propertyService: PropertyService,
|
private readonly propertyService: PropertyService,
|
||||||
private readonly redisCacheService: RedisCacheService
|
private readonly redisCacheService: RedisCacheService,
|
||||||
|
private readonly tagService: TagService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async get(): Promise<InfoItem> {
|
public async get(): Promise<InfoItem> {
|
||||||
@ -105,7 +107,8 @@ export class InfoService {
|
|||||||
demoAuthToken: this.getDemoAuthToken(),
|
demoAuthToken: this.getDemoAuthToken(),
|
||||||
lastDataGathering: await this.getLastDataGathering(),
|
lastDataGathering: await this.getLastDataGathering(),
|
||||||
statistics: await this.getStatistics(),
|
statistics: await this.getStatistics(),
|
||||||
subscriptions: await this.getSubscriptions()
|
subscriptions: await this.getSubscriptions(),
|
||||||
|
tags: await this.tagService.get()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,11 +152,13 @@ export class OrderService {
|
|||||||
|
|
||||||
public async getOrders({
|
public async getOrders({
|
||||||
includeDrafts = false,
|
includeDrafts = false,
|
||||||
|
tags,
|
||||||
types,
|
types,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId
|
userId
|
||||||
}: {
|
}: {
|
||||||
includeDrafts?: boolean;
|
includeDrafts?: boolean;
|
||||||
|
tags?: string[];
|
||||||
types?: TypeOfOrder[];
|
types?: TypeOfOrder[];
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -167,6 +169,18 @@ export class OrderService {
|
|||||||
where.isDraft = false;
|
where.isDraft = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tags?.length > 0) {
|
||||||
|
where.tags = {
|
||||||
|
some: {
|
||||||
|
OR: tags.map((tag) => {
|
||||||
|
return {
|
||||||
|
name: tag
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (types) {
|
if (types) {
|
||||||
where.OR = types.map((type) => {
|
where.OR = types.map((type) => {
|
||||||
return {
|
return {
|
||||||
|
@ -105,7 +105,8 @@ export class PortfolioController {
|
|||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
public async getDetails(
|
public async getDetails(
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range
|
@Query('range') range,
|
||||||
|
@Query('tags') tags?: string
|
||||||
): Promise<PortfolioDetails & { hasError: boolean }> {
|
): Promise<PortfolioDetails & { hasError: boolean }> {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
@ -113,7 +114,8 @@ export class PortfolioController {
|
|||||||
await this.portfolioService.getDetails(
|
await this.portfolioService.getDetails(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
this.request.user.id,
|
this.request.user.id,
|
||||||
range
|
range,
|
||||||
|
tags?.split(',')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
||||||
@ -159,7 +161,11 @@ export class PortfolioController {
|
|||||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||||
this.request.user.subscription.type === 'Basic';
|
this.request.user.subscription.type === 'Basic';
|
||||||
|
|
||||||
return { accounts, hasError, holdings: isBasicUser ? {} : holdings };
|
return {
|
||||||
|
hasError,
|
||||||
|
accounts: tags ? {} : accounts,
|
||||||
|
holdings: isBasicUser ? {} : holdings
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('investments')
|
@Get('investments')
|
||||||
|
@ -303,7 +303,8 @@ export class PortfolioService {
|
|||||||
public async getDetails(
|
public async getDetails(
|
||||||
aImpersonationId: string,
|
aImpersonationId: string,
|
||||||
aUserId: string,
|
aUserId: string,
|
||||||
aDateRange: DateRange = 'max'
|
aDateRange: DateRange = 'max',
|
||||||
|
tags?: string[]
|
||||||
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
||||||
const userId = await this.getUserId(aImpersonationId, aUserId);
|
const userId = await this.getUserId(aImpersonationId, aUserId);
|
||||||
const user = await this.userService.user({ id: userId });
|
const user = await this.userService.user({ id: userId });
|
||||||
@ -318,6 +319,7 @@ export class PortfolioService {
|
|||||||
|
|
||||||
const { orders, portfolioOrders, transactionPoints } =
|
const { orders, portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
|
tags,
|
||||||
userId
|
userId
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -441,8 +443,10 @@ export class PortfolioService {
|
|||||||
value: totalValue
|
value: totalValue
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const symbol of Object.keys(cashPositions)) {
|
if (tags === undefined) {
|
||||||
holdings[symbol] = cashPositions[symbol];
|
for (const symbol of Object.keys(cashPositions)) {
|
||||||
|
holdings[symbol] = cashPositions[symbol];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = await this.getValueOfAccounts(
|
const accounts = await this.getValueOfAccounts(
|
||||||
@ -1178,9 +1182,11 @@ export class PortfolioService {
|
|||||||
|
|
||||||
private async getTransactionPoints({
|
private async getTransactionPoints({
|
||||||
includeDrafts = false,
|
includeDrafts = false,
|
||||||
|
tags,
|
||||||
userId
|
userId
|
||||||
}: {
|
}: {
|
||||||
includeDrafts?: boolean;
|
includeDrafts?: boolean;
|
||||||
|
tags?: string[];
|
||||||
userId: string;
|
userId: string;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
transactionPoints: TransactionPoint[];
|
transactionPoints: TransactionPoint[];
|
||||||
@ -1191,6 +1197,7 @@ export class PortfolioService {
|
|||||||
|
|
||||||
const orders = await this.orderService.getOrders({
|
const orders = await this.orderService.getOrders({
|
||||||
includeDrafts,
|
includeDrafts,
|
||||||
|
tags,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId,
|
userId,
|
||||||
types: ['BUY', 'SELL']
|
types: ['BUY', 'SELL']
|
||||||
|
@ -34,7 +34,7 @@ import { UserService } from './user.service';
|
|||||||
export class UserController {
|
export class UserController {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly propertyService: PropertyService,
|
private readonly propertyService: PropertyService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
private readonly userService: UserService
|
private readonly userService: UserService
|
||||||
|
@ -2,6 +2,7 @@ import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscriptio
|
|||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||||
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||||
|
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
@ -19,7 +20,8 @@ import { UserService } from './user.service';
|
|||||||
}),
|
}),
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
PropertyModule,
|
PropertyModule,
|
||||||
SubscriptionModule
|
SubscriptionModule,
|
||||||
|
TagModule
|
||||||
],
|
],
|
||||||
providers: [UserService]
|
providers: [UserService]
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscripti
|
|||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
||||||
import {
|
import {
|
||||||
PROPERTY_IS_READ_ONLY_MODE,
|
PROPERTY_IS_READ_ONLY_MODE,
|
||||||
baseCurrency,
|
baseCurrency,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
hasRole,
|
hasRole,
|
||||||
permissions
|
permissions
|
||||||
} from '@ghostfolio/common/permissions';
|
} from '@ghostfolio/common/permissions';
|
||||||
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Prisma, Role, User, ViewMode } from '@prisma/client';
|
import { Prisma, Role, User, ViewMode } from '@prisma/client';
|
||||||
|
|
||||||
@ -30,7 +30,8 @@ export class UserService {
|
|||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
private readonly propertyService: PropertyService,
|
private readonly propertyService: PropertyService,
|
||||||
private readonly subscriptionService: SubscriptionService
|
private readonly subscriptionService: SubscriptionService,
|
||||||
|
private readonly tagService: TagService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getUser(
|
public async getUser(
|
||||||
@ -51,12 +52,21 @@ export class UserService {
|
|||||||
orderBy: { User: { alias: 'asc' } },
|
orderBy: { User: { alias: 'asc' } },
|
||||||
where: { GranteeUser: { id } }
|
where: { GranteeUser: { id } }
|
||||||
});
|
});
|
||||||
|
let tags = await this.tagService.getByUser(id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||||
|
subscription.type === 'Basic'
|
||||||
|
) {
|
||||||
|
tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alias,
|
alias,
|
||||||
id,
|
id,
|
||||||
permissions,
|
permissions,
|
||||||
subscription,
|
subscription,
|
||||||
|
tags,
|
||||||
access: access.map((accessItem) => {
|
access: access.map((accessItem) => {
|
||||||
return {
|
return {
|
||||||
alias: accessItem.User.alias,
|
alias: accessItem.User.alias,
|
||||||
|
11
apps/api/src/services/tag/tag.module.ts
Normal file
11
apps/api/src/services/tag/tag.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { TagService } from './tag.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
exports: [TagService],
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [TagService]
|
||||||
|
})
|
||||||
|
export class TagModule {}
|
30
apps/api/src/services/tag/tag.service.ts
Normal file
30
apps/api/src/services/tag/tag.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TagService {
|
||||||
|
public constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|
||||||
|
public async get() {
|
||||||
|
return this.prismaService.tag.findMany({
|
||||||
|
orderBy: {
|
||||||
|
name: 'asc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getByUser(userId: string) {
|
||||||
|
return this.prismaService.tag.findMany({
|
||||||
|
orderBy: {
|
||||||
|
name: 'asc'
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
orders: {
|
||||||
|
some: {
|
||||||
|
userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -48,6 +48,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
{ label: 'Initial', value: 'original' },
|
{ label: 'Initial', value: 'original' },
|
||||||
{ label: 'Current', value: 'current' }
|
{ label: 'Current', value: 'current' }
|
||||||
];
|
];
|
||||||
|
public placeholder = '';
|
||||||
public portfolioDetails: PortfolioDetails;
|
public portfolioDetails: PortfolioDetails;
|
||||||
public positions: {
|
public positions: {
|
||||||
[symbol: string]: Pick<
|
[symbol: string]: Pick<
|
||||||
@ -73,6 +74,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
public tags: string[] = [];
|
||||||
|
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -120,29 +122,22 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.hasImpersonationId = !!aId;
|
this.hasImpersonationId = !!aId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPortfolioDetails({})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((portfolioDetails) => {
|
|
||||||
this.portfolioDetails = portfolioDetails;
|
|
||||||
|
|
||||||
this.initializeAnalysisData(this.period);
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
this.tags = this.user.tags.map((tag) => {
|
||||||
|
return tag.name;
|
||||||
|
});
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public initializeAnalysisData(aPeriod: string) {
|
public initialize() {
|
||||||
this.accounts = {};
|
this.accounts = {};
|
||||||
this.continents = {
|
this.continents = {
|
||||||
[UNKNOWN_KEY]: {
|
[UNKNOWN_KEY]: {
|
||||||
@ -185,6 +180,10 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeAnalysisData(aPeriod: string) {
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
for (const [id, { current, name, original }] of Object.entries(
|
for (const [id, { current, name, original }] of Object.entries(
|
||||||
this.portfolioDetails.accounts
|
this.portfolioDetails.accounts
|
||||||
@ -305,7 +304,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position.assetClass === AssetClass.EQUITY) {
|
if (position.dataSource) {
|
||||||
this.symbols[prettifySymbol(symbol)] = {
|
this.symbols[prettifySymbol(symbol)] = {
|
||||||
dataSource: position.dataSource,
|
dataSource: position.dataSource,
|
||||||
name: position.name,
|
name: position.name,
|
||||||
@ -342,6 +341,25 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onUpdateFilters(tags: string[] = []) {
|
||||||
|
this.update(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(tags?: string[]) {
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchPortfolioDetails({ tags })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((portfolioDetails) => {
|
||||||
|
this.portfolioDetails = portfolioDetails;
|
||||||
|
|
||||||
|
this.initializeAnalysisData(this.period);
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Allocations</h3>
|
<h3 class="d-flex justify-content-center mb-3" i18n>Allocations</h3>
|
||||||
|
<gf-activities-filter
|
||||||
|
[allFilters]="tags"
|
||||||
|
[ngClass]="{ 'd-none': tags.length <= 0 }"
|
||||||
|
[placeholder]="placeholder"
|
||||||
|
(valueChanged)="onUpdateFilters($event)"
|
||||||
|
></gf-activities-filter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="proportion-charts row">
|
<div class="proportion-charts row">
|
||||||
|
@ -4,6 +4,7 @@ import { MatCardModule } from '@angular/material/card';
|
|||||||
import { GfPositionsTableModule } from '@ghostfolio/client/components/positions-table/positions-table.module';
|
import { GfPositionsTableModule } from '@ghostfolio/client/components/positions-table/positions-table.module';
|
||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||||
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 { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import { AllocationsPageComponent } from './allocations-page.component';
|
|||||||
imports: [
|
imports: [
|
||||||
AllocationsPageRoutingModule,
|
AllocationsPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
GfActivitiesFilterModule,
|
||||||
GfPortfolioProportionChartModule,
|
GfPortfolioProportionChartModule,
|
||||||
GfPositionsTableModule,
|
GfPositionsTableModule,
|
||||||
GfToggleModule,
|
GfToggleModule,
|
||||||
|
@ -182,9 +182,15 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioDetails(aParams: { [param: string]: any }) {
|
public fetchPortfolioDetails({ tags }: { tags?: string[] }) {
|
||||||
|
let params = new HttpParams();
|
||||||
|
|
||||||
|
if (tags?.length > 0) {
|
||||||
|
params = params.append('tags', tags.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
return this.http.get<PortfolioDetails>('/api/v1/portfolio/details', {
|
return this.http.get<PortfolioDetails>('/api/v1/portfolio/details', {
|
||||||
params: aParams
|
params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Tag } from '@prisma/client';
|
||||||
import { Statistics } from './statistics.interface';
|
import { Statistics } from './statistics.interface';
|
||||||
import { Subscription } from './subscription.interface';
|
import { Subscription } from './subscription.interface';
|
||||||
|
|
||||||
@ -13,4 +14,5 @@ export interface InfoItem {
|
|||||||
stripePublicKey?: string;
|
stripePublicKey?: string;
|
||||||
subscriptions: Subscription[];
|
subscriptions: Subscription[];
|
||||||
systemMessage?: string;
|
systemMessage?: string;
|
||||||
|
tags: Tag[];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Access } from '@ghostfolio/api/app/user/interfaces/access.interface';
|
import { Access } from '@ghostfolio/api/app/user/interfaces/access.interface';
|
||||||
import { Account } from '@prisma/client';
|
import { Account, Tag } from '@prisma/client';
|
||||||
|
|
||||||
import { UserSettings } from './user-settings.interface';
|
import { UserSettings } from './user-settings.interface';
|
||||||
|
|
||||||
@ -14,4 +14,5 @@ export interface User {
|
|||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
type: 'Basic' | 'Premium';
|
type: 'Basic' | 'Premium';
|
||||||
};
|
};
|
||||||
|
tags: Tag[];
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ model Order {
|
|||||||
quantity Float
|
quantity Float
|
||||||
SymbolProfile SymbolProfile @relation(fields: [symbolProfileId], references: [id])
|
SymbolProfile SymbolProfile @relation(fields: [symbolProfileId], references: [id])
|
||||||
symbolProfileId String
|
symbolProfileId String
|
||||||
|
tags Tag[]
|
||||||
type Type
|
type Type
|
||||||
unitPrice Float
|
unitPrice Float
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@ -148,6 +149,12 @@ model Subscription {
|
|||||||
userId String
|
userId String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Tag {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String @unique
|
||||||
|
orders Order[]
|
||||||
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
Access Access[] @relation("accessGet")
|
Access Access[] @relation("accessGet")
|
||||||
AccessGive Access[] @relation(name: "accessGive")
|
AccessGive Access[] @relation(name: "accessGive")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user