Browse Source

feat(ui): implement gf-tags-selector in holding detail dialog

pull/4256/head
KenTandrian 7 months ago
parent
commit
170df271a4
  1. 60
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  2. 38
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
  3. 6
      libs/ui/src/lib/tags-selector/tags-selector.component.html
  4. 1
      libs/ui/src/lib/tags-selector/tags-selector.component.ts

60
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts

@ -18,26 +18,20 @@ import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-cre
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef,
Inject, Inject,
OnDestroy, OnDestroy,
OnInit, OnInit
ViewChild
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import {
MatAutocompleteModule,
MatAutocompleteSelectedEvent
} from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { import {
@ -53,8 +47,8 @@ import { Router } from '@angular/router';
import { Account, Tag } from '@prisma/client'; import { Account, Tag } from '@prisma/client';
import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Observable, of, Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { map, startWith, takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { HoldingDetailDialogParams } from './interfaces/interfaces'; import { HoldingDetailDialogParams } from './interfaces/interfaces';
@ -70,8 +64,8 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces';
GfDialogHeaderModule, GfDialogHeaderModule,
GfLineChartComponent, GfLineChartComponent,
GfPortfolioProportionChartComponent, GfPortfolioProportionChartComponent,
GfTagsSelectorComponent,
GfValueComponent, GfValueComponent,
MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatChipsModule, MatChipsModule,
MatDialogModule, MatDialogModule,
@ -85,8 +79,6 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces';
templateUrl: 'holding-detail-dialog.html' templateUrl: 'holding-detail-dialog.html'
}) })
export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
public activityForm: FormGroup; public activityForm: FormGroup;
public accounts: Account[]; public accounts: Account[];
public assetClass: string; public assetClass: string;
@ -102,7 +94,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public dividendInBaseCurrencyPrecision = 2; public dividendInBaseCurrencyPrecision = 2;
public dividendYieldPercentWithCurrencyEffect: number; public dividendYieldPercentWithCurrencyEffect: number;
public feeInBaseCurrency: number; public feeInBaseCurrency: number;
public filteredTagsObservable: Observable<Tag[]> = of([]);
public firstBuyDate: string; public firstBuyDate: string;
public historicalDataItems: LineChartItem[]; public historicalDataItems: LineChartItem[];
public investment: number; public investment: number;
@ -122,7 +113,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public sectors: { public sectors: {
[name: string]: { name: string; value: number }; [name: string]: { name: string; value: number };
}; };
public separatorKeysCodes: number[] = [COMMA, ENTER];
public sortColumn = 'date'; public sortColumn = 'date';
public sortDirection: SortDirection = 'desc'; public sortDirection: SortDirection = 'desc';
public SymbolProfile: EnhancedSymbolProfile; public SymbolProfile: EnhancedSymbolProfile;
@ -319,17 +309,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
this.activityForm.setValue({ tags: this.tags }, { emitEvent: false }); this.activityForm.setValue({ tags: this.tags }, { emitEvent: false });
this.filteredTagsObservable = this.activityForm.controls[
'tags'
].valueChanges.pipe(
startWith(this.activityForm.get('tags').value),
map((aTags: Tag[] | null) => {
return aTags
? this.filterTags(aTags)
: this.tagsAvailable.slice();
})
);
this.transactionCount = transactionCount; this.transactionCount = transactionCount;
this.totalItems = transactionCount; this.totalItems = transactionCount;
this.value = value; this.value = value;
@ -437,15 +416,8 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
}); });
} }
public onAddTag(event: MatAutocompleteSelectedEvent) { public onTagsChanged(tags: Tag[]) {
this.activityForm.get('tags').setValue([ this.activityForm.get('tags').setValue(tags);
...(this.activityForm.get('tags').value ?? []),
this.tagsAvailable.find(({ id }) => {
return id === event.option.value;
})
]);
this.tagInput.nativeElement.value = '';
} }
public onCloneActivity(aActivity: Activity) { public onCloneActivity(aActivity: Activity) {
@ -480,14 +452,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
}); });
} }
public onRemoveTag(aTag: Tag) {
this.activityForm.get('tags').setValue(
this.activityForm.get('tags').value.filter(({ id }) => {
return id !== aTag.id;
})
);
}
public onUpdateActivity(aActivity: Activity) { public onUpdateActivity(aActivity: Activity) {
this.router.navigate(['/portfolio', 'activities'], { this.router.navigate(['/portfolio', 'activities'], {
queryParams: { activityId: aActivity.id, editDialog: true } queryParams: { activityId: aActivity.id, editDialog: true }
@ -500,14 +464,4 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private filterTags(aTags: Tag[]) {
const tagIds = aTags.map(({ id }) => {
return id;
});
return this.tagsAvailable.filter(({ id }) => {
return !tagIds.includes(id);
});
}
} }

38
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html

@ -373,38 +373,12 @@
}" }"
> >
<div class="col"> <div class="col">
<mat-form-field appearance="outline" class="w-100 without-hint"> <gf-tags-selector
<mat-label i18n>Tags</mat-label> [tags]="activityForm.get('tags')?.value"
<mat-chip-grid #tagsChipList> [tagsAvailable]="tagsAvailable"
@for (tag of activityForm.get('tags')?.value; track tag.id) { [withoutHint]="true"
<mat-chip-row (tagsChanged)="onTagsChanged($event)"
matChipRemove />
[removable]="true"
(removed)="onRemoveTag(tag)"
>
{{ tag.name }}
<ion-icon class="ml-2" matPrefix name="close-outline" />
</mat-chip-row>
}
<input
#tagInput
name="close-outline"
[matAutocomplete]="autocompleteTags"
[matChipInputFor]="tagsChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
/>
</mat-chip-grid>
<mat-autocomplete
#autocompleteTags="matAutocomplete"
(optionSelected)="onAddTag($event)"
>
@for (tag of filteredTagsObservable | async; track tag.id) {
<mat-option [value]="tag.id">
{{ tag.name }}
</mat-option>
}
</mat-autocomplete>
</mat-form-field>
</div> </div>
</div> </div>

6
libs/ui/src/lib/tags-selector/tags-selector.component.html

@ -1,4 +1,8 @@
<mat-form-field appearance="outline" class="w-100"> <mat-form-field
appearance="outline"
class="w-100"
[ngClass]="{ 'without-hint': this.withoutHint }"
>
<mat-label i18n>Tags</mat-label> <mat-label i18n>Tags</mat-label>
<mat-chip-grid #tagsChipList> <mat-chip-grid #tagsChipList>
@for (tag of tagsSelected(); track tag.id) { @for (tag of tagsSelected(); track tag.id) {

1
libs/ui/src/lib/tags-selector/tags-selector.component.ts

@ -44,6 +44,7 @@ import { Tag } from '@prisma/client';
export class GfTagsSelectorComponent implements OnInit { export class GfTagsSelectorComponent implements OnInit {
@Input() tags: Tag[]; @Input() tags: Tag[];
@Input() tagsAvailable: Tag[]; @Input() tagsAvailable: Tag[];
@Input() withoutHint: boolean;
@Output() tagsChanged = new EventEmitter<Tag[]>(); @Output() tagsChanged = new EventEmitter<Tag[]>();

Loading…
Cancel
Save