mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
IMP-81: New select autocomple for terminals, party and others (#256)
This commit is contained in:
parent
66e16e0af1
commit
eba2e51541
25
package-lock.json
generated
25
package-lock.json
generated
@ -29,7 +29,7 @@
|
||||
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
|
||||
"@vality/fistful-proto": "2.0.0",
|
||||
"@vality/magista-proto": "2.0.2-e46bba2.0",
|
||||
"@vality/ng-core": "16.2.1-pr-33-0ae5287.0",
|
||||
"@vality/ng-core": "16.2.1-pr-33-0e1ce8c.0",
|
||||
"@vality/payout-manager-proto": "2.0.1-b079679.0",
|
||||
"@vality/repairer-proto": "2.0.1-8f7973d.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
@ -44,7 +44,6 @@
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"monaco-editor": "0.21.2",
|
||||
"ngx-mat-select-search": "7.0.2",
|
||||
"papaparse": "5.4.1",
|
||||
"rxjs": "7.8.1",
|
||||
"short-uuid": "4.2.2",
|
||||
@ -6002,9 +6001,9 @@
|
||||
"integrity": "sha512-2cPVToAJRdt1CFQ6G/C6ngw6hp94Jp7WFtPtNvtlcCSXRYRnppoi3KpK14tBH9SlLeqrpSy0IrIsCnAkj7tFfg=="
|
||||
},
|
||||
"node_modules/@vality/ng-core": {
|
||||
"version": "16.2.1-pr-33-0ae5287.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-33-0ae5287.0.tgz",
|
||||
"integrity": "sha512-utA80+v58QMxzgNcLZ1hFRAO5RGcCyDW6/TJl1FZmDhja/P1FInZVWZIonaZJI/a+yf+YsSPboqM+QeQGZ595g==",
|
||||
"version": "16.2.1-pr-33-0e1ce8c.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-33-0e1ce8c.0.tgz",
|
||||
"integrity": "sha512-nLPX5MtNjyBCeaqIngXjcPcp1tpD9GiLCOoCL0YukJT7wC9MQQg4npQl6SKGRR4rzGOotse71Fmk1EzVJEXP3A==",
|
||||
"dependencies": {
|
||||
"@ng-matero/extensions": "^16.0.0",
|
||||
"@s-libs/js-core": "^16.0.0",
|
||||
@ -16754,22 +16753,6 @@
|
||||
"@angular/core": ">=16.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-mat-select-search": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.2.tgz",
|
||||
"integrity": "sha512-69vy/mWh7pnnR6rw1BWxO8E4HyylUvvBlmhrtWqXIYqfIx3mfvWuNj0tE1v+ERPg8a4vViFpfilRUUvvbrv1cA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/material": "^15.0.0 || ^16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-mat-select-search/node_modules/tslib": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
|
||||
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
|
||||
},
|
||||
"node_modules/nice-napi": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||
|
@ -37,7 +37,7 @@
|
||||
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
|
||||
"@vality/fistful-proto": "2.0.0",
|
||||
"@vality/magista-proto": "2.0.2-e46bba2.0",
|
||||
"@vality/ng-core": "16.2.1-pr-33-0ae5287.0",
|
||||
"@vality/ng-core": "16.2.1-pr-33-0e1ce8c.0",
|
||||
"@vality/payout-manager-proto": "2.0.1-b079679.0",
|
||||
"@vality/repairer-proto": "2.0.1-8f7973d.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
@ -52,7 +52,6 @@
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"monaco-editor": "0.21.2",
|
||||
"ngx-mat-select-search": "7.0.2",
|
||||
"papaparse": "5.4.1",
|
||||
"rxjs": "7.8.1",
|
||||
"short-uuid": "4.2.2",
|
||||
|
@ -95,17 +95,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="form.value.terminalType === terminalType.Existent" fxLayout>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>Terminal</mat-label>
|
||||
<mat-select formControlName="existentTerminalID" required>
|
||||
<mat-option
|
||||
*ngFor="let terminal of terminals$ | async"
|
||||
[value]="terminal.ref.id"
|
||||
>
|
||||
{{ terminal.data.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<cc-domain-object-field
|
||||
formControlName="existentTerminalID"
|
||||
fxFlex
|
||||
name="terminal"
|
||||
required
|
||||
></cc-domain-object-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,8 +5,6 @@ import { domain } from '@vality/domain-proto';
|
||||
import { Predicate } from '@vality/domain-proto/domain';
|
||||
import { DialogSuperclass } from '@vality/ng-core';
|
||||
|
||||
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
|
||||
|
||||
import { AddRoutingRuleDialogService, TerminalType } from './add-routing-rule-dialog.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@ -25,12 +23,10 @@ export class AddRoutingRuleDialogComponent extends DialogSuperclass<
|
||||
|
||||
terminalType = TerminalType;
|
||||
riskScore = domain.RiskScore;
|
||||
terminals$ = this.domainStoreService.getObjects('terminal');
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private addShopRoutingRuleDialogService: AddRoutingRuleDialogService,
|
||||
private domainStoreService: DomainStoreService,
|
||||
private fb: FormBuilder,
|
||||
) {
|
||||
super(injector);
|
||||
|
@ -15,6 +15,8 @@ import { DialogModule } from '@vality/ng-core';
|
||||
|
||||
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
|
||||
|
||||
import { DomainObjectFieldComponent } from '../../../../shared';
|
||||
|
||||
import { AddRoutingRuleDialogComponent } from './add-routing-rule-dialog.component';
|
||||
import { ExpanderComponent } from './expander';
|
||||
import { PredicateComponent } from './predicate';
|
||||
@ -35,6 +37,7 @@ import { PredicateComponent } from './predicate';
|
||||
MatAutocompleteModule,
|
||||
MetadataFormModule,
|
||||
DialogModule,
|
||||
DomainObjectFieldComponent,
|
||||
],
|
||||
declarations: [AddRoutingRuleDialogComponent, PredicateComponent, ExpanderComponent],
|
||||
exports: [AddRoutingRuleDialogComponent],
|
||||
|
@ -4,7 +4,7 @@ import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { Predicate } from '@vality/domain-proto/domain';
|
||||
import { DialogResponseStatus } from '@vality/ng-core';
|
||||
import { of } from 'rxjs';
|
||||
import { startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { startWith, take, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { RoutingRulesService } from '../../services/routing-rules';
|
||||
|
||||
@ -22,7 +22,7 @@ export class AddRoutingRuleDialogService {
|
||||
weight: '',
|
||||
priority: 1000,
|
||||
terminalType: [null, Validators.required],
|
||||
existentTerminalID: ['', Validators.required],
|
||||
existentTerminalID: [null, Validators.required],
|
||||
newTerminal: this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
|
@ -62,7 +62,7 @@
|
||||
></cc-pretty-json>
|
||||
</div>
|
||||
</div>
|
||||
<mat-action-row flexLayout fxLayoutAlign="start">
|
||||
<mat-action-row fxLayoutAlign="start">
|
||||
<button color="warn" mat-button (click)="removeShopRule(idx)">Remove</button>
|
||||
</mat-action-row>
|
||||
</mat-expansion-panel>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<cc-select-search-field
|
||||
<v-select-field
|
||||
[formControl]="control"
|
||||
[label]="name | titlecase"
|
||||
[options]="options$ | async"
|
||||
[progress]="isLoading$ | async"
|
||||
></cc-select-search-field>
|
||||
></v-select-field>
|
||||
|
@ -1,8 +1,14 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { DomainObject } from '@vality/domain-proto/internal/domain';
|
||||
import { ComponentChanges } from '@vality/ng-core';
|
||||
import {
|
||||
ComponentChanges,
|
||||
FormControlSuperclass,
|
||||
createControlProviders,
|
||||
SelectFieldModule,
|
||||
Option,
|
||||
} from '@vality/ng-core';
|
||||
import { defer, switchMap, ReplaySubject } from 'rxjs';
|
||||
import { shareReplay, map } from 'rxjs/operators';
|
||||
|
||||
@ -12,24 +18,22 @@ import {
|
||||
OtherDomainObjects,
|
||||
defaultDomainObjectToOption,
|
||||
} from '@cc/app/shared/services/domain-metadata-form-extensions/utils/domains-objects-to-options';
|
||||
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
|
||||
import { ValidatedFormControlSuperclass, provideValueAccessor } from '@cc/utils';
|
||||
|
||||
type DomainObjectID = unknown;
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'cc-domain-object-field',
|
||||
templateUrl: './domain-object-field.component.html',
|
||||
providers: [provideValueAccessor(() => DomainObjectFieldComponent)],
|
||||
imports: [CommonModule, SelectSearchFieldModule, ReactiveFormsModule],
|
||||
providers: createControlProviders(() => DomainObjectFieldComponent),
|
||||
imports: [CommonModule, ReactiveFormsModule, SelectFieldModule],
|
||||
})
|
||||
export class DomainObjectFieldComponent<T extends keyof DomainObject>
|
||||
extends ValidatedFormControlSuperclass<DomainObject[T]>
|
||||
extends FormControlSuperclass<DomainObjectID>
|
||||
implements OnChanges
|
||||
{
|
||||
@Input() name: T;
|
||||
|
||||
control = new FormControl<DomainObject[T]>(null);
|
||||
|
||||
options$ = defer(() => this.name$).pipe(
|
||||
switchMap((name) => this.domainStoreService.getObjects(name)),
|
||||
map((objs) => {
|
||||
@ -37,9 +41,13 @@ export class DomainObjectFieldComponent<T extends keyof DomainObject>
|
||||
this.name in DOMAIN_OBJECTS_TO_OPTIONS
|
||||
? DOMAIN_OBJECTS_TO_OPTIONS[this.name as keyof OtherDomainObjects]
|
||||
: defaultDomainObjectToOption;
|
||||
return objs
|
||||
.map(domainObjectToOption)
|
||||
.map((o) => ({ ...o, description: `#${String(o.value)}` }));
|
||||
return objs.map(domainObjectToOption).map(
|
||||
(o): Option<DomainObjectID> => ({
|
||||
label: o.label,
|
||||
value: o.value,
|
||||
description: String(o.value),
|
||||
}),
|
||||
);
|
||||
}),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
@ -1,9 +1,9 @@
|
||||
<cc-select-search-field
|
||||
<v-select-field
|
||||
[formControl]="control"
|
||||
[label]="label || 'Merchant'"
|
||||
[options]="options$ | async"
|
||||
[progress]="!!(progress$ | async)"
|
||||
[required]="required"
|
||||
isExternalSearch
|
||||
externalSearch
|
||||
(searchChange)="this.searchChange$.next($event)"
|
||||
></cc-select-search-field>
|
||||
></v-select-field>
|
||||
|
@ -1,24 +1,17 @@
|
||||
import { Component, Input, AfterViewInit } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { PartyID } from '@vality/domain-proto/domain';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
first,
|
||||
takeUntil,
|
||||
startWith,
|
||||
} from 'rxjs/operators';
|
||||
Option,
|
||||
NotifyLogService,
|
||||
FormControlSuperclass,
|
||||
createControlProviders,
|
||||
} from '@vality/ng-core';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject } from 'rxjs';
|
||||
import { catchError, debounceTime, map, switchMap, tap, startWith } from 'rxjs/operators';
|
||||
|
||||
import { DeanonimusService } from '@cc/app/api/deanonimus';
|
||||
import { Option } from '@cc/components/select-search-field';
|
||||
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
|
||||
import { progressTo } from '@cc/utils/operators';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
@ -27,7 +20,7 @@ import { progressTo } from '@cc/utils/operators';
|
||||
providers: createControlProviders(() => MerchantFieldComponent),
|
||||
})
|
||||
export class MerchantFieldComponent
|
||||
extends ValidatedFormControlSuperclass<PartyID>
|
||||
extends FormControlSuperclass<PartyID>
|
||||
implements AfterViewInit
|
||||
{
|
||||
@Input() label: string;
|
||||
@ -35,41 +28,45 @@ export class MerchantFieldComponent
|
||||
|
||||
options$ = new ReplaySubject<Option<PartyID>[]>(1);
|
||||
searchChange$ = new Subject<string>();
|
||||
progress$ = new BehaviorSubject(0);
|
||||
progress$ = new BehaviorSubject(false);
|
||||
|
||||
constructor(
|
||||
private deanonimusService: DeanonimusService,
|
||||
private snackBar: MatSnackBar,
|
||||
private log: NotifyLogService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
merge(
|
||||
this.searchChange$,
|
||||
this.control.valueChanges.pipe(
|
||||
startWith(this.control.value),
|
||||
filter(Boolean),
|
||||
first(),
|
||||
takeUntil(this.searchChange$),
|
||||
),
|
||||
)
|
||||
this.searchChange$
|
||||
.pipe(
|
||||
filter(Boolean),
|
||||
startWith(this.control.value),
|
||||
tap(() => {
|
||||
this.options$.next([]);
|
||||
this.progress$.next(true);
|
||||
}),
|
||||
debounceTime(600),
|
||||
switchMap((str) => this.searchOptions(str)),
|
||||
switchMap((term) => this.searchOptions(term)),
|
||||
untilDestroyed(this),
|
||||
)
|
||||
.subscribe((options) => this.options$.next(options));
|
||||
.subscribe((options) => {
|
||||
this.options$.next(options);
|
||||
this.progress$.next(false);
|
||||
});
|
||||
}
|
||||
|
||||
private searchOptions(str: string): Observable<Option<PartyID>[]> {
|
||||
if (!str) return of([]);
|
||||
return this.deanonimusService.searchParty(str).pipe(
|
||||
map((parties) => parties.map((p) => ({ label: p.party.email, value: p.party.id }))),
|
||||
progressTo(this.progress$),
|
||||
map((parties) =>
|
||||
parties.map((p) => ({
|
||||
label: p.party.email,
|
||||
value: p.party.id,
|
||||
description: p.party.id,
|
||||
})),
|
||||
),
|
||||
catchError((err) => {
|
||||
this.snackBar.open('Search error', 'OK', { duration: 2000 });
|
||||
console.error(err);
|
||||
this.log.error(err, 'Search error');
|
||||
return of([]);
|
||||
}),
|
||||
);
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
|
||||
import { SelectFieldModule } from '@vality/ng-core';
|
||||
|
||||
import { MerchantFieldComponent } from './merchant-field.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, ReactiveFormsModule, SelectSearchFieldModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, SelectFieldModule],
|
||||
declarations: [MerchantFieldComponent],
|
||||
exports: [MerchantFieldComponent],
|
||||
})
|
||||
|
@ -1,9 +1,7 @@
|
||||
<cc-select-search-field
|
||||
<v-select-field
|
||||
[disabled]="!(partyId$ | async) || !(shopId$ | async)"
|
||||
[formControl]="control"
|
||||
[label]="label || 'Payout Tool'"
|
||||
[options]="options$ | async"
|
||||
[required]="required"
|
||||
isExternalSearch
|
||||
(searchChange)="searchChange$.next($event)"
|
||||
></cc-select-search-field>
|
||||
></v-select-field>
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { PayoutTool } from '@vality/domain-proto/domain';
|
||||
import { PartyID, ShopID } from '@vality/domain-proto/payment_processing';
|
||||
import { createControlProviders, FormControlSuperclass, Option } from '@vality/ng-core';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
import { BehaviorSubject, combineLatest, defer, Observable, of, Subject, switchMap } from 'rxjs';
|
||||
import { map, pluck, shareReplay, startWith } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, defer, Observable, of, switchMap } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
import { Option } from '@cc/components/select-search-field';
|
||||
import { createControlProviders, ValidatedControlSuperclass } from '@cc/utils/forms';
|
||||
|
||||
import { handleError, NotificationErrorService } from '../../services/notification-error';
|
||||
|
||||
@ -17,11 +15,10 @@ import { handleError, NotificationErrorService } from '../../services/notificati
|
||||
@Component({
|
||||
selector: 'cc-payout-tool-field',
|
||||
templateUrl: 'payout-tool-field.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: createControlProviders(() => PayoutToolFieldComponent),
|
||||
})
|
||||
export class PayoutToolFieldComponent
|
||||
extends ValidatedControlSuperclass<PartyID>
|
||||
extends FormControlSuperclass<PayoutTool['id']>
|
||||
implements OnInit
|
||||
{
|
||||
@Input() label: string;
|
||||
@ -33,18 +30,12 @@ export class PayoutToolFieldComponent
|
||||
this.shopId$.next(shopId);
|
||||
}
|
||||
|
||||
control = new FormControl() as FormControl<PartyID>;
|
||||
|
||||
partyId$ = new BehaviorSubject<PartyID>(null);
|
||||
shopId$ = new BehaviorSubject<ShopID>(null);
|
||||
|
||||
searchChange$ = new Subject<string>();
|
||||
options$: Observable<Option<PayoutTool['id']>[]> = combineLatest([
|
||||
this.searchChange$.pipe(map((str) => str?.trim()?.toLowerCase())).pipe(startWith('')),
|
||||
defer(() => this.payoutTools$),
|
||||
]).pipe(
|
||||
map(([str, payoutTools]) => payoutTools.filter((t) => t.id.includes(str))),
|
||||
map((payoutTools) => payoutTools.map((t) => ({ label: t.id, value: t.id }))),
|
||||
options$: Observable<Option<PayoutTool['id']>[]> = defer(() => this.payoutTools$).pipe(
|
||||
map((payoutTools) =>
|
||||
payoutTools.map((t) => ({ label: t.id, value: t.id, description: t.id })),
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
@ -57,7 +48,7 @@ export class PayoutToolFieldComponent
|
||||
switchMap(({ contract_id }) =>
|
||||
this.partyManagementService.GetContract(partyId, contract_id),
|
||||
),
|
||||
pluck('payout_tools'),
|
||||
map((contract) => contract.payout_tools),
|
||||
)
|
||||
.pipe(
|
||||
handleError(
|
||||
|
@ -4,8 +4,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
|
||||
import { SelectFieldModule } from '@vality/ng-core';
|
||||
|
||||
import { PayoutToolFieldComponent } from './payout-tool-field.component';
|
||||
|
||||
@ -16,10 +15,9 @@ import { PayoutToolFieldComponent } from './payout-tool-field.component';
|
||||
MatAutocompleteModule,
|
||||
MatInputModule,
|
||||
CommonModule,
|
||||
SelectSearchFieldModule,
|
||||
SelectFieldModule,
|
||||
],
|
||||
declarations: [PayoutToolFieldComponent],
|
||||
exports: [PayoutToolFieldComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class PayoutToolFieldModule {}
|
||||
|
@ -12,11 +12,13 @@ import { handleError, NotificationErrorService } from './notification-error';
|
||||
export class FetchPartiesService {
|
||||
parties$: Observable<Party[]> = defer(() => this.searchParties$).pipe(
|
||||
switchMap((text) =>
|
||||
this.deanonimusService.searchParty(text).pipe(
|
||||
map((hits) => hits.map((hit) => hit.party)),
|
||||
handleError(this.notificationErrorService.error, null, of<Party[]>([])),
|
||||
progressTo(this.progress$),
|
||||
),
|
||||
text
|
||||
? this.deanonimusService.searchParty(text).pipe(
|
||||
map((hits) => hits.map((hit) => hit.party)),
|
||||
handleError(this.notificationErrorService.error, null, of<Party[]>([])),
|
||||
progressTo(this.progress$),
|
||||
)
|
||||
: of([]),
|
||||
),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
@ -1,4 +0,0 @@
|
||||
export * from './select-search-field.component';
|
||||
export * from './select-search-field.module';
|
||||
export * from './types';
|
||||
export * from './tokens';
|
@ -1,59 +0,0 @@
|
||||
<mat-form-field>
|
||||
<mat-label>{{ label }}</mat-label>
|
||||
<mat-hint *ngIf="hint">{{ hint }}</mat-hint>
|
||||
<mat-select
|
||||
[disabled]="disabled"
|
||||
[required]="required"
|
||||
[value]="selected$ | async"
|
||||
(selectionChange)="select($event.value)"
|
||||
>
|
||||
<mat-option>
|
||||
<ngx-mat-select-search
|
||||
[formControl]="selectSearchControl"
|
||||
noEntriesFoundLabel="No entries found"
|
||||
placeholderLabel="Search..."
|
||||
>
|
||||
<mat-icon *ngIf="svgIcon" [svgIcon]="svgIcon" ngxMatSelectSearchClear></mat-icon>
|
||||
</ngx-mat-select-search>
|
||||
</mat-option>
|
||||
<ng-container *ngIf="!progress; else progressBar">
|
||||
<mat-option
|
||||
*ngFor="let option of isExternalSearch ? options : (filteredOptions$ | async)"
|
||||
[value]="option.value"
|
||||
>
|
||||
<div class="label">{{ option.label }}</div>
|
||||
<div *ngIf="option.description" class="mat-caption mat-secondary-text">
|
||||
{{ option.description }}
|
||||
</div>
|
||||
</mat-option>
|
||||
<mat-option
|
||||
*ngIf="
|
||||
!(isExternalSearch ? options : (filteredOptions$ | async))?.length &&
|
||||
!selectSearchControl.value &&
|
||||
cachedOption?.value &&
|
||||
(selected$ | async) === cachedOption?.value
|
||||
"
|
||||
[value]="cachedOption.value"
|
||||
>
|
||||
<div class="label">{{ cachedOption.label }}</div>
|
||||
<div *ngIf="cachedOption.description" class="mat-caption mat-secondary-text">
|
||||
{{ cachedOption.description }}
|
||||
</div>
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
<ng-template #progressBar>
|
||||
<mat-option disabled fxLayout="column" fxLayoutAlign="center none">
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</mat-option>
|
||||
</ng-template>
|
||||
</mat-select>
|
||||
<button *ngIf="selected$ | async" mat-icon-button matSuffix (click)="clear($event)">
|
||||
<mat-icon *ngIf="!options?.length; else clearIcon">sync</mat-icon>
|
||||
<ng-template #clearIcon>
|
||||
<ng-container *ngIf="svgIcon; else defaultIcon">
|
||||
<mat-icon [svgIcon]="svgIcon"></mat-icon>
|
||||
</ng-container>
|
||||
<ng-template #defaultIcon><mat-icon>close</mat-icon></ng-template>
|
||||
</ng-template>
|
||||
</button>
|
||||
</mat-form-field>
|
@ -1,9 +0,0 @@
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
Inject,
|
||||
Injector,
|
||||
Input,
|
||||
OnChanges,
|
||||
Optional,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { FormComponentSuperclass } from '@s-libs/ng-core';
|
||||
import { ComponentChanges } from '@vality/ng-core';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
import { BehaviorSubject, combineLatest, defer, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
|
||||
import { getFormValueChanges, provideValueAccessor } from '@cc/utils/forms';
|
||||
|
||||
import { SelectSearchFieldOptions, SELECT_SEARCH_FIELD_OPTIONS } from './tokens';
|
||||
import { Option } from './types';
|
||||
import { filterOptions } from './utils';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'cc-select-search-field',
|
||||
templateUrl: 'select-search-field.component.html',
|
||||
styleUrls: ['select-search-field.component.scss'],
|
||||
providers: [provideValueAccessor(() => SelectSearchFieldComponent)],
|
||||
})
|
||||
export class SelectSearchFieldComponent<Value>
|
||||
extends FormComponentSuperclass<Value>
|
||||
implements OnInit, OnChanges
|
||||
{
|
||||
@Input() label: string;
|
||||
@Input() @coerceBoolean required = false;
|
||||
@Input() @coerceBoolean disabled = false;
|
||||
@Input() options: Option<Value>[];
|
||||
@Input() svgIcon: string | null = this.fieldOptions?.svgIcon;
|
||||
@Input() hint: string | null;
|
||||
@Input() @coerceBoolean isExternalSearch: boolean | '' = false;
|
||||
@Input() @coerceBoolean progress: boolean = false;
|
||||
|
||||
@Output() searchChange = new EventEmitter<string>();
|
||||
|
||||
selectSearchControl = new FormControl<string>('');
|
||||
filteredOptions$: Observable<Option<Value>[]> = combineLatest([
|
||||
getFormValueChanges(this.selectSearchControl),
|
||||
defer(() => this.options$),
|
||||
]).pipe(map(([value, options]) => filterOptions(options, value)));
|
||||
selected$ = new BehaviorSubject<Value>(null);
|
||||
cachedOption: Option<Value> = null;
|
||||
|
||||
private options$ = new BehaviorSubject<Option<Value>[]>([]);
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
@Optional()
|
||||
@Inject(SELECT_SEARCH_FIELD_OPTIONS)
|
||||
private fieldOptions: SelectSearchFieldOptions,
|
||||
) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectSearchControl.valueChanges
|
||||
.pipe(distinctUntilChanged(), untilDestroyed(this))
|
||||
.subscribe((v) => this.searchChange.emit(v));
|
||||
}
|
||||
|
||||
handleIncomingValue(value: Value): void {
|
||||
this.select(value);
|
||||
}
|
||||
|
||||
ngOnChanges({ options }: ComponentChanges<SelectSearchFieldComponent<Value>>): void {
|
||||
if (options) {
|
||||
this.options$.next(options.currentValue);
|
||||
this.cacheOption();
|
||||
}
|
||||
}
|
||||
|
||||
clear(event: MouseEvent): void {
|
||||
this.select(null);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
select(value: Value): void {
|
||||
this.selected$.next(value);
|
||||
this.emitOutgoingValue(value);
|
||||
this.cacheOption();
|
||||
}
|
||||
|
||||
private cacheOption(): void {
|
||||
const option = this.options?.find((o) => o.value === this.selected$.value);
|
||||
if (option) this.cachedOption = option;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
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 { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { NgxMatSelectSearchModule } from 'ngx-mat-select-search';
|
||||
|
||||
import { SelectSearchFieldComponent } from './select-search-field.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatSelectModule,
|
||||
NgxMatSelectSearchModule,
|
||||
MatProgressBarModule,
|
||||
FlexModule,
|
||||
],
|
||||
declarations: [SelectSearchFieldComponent],
|
||||
exports: [SelectSearchFieldComponent],
|
||||
})
|
||||
export class SelectSearchFieldModule {}
|
@ -1,8 +0,0 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export interface SelectSearchFieldOptions {
|
||||
svgIcon?: string;
|
||||
}
|
||||
|
||||
export const SELECT_SEARCH_FIELD_OPTIONS: InjectionToken<SelectSearchFieldOptions> =
|
||||
new InjectionToken('SelectSearchFieldOptions');
|
@ -1 +0,0 @@
|
||||
export * from './option';
|
@ -1,5 +0,0 @@
|
||||
export interface Option<T> {
|
||||
value: T;
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { Option } from '../types';
|
||||
|
||||
const filterPredicate =
|
||||
<T>(searchStr: string) =>
|
||||
(option: Option<T>) =>
|
||||
option.label.toLowerCase().includes(searchStr) ||
|
||||
(option.description && option.description.toLowerCase().includes(searchStr)) ||
|
||||
(typeof option.value !== 'object' &&
|
||||
String(option.value).toLowerCase().includes(searchStr));
|
||||
|
||||
export const filterOptions = <T>(options: Option<T>[], controlValue: unknown): Option<T>[] =>
|
||||
controlValue && typeof controlValue === 'string'
|
||||
? options?.filter(filterPredicate(controlValue.toLowerCase()))
|
||||
: options;
|
@ -1 +0,0 @@
|
||||
export * from './filter-options';
|
@ -3,6 +3,10 @@ import { Provider, Type } from '@angular/core';
|
||||
import { provideValidators } from './provide-validators';
|
||||
import { provideValueAccessor } from './provide-value-accessor';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param component
|
||||
*/
|
||||
export const createControlProviders = (component: () => Type<unknown>): Provider[] => [
|
||||
provideValueAccessor(component),
|
||||
provideValidators(component),
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { Provider, forwardRef, Type } from '@angular/core';
|
||||
import { NG_VALIDATORS } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param component
|
||||
*/
|
||||
export const provideValidators = (component: () => Type<unknown>): Provider => ({
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(component),
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { Provider, forwardRef, Type } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param component
|
||||
*/
|
||||
export const provideValueAccessor = (component: () => Type<unknown>): Provider => ({
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(component),
|
||||
|
@ -3,6 +3,7 @@ import { AbstractControl, ValidationErrors } from '@angular/forms';
|
||||
import { hasControls } from '../../has-controls';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* FormGroup/FormArray don't return internal control errors,
|
||||
* so you need to get internal errors manually
|
||||
*/
|
||||
|
@ -7,6 +7,9 @@ import { getValue } from '../get-value';
|
||||
|
||||
import { getErrorsTree } from './utils/get-errors-tree';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Directive()
|
||||
export abstract class ValidatedControlSuperclass<OuterType, InnerType = OuterType>
|
||||
extends WrappedControlSuperclass<OuterType, InnerType>
|
||||
|
@ -3,6 +3,9 @@ import { ValidationErrors, FormControl } from '@angular/forms';
|
||||
import { WrappedControlSuperclass } from '@s-libs/ng-core';
|
||||
import { EMPTY, Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Directive()
|
||||
export class ValidatedFormControlSuperclass<
|
||||
OuterType,
|
||||
|
Loading…
Reference in New Issue
Block a user