Browse Source

Merge branch 'main' into feature/add-permission-decorator

pull/2693/head
Thomas Kaul 2 years ago
committed by GitHub
parent
commit
80de565346
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 2
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 8
      apps/api/src/services/account-balance/account-balance.service.ts
  4. 12
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  5. 25
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
  6. 4
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.module.ts
  7. 2
      apps/client/src/app/pages/portfolio/activities/activities-page.html
  8. 7
      apps/client/src/app/services/data.service.ts
  9. 13
      apps/client/src/assets/oss-friends.json
  10. 36
      libs/ui/src/lib/account-balances/account-balances.component.html
  11. 5
      libs/ui/src/lib/account-balances/account-balances.component.scss
  12. 63
      libs/ui/src/lib/account-balances/account-balances.component.ts
  13. 15
      libs/ui/src/lib/account-balances/account-balances.module.ts

5
CHANGELOG.md

@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added a historical cash balances table to the account detail dialog
- Introduced a `HasPermission` annotation for endpoints
### Changed
- Respected the `withExcludedAccounts` flag in the account balance time series
## 2.27.1 - 2023-11-28
### Changed

2
apps/api/src/app/portfolio/portfolio.service.ts

@ -1075,7 +1075,7 @@ export class PortfolioService {
const userCurrency = this.getUserCurrency(user);
const accountBalances = await this.accountBalanceService.getAccountBalances(
{ filters, user }
{ filters, user, withExcludedAccounts }
);
let accountBalanceItems: HistoricalDataItem[] = Object.values(

8
apps/api/src/services/account-balance/account-balance.service.ts

@ -22,10 +22,12 @@ export class AccountBalanceService {
public async getAccountBalances({
filters,
user
user,
withExcludedAccounts
}: {
filters?: Filter[];
user: UserWithSettings;
withExcludedAccounts?: boolean;
}): Promise<AccountBalancesResponse> {
const where: Prisma.AccountBalanceWhereInput = { userId: user.id };
@ -37,6 +39,10 @@ export class AccountBalanceService {
where.accountId = accountFilter.id;
}
if (withExcludedAccounts === false) {
where.Account = { isExcluded: false };
}
const balances = await this.prismaService.accountBalance.findMany({
where,
orderBy: {

12
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts

@ -29,14 +29,15 @@ import { AccountDetailDialogParams } from './interfaces/interfaces';
styleUrls: ['./account-detail-dialog.component.scss']
})
export class AccountDetailDialog implements OnDestroy, OnInit {
public activities: OrderWithAccount[];
public balance: number;
public currency: string;
public equity: number;
public hasImpersonationId: boolean;
public historicalDataItems: HistoricalDataItem[];
public isLoadingActivities: boolean;
public isLoadingChart: boolean;
public name: string;
public orders: OrderWithAccount[];
public platformName: string;
public transactionCount: number;
public user: User;
@ -64,6 +65,7 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
}
public ngOnInit() {
this.isLoadingActivities = true;
this.isLoadingChart = true;
this.dataService
@ -103,7 +105,9 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ activities }) => {
this.orders = activities;
this.activities = activities;
this.isLoadingActivities = false;
this.changeDetectorRef.markForCheck();
});
@ -153,8 +157,8 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
public onExport() {
this.dataService
.fetchExport(
this.orders.map((order) => {
return order.id;
this.activities.map(({ id }) => {
return id;
})
)
.pipe(takeUntil(this.unsubscribeSubject))

25
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html

@ -31,7 +31,7 @@
></gf-investment-chart>
</div>
<div class="row">
<div class="mb-3 row">
<div class="col-6 mb-3">
<gf-value
i18n
@ -64,11 +64,15 @@
</div>
</div>
<div class="row" [ngClass]="{ 'd-none': !orders?.length }">
<div class="col mb-3">
<div class="h5 mb-0" i18n>Activities</div>
<mat-tab-group
animationDuration="0"
[mat-stretch-tabs]="false"
[ngClass]="{ 'd-none': isLoadingActivities }"
>
<mat-tab>
<ng-template i18n mat-tab-label>Activities</ng-template>
<gf-activities-table
[activities]="orders"
[activities]="activities"
[baseCurrency]="user?.settings?.baseCurrency"
[deviceType]="data.deviceType"
[hasPermissionToCreateActivity]="false"
@ -79,8 +83,15 @@
[showActions]="false"
(export)="onExport()"
></gf-activities-table>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template i18n mat-tab-label>Cash Balances</ng-template>
<gf-account-balances
[accountId]="data.accountId"
[locale]="user?.settings?.locale"
></gf-account-balances>
</mat-tab>
</mat-tab-group>
</div>
</div>

4
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.module.ts

@ -2,9 +2,11 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTabsModule } from '@angular/material/tabs';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
import { GfAccountBalancesModule } from '@ghostfolio/ui/account-balances/account-balances.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
import { GfValueModule } from '@ghostfolio/ui/value';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -15,6 +17,7 @@ import { AccountDetailDialog } from './account-detail-dialog.component';
declarations: [AccountDetailDialog],
imports: [
CommonModule,
GfAccountBalancesModule,
GfActivitiesTableModule,
GfDialogFooterModule,
GfDialogHeaderModule,
@ -22,6 +25,7 @@ import { AccountDetailDialog } from './account-detail-dialog.component';
GfValueModule,
MatButtonModule,
MatDialogModule,
MatTabsModule,
NgxSkeletonLoaderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]

2
apps/client/src/app/pages/portfolio/activities/activities-page.html

@ -1,5 +1,5 @@
<div class="container">
<div class="row mb-3">
<div class="mb-3 row">
<div class="col">
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Activities</h1>
<gf-activities-table

7
apps/client/src/app/services/data.service.ts

@ -18,6 +18,7 @@ import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
Access,
AccountBalancesResponse,
Accounts,
BenchmarkMarketDataDetails,
BenchmarkResponse,
@ -137,6 +138,12 @@ export class DataService {
return this.http.get<AccountWithValue>(`/api/v1/account/${aAccountId}`);
}
public fetchAccountBalances(aAccountId: string) {
return this.http.get<AccountBalancesResponse>(
`/api/v1/account/${aAccountId}/balances`
);
}
public fetchAccounts() {
return this.http.get<Accounts>('/api/v1/account');
}

13
apps/client/src/assets/oss-friends.json

@ -1,5 +1,5 @@
{
"createdAt": "2023-11-17T00:00:00.000Z",
"createdAt": "2023-11-30T00:00:00.000Z",
"data": [
{
"name": "BoxyHQ",
@ -16,6 +16,11 @@
"description": "Centralize community, product, and customer data to understand which companies are engaging with your open source project.",
"href": "https://www.crowd.dev"
},
{
"name": "DevHunt",
"description": "Find the best Dev Tools upvoted by the community every week.",
"href": "https://devhunt.org"
},
{
"name": "Documenso",
"description": "The Open-Source DocuSign Alternative. We aim to earn your trust by enabling you to self-host the platform and examine its inner workings.",
@ -59,7 +64,7 @@
{
"name": "Hook0",
"description": "Open-Source Webhooks-as-a-service (WaaS) that makes it easy for developers to send webhooks.",
"href": "https://www.hook0.com/"
"href": "https://www.hook0.com"
},
{
"name": "HTMX",
@ -89,7 +94,7 @@
{
"name": "Papermark",
"description": "Open-Source Docsend Alternative to securely share documents with real-time analytics.",
"href": "https://www.papermark.io/"
"href": "https://www.papermark.io"
},
{
"name": "Requestly",
@ -109,7 +114,7 @@
{
"name": "Shelf.nu",
"description": "Open Source Asset and Equipment tracking software that lets you create QR asset labels, manage and overview your assets across locations.",
"href": "https://www.shelf.nu/"
"href": "https://www.shelf.nu"
},
{
"name": "Sniffnet",

36
libs/ui/src/lib/account-balances/account-balances.component.html

@ -0,0 +1,36 @@
<table
class="gf-table w-100"
mat-table
matSort
matSortActive="date"
matSortDirection="desc"
[dataSource]="dataSource"
>
<ng-container matColumnDef="date">
<th *matHeaderCellDef class="px-2" mat-header-cell mat-sort-header>
<ng-container i18n>Date</ng-container>
</th>
<td *matCellDef="let element" class="px-2" mat-cell>
<gf-value [isDate]="true" [locale]="locale" [value]="element?.date" />
</td>
</ng-container>
<ng-container matColumnDef="value">
<th *matHeaderCellDef class="px-2 text-right" mat-header-cell>
<ng-container i18n>Value</ng-container>
</th>
<td *matCellDef="let element" class="px-2" mat-cell>
<div class="d-flex justify-content-end">
<gf-value
[isCurrency]="true"
[locale]="locale"
[unit]="element?.Account?.currency"
[value]="element?.value"
></gf-value>
</div>
</td>
</ng-container>
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
</table>

5
libs/ui/src/lib/account-balances/account-balances.component.scss

@ -0,0 +1,5 @@
@import 'apps/client/src/styles/ghostfolio-style';
:host {
display: block;
}

63
libs/ui/src/lib/account-balances/account-balances.component.ts

@ -0,0 +1,63 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
ViewChild
} from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { DataService } from '@ghostfolio/client/services/data.service';
import { AccountBalancesResponse } from '@ghostfolio/common/interfaces';
import { get } from 'lodash';
import { Subject, takeUntil } from 'rxjs';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-account-balances',
styleUrls: ['./account-balances.component.scss'],
templateUrl: './account-balances.component.html'
})
export class AccountBalancesComponent implements OnDestroy, OnInit {
@Input() accountId: string;
@Input() locale: string;
@ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<
AccountBalancesResponse['balances'][0]
> = new MatTableDataSource();
public displayedColumns: string[] = ['date', 'value'];
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService
) {}
public ngOnInit() {
this.fetchBalances();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private fetchBalances() {
this.dataService
.fetchAccountBalances(this.accountId)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ balances }) => {
this.dataSource = new MatTableDataSource(balances);
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = get;
this.changeDetectorRef.markForCheck();
});
}
}

15
libs/ui/src/lib/account-balances/account-balances.module.ts

@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { GfValueModule } from '@ghostfolio/ui/value';
import { AccountBalancesComponent } from './account-balances.component';
@NgModule({
declarations: [AccountBalancesComponent],
exports: [AccountBalancesComponent],
imports: [CommonModule, GfValueModule, MatSortModule, MatTableModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfAccountBalancesModule {}
Loading…
Cancel
Save