mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Add CustomerSearch Service
This commit is contained in:
@@ -1,35 +1,31 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
AfterContentInit,
|
||||
forwardRef,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AutocompleteDTO } from '@swagger/crm';
|
||||
import { UiSearchboxAutocompleteComponent } from '@ui/searchbox';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import { CustomerSearchService } from './customer-search.service';
|
||||
import { CustomerSearch } from './customer-search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-search',
|
||||
templateUrl: 'customer-search.component.html',
|
||||
styleUrls: ['customer-search.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [CustomerSearchService],
|
||||
providers: [
|
||||
{
|
||||
provide: CustomerSearch,
|
||||
useExisting: forwardRef(() => CustomerSearchComponent),
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CustomerSearchComponent {}
|
||||
export class CustomerSearchComponent extends CustomerSearch {
|
||||
constructor(
|
||||
protected customerSearch: CrmCustomerService,
|
||||
zone: NgZone,
|
||||
router: Router
|
||||
) {
|
||||
super(zone, router);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,85 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AutocompleteDTO } from '@swagger/crm';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
filter,
|
||||
map,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class CustomerSearchService {
|
||||
constructor() {}
|
||||
export abstract class CustomerSearch implements OnInit, OnDestroy {
|
||||
protected abstract customerSearch: CrmCustomerService;
|
||||
|
||||
private destroy$ = new Subject();
|
||||
|
||||
queryControl: FormControl;
|
||||
|
||||
autocompleteResult$: Observable<AutocompleteDTO[]>;
|
||||
|
||||
inputChange$ = new Subject<string>();
|
||||
|
||||
constructor(private zone: NgZone, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initQueryControl();
|
||||
this.initAutocomplete();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private initAutocomplete() {
|
||||
this.autocompleteResult$ = this.inputChange$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
filter(() => this.queryControl.valid),
|
||||
debounceTime(150),
|
||||
switchMap((queryString) =>
|
||||
this.customerSearch.complete(queryString).pipe(
|
||||
map((response) => response.result),
|
||||
catchError(() => {
|
||||
// TODO Dialog Service impl. fur Anzeige der Fehlermeldung
|
||||
return [];
|
||||
})
|
||||
)
|
||||
),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
private initQueryControl() {
|
||||
this.queryControl = new FormControl('', [Validators.required]);
|
||||
}
|
||||
|
||||
getQueryParams() {
|
||||
return {
|
||||
query: this.queryControl.value,
|
||||
};
|
||||
}
|
||||
|
||||
setQueryParams(queryParams: Params) {
|
||||
this.queryControl.setValue(queryParams.query || '', { emitEvent: false });
|
||||
}
|
||||
|
||||
parseQueryParams(activatedRoute: ActivatedRoute) {
|
||||
this.setQueryParams(activatedRoute.snapshot.queryParams);
|
||||
}
|
||||
|
||||
updateUrlQueryParams() {
|
||||
this.zone.run(() => {
|
||||
this.router.navigate([], {
|
||||
queryParams: this.getQueryParams(),
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-filter',
|
||||
templateUrl: 'search-filter.component.html',
|
||||
styleUrls: ['search-filter.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchFilterComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,20 @@
|
||||
</p>
|
||||
<ui-searchbox>
|
||||
<input
|
||||
[formControl]="queryControl"
|
||||
[formControl]="search.queryControl"
|
||||
type="text"
|
||||
uiSearchboxInput
|
||||
placeholder="Name, E-Mail, Kundennummer, ..."
|
||||
(inputChange)="inputChange$.next($event)"
|
||||
(inputChange)="search.inputChange$.next($event)"
|
||||
/>
|
||||
<ui-searchbox-warning>
|
||||
Keine Suchergebnisse
|
||||
</ui-searchbox-warning>
|
||||
<button
|
||||
*ngIf="queryControl?.value?.length"
|
||||
*ngIf="search.queryControl?.value?.length"
|
||||
type="reset"
|
||||
uiSearchboxClearButton
|
||||
(click)="queryControl.reset()"
|
||||
(click)="search.queryControl.reset()"
|
||||
>
|
||||
<ui-icon icon="close" size="22px"></ui-icon>
|
||||
</button>
|
||||
@@ -37,7 +37,7 @@
|
||||
<button
|
||||
uiSearchboxAutocompleteOption
|
||||
[value]="result?.query"
|
||||
*ngFor="let result of autocompleteResult$ | async"
|
||||
*ngFor="let result of search.autocompleteResult$ | async"
|
||||
>
|
||||
{{ result?.display }}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AutocompleteDTO } from '@swagger/crm';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSearchboxModule } from '@ui/searchbox';
|
||||
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
||||
import { skip, take } from 'rxjs/operators';
|
||||
import { CustomerSearch } from '../customer-search.service';
|
||||
import { CustomerSearchMainComponent } from './search-main.component';
|
||||
|
||||
fdescribe('CustomerSearchMainComponent', () => {
|
||||
let fixture: ComponentFixture<CustomerSearchMainComponent>;
|
||||
let component: CustomerSearchMainComponent;
|
||||
|
||||
let searchService: jasmine.SpyObj<CustomerSearch>;
|
||||
|
||||
const mockActivatedRoute = ({
|
||||
snapshot: { queryParams: { query: 'test' } },
|
||||
} as unknown) as ActivatedRoute;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CustomerSearchMainComponent],
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
ReactiveFormsModule,
|
||||
UiSearchboxModule,
|
||||
UiIconModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||
{
|
||||
provide: CustomerSearch,
|
||||
useClass: CustomerSearchMock,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CustomerSearchMainComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
component.search.queryControl = new FormControl('');
|
||||
searchService = TestBed.inject(CustomerSearch) as jasmine.SpyObj<
|
||||
CustomerSearch
|
||||
>;
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component instanceof CustomerSearchMainComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('initAutocomplete', () => {
|
||||
let result: AutocompleteDTO[];
|
||||
|
||||
beforeEach(() => {
|
||||
result = [{ id: 'test 1' }, { id: 'test 2' }];
|
||||
});
|
||||
|
||||
it('should be called OnInit', async () => {
|
||||
spyOn(component, 'initAutocomplete').and.callFake(() => {});
|
||||
spyOnProperty(searchService, 'autocompleteResult$').and.returnValue(
|
||||
of(result)
|
||||
);
|
||||
spyOn(component.autocomplete, 'open').and.callFake(() => {});
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
await searchService.autocompleteResult$.pipe(take(1)).toPromise();
|
||||
|
||||
expect(component.initAutocomplete).toHaveBeenCalledTimes(1);
|
||||
// expect(component.autocomplete.open).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export class CustomerSearchMock {
|
||||
get autocompleteResult$(): Observable<AutocompleteDTO[]> {
|
||||
return of([{ id: '1' }]);
|
||||
}
|
||||
parseQueryParams() {}
|
||||
setQueryParams() {}
|
||||
getQueryParams() {}
|
||||
}
|
||||
@@ -2,27 +2,16 @@ import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { CrmCustomerService } from '@domain/crm';
|
||||
import { AutocompleteDTO } from '@swagger/crm';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { UiSearchboxAutocompleteComponent } from '@ui/searchbox';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import { CustomerSearchService } from '../customer-search.service';
|
||||
import { Subject } from 'rxjs';
|
||||
import { delay, takeUntil, tap } from 'rxjs/operators';
|
||||
import { CustomerSearch } from '../customer-search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'customer-search-main',
|
||||
@@ -33,12 +22,6 @@ import { CustomerSearchService } from '../customer-search.service';
|
||||
export class CustomerSearchMainComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject();
|
||||
|
||||
queryControl: FormControl;
|
||||
|
||||
autocompleteResult$: Observable<AutocompleteDTO[]>;
|
||||
|
||||
inputChange$ = new Subject<string>();
|
||||
|
||||
@ViewChild(UiSearchboxAutocompleteComponent, {
|
||||
read: UiSearchboxAutocompleteComponent,
|
||||
static: true,
|
||||
@@ -46,17 +29,14 @@ export class CustomerSearchMainComponent implements OnInit, OnDestroy {
|
||||
autocomplete: UiSearchboxAutocompleteComponent;
|
||||
|
||||
constructor(
|
||||
public search: CustomerSearchService,
|
||||
public search: CustomerSearch,
|
||||
public cdr: ChangeDetectorRef,
|
||||
private customerSearch: CrmCustomerService,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private zone: NgZone
|
||||
private activatedRoute: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initQueryControl();
|
||||
this.initAutocomplete();
|
||||
this.search.parseQueryParams(this.activatedRoute);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -65,56 +45,21 @@ export class CustomerSearchMainComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
initAutocomplete() {
|
||||
this.autocompleteResult$ = this.inputChange$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
filter(() => this.queryControl.valid),
|
||||
debounceTime(150),
|
||||
switchMap((queryString) =>
|
||||
this.customerSearch.complete(queryString).pipe(
|
||||
map((response) => response.result),
|
||||
catchError(() => {
|
||||
// TODO Dialog Service impl. fur Anzeige der Fehlermeldung
|
||||
return [];
|
||||
})
|
||||
)
|
||||
),
|
||||
tap((result) => {
|
||||
if (result.length) {
|
||||
this.autocomplete.open();
|
||||
} else {
|
||||
this.autocomplete.close();
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
initQueryControl() {
|
||||
this.queryControl = new FormControl(
|
||||
this.activatedRoute.snapshot.queryParams.query || '',
|
||||
[Validators.required]
|
||||
);
|
||||
|
||||
this.queryControl.valueChanges
|
||||
this.search.autocompleteResult$
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
debounceTime(500),
|
||||
tap((query) => this.updateQueryParam({ query }))
|
||||
tap((results) => {
|
||||
console.log({ results });
|
||||
if (results.length > 0) {
|
||||
this.autocomplete.open();
|
||||
} else {
|
||||
this.autocomplete.close();
|
||||
}
|
||||
}),
|
||||
delay(1)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
updateQueryParam(params: { query?: string }) {
|
||||
this.zone.run(() => {
|
||||
this.router.navigate(['./'], {
|
||||
queryParams: params,
|
||||
relativeTo: this.activatedRoute,
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -10843,15 +10843,6 @@
|
||||
"integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==",
|
||||
"dev": true
|
||||
},
|
||||
"jasmine-marbles": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/jasmine-marbles/-/jasmine-marbles-0.6.0.tgz",
|
||||
"integrity": "sha1-943Bo7xFKXbeEO6LR8c9YWUyqVQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"jasmine-spec-reporter": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.2.tgz",
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
"codelyzer": "^5.1.2",
|
||||
"husky": "^4.2.3",
|
||||
"jasmine-core": "~3.5.0",
|
||||
"jasmine-marbles": "^0.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~5.0.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
|
||||
Reference in New Issue
Block a user