Compare commits

..

11 Commits

Author SHA1 Message Date
91ec9aa0a4 Release 1.1.0 (#79) 2021-05-11 18:14:49 +02:00
565e920f1b Add link to retrieve manually the current market price (#74)
* feat: add link to retrieve manually the current market price from data source

* Add icon

* Update changelog

Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com>
2021-05-11 18:01:46 +02:00
5d24adfa75 Feature/improve transaction filtering (#76)
* add multi-filter support for transaction filtering with auto completion

* update changelog

* fix table for transaction for accounts without platform

* simplify readme file since docker compose build is not required (#75)

* simplify readme file since docker compose build is not required

* add anchor navigation in README.md

* Improve UI

* Refactoring

* Refactoring

* Feature/travis (#77)

* integrate travis

* fix prettier transactions-page.component.ts

* change base branch to main

* fetch all branches in .travis.yml

* Bugfix/keep current menu item active (#78)

* Keep current menu item active

* Update changelog

* Feature/travis (#77)

* integrate travis

* fix prettier transactions-page.component.ts

* change base branch to main

* fetch all branches in .travis.yml

* Keep current menu item active

* Update changelog

Co-authored-by: Valentin Zickner <3200232+vzickner@users.noreply.github.com>

* add multi-filter support for transaction filtering with auto completion

* update changelog

* fix table for transaction for accounts without platform

* Improve UI

* Refactoring

* Refactoring

* Update changelog

Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com>
2021-05-11 17:55:55 +02:00
1dc94c0027 Bugfix/keep current menu item active (#78)
* Keep current menu item active

* Update changelog

* Feature/travis (#77)

* integrate travis

* fix prettier transactions-page.component.ts

* change base branch to main

* fetch all branches in .travis.yml

* Keep current menu item active

* Update changelog

Co-authored-by: Valentin Zickner <3200232+vzickner@users.noreply.github.com>
2021-05-11 17:49:35 +02:00
ebae2f4ec9 Feature/travis (#77)
* integrate travis

* fix prettier transactions-page.component.ts

* change base branch to main

* fetch all branches in .travis.yml
2021-05-11 17:43:35 +02:00
7099edc591 simplify readme file since docker compose build is not required (#75)
* simplify readme file since docker compose build is not required

* add anchor navigation in README.md
2021-05-09 20:11:10 +02:00
de973d6bda Add filterPredicate on transactions table to filter by account name (#73)
* fix: add filterPredicate on transactions table to filter by account name

* Minor refactoring

* Update changelog

Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com>
2021-05-06 22:57:15 +02:00
993a491d24 Release 1.0.0 (#70) 2021-05-05 12:05:43 +02:00
631efff7ae Add duplicate action on transactions table (#68)
* feat: add duplicate action on transactions table

* fix: review changes

* fix: add type and dataSource
2021-05-05 12:01:56 +02:00
a3d1ac2ce4 Feature/update icon and add google play badge (#69)
* Add maskable icons

* Add Google Play badge

* Update changelog
2021-05-04 21:48:51 +02:00
4484c21757 Clean up platform id (#67) 2021-05-04 17:49:47 +02:00
25 changed files with 674 additions and 383 deletions

9
.travis.yml Normal file
View File

@ -0,0 +1,9 @@
language: node_js
git:
depth: false
node_js:
- 14
before_script:
- yarn
script:
- yarn format:check

View File

@ -5,6 +5,32 @@ 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).
## 1.1.0 - 11.05.2021
### Added
- Added a button to fetch the current market price in the create or edit transaction dialog
### Changed
- Improved the transaction filtering with multi filter support
### Fixed
- Fixed the filtering by account name in the transactions table
- Fixed the active menu item state when a modal has opened
## 1.0.0 - 05.05.2021
### Added
- Added the functionality to clone a transaction
- Added a _Google Play_ badge on the landing page
### Changed
- Changed to maskable icons
## 0.99.0 - 03.05.2021
### Added

View File

@ -68,19 +68,18 @@ The frontend is built with [Angular](https://angular.io).
### Setup
1. Run `yarn install`
2. Run `cd docker`
3. Run `docker compose build`
4. Run `docker compose up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
5. Run `cd -` to go back to the project root directory
6. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
7. Start server and client (see _Development_)
8. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
9. Go to the _Admin Control Panel_ and press _Gather All Data_ to fetch historical data
10. Press _Sign out_ and check out the _Live Demo_
1. Run `cd docker`
1. Run `docker compose up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
1. Run `cd -` to go back to the project root directory
1. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
1. Start server and client (see [_Development_](#Development))
1. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
1. Go to the _Admin Control Panel_ and press _Gather All Data_ to fetch historical data
1. Press _Sign out_ and check out the _Live Demo_
## Development
Please make sure you have completed the instructions from _Setup_
Please make sure you have completed the instructions from [_Setup_](#Setup)
### Start server

View File

@ -1,3 +1,5 @@
import { Account, Order } from '@prisma/client';
import { Account, Order, Platform } from '@prisma/client';
export type OrderWithAccount = Order & { Account?: Account };
type AccountWithPlatform = Account & { Platform?: Platform };
export type OrderWithAccount = Order & { Account?: AccountWithPlatform };

View File

@ -57,9 +57,8 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe((test) => {
.subscribe(() => {
this.currentRoute = this.router.url.toString().substring(1);
// this.initializeTheme();
});
this.tokenStorageService

View File

@ -9,7 +9,7 @@
[routerLink]="['/']"
i18n
mat-flat-button
[color]="currentRoute === 'home' ? 'primary' : null"
[color]="currentRoute?.startsWith('home') ? 'primary' : null"
>Overview</a
>
<a
@ -17,7 +17,7 @@
[routerLink]="['/analysis']"
i18n
mat-flat-button
[color]="currentRoute === 'analysis' ? 'primary' : null"
[color]="currentRoute?.startsWith('analysis') ? 'primary' : null"
>Analysis</a
>
<a
@ -25,7 +25,7 @@
[routerLink]="['/report']"
i18n
mat-flat-button
[color]="currentRoute === 'report' ? 'primary' : null"
[color]="currentRoute?.startsWith('report') ? 'primary' : null"
>X-ray</a
>
<a
@ -33,7 +33,7 @@
[routerLink]="['/transactions']"
i18n
mat-flat-button
[color]="currentRoute === 'transactions' ? 'primary' : null"
[color]="currentRoute?.startsWith('transactions') ? 'primary' : null"
>Transactions</a
>
<a
@ -41,7 +41,7 @@
[routerLink]="['/accounts']"
i18n
mat-flat-button
[color]="currentRoute === 'accounts' ? 'primary' : null"
[color]="currentRoute?.startsWith('accounts') ? 'primary' : null"
>Accounts</a
>
<a
@ -50,7 +50,7 @@
[routerLink]="['/admin']"
i18n
mat-flat-button
[color]="currentRoute === 'admin' ? 'primary' : null"
[color]="currentRoute?.startsWith('admin') ? 'primary' : null"
>Admin Control</a
>
<a
@ -58,7 +58,7 @@
[routerLink]="['/resources']"
i18n
mat-flat-button
[color]="currentRoute === 'resources' ? 'primary' : null"
[color]="currentRoute?.startsWith('resources') ? 'primary' : null"
>Resources</a
>
<a
@ -66,7 +66,7 @@
[routerLink]="['/about']"
i18n
mat-flat-button
[color]="currentRoute === 'about' ? 'primary' : null"
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
>About</a
>
<button
@ -130,7 +130,7 @@
[routerLink]="['/analysis']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'analysis' }"
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('analysis') }"
>Analysis</a
>
<a
@ -138,7 +138,7 @@
[routerLink]="['/report']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'report' }"
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('report') }"
>X-ray</a
>
<a
@ -146,7 +146,9 @@
[routerLink]="['/transactions']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'transactions' }"
[ngClass]="{
'font-weight-bold': currentRoute?.startsWith('transactions')
}"
>Transactions</a
>
<a
@ -154,7 +156,7 @@
[routerLink]="['/accounts']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'accounts' }"
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('accounts') }"
>Accounts</a
>
<a
@ -162,7 +164,7 @@
[routerLink]="['/account']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'account' }"
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('account') }"
>Ghostfolio Account</a
>
<a
@ -171,7 +173,7 @@
[routerLink]="['/admin']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'admin' }"
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('admin') }"
>Admin Control</a
>
<hr class="m-0" />
@ -180,7 +182,9 @@
[routerLink]="['/resources']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'resources' }"
[ngClass]="{
'font-weight-bold': currentRoute?.startsWith('resources')
}"
>Resources</a
>
<a
@ -188,7 +192,7 @@
[routerLink]="['/about']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute === 'about' }"
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('about') }"
>About Ghostfolio</a
>
<hr class="d-block d-sm-none m-0" />
@ -210,7 +214,7 @@
[routerLink]="['/about']"
i18n
mat-flat-button
[color]="currentRoute === 'about' ? 'primary' : null"
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
>About</a
>
<a

View File

@ -1,12 +1,37 @@
<mat-form-field appearance="outline" class="w-100">
<input
#input
autocomplete="off"
matInput
placeholder="Search for transactions..."
(keyup)="applyFilter($event)"
/>
<ion-icon class="mr-1" matPrefix name="search-outline"></ion-icon>
<mat-chip-list #chipList aria-label="Search keywords">
<mat-chip
*ngFor="let searchKeyword of searchKeywords"
matChipRemove
[removable]="true"
(removed)="removeKeyword(searchKeyword)"
>
{{ searchKeyword }}
<ion-icon class="ml-2" matPrefix name="close-outline"></ion-icon>
</mat-chip>
<input
#searchInput
name="close-outline"
placeholder="Search for account, currency, symbol or type..."
[formControl]="searchControl"
[matAutocomplete]="autocomplete"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addKeyword($event)"
/>
</mat-chip-list>
<mat-autocomplete
#autocomplete="matAutocomplete"
(optionSelected)="keywordSelected($event)"
>
<mat-option
*ngFor="let transaction of filteredTransactions | async"
[value]="transaction"
>
{{ transaction }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<table
@ -177,6 +202,9 @@
<button i18n mat-menu-item (click)="onUpdateTransaction(element)">
Edit
</button>
<button i18n mat-menu-item (click)="onCloneTransaction(element)">
Clone
</button>
<button i18n mat-menu-item (click)="onDeleteTransaction(element.id)">
Delete
</button>

View File

@ -7,6 +7,10 @@
}
}
.mat-chip {
cursor: pointer;
}
.mat-table {
td {
border: 0;

View File

@ -1,6 +1,7 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
@ -13,12 +14,21 @@ import { MatDialog } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { OrderWithAccount } from '@ghostfolio/api/app/order/interfaces/order-with-account.type';
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
import { Order as OrderModel } from '@prisma/client';
import { Subject, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { PositionDetailDialog } from '../position/position-detail-dialog/position-detail-dialog.component';
import {
MatAutocomplete,
MatAutocompleteSelectedEvent
} from '@angular/material/autocomplete';
import { FormControl } from '@angular/forms';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
const SEARCH_STRING_SEPARATOR = ',';
@Component({
selector: 'gf-transactions-table',
@ -32,19 +42,30 @@ export class TransactionsTableComponent
@Input() deviceType: string;
@Input() locale: string;
@Input() showActions: boolean;
@Input() transactions: OrderModel[];
@Input() transactions: OrderWithAccount[];
@Output() transactionDeleted = new EventEmitter<string>();
@Output() transactionToUpdate = new EventEmitter<OrderModel>();
@Output() transactionToClone = new EventEmitter<OrderWithAccount>();
@Output() transactionToUpdate = new EventEmitter<OrderWithAccount>();
@ViewChild('autocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
@ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<OrderModel> = new MatTableDataSource();
public dataSource: MatTableDataSource<OrderWithAccount> = new MatTableDataSource();
public defaultDateFormat = DEFAULT_DATE_FORMAT;
public displayedColumns = [];
public filteredTransactions$: Subject<string[]> = new BehaviorSubject([]);
public filteredTransactions: Observable<
string[]
> = this.filteredTransactions$.asObservable();
public isLoading = true;
public routeQueryParams: Subscription;
public searchControl = new FormControl();
public searchKeywords: string[] = [];
public separatorKeysCodes: number[] = [ENTER, COMMA];
private allFilteredTransactions: string[];
private unsubscribeSubject = new Subject<void>();
public constructor(
@ -62,6 +83,49 @@ export class TransactionsTableComponent
});
}
});
this.searchControl.valueChanges.subscribe((keyword) => {
if (keyword) {
const filterValue = keyword.toLowerCase();
this.filteredTransactions$.next(
this.allFilteredTransactions.filter(
(filter) => filter.toLowerCase().indexOf(filterValue) === 0
)
);
} else {
this.filteredTransactions$.next(this.allFilteredTransactions);
}
});
}
public addKeyword({ input, value }: MatChipInputEvent): void {
if (value?.trim()) {
this.searchKeywords.push(value.trim());
this.updateFilter();
}
// Reset the input value
if (input) {
input.value = '';
}
this.searchControl.setValue(null);
}
public removeKeyword(keyword: string): void {
const index = this.searchKeywords.indexOf(keyword);
if (index >= 0) {
this.searchKeywords.splice(index, 1);
this.updateFilter();
}
}
public keywordSelected(event: MatAutocompleteSelectedEvent): void {
this.searchKeywords.push(event.option.viewValue);
this.updateFilter();
this.searchInput.nativeElement.value = '';
this.searchControl.setValue(null);
}
public ngOnInit() {}
@ -86,17 +150,23 @@ export class TransactionsTableComponent
if (this.transactions) {
this.dataSource = new MatTableDataSource(this.transactions);
this.dataSource.filterPredicate = (data, filter) => {
const dataString = TransactionsTableComponent.getFilterableValues(data)
.join(' ')
.toLowerCase();
let contains = true;
for (const singleFilter of filter.split(SEARCH_STRING_SEPARATOR)) {
contains =
contains && dataString.includes(singleFilter.trim().toLowerCase());
}
return contains;
};
this.dataSource.sort = this.sort;
this.updateFilter();
this.isLoading = false;
}
}
public applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
public onDeleteTransaction(aId: string) {
const confirmation = confirm(
'Do you really want to delete this transaction?'
@ -119,10 +189,14 @@ export class TransactionsTableComponent
});
}
public onUpdateTransaction(aTransaction: OrderModel) {
public onUpdateTransaction(aTransaction: OrderWithAccount) {
this.transactionToUpdate.emit(aTransaction);
}
public onCloneTransaction(aTransaction: OrderWithAccount) {
this.transactionToClone.emit(aTransaction);
}
public openPositionDialog({
symbol,
title
@ -152,4 +226,40 @@ export class TransactionsTableComponent
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private updateFilter() {
this.dataSource.filter = this.searchKeywords.join(SEARCH_STRING_SEPARATOR);
const lowercaseSearchKeywords = this.searchKeywords.map((keyword) =>
keyword.trim().toLowerCase()
);
this.allFilteredTransactions = TransactionsTableComponent.getSearchableFieldValues(
this.transactions
).filter((item) => {
return !lowercaseSearchKeywords.includes(item.trim().toLowerCase());
});
this.filteredTransactions$.next(this.allFilteredTransactions);
}
private static getSearchableFieldValues(
transactions: OrderWithAccount[]
): string[] {
const fieldValues = new Set<string>();
for (const transaction of transactions) {
this.getFilterableValues(transaction, fieldValues);
}
return [...fieldValues].filter((item) => item != undefined).sort();
}
private static getFilterableValues(
transaction,
fieldValues: Set<string> = new Set<string>()
): string[] {
fieldValues.add(transaction.currency);
fieldValues.add(transaction.symbol);
fieldValues.add(transaction.type);
fieldValues.add(transaction.Account?.name);
fieldValues.add(transaction.Account?.Platform?.name);
return [...fieldValues].filter((item) => item != undefined);
}
}

View File

@ -13,6 +13,9 @@ import { GfPositionDetailDialogModule } from '../position/position-detail-dialog
import { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
import { GfValueModule } from '../value/value.module';
import { TransactionsTableComponent } from './transactions-table.component';
import { MatChipsModule } from '@angular/material/chips';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [TransactionsTableComponent],
@ -23,12 +26,15 @@ import { TransactionsTableComponent } from './transactions-table.component';
GfSymbolIconModule,
GfSymbolModule,
GfValueModule,
MatAutocompleteModule,
MatButtonModule,
MatChipsModule,
MatInputModule,
MatMenuModule,
MatSortModule,
MatTableModule,
NgxSkeletonLoaderModule,
ReactiveFormsModule,
RouterModule
],
providers: [],

View File

@ -156,4 +156,13 @@
</div>
</div>
</div>
<div class="downloads my-5 row justify-content-center">
<a
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
title="Get Ghostfolio on Google Play"
>
<img alt="Google Play Badge" src="assets/badge-en-google-play.png" />
</a>
</div>
</div>

View File

@ -18,6 +18,12 @@
background-color: var(--light-background);
}
}
.downloads {
img {
height: 2.5rem;
}
}
}
:host-context(.is-dark-theme) {

View File

@ -30,6 +30,7 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
})
export class CreateOrUpdateTransactionDialog {
public currencies: Currency[] = [];
public currentMarketPrice = null;
public filteredLookupItems: Observable<LookupItem[]>;
public isLoading = false;
public platforms: { id: string; name: string }[];
@ -65,6 +66,20 @@ export class CreateOrUpdateTransactionDialog {
return [];
})
);
if (this.data.transaction.symbol) {
this.dataService
.fetchSymbolItem(this.data.transaction.symbol)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => {
this.currentMarketPrice = marketPrice;
this.cd.markForCheck();
});
}
}
public applyCurrentMarketPrice() {
this.data.transaction.unitPrice = this.currentMarketPrice;
}
public onCancel(): void {
@ -81,7 +96,7 @@ export class CreateOrUpdateTransactionDialog {
.subscribe(({ currency, dataSource, marketPrice }) => {
this.data.transaction.currency = currency;
this.data.transaction.dataSource = dataSource;
this.data.transaction.unitPrice = marketPrice;
this.currentMarketPrice = marketPrice;
this.isLoading = false;

View File

@ -81,7 +81,13 @@
[matDatepicker]="date"
[(ngModel)]="data.transaction.date"
/>
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
<mat-datepicker-toggle matSuffix [for]="date">
<ion-icon
class="text-muted"
matDatepickerToggleIcon
name="calendar-clear-outline"
></ion-icon>
</mat-datepicker-toggle>
<mat-datepicker #date disabled="false"></mat-datepicker>
</mat-form-field>
</div>
@ -110,7 +116,7 @@
</mat-form-field>
</div>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-form-field appearance="outline" class="unit-price w-100">
<mat-label i18n>Unit Price</mat-label>
<input
matInput
@ -119,6 +125,15 @@
type="number"
[(ngModel)]="data.transaction.unitPrice"
/>
<button
*ngIf="currentMarketPrice"
mat-icon-button
matSuffix
title="Apply current market price"
(click)="applyCurrentMarketPrice()"
>
<ion-icon class="text-muted" name="refresh-outline"></ion-icon>
</button>
</mat-form-field>
</div>
<div>

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
@ -30,6 +30,7 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
MatSelectModule,
ReactiveFormsModule
],
providers: []
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class CreateOrUpdateTransactionDialogModule {}

View File

@ -14,6 +14,18 @@
}
}
.mat-form-field-appearance-outline {
::ng-deep {
.mat-form-field-suffix {
top: -0.3rem;
}
}
ion-icon {
font-size: 130%;
}
}
.mat-select {
&.no-arrow {
::ng-deep {

View File

@ -109,6 +109,10 @@ export class TransactionsPageComponent implements OnInit {
});
}
public onCloneTransaction(aTransaction: OrderModel) {
this.openCreateTransactionDialog(aTransaction);
}
public onDeleteTransaction(aId: string) {
this.dataService.deleteOrder(aId).subscribe({
next: () => {
@ -175,20 +179,23 @@ export class TransactionsPageComponent implements OnInit {
this.unsubscribeSubject.complete();
}
private openCreateTransactionDialog(): void {
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
data: {
accounts: this.user?.accounts,
transaction: {
accountId: this.user?.accounts.find((account) => {
return account.isDefault;
})?.id,
currency: null,
accountId:
aTransaction?.accountId ??
this.user?.accounts.find((account) => {
return account.isDefault;
})?.id,
currency: aTransaction?.currency ?? null,
dataSource: aTransaction?.dataSource ?? null,
date: new Date(),
fee: 0,
quantity: null,
symbol: null,
type: 'BUY',
symbol: aTransaction?.symbol ?? null,
type: aTransaction?.type ?? 'BUY',
unitPrice: null
}
},

View File

@ -9,6 +9,7 @@
[showActions]="!hasImpersonationId && hasPermissionToDeleteOrder"
[transactions]="transactions"
(transactionDeleted)="onDeleteTransaction($event)"
(transactionToClone)="onCloneTransaction($event)"
(transactionToUpdate)="onUpdateTransaction($event)"
></gf-transactions-table>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -6,7 +6,7 @@
".eslintrc.json": "*",
"nx.json": "*"
},
"affected": { "defaultBase": "master" },
"affected": { "defaultBase": "origin/main" },
"npmScope": "ghostfolio",
"tasksRunnerOptions": {
"default": {

View File

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "0.99.0",
"version": "1.1.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {

View File

@ -144,7 +144,6 @@ async function main() {
date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)),
fee: 30,
id: 'cf7c0418-8535-4089-ae3d-5dbfa0aec2e1',
platformId: platformDegiro.id,
quantity: 50,
symbol: 'TSLA',
type: Type.BUY,
@ -158,7 +157,6 @@ async function main() {
date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)),
fee: 29.9,
id: 'a1c5d73a-8631-44e5-ac44-356827a5212c',
platformId: platformCoinbase.id,
quantity: 0.5614682,
symbol: 'BTCUSD',
type: Type.BUY,
@ -172,7 +170,6 @@ async function main() {
date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)),
fee: 80.79,
id: '71c08e2a-4a86-44ae-a890-c337de5d5f9b',
platformId: platformInteractiveBrokers.id,
quantity: 5,
symbol: 'AMZN',
type: Type.BUY,
@ -186,7 +183,6 @@ async function main() {
date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)),
fee: 19.9,
id: '385f2c2c-d53e-4937-b0e5-e92ef6020d4e',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
@ -200,7 +196,6 @@ async function main() {
date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)),
fee: 19.9,
id: '185f2c2c-d53e-4937-b0e5-a93ef6020d4e',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
@ -214,7 +209,6 @@ async function main() {
date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)),
fee: 19.9,
id: '347b0430-a84f-4031-a0f9-390399066ad6',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
@ -228,7 +222,6 @@ async function main() {
date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)),
fee: 19.9,
id: '67ec3f47-3189-4b63-ba05-60d3a06b302f',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
@ -242,7 +235,6 @@ async function main() {
date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)),
fee: 19.9,
id: 'd01c6fbc-fa8d-47e6-8e80-66f882d2bfd2',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,

668
yarn.lock

File diff suppressed because it is too large Load Diff