FRONTENT-506. Wallet autocomplete (#431)

* wallet autocomplete

* fix

* fix with bug

* dsh-autocomplete-input

* fix

* lint fix

* package lock fix

* fixes

* more fixes

* more more fixes

* fixes

* more fixes

* remove validator

* fixes
This commit is contained in:
Denis Ezhov 2021-04-29 14:26:49 +03:00 committed by GitHub
parent f57f68e51b
commit 8714939d3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 206 additions and 43 deletions

View File

@ -5,10 +5,7 @@
<mat-label>{{ t('depositID') }}</mat-label>
<input aria-label="depositID" matInput type="text" formControlName="depositID" />
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ t('walletID') }}</mat-label>
<input aria-label="walletID" matInput type="text" formControlName="walletID" />
</mat-form-field>
<dsh-wallet-autocomplete-field fxFlex [control]="form?.controls?.walletID"></dsh-wallet-autocomplete-field>
</div>
<div fxLayout="row" fxLayoutAlign="space-between" fxLayoutGap="24px">
<mat-form-field fxFlex>

View File

@ -6,10 +6,20 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TranslocoModule } from '@ngneat/transloco';
import { WalletAutocompleteFieldModule } from '@dsh/app/shared/components/inputs/wallet-autocomplete-field';
import { MainFiltersComponent } from './main-filters.component';
@NgModule({
imports: [CommonModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, FlexLayoutModule, TranslocoModule],
imports: [
CommonModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
FlexLayoutModule,
TranslocoModule,
WalletAutocompleteFieldModule,
],
declarations: [MainFiltersComponent],
exports: [MainFiltersComponent],
})

View File

@ -28,17 +28,10 @@
t('createWebhook.eventTypes.destination')
}}</mat-radio-button>
</mat-radio-group>
<mat-form-field *ngIf="(activeTopic$ | async) === 'WithdrawalsTopic'" fxFlex>
<mat-label>{{ t('createWebhook.wallet') }}</mat-label>
<mat-select formControlName="walletID">
<mat-option *transloco="let c">
{{ c('any') }}
</mat-option>
<mat-option *ngFor="let wallet of wallets$ | async" [value]="wallet.id">
{{ wallet.name }}
</mat-option>
</mat-select>
</mat-form-field>
<dsh-wallet-autocomplete-field
*ngIf="(activeTopic$ | async) === 'WithdrawalsTopic'"
[control]="form?.controls?.walletID"
></dsh-wallet-autocomplete-field>
<mat-divider></mat-divider>
<div fxLayout="column" fxLayoutGap="8px">
<div class="dsh-title">{{ t('createWebhook.events') }}</div>

View File

@ -1,38 +1,32 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { BehaviorSubject } from 'rxjs';
import { WebhookScope } from '@dsh/api-codegen/wallet-api/swagger-codegen';
import { IdentityService } from '@dsh/api/identity';
import { WalletService } from '@dsh/api/wallet';
import { oneMustBeSelected } from '@dsh/components/form-controls';
import { getEventsByTopic } from '../get-events-by-topic';
import TopicEnum = WebhookScope.TopicEnum;
@UntilDestroy()
@Component({
selector: 'dsh-create-webhook-form',
templateUrl: 'create-webhook-form.component.html',
})
export class CreateWebhookFormComponent implements OnInit {
@Input()
form: FormGroup;
wallets$ = this.walletService.wallets$;
@Input() form: FormGroup;
identities$ = this.identityService.identities$;
activeTopic$ = new BehaviorSubject<TopicEnum>('WithdrawalsTopic');
constructor(
private walletService: WalletService,
private identityService: IdentityService,
private fb: FormBuilder
) {}
constructor(private identityService: IdentityService, private fb: FormBuilder) {}
ngOnInit() {
this.activeTopic$.subscribe((activeTopic) => {
ngOnInit(): void {
this.activeTopic$.pipe(untilDestroyed(this)).subscribe((activeTopic) => {
if (activeTopic === 'WithdrawalsTopic') {
this.form.addControl('walletID', this.fb.control(''));
} else {
@ -54,11 +48,11 @@ export class CreateWebhookFormComponent implements OnInit {
});
}
changeActiveTopic(topic: TopicEnum) {
changeActiveTopic(topic: TopicEnum): void {
this.activeTopic$.next(topic);
}
get eventTypes() {
get eventTypes(): FormArray {
return this.form.get('eventTypes') as FormArray;
}
}

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
@ -12,6 +13,7 @@ import { MatSelectModule } from '@angular/material/select';
import { TranslocoModule } from '@ngneat/transloco';
import { BaseDialogModule } from '@dsh/app/shared/components/dialog/base-dialog';
import { WalletAutocompleteFieldModule } from '@dsh/app/shared/components/inputs/wallet-autocomplete-field';
import { ButtonModule } from '@dsh/components/buttons';
import { CreateWebhookDialogComponent } from './create-webhook-dialog.component';
@ -33,6 +35,8 @@ import { CreateWebhookService } from './create-webhook.service';
MatCheckboxModule,
MatInputModule,
BaseDialogModule,
MatAutocompleteModule,
WalletAutocompleteFieldModule,
],
declarations: [CreateWebhookDialogComponent, CreateWebhookFormComponent],
providers: [CreateWebhookService],

View File

@ -31,10 +31,10 @@
<mat-label>{{ t('withdrawalID') }}</mat-label>
<input matInput type="text" formControlName="withdrawalID" />
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ t('walletID') }}</mat-label>
<input matInput type="text" formControlName="walletID" />
</mat-form-field>
<dsh-wallet-autocomplete-field
fxFlex
[control]="form?.controls?.walletID"
></dsh-wallet-autocomplete-field>
</div>
<div fxLayout fxLayout.sm="column" fxLayoutGap="20px">
<mat-form-field fxFlex>

View File

@ -11,11 +11,14 @@ import { SearchFormService } from './search-form.service';
})
export class SearchFormComponent {
form = this.searchFormService.form;
reset = this.searchFormService.reset;
withdrawalStatuses: WithdrawalStatus.StatusEnum[] = ['Pending', 'Succeeded', 'Failed'];
expanded = false;
constructor(private searchFormService: SearchFormService) {}
reset(): void {
this.searchFormService.reset();
}
}

View File

@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import moment from 'moment';
import { map, startWith } from 'rxjs/operators';
import { removeEmptyProperties } from '../../../payment-section/operations/operators';
import { WithdrawalsService } from '../withdrawals.service';
import { FormParams } from './form-params';
import { toFormValue } from './to-form-value';
@ -38,24 +39,26 @@ export class SearchFormService {
this.init();
}
search(value) {
search(value): void {
this.depositsService.search(toSearchParams(value));
}
reset() {
reset(): void {
this.form.setValue(SearchFormService.defaultParams);
}
private init() {
private init(): void {
this.syncQueryParams();
this.form.valueChanges.pipe(startWith(this.form.value)).subscribe((v) => this.search(v));
this.form.valueChanges.pipe(startWith(this.form.value), removeEmptyProperties).subscribe((v) => this.search(v));
}
private syncQueryParams() {
private syncQueryParams(): void {
const formValue = toFormValue(this.route.snapshot.queryParams, SearchFormService.defaultParams);
this.form.setValue(formValue);
this.form.valueChanges.pipe(startWith(formValue), map(toQueryParams)).subscribe((queryParams) => {
this.router.navigate([location.pathname], { queryParams });
});
this.form.valueChanges
.pipe(startWith(formValue), removeEmptyProperties, map(toQueryParams))
.subscribe((queryParams) => {
this.router.navigate([location.pathname], { queryParams });
});
}
}

View File

@ -8,6 +8,7 @@ import { MatSelectModule } from '@angular/material/select';
import { TranslocoModule } from '@ngneat/transloco';
import { WithdrawalsModule as WithdrawalsApiModule } from '@dsh/api/withdrawals';
import { WalletAutocompleteFieldModule } from '@dsh/app/shared/components/inputs/wallet-autocomplete-field';
import { ToMajorModule } from '@dsh/app/shared/pipes';
import { ButtonModule } from '@dsh/components/buttons';
import { RangeDatepickerModule } from '@dsh/components/form-controls';
@ -50,6 +51,7 @@ import { WithdrawalsComponent } from './withdrawals.component';
InvoiceDetailsModule,
WalletSectionModule,
WithdrawalInfoModule,
WalletAutocompleteFieldModule,
],
declarations: [WithdrawalsComponent, SearchFormComponent, WithdrawalListComponent],
})

View File

@ -0,0 +1,15 @@
<mat-form-field fxFlex>
<mat-label>{{ title }}</mat-label>
<input *ngIf="!options; else autocomplete" type="text" matInput [formControl]="control" />
<ng-template #autocomplete>
<input [required]="required" type="text" matInput [formControl]="control" [matAutocomplete]="auto" />
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayLabel">
<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option.value">
{{ option.label }}
</mat-option>
</mat-autocomplete>
</ng-template>
<button *ngIf="control?.value" dsh-icon-button matSuffix (click)="clearValue()">
<mat-icon svgIcon="cross"></mat-icon>
</button>
</mat-form-field>

View File

@ -0,0 +1,50 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormControl } from '@ngneat/reactive-forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { coerceBoolean } from '@dsh/utils';
import { Option } from './types';
@Component({
selector: 'dsh-autocomplete-field',
templateUrl: 'autocomplete-field.component.html',
})
export class AutocompleteFieldComponent implements OnInit {
@Input() control: FormControl;
@Input() title: string;
@Input() options: Option[];
filteredOptions$: Observable<Option[]>;
@Input() @coerceBoolean required = false;
constructor() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.displayLabel = this.displayLabel.bind(this);
}
ngOnInit(): void {
this.filteredOptions$ = this.control.valueChanges.pipe(
startWith(this.control.value),
map((searchValue) => (searchValue ? this.filterOptions(searchValue) : this.options))
);
}
displayLabel(selectedValue: unknown): string {
const label = this.options?.find((option) => option.value === selectedValue)?.label;
return label ? label : '';
}
clearValue(): void {
this.control.reset();
}
private filterOptions(searchValue: string): Option[] {
const filterValue = searchValue.toLowerCase();
return this.options?.filter((option) => option.label.toLowerCase().includes(filterValue));
}
}

View File

@ -0,0 +1,30 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { ButtonModule } from '@dsh/components/buttons';
import { AutocompleteFieldComponent } from './autocomplete-field.component';
@NgModule({
imports: [
CommonModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
FlexLayoutModule,
MatAutocompleteModule,
MatButtonModule,
MatIconModule,
ButtonModule,
],
declarations: [AutocompleteFieldComponent],
exports: [AutocompleteFieldComponent],
})
export class AutocompleteFieldModule {}

View File

@ -0,0 +1,3 @@
export * from './autocomplete-field.module';
export * from './autocomplete-field.component';
export * from './types';

View File

@ -0,0 +1 @@
export * from './option';

View File

@ -0,0 +1,4 @@
export interface Option<T = any> {
value: T;
label: string;
}

View File

@ -0,0 +1,2 @@
export * from './wallet-autocomplete-field.module';
export * from './wallet-autocomplete-field.component';

View File

@ -0,0 +1,6 @@
import { Wallet } from '@dsh/api-codegen/wallet-api';
import { Option } from '@dsh/app/shared/components/inputs/autocomplete-field';
export function walletsToOptions(wallets: Wallet[]): Option[] {
return wallets.map((wallet) => ({ label: wallet.name, value: wallet.id }));
}

View File

@ -0,0 +1,6 @@
<dsh-autocomplete-field
*transloco="let t; scope: 'wallet-autocomplete-field'; read: 'walletAutocompleteField'"
[title]="t('title')"
[control]="control"
[options]="options$ | async"
></dsh-autocomplete-field>

View File

@ -0,0 +1,23 @@
import { Component, Input } from '@angular/core';
import { FormControl } from '@ngneat/reactive-forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { map, shareReplay } from 'rxjs/operators';
import { WalletService } from '@dsh/api';
import { walletsToOptions } from '@dsh/app/shared/components/inputs/wallet-autocomplete-field/utils/wallets-to-options';
import { coerceBoolean } from '@dsh/utils';
@UntilDestroy()
@Component({
selector: 'dsh-wallet-autocomplete-field',
templateUrl: 'wallet-autocomplete-field.component.html',
})
export class WalletAutocompleteFieldComponent {
@Input() control: FormControl;
options$ = this.walletService.wallets$.pipe(map(walletsToOptions), untilDestroyed(this), shareReplay(1));
@Input() @coerceBoolean required = false;
constructor(private walletService: WalletService) {}
}

View File

@ -0,0 +1,14 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslocoModule } from '@ngneat/transloco';
import { AutocompleteFieldModule } from '@dsh/app/shared/components/inputs/autocomplete-field';
import { WalletAutocompleteFieldComponent } from './wallet-autocomplete-field.component';
@NgModule({
imports: [CommonModule, TranslocoModule, AutocompleteFieldModule],
declarations: [WalletAutocompleteFieldComponent],
exports: [WalletAutocompleteFieldComponent],
})
export class WalletAutocompleteFieldModule {}

View File

@ -0,0 +1,3 @@
{
"title": "Кошелек"
}