Feature/extract activities table filter component (#858)
* Extract activities table component * Update changelogpull/860/head
parent
edca05f542
commit
8f61f7c169
@ -0,0 +1,33 @@
|
|||||||
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
|
<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"
|
||||||
|
class="mx-1 my-0 px-2 py-0"
|
||||||
|
matChipRemove
|
||||||
|
[removable]="true"
|
||||||
|
(removed)="removeKeyword(searchKeyword)"
|
||||||
|
>
|
||||||
|
{{ searchKeyword | gfSymbol }}
|
||||||
|
<ion-icon class="ml-2" matPrefix name="close-outline"></ion-icon>
|
||||||
|
</mat-chip>
|
||||||
|
<input
|
||||||
|
#searchInput
|
||||||
|
name="close-outline"
|
||||||
|
[formControl]="searchControl"
|
||||||
|
[matAutocomplete]="autocomplete"
|
||||||
|
[matChipInputFor]="chipList"
|
||||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
|
[placeholder]="placeholder"
|
||||||
|
(matChipInputTokenEnd)="addKeyword($event)"
|
||||||
|
/>
|
||||||
|
</mat-chip-list>
|
||||||
|
<mat-autocomplete
|
||||||
|
#autocomplete="matAutocomplete"
|
||||||
|
(optionSelected)="keywordSelected($event)"
|
||||||
|
>
|
||||||
|
<mat-option *ngFor="let filter of filters | async" [value]="filter">
|
||||||
|
{{ filter | gfSymbol }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
@ -0,0 +1,22 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.mat-form-field-infix {
|
||||||
|
border-top: 0 solid transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-chip {
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 1.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.is-dark-theme) {
|
||||||
|
.mat-form-field {
|
||||||
|
color: rgba(var(--light-primary-text));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
|
Output,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
import {
|
||||||
|
MatAutocomplete,
|
||||||
|
MatAutocompleteSelectedEvent
|
||||||
|
} from '@angular/material/autocomplete';
|
||||||
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
selector: 'gf-activities-filter',
|
||||||
|
styleUrls: ['./activities-filter.component.scss'],
|
||||||
|
templateUrl: './activities-filter.component.html'
|
||||||
|
})
|
||||||
|
export class ActivitiesFilterComponent implements OnChanges, OnDestroy {
|
||||||
|
@Input() allFilters: string[];
|
||||||
|
@Input() placeholder: string;
|
||||||
|
|
||||||
|
@Output() valueChanged = new EventEmitter<string[]>();
|
||||||
|
|
||||||
|
@ViewChild('autocomplete') matAutocomplete: MatAutocomplete;
|
||||||
|
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
|
||||||
|
|
||||||
|
public filters$: Subject<string[]> = new BehaviorSubject([]);
|
||||||
|
public filters: Observable<string[]> = this.filters$.asObservable();
|
||||||
|
public searchControl = new FormControl();
|
||||||
|
public searchKeywords: string[] = [];
|
||||||
|
public separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.searchControl.valueChanges
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((keyword) => {
|
||||||
|
if (keyword) {
|
||||||
|
const filterValue = keyword.toLowerCase();
|
||||||
|
this.filters$.next(
|
||||||
|
this.allFilters.filter(
|
||||||
|
(filter) => filter.toLowerCase().indexOf(filterValue) === 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.filters$.next(this.allFilters);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnChanges() {
|
||||||
|
if (this.allFilters) {
|
||||||
|
this.updateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 keywordSelected(event: MatAutocompleteSelectedEvent): void {
|
||||||
|
this.searchKeywords.push(event.option.viewValue);
|
||||||
|
this.updateFilter();
|
||||||
|
this.searchInput.nativeElement.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 ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFilter() {
|
||||||
|
this.filters$.next(this.allFilters);
|
||||||
|
|
||||||
|
this.valueChanged.emit(this.searchKeywords);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
|
|
||||||
|
import { ActivitiesFilterComponent } from './activities-filter.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ActivitiesFilterComponent],
|
||||||
|
exports: [ActivitiesFilterComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
GfSymbolModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatInputModule,
|
||||||
|
ReactiveFormsModule
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class GfActivitiesFilterModule {}
|
Loading…
Reference in new issue