Browse Source

Update Tags Selector and GfHoldingDetailDialogComponent

pull/3708/head
Daniel Idem 1 year ago
parent
commit
19184bb382
  1. 60
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  2. 37
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
  3. 6
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  4. 6
      libs/ui/src/lib/tags-selector/tags-selector.component.html
  5. 107
      libs/ui/src/lib/tags-selector/tags-selector.component.ts

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

@ -17,6 +17,7 @@ import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-cre
import { translate } from '@ghostfolio/ui/i18n';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
@ -26,19 +27,13 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Inject,
OnDestroy,
OnInit,
ViewChild
OnInit
} from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
MatAutocompleteModule,
MatAutocompleteSelectedEvent
} from '@angular/material/autocomplete';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import {
MAT_DIALOG_DATA,
MatDialogModule,
@ -52,8 +47,8 @@ import { Router } from '@angular/router';
import { Account, Tag } from '@prisma/client';
import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Observable, of, Subject } from 'rxjs';
import { map, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { HoldingDetailDialogParams } from './interfaces/interfaces';
@ -69,10 +64,10 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces';
GfDialogHeaderModule,
GfLineChartComponent,
GfPortfolioProportionChartComponent,
GfTagsSelectorComponent,
GfValueComponent,
MatAutocompleteModule,
MatButtonModule,
MatChipsModule,
MatDialogModule,
MatFormFieldModule,
MatTabsModule,
@ -85,8 +80,6 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces';
templateUrl: 'holding-detail-dialog.html'
})
export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
public activityForm: FormGroup;
public accounts: Account[];
public activities: Activity[];
@ -103,7 +96,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public dividendInBaseCurrencyPrecision = 2;
public dividendYieldPercentWithCurrencyEffect: number;
public feeInBaseCurrency: number;
public filteredTagsObservable: Observable<Tag[]> = of([]);
public firstBuyDate: string;
public historicalDataItems: LineChartItem[];
public investment: number;
@ -305,17 +297,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
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.totalItems = transactionCount;
this.value = value;
@ -415,15 +396,8 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
});
}
public onAddTag(event: MatAutocompleteSelectedEvent) {
this.activityForm.get('tags').setValue([
...(this.activityForm.get('tags').value ?? []),
this.tagsAvailable.find(({ id }) => {
return id === event.option.value;
})
]);
this.tagInput.nativeElement.value = '';
public onAddTag(event: Tag[]) {
this.activityForm.get('tags').setValue(event);
}
public onCloneActivity(aActivity: Activity) {
@ -458,14 +432,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) {
this.router.navigate(['/portfolio', 'activities'], {
queryParams: { activityId: aActivity.id, editDialog: true }
@ -478,14 +444,4 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private filterTags(aTags: Tag[]) {
const tagIds = aTags.map(({ id }) => {
return id;
});
return this.tagsAvailable.filter(({ id }) => {
return !tagIds.includes(id);
});
}
}

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

@ -373,38 +373,11 @@
}"
>
<div class="col">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Tags</mat-label>
<mat-chip-grid #tagsChipList>
@for (tag of activityForm.get('tags')?.value; track tag.id) {
<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>
<gf-tags-selector
[tags]="activityForm.get('tags')?.value"
[tagsAvailable]="tagsAvailable"
(tagsChanged)="onAddTag($event)"
/>
</div>
</div>

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

@ -140,12 +140,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
updateAccountBalance: [false]
});
console.log(
'Initialized Activity Form: ',
this.activityForm.get('tags').value
);
console.log('Initialized Activity Tags Available: ', this.tagsAvailable);
this.activityForm.valueChanges
.pipe(
// Slightly delay until the more specific form control value changes have

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

@ -1,7 +1,7 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Tags</mat-label>
<mat-chip-grid #tagsChipList>
@for (tag of fruits(); track tag.id) {
@for (tag of tagsSignal(); track tag.id) {
<mat-chip-row
matChipRemove
[removable]="true"
@ -16,16 +16,14 @@
name="close-outline"
[matAutocomplete]="autocompleteTags"
[matChipInputFor]="tagsChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[(ngModel)]="currentFruit"
/>
<!-- (matChipInputTokenEnd)="onAddTagInput($event)" -->
</mat-chip-grid>
<mat-autocomplete
#autocompleteTags="matAutocomplete"
(optionSelected)="onAddTag($event)"
>
@for (tag of filteredFruits(); track tag.id) {
@for (tag of filteredTags(); track tag.id) {
<mat-option [value]="tag.id">
{{ tag.name }}
</mat-option>

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

@ -1,12 +1,6 @@
// import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { FocusMonitor } from '@angular/cdk/a11y';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
@ -20,36 +14,19 @@ import {
signal,
ViewChild
} from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormsModule } from '@angular/forms';
import {
MatAutocompleteTrigger,
MatAutocompleteModule,
MatAutocompleteSelectedEvent
} from '@angular/material/autocomplete';
import {
MatChipEditedEvent,
MatChipInputEvent,
MatChipsModule
} from '@angular/material/chips';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import {
MatFormFieldControl,
MatFormFieldModule
} from '@angular/material/form-field';
import { MatChipsModule } from '@angular/material/chips';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInput, MatInputModule } from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
import { Tag } from '@prisma/client';
import { map, Observable, of, startWith, Subject } from 'rxjs';
import { translate } from '../i18n';
import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
// host: {
// '[attr.aria-describedBy]': 'describedBy',
// '[id]': 'id'
// },
imports: [
CommonModule,
FormsModule,
@ -57,15 +34,8 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
MatFormFieldModule,
MatInputModule,
MatChipsModule,
MatIconModule,
ReactiveFormsModule
MatIconModule
],
// providers: [
// {
// provide: MatFormFieldControl,
// useExisting: GfTagsSelectorComponent
// }
// ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-tags-selector',
standalone: true,
@ -73,9 +43,6 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
templateUrl: 'tags-selector.component.html'
})
export class GfTagsSelectorComponent implements OnInit {
public focus(): void {
throw new Error('Method not implemented.');
}
@Input() tags: Tag[];
@Input() tagsAvailable: Tag[];
@ -83,75 +50,57 @@ export class GfTagsSelectorComponent implements OnInit {
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
// public activityForm: FormGroup;
public filteredTagsObservable: Observable<Tag[]> = of([]);
public separatorKeysCodes: number[] = [COMMA, ENTER];
readonly fruits = signal([{ id: '', name: '' }]);
readonly tagsSignal = signal([{ id: '', name: '' }]);
readonly currentFruit = model('');
readonly filteredFruits = computed(() => {
readonly filteredTags = computed(() => {
const currentFruit = this.currentFruit().toLowerCase();
const aTags = this.tagsAvailable
? this.tagsAvailable
: [{ id: '', name: '' }];
const aTags = this.tagsAvailable ?? [{ id: '', name: '' }];
const bTags = this.tagsSignal() ?? [{ id: '', name: '' }];
const cTags = aTags.filter((value) => !bTags.includes(value));
return currentFruit
? aTags.filter((tag) => tag.name.toLowerCase().includes(currentFruit))
: this.tagsAvailable;
? cTags.filter((tag) => tag.name.toLowerCase().includes(currentFruit))
: cTags;
});
public constructor() {
effect(() => {
if (this.fruits()) {
console.log('Emit Fruits: ', this.fruits());
this.tagsChanged.emit(this.fruits());
if (this.tagsSignal()) {
this.tagsChanged.emit(this.tagsSignal());
}
});
}
ngOnInit() {
this.fruits.set(this.tags);
console.log('Tags Available : ', this.tagsAvailable);
console.log('Tags : ', this.tags);
this.tagsSignal.set(this.tags);
}
public onAddTag(event: MatAutocompleteSelectedEvent) {
if (
this.fruits() &&
this.fruits().some((el) => el.id === event.option.value)
) {
const tagId = event.option.value;
const newTag = this.tagsAvailable.find(({ id }) => id === tagId);
if (this.tagsSignal()?.some((el) => el.id === tagId)) {
this.currentFruit.set('');
event.option.deselect();
return;
}
this.fruits()
? this.fruits.update((fruits) => [
...fruits,
this.tagsAvailable.find(({ id }) => {
return id === event.option.value;
})
])
: this.fruits.update(() => [
this.tagsAvailable.find(({ id }) => {
return id === event.option.value;
})
]);
this.tagsSignal()
? this.tagsSignal.update((tags) => [...tags, newTag])
: this.tagsSignal.update(() => [newTag]);
this.currentFruit.set('');
event.option.deselect();
}
public onRemoveTag(aTag: Tag) {
this.fruits.update((fruits) => {
const index = fruits.indexOf(aTag);
this.tagsSignal.update((tagsSignal) => {
const index = tagsSignal.indexOf(aTag);
if (index < 0) {
return fruits;
return tagsSignal;
}
fruits.splice(index, 1);
return [...fruits];
tagsSignal.splice(index, 1);
return [...tagsSignal];
});
}
}

Loading…
Cancel
Save