|
|
@ -1,6 +1,7 @@ |
|
|
|
import { |
|
|
|
ChangeDetectionStrategy, |
|
|
|
Component, |
|
|
|
ElementRef, |
|
|
|
EventEmitter, |
|
|
|
Input, |
|
|
|
OnChanges, |
|
|
@ -15,10 +16,19 @@ 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 { 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', |
|
|
@ -39,13 +49,23 @@ export class TransactionsTableComponent |
|
|
|
@Output() transactionToUpdate = new EventEmitter<OrderWithAccount>(); |
|
|
|
|
|
|
|
@ViewChild(MatSort) sort: MatSort; |
|
|
|
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>; |
|
|
|
@ViewChild('auto') matAutocomplete: MatAutocomplete; |
|
|
|
|
|
|
|
public dataSource: MatTableDataSource<OrderWithAccount> = new MatTableDataSource(); |
|
|
|
public defaultDateFormat = DEFAULT_DATE_FORMAT; |
|
|
|
public displayedColumns = []; |
|
|
|
public isLoading = true; |
|
|
|
public routeQueryParams: Subscription; |
|
|
|
public separatorKeysCodes: number[] = [ENTER, COMMA]; |
|
|
|
public searchKeywords: string[] = []; |
|
|
|
public searchControl = new FormControl(); |
|
|
|
public filteredTransactions$: Subject<string[]> = new BehaviorSubject([]); |
|
|
|
public filteredTransactions: Observable< |
|
|
|
string[] |
|
|
|
> = this.filteredTransactions$.asObservable(); |
|
|
|
|
|
|
|
private allFilteredTransactions: string[]; |
|
|
|
private unsubscribeSubject = new Subject<void>(); |
|
|
|
|
|
|
|
public constructor( |
|
|
@ -63,6 +83,50 @@ 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 |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public addKeyword(event: MatChipInputEvent): void { |
|
|
|
const input = event.input; |
|
|
|
const value = event.value; |
|
|
|
|
|
|
|
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() {} |
|
|
@ -88,28 +152,22 @@ export class TransactionsTableComponent |
|
|
|
if (this.transactions) { |
|
|
|
this.dataSource = new MatTableDataSource(this.transactions); |
|
|
|
this.dataSource.filterPredicate = (data, filter) => { |
|
|
|
const accumulator = (currentTerm: string, key: string) => { |
|
|
|
return key === 'Account' |
|
|
|
? currentTerm + data.Account.name |
|
|
|
: currentTerm + data[key]; |
|
|
|
}; |
|
|
|
const dataString = Object.keys(data) |
|
|
|
.reduce(accumulator, '') |
|
|
|
let dataString = TransactionsTableComponent.getFilterableValues(data) |
|
|
|
.join(' ') |
|
|
|
.toLowerCase(); |
|
|
|
const transformedFilter = filter.trim().toLowerCase(); |
|
|
|
return dataString.includes(transformedFilter); |
|
|
|
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?' |
|
|
@ -169,4 +227,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].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]; |
|
|
|
} |
|
|
|
} |
|
|
|