Browse Source

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

pull/4256/head
KenTandrian 7 months ago
parent
commit
6ede07579b
  1. 53
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  2. 35
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  3. 6
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.module.ts

53
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -3,24 +3,20 @@ import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef,
Inject, Inject,
OnDestroy, OnDestroy
ViewChild
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client'; import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client';
import { isAfter, isToday } from 'date-fns'; import { isAfter, isToday } from 'date-fns';
import { EMPTY, Observable, Subject, lastValueFrom, of } from 'rxjs'; import { EMPTY, Subject, lastValueFrom } from 'rxjs';
import { catchError, delay, map, startWith, takeUntil } from 'rxjs/operators'; import { catchError, delay, takeUntil } from 'rxjs/operators';
import { DataService } from '../../../../services/data.service'; import { DataService } from '../../../../services/data.service';
import { validateObjectForForm } from '../../../../util/form.util'; import { validateObjectForForm } from '../../../../util/form.util';
@ -35,9 +31,6 @@ import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces';
standalone: false standalone: false
}) })
export class CreateOrUpdateActivityDialog implements OnDestroy { export class CreateOrUpdateActivityDialog implements OnDestroy {
@ViewChild('symbolAutocomplete') symbolAutocomplete;
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
public activityForm: FormGroup; public activityForm: FormGroup;
public assetClasses = Object.keys(AssetClass).map((assetClass) => { public assetClasses = Object.keys(AssetClass).map((assetClass) => {
return { id: assetClass, label: translate(assetClass) }; return { id: assetClass, label: translate(assetClass) };
@ -48,12 +41,10 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
public currencies: string[] = []; public currencies: string[] = [];
public currentMarketPrice = null; public currentMarketPrice = null;
public defaultDateFormat: string; public defaultDateFormat: string;
public filteredTagsObservable: Observable<Tag[]> = of([]);
public isLoading = false; public isLoading = false;
public isToday = isToday; public isToday = isToday;
public mode: 'create' | 'update'; public mode: 'create' | 'update';
public platforms: { id: string; name: string }[]; public platforms: { id: string; name: string }[];
public separatorKeysCodes: number[] = [COMMA, ENTER];
public tagsAvailable: Tag[] = []; public tagsAvailable: Tag[] = [];
public total = 0; public total = 0;
public typesTranslationMap = new Map<Type, string>(); public typesTranslationMap = new Map<Type, string>();
@ -284,15 +275,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
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.activityForm this.activityForm
.get('type') .get('type')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject)) .valueChanges.pipe(takeUntil(this.unsubscribeSubject))
@ -440,29 +422,14 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
return isAfter(aDate, new Date(0)); return isAfter(aDate, new Date(0));
} }
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 onCancel() { public onCancel() {
this.dialogRef.close(); this.dialogRef.close();
} }
public onRemoveTag(aTag: Tag) {
this.activityForm.get('tags').setValue(
this.activityForm.get('tags').value.filter(({ id }) => {
return id !== aTag.id;
})
);
}
public async onSubmit() { public async onSubmit() {
const activity: CreateOrderDto | UpdateOrderDto = { const activity: CreateOrderDto | UpdateOrderDto = {
accountId: this.activityForm.get('accountId').value, accountId: this.activityForm.get('accountId').value,
@ -523,16 +490,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
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);
});
}
private updateSymbol() { private updateSymbol() {
this.isLoading = true; this.isLoading = true;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();

35
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -379,38 +379,11 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="mb-3" [ngClass]="{ 'd-none': tagsAvailable?.length < 1 }"> <div class="mb-3" [ngClass]="{ 'd-none': tagsAvailable?.length < 1 }">
<mat-form-field appearance="outline" class="w-100"> <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) { (tagsChanged)="onTagsChanged($event)"
<mat-chip-row
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>
<div class="d-flex" mat-dialog-actions> <div class="d-flex" mat-dialog-actions>

6
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.module.ts

@ -1,14 +1,13 @@
import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component'; import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete';
import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
@ -24,11 +23,10 @@ import { CreateOrUpdateActivityDialog } from './create-or-update-activity-dialog
FormsModule, FormsModule,
GfAssetProfileIconComponent, GfAssetProfileIconComponent,
GfSymbolAutocompleteComponent, GfSymbolAutocompleteComponent,
GfTagsSelectorComponent,
GfValueComponent, GfValueComponent,
MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatCheckboxModule, MatCheckboxModule,
MatChipsModule,
MatDatepickerModule, MatDatepickerModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,

Loading…
Cancel
Save