From 49ce4803ceaddd623327c194c2b65f4659c7afa9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 25 Dec 2022 12:23:52 +0100 Subject: [PATCH] Feature/add support to manage tags in create or edit activity dialog (#1532) * Add support to manage tags * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/order/order.service.ts | 6 +++ ...ate-or-update-activity-dialog.component.ts | 51 +++++++++++++++++-- .../create-or-update-activity-dialog.html | 32 ++++++++++-- .../create-or-update-activity-dialog.scss | 5 ++ 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ddced56a..da0c08822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added support to manage the tags in the create or edit activity dialog - Added the tags to the admin control panel ### Changed diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index b95c96975..c91332785 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -362,6 +362,12 @@ export class OrderService { delete data.symbol; delete data.tags; + // Remove existing tags + await this.prismaService.order.update({ + data: { tags: { set: [] } }, + where + }); + return this.prismaService.order.update({ data: { ...data, diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 369f3121d..04e12002e 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -1,7 +1,9 @@ +import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + ElementRef, Inject, OnDestroy, ViewChild @@ -15,7 +17,7 @@ import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { translate } from '@ghostfolio/ui/i18n'; -import { AssetClass, AssetSubClass, Type } from '@prisma/client'; +import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client'; import { isUUID } from 'class-validator'; import { isString } from 'lodash'; import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs'; @@ -23,6 +25,7 @@ import { catchError, debounceTime, distinctUntilChanged, + map, startWith, switchMap, takeUntil @@ -39,6 +42,7 @@ import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; }) export class CreateOrUpdateActivityDialog implements OnDestroy { @ViewChild('autocomplete') autocomplete; + @ViewChild('tagInput') tagInput: ElementRef; public activityForm: FormGroup; public assetClasses = Object.keys(AssetClass).map((assetClass) => { @@ -51,8 +55,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { public currentMarketPrice = null; public filteredLookupItems: LookupItem[]; public filteredLookupItemsObservable: Observable; + public filteredTagsObservable: Observable; public isLoading = false; public platforms: { id: string; name: string }[]; + public separatorKeysCodes: number[] = [ENTER, COMMA]; + public tags: Tag[] = []; public total = 0; public Validators = Validators; @@ -72,10 +79,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.locale = this.data.user?.settings?.locale; this.dateAdapter.setLocale(this.locale); - const { currencies, platforms } = this.dataService.fetchInfo(); + const { currencies, platforms, tags } = this.dataService.fetchInfo(); this.currencies = currencies; this.platforms = platforms; + this.tags = tags; this.activityForm = this.formBuilder.group({ accountId: [this.data.activity?.accountId, Validators.required], @@ -185,6 +193,15 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { }) ); + this.filteredTagsObservable = this.activityForm.controls[ + 'tags' + ].valueChanges.pipe( + startWith(this.activityForm.controls['tags'].value), + map((aTags: Tag[] | null) => { + return aTags ? this.filterTags(aTags) : this.tags.slice(); + }) + ); + this.activityForm.controls['type'].valueChanges .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((type: Type) => { @@ -264,6 +281,16 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { return aLookupItem?.symbol ?? ''; } + public onAddTag(event: MatAutocompleteSelectedEvent) { + this.activityForm.controls['tags'].setValue([ + ...(this.activityForm.controls['tags'].value ?? []), + this.tags.find(({ id }) => { + return id === event.option.value; + }) + ]); + this.tagInput.nativeElement.value = ''; + } + public onBlurSymbol() { const currentLookupItem = this.filteredLookupItems.find((lookupItem) => { return ( @@ -283,10 +310,18 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.changeDetectorRef.markForCheck(); } - public onCancel(): void { + public onCancel() { this.dialogRef.close(); } + public onRemoveTag(aTag: Tag) { + this.activityForm.controls['tags'].setValue( + this.activityForm.controls['tags'].value.filter(({ id }) => { + return id !== aTag.id; + }) + ); + } + public onSubmit() { const activity: CreateOrderDto | UpdateOrderDto = { accountId: this.activityForm.controls['accountId'].value, @@ -327,6 +362,16 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.unsubscribeSubject.complete(); } + private filterTags(aTags: Tag[]) { + const tagIds = aTags.map((tag) => { + return tag.id; + }); + + return this.tags.filter((tag) => { + return !tagIds.includes(tag.id); + }); + } + private updateSymbol(symbol: string) { this.isLoading = true; diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 7ecb35b44..e3b3d21d1 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -194,16 +194,38 @@ -
+
Tags - - + + {{ tag.name }} + + + + + {{ tag.name }} + +
diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.scss b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.scss index b5f424dcf..bedc7d4b7 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.scss +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.scss @@ -20,6 +20,11 @@ } } + .mat-chip { + cursor: pointer; + min-height: 1.5rem !important; + } + .mat-form-field-appearance-outline { ::ng-deep { .mat-form-field-suffix {