mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
#667 Add Automplete Result Styling
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<div class="container" *ngIf="!!results">
|
||||
<app-autocomplete-result-item
|
||||
*ngFor="let result of results"
|
||||
[result]="result"
|
||||
(clicked)="onItemClicked($event)"
|
||||
></app-autocomplete-result-item>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
@import '../../../../../scss/variables';
|
||||
|
||||
.container {
|
||||
background-color: $content-background-color;
|
||||
border-radius: 0 0 $input-round-borders $input-round-borders;
|
||||
width: 550px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 6px 24px 0px rgba(214, 215, 217, 0.8);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Component,
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
HostListener,
|
||||
Output,
|
||||
EventEmitter,
|
||||
} from '@angular/core';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms/lib';
|
||||
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
|
||||
import { ResultItemComponent } from './result-item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-autocomplete-results',
|
||||
templateUrl: 'autocomplete-results.component.html',
|
||||
styleUrls: ['./autocomplete-results.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AutocompleteResultsComponent implements AfterViewInit {
|
||||
@ViewChildren(ResultItemComponent) items: QueryList<ResultItemComponent>;
|
||||
|
||||
@Input() results: OrderItemListItemDTO[];
|
||||
@Output() selectItem = new EventEmitter<
|
||||
OrderItemListItemDTO & { shouldNavigate?: boolean }
|
||||
>();
|
||||
|
||||
private keyManager: ActiveDescendantKeyManager<ResultItemComponent>;
|
||||
|
||||
constructor() {}
|
||||
|
||||
@HostListener('window:keyup', ['$event']) onKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'ArrowDown' || 'ArrowUp') {
|
||||
this.keyManager.onKeydown(event);
|
||||
if (this.keyManager.activeItem) {
|
||||
this.selectItem.emit(this.keyManager.activeItem.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.keyManager = new ActiveDescendantKeyManager(this.items).withWrap();
|
||||
}
|
||||
|
||||
onItemClicked(item: OrderItemListItemDTO) {
|
||||
console.log({ item });
|
||||
this.keyManager.setActiveItem(this.getItemIndex(item));
|
||||
this.selectItem.emit({
|
||||
...this.keyManager.activeItem.result,
|
||||
shouldNavigate: true,
|
||||
});
|
||||
}
|
||||
|
||||
private getItemIndex(item: OrderItemListItemDTO): number {
|
||||
return this.results.indexOf(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ResultItemComponent } from './result-item';
|
||||
import { AutocompleteResultsComponent } from './autocomplete-results.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [AutocompleteResultsComponent, ResultItemComponent],
|
||||
declarations: [AutocompleteResultsComponent, ResultItemComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class AutocompleteResultsModule {}
|
||||
@@ -0,0 +1,5 @@
|
||||
// start:ng42.barrel
|
||||
export * from './autocomplete-results.component';
|
||||
export * from './autocomplete-results.module';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './result-item.component';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="item isa-font-lightgrey" (click)="clicked.emit(result)">
|
||||
{{ result.firstName }} {{ result.lastName }}
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
@import '../../../../../../scss/variables';
|
||||
|
||||
.item {
|
||||
font-family: $font-family;
|
||||
font-size: $font-size;
|
||||
color: $text-black;
|
||||
line-height: 21px;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 16px;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
:host.active {
|
||||
.item {
|
||||
background-color: $isa-branch-bg;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
.item {
|
||||
border-radius: 0 0 $input-round-borders $input-round-borders;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
HostBinding,
|
||||
Output,
|
||||
EventEmitter,
|
||||
} from '@angular/core';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms/lib';
|
||||
import { Highlightable } from '@angular/cdk/a11y';
|
||||
|
||||
@Component({
|
||||
selector: 'app-autocomplete-result-item',
|
||||
templateUrl: 'result-item.component.html',
|
||||
styleUrls: ['./result-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ResultItemComponent implements Highlightable {
|
||||
@Input() result: OrderItemListItemDTO;
|
||||
@Output() clicked = new EventEmitter<OrderItemListItemDTO>();
|
||||
|
||||
private _isActive: boolean;
|
||||
|
||||
@HostBinding('class.active') get isActive() {
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
set isActive(isActive: boolean) {
|
||||
this._isActive = isActive;
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
setActiveStyles(): void {
|
||||
this.isActive = true;
|
||||
}
|
||||
setInactiveStyles(): void {
|
||||
this.isActive = false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="input-wrapper">
|
||||
<div class="input-wrapper" [class.extend-for-content]="mode === 'autocomplete'">
|
||||
<input
|
||||
focus
|
||||
autocomplete="off"
|
||||
@@ -9,7 +9,8 @@
|
||||
name="shelf-search"
|
||||
id="shelf-search-input"
|
||||
[formControl]="searchForm"
|
||||
(keyup.enter)="triggerSearch()"
|
||||
(keyup.enter)="triggerSearch('enter')"
|
||||
(keyup)="onInput($event)"
|
||||
/>
|
||||
<span class="isa-input-error" *ngIf="!!errorMessage.length">{{
|
||||
errorMessage
|
||||
@@ -30,13 +31,7 @@
|
||||
class="isa-input-submit"
|
||||
[class.scan]="!errorMessage && !(searchQuery$ | async) && isIPad"
|
||||
type="submit"
|
||||
(click)="triggerSearch()"
|
||||
(click)="triggerSearch('click')"
|
||||
></button>
|
||||
</ng-container>
|
||||
<div
|
||||
class="autocomplete-results"
|
||||
*ngFor="let result of autocompleteResults$ | async"
|
||||
>
|
||||
<!-- Create own component using ListKey CDK -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../../../assets/scss/variables';
|
||||
@import '../../../../../scss/variables';
|
||||
|
||||
app-search {
|
||||
width: 80vw;
|
||||
@@ -11,6 +11,24 @@ app-search {
|
||||
align-items: center;
|
||||
width: 80vw;
|
||||
max-width: 550px;
|
||||
border-radius: 30px;
|
||||
background-color: $content-background-color;
|
||||
border-radius: $input-round-borders;
|
||||
box-shadow: 0px 6px 24px 0px rgba(214, 215, 217, 0.8);
|
||||
|
||||
transition: border-radius 1s linear;
|
||||
|
||||
&.extend-for-content {
|
||||
border-radius: $input-round-borders $input-round-borders 0 0;
|
||||
box-shadow: 0px -7px 10px -3px rgba(214, 215, 217, 0.8);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 20px;
|
||||
width: calc(100% - 40px);
|
||||
border-bottom: 2px solid #e9f0f8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { Process } from 'apps/sales/src/app/core/models/process.model';
|
||||
import { AddProcess } from 'apps/sales/src/app/core/store/actions/process.actions';
|
||||
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
|
||||
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { FormControl } from '@angular/forms';
|
||||
@@ -25,17 +21,19 @@ import { FormControl } from '@angular/forms';
|
||||
export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
searchForm: FormControl;
|
||||
searchQuery$: Observable<string>;
|
||||
autocompleteResults$: Observable<any[]>;
|
||||
|
||||
@Input() isIPad = false;
|
||||
@Input() isFetchingData = false;
|
||||
@Input() mode: 'standalone' | 'autocomplete' = 'standalone';
|
||||
@Input() errorMessage = '';
|
||||
@Input() placeholder =
|
||||
'Kundenname, Abholfach, Abholschein, Kundenkartennummer';
|
||||
@Output() change = new EventEmitter<string>();
|
||||
@Output() reset = new EventEmitter<void>();
|
||||
@Output() search = new EventEmitter<{
|
||||
type: 'scan' | 'search';
|
||||
value: string;
|
||||
target: 'click' | 'enter';
|
||||
}>();
|
||||
|
||||
destroy$ = new Subject();
|
||||
@@ -64,7 +62,7 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
this.errorMessage = message;
|
||||
}
|
||||
|
||||
triggerSearch() {
|
||||
triggerSearch(target: 'click' | 'enter') {
|
||||
const isValidInput = this.validateSearchInputBeforeSubmit(
|
||||
this.searchForm.value
|
||||
);
|
||||
@@ -76,9 +74,16 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
this.search.emit({
|
||||
type: this.isSearch() ? 'search' : 'scan',
|
||||
value: this.searchForm.value,
|
||||
target,
|
||||
});
|
||||
}
|
||||
|
||||
onInput(event: KeyboardEvent) {
|
||||
if (!this.isNumberOrLetter(event.key)) {
|
||||
this.change.emit(this.searchForm.value);
|
||||
}
|
||||
}
|
||||
|
||||
private validateSearchInputBeforeSubmit(searchInput: string): boolean {
|
||||
return !isNullOrUndefined(searchInput) && searchInput.length >= 1;
|
||||
}
|
||||
@@ -87,130 +92,7 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
return this.searchForm.value && this.searchForm.value.length;
|
||||
}
|
||||
|
||||
/* search2(searchParams: string) {
|
||||
const branchNumber = this.store.selectSnapshot(
|
||||
BranchSelectors.getUserBranch
|
||||
);
|
||||
this.collectingShelf
|
||||
.searchShelfHasResultsWithResult(searchParams, branchNumber)
|
||||
.pipe(take(1))
|
||||
.subscribe((reponse: ListResponseArgsOfOrderItemListItemDTO) => {
|
||||
if (reponse) {
|
||||
if (
|
||||
reponse.hits > 1 ||
|
||||
(reponse.result && reponse.result.length > 1)
|
||||
) {
|
||||
this.store.dispatch(
|
||||
new SetShelfSearch(<ShelfSearch>{
|
||||
input: searchParams,
|
||||
branchnumber: branchNumber,
|
||||
})
|
||||
);
|
||||
sessionStorage.removeItem(SHELF_SCROLL_INDEX);
|
||||
this.store
|
||||
.dispatch(
|
||||
new SetCachedResults({
|
||||
result: this.mapper.fromOrderItemListItemDTOArrayToCollectingShelfOrder(
|
||||
reponse.result,
|
||||
this.collectingShelf,
|
||||
true
|
||||
),
|
||||
hits: reponse.hits,
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
.then(() => {
|
||||
// this.searchInput.stopLoading();
|
||||
this.store.dispatch(new SetCollectingShelfCacheState(true));
|
||||
this.navigateToRoute(
|
||||
'/shelf/results',
|
||||
`${searchParams} (${
|
||||
reponse.hits ? reponse.hits : reponse.result.length
|
||||
} Ergebnisse)`
|
||||
);
|
||||
});
|
||||
} else if (
|
||||
reponse.hits === 1 ||
|
||||
(reponse.result && reponse.result.length === 1)
|
||||
) {
|
||||
this.details(reponse.result[0]);
|
||||
this.store.dispatch(new ClearCachedResults());
|
||||
} else {
|
||||
// this.searchInput.stopLoading();
|
||||
this.store.dispatch(new ClearCachedResults());
|
||||
this.error = 'Ergibt keine Suchergebnisse';
|
||||
}
|
||||
} else {
|
||||
// this.searchInput.stopLoading();
|
||||
this.store.dispatch(new ClearCachedResults());
|
||||
this.error = 'Ergibt keine Suchergebnisse';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
details(order: OrderItemListItemDTO) {
|
||||
if (order && (order.compartmentCode || order.orderId)) {
|
||||
const path = `/shelf/details/${
|
||||
order.compartmentCode ? order.compartmentCode : order.orderId
|
||||
}/${order.orderId}/${order.processingStatus}/${
|
||||
order.compartmentCode ? 'c' : 'o'
|
||||
}`;
|
||||
this.store.dispatch(
|
||||
new AddBreadcrumb(
|
||||
<Breadcrumb>{
|
||||
name: order.firstName + ' ' + order.lastName,
|
||||
path: path,
|
||||
},
|
||||
'shelf'
|
||||
)
|
||||
);
|
||||
this.store.dispatch(new ChangeCurrentRoute(path));
|
||||
this.router.navigate([path]);
|
||||
}
|
||||
}
|
||||
|
||||
private navigateToRoute(route: string, breadcrumbName: string) {
|
||||
this.store.dispatch(
|
||||
new AddBreadcrumb(
|
||||
<Breadcrumb>{
|
||||
name: breadcrumbName,
|
||||
path: route,
|
||||
},
|
||||
'shelf'
|
||||
)
|
||||
);
|
||||
this.store.dispatch(new ChangeCurrentRoute(route));
|
||||
this.router.navigate([route]);
|
||||
}
|
||||
|
||||
createTab() {
|
||||
const processExists =
|
||||
this.store.selectSnapshot(ProcessSelectors.getProcessesCount) > 0;
|
||||
if (!processExists) {
|
||||
this.createProcess();
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.error = '';
|
||||
} */
|
||||
|
||||
private createProcess() {
|
||||
const newProcess = <Process>{
|
||||
id: 1,
|
||||
name: 'Vorgang 1',
|
||||
currentRoute: '/shelf/search',
|
||||
};
|
||||
|
||||
this.store.dispatch(new AddProcess(newProcess));
|
||||
this.store.dispatch(
|
||||
new AddBreadcrumb(
|
||||
<Breadcrumb>{
|
||||
name: 'Abholfach',
|
||||
path: '/shelf/search',
|
||||
},
|
||||
'shelf'
|
||||
)
|
||||
);
|
||||
private isNumberOrLetter(key: string): boolean {
|
||||
return key.includes('Digit') || key.includes('Key');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,13 @@ import { BehaviorSubject } from 'rxjs';
|
||||
export abstract class ShelfSearchDeviceComponent {
|
||||
isFetchingData$: BehaviorSubject<boolean>;
|
||||
errorMessage$: BehaviorSubject<string>;
|
||||
triggerSearch({ type, value }: { type: 'search' | 'scan'; value: string }) {}
|
||||
triggerSearch({
|
||||
type,
|
||||
value,
|
||||
target,
|
||||
}: {
|
||||
type: 'search' | 'scan';
|
||||
value: string;
|
||||
target: 'click' | 'enter';
|
||||
}) {}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<app-shelf-searchbar
|
||||
[isFetchingData]="isFetchingData$ | async"
|
||||
[errorMessage]="errorMessage$ | async"
|
||||
[mode]="searchMode$ | async"
|
||||
(change)="updateAutocomplete($event)"
|
||||
(search)="triggerSearch($event)"
|
||||
(reset)="reset()"
|
||||
></app-shelf-searchbar>
|
||||
<app-autocomplete-results
|
||||
*ngIf="(searchMode$ | async) === 'autocomplete'"
|
||||
[results]="autocompleteResult$ | async"
|
||||
(selectItem)="selectedItem$.next($event)"
|
||||
></app-autocomplete-results>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core';
|
||||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Subject, BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import {
|
||||
ShelfSearchFacadeService,
|
||||
ShelfStoreFacadeService,
|
||||
ShelfNavigationService,
|
||||
} from '../../../shared/services';
|
||||
import { ShelfSearchDeviceComponent } from '../../../defs';
|
||||
import { takeUntil, first } from 'rxjs/operators';
|
||||
import {
|
||||
takeUntil,
|
||||
first,
|
||||
distinctUntilChanged,
|
||||
debounceTime,
|
||||
switchMap,
|
||||
map,
|
||||
filter,
|
||||
withLatestFrom,
|
||||
} from 'rxjs/operators';
|
||||
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
|
||||
import { ListResponseArgsOfOrderItemListItemDTO } from '@swagger/oms/lib';
|
||||
import {
|
||||
ListResponseArgsOfOrderItemListItemDTO,
|
||||
OrderItemListItemDTO,
|
||||
} from '@swagger/oms/lib';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shelf-search-desktop',
|
||||
@@ -17,10 +35,16 @@ import { ListResponseArgsOfOrderItemListItemDTO } from '@swagger/oms/lib';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ShelfSearchDesktopComponent
|
||||
implements OnDestroy, ShelfSearchDeviceComponent {
|
||||
implements OnInit, OnDestroy, ShelfSearchDeviceComponent {
|
||||
destroy$ = new Subject();
|
||||
isFetchingData$ = new BehaviorSubject<boolean>(false);
|
||||
errorMessage$ = new BehaviorSubject<string>('');
|
||||
autocompleteQueryString$ = new BehaviorSubject<string>('');
|
||||
autocompleteResult$: Observable<OrderItemListItemDTO[]>;
|
||||
searchMode$: Observable<'standalone' | 'autocomplete'>;
|
||||
selectedItem$ = new BehaviorSubject<
|
||||
OrderItemListItemDTO & { shouldNavigate?: boolean }
|
||||
>(null);
|
||||
|
||||
constructor(
|
||||
private shelfSearchService: ShelfSearchFacadeService,
|
||||
@@ -28,6 +52,12 @@ export class ShelfSearchDesktopComponent
|
||||
private shelfNavigationService: ShelfNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.setupAutocompletion();
|
||||
this.setUpSearchMode();
|
||||
this.setUpNavigationToDetailsPage();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
@@ -35,13 +65,26 @@ export class ShelfSearchDesktopComponent
|
||||
|
||||
reset() {
|
||||
this.setIsFetchingData(false);
|
||||
this.autocompleteQueryString$.next('');
|
||||
this.resetError();
|
||||
}
|
||||
|
||||
triggerSearch({ value }: { value: string }) {
|
||||
triggerSearch({
|
||||
value,
|
||||
target,
|
||||
}: {
|
||||
value: string;
|
||||
target: 'click' | 'enter';
|
||||
}) {
|
||||
this.setIsFetchingData(true);
|
||||
this.resetError();
|
||||
|
||||
if (this.shouldNavigateToSelectedItem(target)) {
|
||||
return this.shelfNavigationService.navigateToDetails(
|
||||
this.selectedItem$.value
|
||||
);
|
||||
}
|
||||
|
||||
this.shelfSearchService
|
||||
.search(value)
|
||||
.pipe(takeUntil(this.destroy$), first())
|
||||
@@ -51,6 +94,46 @@ export class ShelfSearchDesktopComponent
|
||||
);
|
||||
}
|
||||
|
||||
updateAutocomplete(value: string) {
|
||||
this.autocompleteQueryString$.next(value);
|
||||
}
|
||||
|
||||
private setupAutocompletion() {
|
||||
this.autocompleteResult$ = this.autocompleteQueryString$.pipe(
|
||||
distinctUntilChanged(),
|
||||
debounceTime(250),
|
||||
switchMap((queryString: string) => {
|
||||
if (this.isValidQuery(queryString)) {
|
||||
return this.shelfSearchService
|
||||
.search(queryString)
|
||||
.pipe(map((result) => result.result && result.result.slice(0, 5)));
|
||||
}
|
||||
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private setUpSearchMode() {
|
||||
this.searchMode$ = this.autocompleteResult$.pipe(
|
||||
map((result) =>
|
||||
!!result && !!result.length ? 'autocomplete' : 'standalone'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private setUpNavigationToDetailsPage() {
|
||||
this.selectedItem$
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
filter((item) => !isNullOrUndefined(item)),
|
||||
distinctUntilChanged(),
|
||||
filter(({ shouldNavigate }) => !!shouldNavigate),
|
||||
map(({ shouldNavigate, ...item }) => item)
|
||||
)
|
||||
.subscribe((item) => this.shelfNavigationService.navigateToDetails(item));
|
||||
}
|
||||
|
||||
private handleSearchResult(
|
||||
result: ListResponseArgsOfOrderItemListItemDTO,
|
||||
value: string
|
||||
@@ -104,4 +187,12 @@ export class ShelfSearchDesktopComponent
|
||||
private setErrorMessage(message: string) {
|
||||
this.errorMessage$.next(message);
|
||||
}
|
||||
|
||||
private isValidQuery(queryString): boolean {
|
||||
return !!queryString && queryString.length > 3;
|
||||
}
|
||||
|
||||
private shouldNavigateToSelectedItem(target: 'click' | 'enter'): boolean {
|
||||
return !!this.selectedItem$.value && target === 'enter';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ShelfSearchDesktopComponent } from './shelf-search-desktop.component';
|
||||
import { ShelfSearchBarModule } from '../../../components';
|
||||
import { AutocompleteResultsModule } from '../../../components/autocomplete-results';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, ShelfSearchBarModule],
|
||||
imports: [CommonModule, ShelfSearchBarModule, AutocompleteResultsModule],
|
||||
exports: [ShelfSearchDesktopComponent],
|
||||
declarations: [ShelfSearchDesktopComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
[isFetchingData]="isFetchingData$ | async"
|
||||
[errorMessage]="errorMessage$ | async"
|
||||
[isIPad]="true"
|
||||
[mode]="searchMode$ | async"
|
||||
(change)="updateAutocomplete($event)"
|
||||
(search)="triggerSearch($event)"
|
||||
(reset)="reset()"
|
||||
></app-shelf-searchbar>
|
||||
<app-autocomplete-results
|
||||
*ngIf="(searchMode$ | async) === 'autocomplete'"
|
||||
[results]="autocompleteResult$ | async"
|
||||
(selectItem)="selectedItem$.next($event)"
|
||||
></app-autocomplete-results>
|
||||
|
||||
<lib-collecting-shelf-scanner
|
||||
#scanner
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import {
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { BarcodeScannerScanditComponent } from 'shared/lib/barcode-scanner';
|
||||
import { ShelfSearchFacadeService } from '../../../shared/services/shelf-search.facade.service';
|
||||
import { ListResponseArgsOfOrderItemListItemDTO } from 'apps/swagger/oms/src/lib';
|
||||
import { Observable, Subject, BehaviorSubject } from 'rxjs';
|
||||
import { first, takeUntil } from 'rxjs/operators';
|
||||
import { Observable, Subject, BehaviorSubject, of } from 'rxjs';
|
||||
import {
|
||||
first,
|
||||
takeUntil,
|
||||
distinctUntilChanged,
|
||||
debounceTime,
|
||||
switchMap,
|
||||
map,
|
||||
filter,
|
||||
} from 'rxjs/operators';
|
||||
import {
|
||||
ShelfStoreFacadeService,
|
||||
ShelfNavigationService,
|
||||
} from '../../../shared/services';
|
||||
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
|
||||
import { ShelfSearchDeviceComponent } from '../../../defs';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms/lib';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shelf-search-ipad',
|
||||
@@ -23,12 +34,18 @@ import { ShelfSearchDeviceComponent } from '../../../defs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ShelfSearchIpadComponent
|
||||
implements OnDestroy, ShelfSearchDeviceComponent {
|
||||
implements OnInit, OnDestroy, ShelfSearchDeviceComponent {
|
||||
@ViewChild('scanner', { static: false })
|
||||
scanner: BarcodeScannerScanditComponent;
|
||||
destroy$ = new Subject();
|
||||
isFetchingData$ = new BehaviorSubject<boolean>(false);
|
||||
errorMessage$ = new BehaviorSubject<string>('');
|
||||
autocompleteQueryString$ = new BehaviorSubject<string>('');
|
||||
autocompleteResult$: Observable<OrderItemListItemDTO[]>;
|
||||
searchMode$: Observable<'standalone' | 'autocomplete'>;
|
||||
selectedItem$ = new BehaviorSubject<
|
||||
OrderItemListItemDTO & { shouldNavigate?: boolean }
|
||||
>(null);
|
||||
|
||||
constructor(
|
||||
private shelfSearchService: ShelfSearchFacadeService,
|
||||
@@ -36,6 +53,12 @@ export class ShelfSearchIpadComponent
|
||||
private shelfNavigationService: ShelfNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.setupAutocompletion();
|
||||
this.setUpSearchMode();
|
||||
this.setUpNavigationToDetailsPage();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
@@ -43,18 +66,33 @@ export class ShelfSearchIpadComponent
|
||||
|
||||
reset() {
|
||||
this.setIsFetchingData(false);
|
||||
this.autocompleteQueryString$.next('');
|
||||
this.resetError();
|
||||
}
|
||||
|
||||
triggerBarcodeSearch(barcode: string) {
|
||||
this.triggerSearch({ type: 'scan', value: barcode });
|
||||
this.triggerSearch({ type: 'scan', value: barcode, target: 'click' });
|
||||
}
|
||||
|
||||
triggerSearch({ type, value }: { type: 'search' | 'scan'; value: string }) {
|
||||
triggerSearch({
|
||||
type,
|
||||
value,
|
||||
target,
|
||||
}: {
|
||||
type: 'search' | 'scan';
|
||||
value: string;
|
||||
target: 'click' | 'enter';
|
||||
}) {
|
||||
let result$: Observable<ListResponseArgsOfOrderItemListItemDTO>;
|
||||
this.setIsFetchingData(true);
|
||||
this.resetError();
|
||||
|
||||
if (this.shouldNavigateToSelectedItem(target)) {
|
||||
return this.shelfNavigationService.navigateToDetails(
|
||||
this.selectedItem$.value
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'search') {
|
||||
result$ = this.shelfSearchService.search(value);
|
||||
} else if (type === 'scan') {
|
||||
@@ -69,6 +107,46 @@ export class ShelfSearchIpadComponent
|
||||
);
|
||||
}
|
||||
|
||||
updateAutocomplete(value: string) {
|
||||
this.autocompleteQueryString$.next(value);
|
||||
}
|
||||
|
||||
private setupAutocompletion() {
|
||||
this.autocompleteResult$ = this.autocompleteQueryString$.pipe(
|
||||
distinctUntilChanged(),
|
||||
debounceTime(250),
|
||||
switchMap((queryString: string) => {
|
||||
if (this.isValidQuery(queryString)) {
|
||||
return this.shelfSearchService
|
||||
.search(queryString)
|
||||
.pipe(map((result) => result.result && result.result.slice(0, 5)));
|
||||
}
|
||||
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private setUpSearchMode() {
|
||||
this.searchMode$ = this.autocompleteResult$.pipe(
|
||||
map((result) =>
|
||||
!!result && !!result.length ? 'autocomplete' : 'standalone'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private setUpNavigationToDetailsPage() {
|
||||
this.selectedItem$
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
filter((item) => !isNullOrUndefined(item)),
|
||||
distinctUntilChanged(),
|
||||
filter(({ shouldNavigate }) => !!shouldNavigate),
|
||||
map(({ shouldNavigate, ...item }) => item)
|
||||
)
|
||||
.subscribe((item) => this.shelfNavigationService.navigateToDetails(item));
|
||||
}
|
||||
|
||||
private handleSearchResult(
|
||||
result: ListResponseArgsOfOrderItemListItemDTO,
|
||||
value: string
|
||||
@@ -122,4 +200,12 @@ export class ShelfSearchIpadComponent
|
||||
private setErrorMessage(message: string) {
|
||||
this.errorMessage$.next(message);
|
||||
}
|
||||
|
||||
private isValidQuery(queryString): boolean {
|
||||
return !!queryString && queryString.length > 3;
|
||||
}
|
||||
|
||||
private shouldNavigateToSelectedItem(target: 'click' | 'enter'): boolean {
|
||||
return !!this.selectedItem$.value && target === 'enter';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,15 @@ import { CommonModule } from '@angular/common';
|
||||
import { CollectingShelfScannerModule } from 'shared/public_api';
|
||||
import { ShelfSearchIpadComponent } from './shelf-search-ipad.component';
|
||||
import { ShelfSearchBarModule } from '../../../components';
|
||||
import { AutocompleteResultsModule } from '../../../components/autocomplete-results';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, CollectingShelfScannerModule, ShelfSearchBarModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CollectingShelfScannerModule,
|
||||
ShelfSearchBarModule,
|
||||
AutocompleteResultsModule,
|
||||
],
|
||||
exports: [ShelfSearchIpadComponent],
|
||||
declarations: [ShelfSearchIpadComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -3,9 +3,15 @@ import { NgModule } from '@angular/core';
|
||||
import { ShelfSearchComponent } from './shelf-search.component';
|
||||
import { ShelfSearchIpadModule } from './shelf-search-ipad';
|
||||
import { ShelfSearchDesktopModule } from './shelf-search-desktop';
|
||||
import { AutocompleteResultsModule } from '../../components/autocomplete-results';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, ShelfSearchIpadModule, ShelfSearchDesktopModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ShelfSearchIpadModule,
|
||||
ShelfSearchDesktopModule,
|
||||
AutocompleteResultsModule,
|
||||
],
|
||||
exports: [ShelfSearchComponent],
|
||||
declarations: [ShelfSearchComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -2,15 +2,21 @@ import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
|
||||
import { ChangeCurrentRoute } from 'apps/sales/src/app/core/store/actions/process.actions';
|
||||
import {
|
||||
ChangeCurrentRoute,
|
||||
AddProcess,
|
||||
} from 'apps/sales/src/app/core/store/actions/process.actions';
|
||||
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms/lib';
|
||||
import { ProcessSelectors } from 'apps/sales/src/app/core/store/selectors/process.selectors';
|
||||
import { Process } from 'apps/sales/src/app/core/models/process.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ShelfNavigationService {
|
||||
constructor(private store: Store, private router: Router) {}
|
||||
|
||||
navigateToDetails(order: OrderItemListItemDTO) {
|
||||
this.createTab();
|
||||
const path = this.getDetailsPath(order);
|
||||
const breadcrumb = this.getDetailsBreadcrumb(order);
|
||||
|
||||
@@ -24,9 +30,9 @@ export class ShelfNavigationService {
|
||||
searchQuery: string;
|
||||
numberOfHits: number;
|
||||
}) {
|
||||
this.createTab();
|
||||
const path = '/shelf/results';
|
||||
const breadcrumb = this.getResultListBreadcrumb(searchQuery, numberOfHits);
|
||||
console.log('navigateo');
|
||||
this.navigateToRoute(path, breadcrumb);
|
||||
}
|
||||
|
||||
@@ -62,4 +68,31 @@ export class ShelfNavigationService {
|
||||
order.compartmentCode ? 'c' : 'o'
|
||||
}`;
|
||||
}
|
||||
|
||||
private createTab() {
|
||||
const processExists =
|
||||
this.store.selectSnapshot(ProcessSelectors.getProcessesCount) > 0;
|
||||
if (!processExists) {
|
||||
this.createProcess();
|
||||
}
|
||||
}
|
||||
|
||||
private createProcess() {
|
||||
const newProcess = <Process>{
|
||||
id: 1,
|
||||
name: 'Vorgang 1',
|
||||
currentRoute: '/shelf/search',
|
||||
};
|
||||
|
||||
this.store.dispatch(new AddProcess(newProcess));
|
||||
this.store.dispatch(
|
||||
new AddBreadcrumb(
|
||||
<Breadcrumb>{
|
||||
name: 'Abholfach',
|
||||
path: '/shelf/search',
|
||||
},
|
||||
'shelf'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,7 @@ export class ShelfSearchFacadeService {
|
||||
string
|
||||
>;
|
||||
|
||||
constructor(
|
||||
private store: Store,
|
||||
private collectingShelfService: CollectingShelfService
|
||||
) {}
|
||||
constructor(private collectingShelfService: CollectingShelfService) {}
|
||||
|
||||
search(queryString: string) {
|
||||
const searchParams = queryString.trim();
|
||||
|
||||
@@ -10,6 +10,7 @@ $max-content-width: 916px;
|
||||
|
||||
/* COLORS */
|
||||
$text-light: #fff;
|
||||
$text-black: #000000;
|
||||
$isa-red: #f70400;
|
||||
$isa-white: #ffffff;
|
||||
$isa-lightgray: #e2e2e2;
|
||||
@@ -20,6 +21,7 @@ $layout-height: 90%;
|
||||
$layout-background: $isa-white;
|
||||
$layout-content-margin: 5px 3% 3% 3%;
|
||||
$layout-content-padding: 2rem 0.3rem 3rem;
|
||||
$isa-branch-bg: #e6eff9;
|
||||
$text-grey: #596470;
|
||||
$text-lightgrey: #596470;
|
||||
|
||||
@@ -40,11 +42,13 @@ $content-border-radius: 5px;
|
||||
/* HEADLINE */
|
||||
$headline-font-size-l: 26px;
|
||||
$headline-font-size-m: 22px;
|
||||
$headline-font-size-s: 20px;
|
||||
|
||||
/* INPUT */
|
||||
$input-height-size-l: 60px;
|
||||
$input-font-size: 21px;
|
||||
$input-letter-spacing: -0.01rem;
|
||||
$input-round-borders: 30px;
|
||||
|
||||
/* BUTTON */
|
||||
$button-font-weight: bold;
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: $headline-font-size-l;
|
||||
text-align: center;
|
||||
|
||||
@include mq-desktop() {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.isa-content-subline {
|
||||
|
||||
@@ -14,7 +14,7 @@ body {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
background-color: #e6eff9;
|
||||
background-color: $isa-branch-bg;
|
||||
|
||||
&.branch {
|
||||
background-color: #edeff0;
|
||||
|
||||
Reference in New Issue
Block a user