Change platform control from select to autocomplete in account dialog (#2429)
* Change platform control from select to autocomplete in account dialog * Update changelog
This commit is contained in:
parent
8236091477
commit
37ff7acf04
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added support for notes in the activities import
|
- Added support for notes in the activities import
|
||||||
|
- Added support to search in the platform selector of the create or update account dialog
|
||||||
- Added support for a search query in the portfolio position endpoint
|
- Added support for a search query in the portfolio position endpoint
|
||||||
- Added the application version to the endpoint `GET api/v1/admin`
|
- Added the application version to the endpoint `GET api/v1/admin`
|
||||||
- Introduced a carousel component for the testimonial section on the landing page
|
- Introduced a carousel component for the testimonial section on the landing page
|
||||||
|
@ -55,12 +55,8 @@ export class InfoService {
|
|||||||
public async get(): Promise<InfoItem> {
|
public async get(): Promise<InfoItem> {
|
||||||
const info: Partial<InfoItem> = {};
|
const info: Partial<InfoItem> = {};
|
||||||
let isReadOnlyMode: boolean;
|
let isReadOnlyMode: boolean;
|
||||||
const platforms = (
|
const platforms = await this.platformService.getPlatforms({
|
||||||
await this.platformService.getPlatforms({
|
orderBy: { name: 'asc' }
|
||||||
orderBy: { name: 'asc' }
|
|
||||||
})
|
|
||||||
).map(({ id, name }) => {
|
|
||||||
return { id, name };
|
|
||||||
});
|
});
|
||||||
let systemMessage: string;
|
let systemMessage: string;
|
||||||
|
|
||||||
|
@ -4,12 +4,20 @@ import {
|
|||||||
Inject,
|
Inject,
|
||||||
OnDestroy
|
OnDestroy
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import {
|
||||||
|
AbstractControl,
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
ValidatorFn,
|
||||||
|
Validators
|
||||||
|
} from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
|
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
|
||||||
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Platform } from '@prisma/client';
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
import { map, startWith } from 'rxjs/operators';
|
||||||
|
|
||||||
import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
|
import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
|
||||||
|
|
||||||
@ -23,7 +31,8 @@ import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
|
|||||||
export class CreateOrUpdateAccountDialog implements OnDestroy {
|
export class CreateOrUpdateAccountDialog implements OnDestroy {
|
||||||
public accountForm: FormGroup;
|
public accountForm: FormGroup;
|
||||||
public currencies: string[] = [];
|
public currencies: string[] = [];
|
||||||
public platforms: { id: string; name: string }[];
|
public filteredPlatforms: Observable<Platform[]>;
|
||||||
|
public platforms: Platform[];
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
@ -34,7 +43,7 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
|
|||||||
private formBuilder: FormBuilder
|
private formBuilder: FormBuilder
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
public ngOnInit() {
|
||||||
const { currencies, platforms } = this.dataService.fetchInfo();
|
const { currencies, platforms } = this.dataService.fetchInfo();
|
||||||
|
|
||||||
this.currencies = currencies;
|
this.currencies = currencies;
|
||||||
@ -47,8 +56,41 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
|
|||||||
currency: [this.data.account.currency, Validators.required],
|
currency: [this.data.account.currency, Validators.required],
|
||||||
isExcluded: [this.data.account.isExcluded],
|
isExcluded: [this.data.account.isExcluded],
|
||||||
name: [this.data.account.name, Validators.required],
|
name: [this.data.account.name, Validators.required],
|
||||||
platformId: [this.data.account.platformId]
|
platformId: [
|
||||||
|
this.platforms.find(({ id }) => {
|
||||||
|
return id === this.data.account.platformId;
|
||||||
|
}),
|
||||||
|
this.autocompleteObjectValidator()
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.filteredPlatforms = this.accountForm
|
||||||
|
.get('platformId')
|
||||||
|
.valueChanges.pipe(
|
||||||
|
startWith(''),
|
||||||
|
map((value) => {
|
||||||
|
const name = typeof value === 'string' ? value : value?.name;
|
||||||
|
return name ? this.filter(name as string) : this.platforms.slice();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public autoCompleteCheck() {
|
||||||
|
const inputValue = this.accountForm.controls['platformId'].value;
|
||||||
|
|
||||||
|
if (typeof inputValue === 'string') {
|
||||||
|
const matchingEntry = this.platforms.find(({ name }) => {
|
||||||
|
return name === inputValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchingEntry) {
|
||||||
|
this.accountForm.controls['platformId'].setValue(matchingEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public displayFn(platform: Platform) {
|
||||||
|
return platform?.name ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCancel() {
|
public onCancel() {
|
||||||
@ -63,7 +105,7 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
|
|||||||
id: this.accountForm.controls['accountId'].value,
|
id: this.accountForm.controls['accountId'].value,
|
||||||
isExcluded: this.accountForm.controls['isExcluded'].value,
|
isExcluded: this.accountForm.controls['isExcluded'].value,
|
||||||
name: this.accountForm.controls['name'].value,
|
name: this.accountForm.controls['name'].value,
|
||||||
platformId: this.accountForm.controls['platformId'].value
|
platformId: this.accountForm.controls['platformId'].value?.id ?? null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.data.account.id) {
|
if (this.data.account.id) {
|
||||||
@ -79,4 +121,22 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
|
|||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private autocompleteObjectValidator(): ValidatorFn {
|
||||||
|
return (control: AbstractControl) => {
|
||||||
|
if (control.value && typeof control.value === 'string') {
|
||||||
|
return { invalidAutocompleteObject: { value: control.value } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private filter(value: string): Platform[] {
|
||||||
|
const filterValue = value.toLowerCase();
|
||||||
|
|
||||||
|
return this.platforms.filter(({ name }) => {
|
||||||
|
return name.toLowerCase().startsWith(filterValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,29 @@
|
|||||||
<div [ngClass]="{ 'd-none': platforms?.length < 1 }">
|
<div [ngClass]="{ 'd-none': platforms?.length < 1 }">
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Platform</mat-label>
|
<mat-label i18n>Platform</mat-label>
|
||||||
<mat-select formControlName="platformId">
|
<input
|
||||||
<mat-option [value]="null"></mat-option>
|
formControlName="platformId"
|
||||||
<mat-option *ngFor="let platform of platforms" [value]="platform.id"
|
matInput
|
||||||
>{{ platform.name }}</mat-option
|
type="text"
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
(blur)="autoCompleteCheck()"
|
||||||
|
(keydown.enter)="$event.stopPropagation()"
|
||||||
|
/>
|
||||||
|
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let platformEntry of filteredPlatforms | async"
|
||||||
|
[value]="platformEntry"
|
||||||
>
|
>
|
||||||
</mat-select>
|
<span class="d-flex">
|
||||||
|
<gf-symbol-icon
|
||||||
|
class="mr-1"
|
||||||
|
[tooltip]="platformEntry.name"
|
||||||
|
[url]="platformEntry.url"
|
||||||
|
></gf-symbol-icon>
|
||||||
|
<span>{{ platformEntry.name }}</span>
|
||||||
|
</span>
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -7,6 +7,8 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
||||||
|
|
||||||
import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.component';
|
import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.component';
|
||||||
|
|
||||||
@ -15,6 +17,8 @@ import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.c
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
GfSymbolIconModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { SubscriptionOffer } from '@ghostfolio/common/types';
|
import { SubscriptionOffer } from '@ghostfolio/common/types';
|
||||||
import { SymbolProfile, Tag } from '@prisma/client';
|
import { Platform, SymbolProfile, Tag } from '@prisma/client';
|
||||||
|
|
||||||
import { Statistics } from './statistics.interface';
|
import { Statistics } from './statistics.interface';
|
||||||
import { Subscription } from './subscription.interface';
|
import { Subscription } from './subscription.interface';
|
||||||
@ -13,7 +13,7 @@ export interface InfoItem {
|
|||||||
fearAndGreedDataSource?: string;
|
fearAndGreedDataSource?: string;
|
||||||
globalPermissions: string[];
|
globalPermissions: string[];
|
||||||
isReadOnlyMode?: boolean;
|
isReadOnlyMode?: boolean;
|
||||||
platforms: { id: string; name: string }[];
|
platforms: Platform[];
|
||||||
statistics: Statistics;
|
statistics: Statistics;
|
||||||
stripePublicKey?: string;
|
stripePublicKey?: string;
|
||||||
subscriptions: { [offer in SubscriptionOffer]: Subscription };
|
subscriptions: { [offer in SubscriptionOffer]: Subscription };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user