mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
[iPad Scanning] Open Scanner on Scan Icon Click
[Fix] Select Filters In HitsOnly Call {Search}
Set Autocomplete Result on Click
Close Autoresults on Searchbar X Icon
Reset Focus on Input after Clicking Autocomplete Result
This commit is contained in:
@@ -45,7 +45,6 @@ export class ContentHeaderService {
|
||||
switchMap((activeSection) => {
|
||||
if (activeSection.includes('remission')) {
|
||||
return this.remissionFilters$.pipe(
|
||||
tap((selectedFilters) => console.log({ selectedFilters })),
|
||||
map(
|
||||
(selectedFilters) =>
|
||||
selectedFilters && !!Object.entries(selectedFilters).length
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { AutocompleteDTO } from '@swagger/oms/lib';
|
||||
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
|
||||
import { ResultItemComponent } from './result-item';
|
||||
import { AutocompleteOptions } from '../../defs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-autocomplete-results',
|
||||
@@ -24,7 +25,7 @@ export class AutocompleteResultsComponent implements AfterViewInit {
|
||||
|
||||
@Input() results: AutocompleteDTO[];
|
||||
@Output() selectItem = new EventEmitter<
|
||||
AutocompleteDTO & { shouldNavigate?: boolean }
|
||||
AutocompleteDTO & AutocompleteOptions
|
||||
>();
|
||||
|
||||
private keyManager: ActiveDescendantKeyManager<ResultItemComponent>;
|
||||
@@ -48,7 +49,8 @@ export class AutocompleteResultsComponent implements AfterViewInit {
|
||||
this.keyManager.setActiveItem(this.getItemIndex(item));
|
||||
this.selectItem.emit({
|
||||
...this.keyManager.activeItem.result,
|
||||
shouldNavigate: true,
|
||||
shouldNavigate: false,
|
||||
shouldUpdateAutocomplete: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
name="shelf-search"
|
||||
id="shelf-search-input"
|
||||
[formControl]="searchForm"
|
||||
(keyup.enter)="triggerSearch('enter')"
|
||||
(keyup.enter)="triggerSearch('search')"
|
||||
(keyup)="onInput($event)"
|
||||
/>
|
||||
<span class="isa-input-error" *ngIf="!!errorMessage.length">{{
|
||||
@@ -33,7 +33,7 @@
|
||||
class="isa-input-submit"
|
||||
[class.scan]="!errorMessage && !(searchQuery$ | async) && isIPad"
|
||||
type="submit"
|
||||
(click)="triggerSearch('click')"
|
||||
(click)="handleBtnClick(submitButton)"
|
||||
></button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
} from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { FormControl } from '@angular/forms';
|
||||
@@ -40,14 +39,14 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
@Output() change = new EventEmitter<string>();
|
||||
@Output() reset = new EventEmitter<void>();
|
||||
@Output() search = new EventEmitter<{
|
||||
type: 'scan' | 'search';
|
||||
value: string;
|
||||
target: 'click' | 'enter';
|
||||
type: 'search' | 'scan';
|
||||
}>();
|
||||
@Output() scan = new EventEmitter<void>();
|
||||
|
||||
destroy$ = new Subject();
|
||||
|
||||
constructor(private store: Store) {}
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initForm();
|
||||
@@ -71,7 +70,15 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
this.errorMessage = message;
|
||||
}
|
||||
|
||||
triggerSearch(target: 'click' | 'enter') {
|
||||
handleBtnClick(targetEl: HTMLButtonElement) {
|
||||
this.triggerSearch(this.getSearchType(targetEl));
|
||||
}
|
||||
|
||||
triggerSearch(type: 'search' | 'scan') {
|
||||
if (type === 'scan') {
|
||||
return this.triggerScan();
|
||||
}
|
||||
|
||||
const isValidInput = this.validateSearchInputBeforeSubmit(
|
||||
this.searchForm.value
|
||||
);
|
||||
@@ -81,12 +88,15 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.search.emit({
|
||||
type: this.isSearch() ? 'search' : 'scan',
|
||||
value: this.searchForm.value,
|
||||
target,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
||||
triggerScan() {
|
||||
this.scan.emit();
|
||||
}
|
||||
|
||||
onInput(event: KeyboardEvent) {
|
||||
if (!this.isNumberOrLetter(event.key)) {
|
||||
this.change.emit(this.searchForm.value);
|
||||
@@ -116,8 +126,11 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
return !isNullOrUndefined(searchInput) && searchInput.length >= 1;
|
||||
}
|
||||
|
||||
private isSearch(): boolean {
|
||||
return this.searchForm.value && this.searchForm.value.length;
|
||||
private getSearchType(target: HTMLButtonElement): 'search' | 'scan' {
|
||||
if (!target) {
|
||||
return 'search';
|
||||
}
|
||||
return target.classList.contains('scan') ? 'scan' : 'search';
|
||||
}
|
||||
|
||||
private isNumberOrLetter(key: string): boolean {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface AutocompleteOptions {
|
||||
shouldNavigate?: boolean;
|
||||
shouldUpdateAutocomplete?: boolean;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './autocomplete-options.model';
|
||||
export * from './shelf-primary-filter-options';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -17,7 +17,6 @@ import { startWith, first } from 'rxjs/operators';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { ShelfSearchInputComponent } from '../../pages/shelf-search/search';
|
||||
import { cloneFilter } from '../../../filter/utils';
|
||||
import { SearchStateFacade } from '@shelf-store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shelf-filter',
|
||||
@@ -47,7 +46,6 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private overlayRef: IsaOverlayRef,
|
||||
private filterService: ShelfFilterService,
|
||||
private searchStateFacade: SearchStateFacade,
|
||||
private renderer: Renderer2,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
@@ -154,17 +152,12 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
|
||||
this.searchInput.searchbar.searchForm.value) ||
|
||||
'';
|
||||
|
||||
this.searchStateFacade.selectedFilter$
|
||||
.pipe(first())
|
||||
.subscribe((selectedFilters) =>
|
||||
this.searchInput.triggerSearch({
|
||||
type: 'search',
|
||||
value: searchbarValue,
|
||||
selectedFilters,
|
||||
bypassValidation: true,
|
||||
closeOverlay: () => this.close(),
|
||||
})
|
||||
);
|
||||
this.searchInput.triggerSearch({
|
||||
type: 'search',
|
||||
value: searchbarValue,
|
||||
bypassValidation: true,
|
||||
closeOverlay: () => this.close(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
[mode]="searchMode$ | async"
|
||||
(change)="updateAutocomplete($event)"
|
||||
(search)="triggerSearch($event)"
|
||||
(scan)="openScanner()"
|
||||
(reset)="reset()"
|
||||
></app-shelf-searchbar>
|
||||
<app-autocomplete-results
|
||||
*ngIf="(searchMode$ | async) === 'autocomplete'"
|
||||
[results]="autocompleteResult$ | async"
|
||||
(selectItem)="selectedItem$.next($event)"
|
||||
(selectItem)="handleAutocompleteAction($event)"
|
||||
></app-autocomplete-results>
|
||||
|
||||
<lib-collecting-shelf-scanner
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ViewChild,
|
||||
AfterViewInit,
|
||||
} from '@angular/core';
|
||||
import { Subject, BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { Subject, BehaviorSubject, Observable, of, NEVER } from 'rxjs';
|
||||
import {
|
||||
ShelfSearchFacadeService,
|
||||
ShelfNavigationService,
|
||||
@@ -32,6 +32,9 @@ import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants'
|
||||
import { ShelfSearchbarComponent } from '../../../components';
|
||||
import { ShelfFilterService } from '../../../services/shelf-filter.service';
|
||||
import { SearchStateFacade } from '@shelf-store';
|
||||
import { CollectingShelfScannerScanditComponent } from 'shared/public_api';
|
||||
import { AutocompleteOptions } from '../../../defs';
|
||||
import { ClickOutsideDirective } from 'apps/sales/src/app/shared/directives';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shelf-search-input',
|
||||
@@ -47,6 +50,8 @@ export class ShelfSearchInputComponent
|
||||
|
||||
@ViewChild(ShelfSearchbarComponent, { static: true })
|
||||
searchbar: ShelfSearchbarComponent;
|
||||
@ViewChild(CollectingShelfScannerScanditComponent, { static: false })
|
||||
scanner: CollectingShelfScannerScanditComponent;
|
||||
|
||||
destroy$ = new Subject();
|
||||
|
||||
@@ -59,9 +64,12 @@ export class ShelfSearchInputComponent
|
||||
|
||||
searchMode$: Observable<'standalone' | 'autocomplete'>;
|
||||
|
||||
selectedItem$ = new BehaviorSubject<
|
||||
AutocompleteDTO & { shouldNavigate?: boolean }
|
||||
>(null);
|
||||
selectedItem$ = new BehaviorSubject<AutocompleteDTO & AutocompleteOptions>(
|
||||
null
|
||||
);
|
||||
selectedFilters$: Observable<{
|
||||
[key: string]: string;
|
||||
}>;
|
||||
|
||||
constructor(
|
||||
private shelfSearchService: ShelfSearchFacadeService,
|
||||
@@ -76,6 +84,7 @@ export class ShelfSearchInputComponent
|
||||
}
|
||||
|
||||
this.setUpSearchMode();
|
||||
this.initSelectedFilters();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
@@ -101,7 +110,6 @@ export class ShelfSearchInputComponent
|
||||
{
|
||||
type,
|
||||
value,
|
||||
selectedFilters,
|
||||
bypassValidation,
|
||||
closeOverlay,
|
||||
}: {
|
||||
@@ -124,18 +132,30 @@ export class ShelfSearchInputComponent
|
||||
|
||||
if (this.isAutocompleteSelected()) {
|
||||
searchQuery = this.selectedItem$.value.query;
|
||||
result$ = this.shelfSearchService.search(searchQuery, {
|
||||
hitsOnly: true,
|
||||
bypassValidation,
|
||||
selectedFilters,
|
||||
});
|
||||
result$ = this.selectedFilters$.pipe(
|
||||
debounceTime(250),
|
||||
first(),
|
||||
switchMap((selectedFilters) =>
|
||||
this.shelfSearchService.search(searchQuery, {
|
||||
hitsOnly: true,
|
||||
bypassValidation,
|
||||
selectedFilters,
|
||||
})
|
||||
)
|
||||
);
|
||||
this.updateSearchbarValue(searchQuery);
|
||||
} else if (type === 'search') {
|
||||
result$ = this.shelfSearchService.search(value, {
|
||||
hitsOnly: true,
|
||||
bypassValidation,
|
||||
selectedFilters,
|
||||
});
|
||||
result$ = this.selectedFilters$.pipe(
|
||||
debounceTime(250),
|
||||
first(),
|
||||
switchMap((selectedFilters) =>
|
||||
this.shelfSearchService.search(value, {
|
||||
hitsOnly: true,
|
||||
bypassValidation,
|
||||
selectedFilters,
|
||||
})
|
||||
)
|
||||
);
|
||||
} else if (type === 'scan') {
|
||||
result$ = this.shelfSearchService.searchWithBarcode(value);
|
||||
}
|
||||
@@ -157,10 +177,43 @@ export class ShelfSearchInputComponent
|
||||
);
|
||||
}
|
||||
|
||||
updateAutocomplete(searchQuery: string) {
|
||||
openScanner() {
|
||||
if (this.scanner) {
|
||||
this.scanner.openNativeScanner();
|
||||
}
|
||||
}
|
||||
|
||||
updateAutocomplete(searchQuery: string | Event) {
|
||||
if (this.isEvent(searchQuery)) {
|
||||
if (this.isClearSearchbarEvent(searchQuery)) {
|
||||
this.setSelectedAutocompleteItem(null);
|
||||
return this.autocompleteQueryString$.next('');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
this.autocompleteQueryString$.next(searchQuery);
|
||||
}
|
||||
|
||||
handleAutocompleteAction(selectItem: AutocompleteDTO & AutocompleteOptions) {
|
||||
const oldValue = this.selectedItem$.value && this.selectedItem$.value.query;
|
||||
const newValue = selectItem.query;
|
||||
console.log({
|
||||
oldValue,
|
||||
newValue,
|
||||
shouldOverwrite: this.isAutocompleteOverwritten(oldValue, newValue),
|
||||
});
|
||||
|
||||
if (this.isAutocompleteOverwritten(oldValue, newValue)) {
|
||||
return this.setSelectedAutocompleteItem(null);
|
||||
}
|
||||
|
||||
this.setSelectedAutocompleteItem(selectItem);
|
||||
if (selectItem.query) {
|
||||
this.updateSearchbarValue(selectItem.query);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSearchResult(
|
||||
result: ListResponseArgsOfOrderItemListItemDTO,
|
||||
value: string,
|
||||
@@ -214,6 +267,10 @@ export class ShelfSearchInputComponent
|
||||
distinctUntilChanged(this.filterDistinctStrings),
|
||||
withLatestFrom(this.searchStateFacade.selectedFilter$),
|
||||
switchMap(([queryString, selectedFilters]) => {
|
||||
if (this.isAutocompleteSelected()) {
|
||||
return NEVER;
|
||||
}
|
||||
|
||||
if (this.isValidQuery(queryString)) {
|
||||
return this.shelfSearchService
|
||||
.searchForAutocomplete(queryString, {
|
||||
@@ -236,6 +293,10 @@ export class ShelfSearchInputComponent
|
||||
);
|
||||
}
|
||||
|
||||
private initSelectedFilters() {
|
||||
this.selectedFilters$ = this.searchStateFacade.selectedFilter$;
|
||||
}
|
||||
|
||||
private setUpInputFocus() {
|
||||
this.filterService.overlayClosed$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
@@ -268,6 +329,12 @@ export class ShelfSearchInputComponent
|
||||
this.errorMessage$.next(message);
|
||||
}
|
||||
|
||||
private setSelectedAutocompleteItem(
|
||||
item: (AutocompleteDTO & AutocompleteOptions) | null
|
||||
) {
|
||||
this.selectedItem$.next(item);
|
||||
}
|
||||
|
||||
private isValidQuery(queryString: string): boolean {
|
||||
return !!queryString && queryString.length >= 3;
|
||||
}
|
||||
@@ -280,9 +347,39 @@ export class ShelfSearchInputComponent
|
||||
return !!this.selectedItem$.value;
|
||||
}
|
||||
|
||||
private isAutocompleteOverwritten(
|
||||
oldQuery: string,
|
||||
newQuery: string
|
||||
): boolean {
|
||||
return this.isEqual(oldQuery, newQuery);
|
||||
}
|
||||
|
||||
private isEqual<T>(val1: T, val2: T): boolean {
|
||||
return val1 === val2;
|
||||
}
|
||||
|
||||
private isEvent(value: any): value is Event {
|
||||
return value && value instanceof Event;
|
||||
}
|
||||
|
||||
private isClearSearchbarEvent(event: Event): boolean {
|
||||
return event.target instanceof HTMLInputElement;
|
||||
}
|
||||
|
||||
private inputOutOfFocus(): boolean {
|
||||
return (
|
||||
document.activeElement &&
|
||||
document.activeElement.tagName &&
|
||||
document.activeElement.tagName.toLowerCase() === 'body'
|
||||
);
|
||||
}
|
||||
|
||||
private updateSearchbarValue(queryString: string) {
|
||||
if (this.searchbar) {
|
||||
this.searchbar.setInputValue(queryString);
|
||||
}
|
||||
if (this.inputOutOfFocus()) {
|
||||
this.setFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user