Merged PR 1600: #4172 #4200 Artikelsuche und Kundenbestellungen Navigation Trefferliste und Anzeige Suchbox Hint

#4172 #4200 Artikelsuche und Kundenbestellungen Navigation Trefferliste und Anzeige Suchbox Hint
This commit is contained in:
Nino Righi
2023-07-21 08:15:57 +00:00
committed by Lorenz Hilpert
parent 4180ee61d6
commit e9b2acca5b
17 changed files with 91 additions and 66 deletions

View File

@@ -52,11 +52,11 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
this._articleSearch.searchCompleted
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(([state, processId]) => {
if (state.searchState === '') {
const params = state.filter.getQueryParams();
if (state.hits === 1) {
const item = state.items.find((f) => f);
.subscribe(([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
this._navigationService.navigateToDetails({
processId,
itemId: item.id,

View File

@@ -1,11 +1,10 @@
import { Injectable } from '@angular/core';
import { DomainCatalogService } from '@domain/catalog';
import { Observable, Subject } from 'rxjs';
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { debounceTime, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO, QueryTokenDTO, UISettingsDTO } from '@swagger/cat';
import { ApplicationService } from '@core/application';
import { BranchDTO } from '@swagger/checkout';
import { Filter } from 'apps/shared/components/filter/src/lib';
@@ -68,7 +67,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
searchStarted = new Subject<{ clear?: boolean; reload?: boolean }>();
searchCompleted = new Subject<ArticleSearchState>();
searchCompleted = new Subject<{ state: ArticleSearchState; clear: boolean; orderBy: boolean }>();
searchboxHint$ = this.select((s) => (s.searchState === 'empty' ? 'Keine Suchergebnisse' : undefined));
@@ -84,7 +83,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
return this.get((s) => s.hits);
}
constructor(private catalog: DomainCatalogService, private _appService: ApplicationService) {
constructor(private catalog: DomainCatalogService) {
super({
filter: undefined,
hits: 0,
@@ -161,7 +160,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
}
}
search = this.effect((options$: Observable<{ clear?: boolean }>) =>
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean }>) =>
options$.pipe(
tap((options) => {
this.searchStarted.next({ clear: options?.clear });
@@ -197,11 +196,11 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
searchState,
});
}
this.searchCompleted.next(this.get());
this.searchCompleted.next({ state: this.get(), clear: options?.clear, orderBy: options?.orderBy });
},
(err) => {
this.patchState({ hits: 0, searchState: 'error', items: [] });
this.searchCompleted.next(this.get());
this.searchCompleted.next({ state: this.get(), clear: options?.clear, orderBy: options?.orderBy });
}
)
);

View File

@@ -35,7 +35,7 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
}
get showFilterClose$() {
return this._environment.matchDesktop$.pipe(map((matches) => !(matches && this.leftOutlet === 'search')));
return this._environment.matchDesktopLarge$.pipe(map((matches) => !(matches && this.leftOutlet === 'search')));
}
get leftOutlet() {
@@ -101,17 +101,17 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
this.articleSearch.search({ clear: true });
this.articleSearch.searchCompleted
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(([state, processId]) => {
if (state.searchState === '') {
.subscribe(([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
// Check if desktop is necessary, otherwise it would trigger navigation twice (Inside Article-Search.component and here)
if (state.hits === 1 && !this.isDesktop) {
const item = state.items.find((f) => f);
if (searchCompleted.state.hits === 1 && !this.isDesktop) {
const item = searchCompleted.state.items.find((f) => f);
this._navigationService.navigateToDetails({
processId,
itemId: item.id,
});
} else if (!this.isDesktop) {
const params = state.filter.getQueryParams();
const params = searchCompleted.state.filter.getQueryParams();
this._navigationService.navigateToResults({
processId,
queryParams: params,

View File

@@ -39,7 +39,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
get isDesktop$() {
return this._environment.matchDesktop$;
return this._environment.matchDesktopLarge$;
}
get leftOutlet() {

View File

@@ -11,7 +11,7 @@
[hint]="searchboxHint$ | async"
[loading]="fetching$ | async"
[inputGroup]="filter?.input | group: 'main'"
(search)="search(filter)"
(search)="search({filter, clear: true})"
[showDescription]="false"
[scanner]="true"
></shared-filter-input-group-main>
@@ -39,7 +39,10 @@
</div>
<div class="page-search-results__order-by" [class.page-search-results__order-by-main]="mainOutletActive$ | async">
<shared-order-by-filter [orderBy]="(filter$ | async)?.orderBy" (selectedOrderByChange)="search(); updateBreadcrumbs()">
<shared-order-by-filter
[orderBy]="(filter$ | async)?.orderBy"
(selectedOrderByChange)="search({ clear: true, orderBy: true }); updateBreadcrumbs()"
>
</shared-order-by-filter>
</div>

View File

@@ -173,7 +173,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
this.searchService.setHits(data.hits);
}
if (data.items?.length === 0 && this.rightOutletLocation !== 'filter') {
this.search();
this.search({ clear: true });
} else {
if (!this.isDesktop || this._navigationService.mainOutletActive(this.route)) {
this.scrollTop(Number(queryParams.scroll_position ?? 0));
@@ -195,7 +195,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
await this.createBreadcrumb(processId, queryParams);
}
if (this.isTablet || this.route?.outlet === 'main') {
if (!this.isDesktop || this.route?.outlet === 'main') {
await this.removeDetailsBreadcrumb(processId);
}
})
@@ -204,11 +204,18 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
this.subscriptions.add(
this.searchService.searchCompleted
.pipe(withLatestFrom(this.application.activatedProcessId$))
.subscribe(async ([state, processId]) => {
if (state.searchState === '') {
const params = state.filter.getQueryParams();
if ((state.hits === 1 && this.isTablet) || (!this.isTablet && !this._navigationService.mainOutletActive(this.route))) {
const item = state.items.find((f) => f);
.subscribe(async ([searchCompleted, processId]) => {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.searchState === '') {
// Keine Navigation bei OrderBy
if (searchCompleted?.orderBy) {
return;
}
// Navigation auf Details bzw. Results | Details wenn hits 1
// Navigation auf Results bei clear search, oder immer auf Tablet oder wenn große Resultliste aktiv ist
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
const ean = this.route?.snapshot?.params?.ean;
const itemId = this.route?.snapshot?.params?.id ? Number(this.route?.snapshot?.params?.id) : item.id; // Nicht zum ersten Item der Liste springen wenn bereits eines selektiert ist
@@ -226,8 +233,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
queryParams: this.isTablet ? undefined : params,
});
}
} else if (this.isTablet || this._navigationService.mainOutletActive(this.route)) {
this._navigationService.navigateToResults({
} else if (searchCompleted?.clear || this.isTablet || this._navigationService.mainOutletActive(this.route)) {
await this._navigationService.navigateToResults({
processId,
queryParams: params,
});
@@ -278,12 +285,12 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
return clean;
}
search(filter?: Filter) {
search({ filter, clear = false, orderBy = false }: { filter?: Filter; clear?: boolean; orderBy?: boolean }) {
if (!!filter) {
this.sharedFilterInputGroupMain.cancelAutocomplete();
}
this.searchService.search({ clear: true });
this.searchService.search({ clear, orderBy });
}
scrollTop(scrollPos: number) {
@@ -310,7 +317,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
if (index >= results.length - 20 && results.length - 20 > 0) {
this.searchService.search({ clear: false });
this.search({ clear: false });
}
}

View File

@@ -34,7 +34,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
}
get isDesktop$() {
return this._environmentService.matchDesktop$;
return this._environmentService.matchDesktopLarge$;
}
routerEvents$ = this._router.events.pipe(shareReplay());

View File

@@ -152,7 +152,7 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy {
}
get isDesktop$() {
return this._environmentService.matchDesktop$;
return this._environmentService.matchDesktopLarge$;
}
private _onDestroy$ = new Subject<void>();

View File

@@ -14,7 +14,7 @@ export class PageCheckoutComponent implements OnInit {
readonly breadcrumbKey$ = this.applicationService.activatedProcessId$.pipe(map((processId) => String(processId)));
get isDesktop$() {
return this._environmentService.matchDesktop$;
return this._environmentService.matchDesktopLarge$;
}
routerEvents$ = this._router.events.pipe(shareReplay());

View File

@@ -38,7 +38,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
}
get showFilterClose$() {
return this._environment.matchDesktop$.pipe(map((matches) => !(matches && this.leftOutlet === 'search')));
return this._environment.matchDesktopLarge$.pipe(map((matches) => !(matches && this.leftOutlet === 'search')));
}
get leftOutlet() {

View File

@@ -103,7 +103,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
readonly queryParams$ = this.select((s) => s.queryParams);
readonly searchResultSubject = new Subject<{ results: ListResponseArgsOfOrderItemListItemDTO; cached: boolean }>();
readonly searchResultSubject = new Subject<{ results: ListResponseArgsOfOrderItemListItemDTO; cached: boolean; clear: boolean }>();
readonly searchResultFromCacheSubject = new Subject<{ hits: number; results: OrderItemListItemDTO[] }>();
@@ -226,11 +226,11 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
fetching: false,
});
this.searchResultSubject.next({ results: res, cached });
this.searchResultSubject.next({ results: res, cached, clear: options?.clear });
},
(err: Error) => {
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
this.searchResultSubject.next({ results: err.error, cached });
this.searchResultSubject.next({ results: err.error, cached, clear: options?.clear });
} else {
this.searchResultSubject.next({
results: {
@@ -238,6 +238,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
message: err.message,
},
cached,
clear: options?.clear,
});
}
this.patchState({ fetching: false, silentFetching: false });

View File

@@ -40,7 +40,7 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
processId$ = this._activatedRoute.parent.parent.data.pipe(map((data) => +data.processId));
get isDesktop$() {
return this._environment.matchDesktop$;
return this._environment.matchDesktopLarge$;
}
get leftOutlet() {

View File

@@ -11,7 +11,7 @@
[hint]="message$ | async"
[loading]="loading$ | async"
[inputGroup]="filter?.input | group: 'main'"
(search)="search(filter)"
(search)="search({ filter, clear: true })"
[showDescription]="false"
[scanner]="true"
></shared-filter-input-group-main>

View File

@@ -166,7 +166,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._activatedRoute.queryParams.subscribe((queryParams) => {
const updateResults = queryParams.updateResults;
if (!!updateResults) {
this.search();
this.search({ clear: false });
const clean = { ...queryParams };
delete clean['updateResults'];
}
@@ -216,7 +216,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._customerOrderSearchStore.search({ siletReload: true });
}
} else if (branchChanged) {
this.search();
this.search({ clear: true });
}
this.createBreadcrumb(processId, params);
@@ -227,15 +227,19 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._customerOrderSearchStore.searchResultSubject.pipe(withLatestFrom(this.processId$)).subscribe(async ([result, processId]) => {
const queryParams = this._customerOrderSearchStore.filter?.getQueryParams();
await this.createBreadcrumb(processId, queryParams);
if (
(result.results.hits === 1 && this.isTablet) ||
(!this.isTablet && !this._navigationService.mainOutletActive(this._activatedRoute))
) {
if (result.results.hits === 0) {
return;
}
// Navigation auf Details bzw. Results | Details wenn hits 1
// Navigation auf Results bei clear search, oder immer auf Tablet oder wenn große Resultliste aktiv ist
if (result.results.hits === 1) {
await this.navigateToDetails(
processId,
result?.results?.result?.find((_) => true)
);
} else if (this.isTablet || this._navigationService.mainOutletActive(this._activatedRoute)) {
} else if (!!result?.clear || this.isTablet || this._navigationService.mainOutletActive(this._activatedRoute)) {
await this._navigationService.navigateToResults({
processId,
queryParams,
@@ -365,16 +369,16 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
!this._customerOrderSearchStore.silentFetching
) {
this.saveScrollPosition(this._customerOrderSearchStore.processId, this.scrollContainer?.scrollPos);
this._customerOrderSearchStore.search({});
this._customerOrderSearchStore.search({ clear: false });
}
}
search(filter?: Filter) {
search({ filter, clear = false }: { filter?: Filter; clear?: boolean }) {
if (!!filter) {
this.sharedFilterInputGroupMain.cancelAutocomplete();
}
this._customerOrderSearchStore.search({ clear: true });
this._customerOrderSearchStore.search({ clear });
}
async navigateToDetails(processId: number, orderItem: OrderItemListItemDTO) {

View File

@@ -35,7 +35,7 @@ export class CustomerOrderComponent implements OnInit {
}
get isDesktop$() {
return this._environmentService.matchDesktop$;
return this._environmentService.matchDesktopLarge$;
}
routerEvents$ = this._router.events.pipe(shareReplay());

View File

@@ -1,17 +1,20 @@
<div class="searchbox-input-wrapper">
<input
id="searchbox"
class="searchbox-input"
autocomplete="off"
#input
type="text"
[placeholder]="placeholder"
[(ngModel)]="query"
(ngModelChange)="setQuery($event, true, true)"
(focus)="clearHint(); focused.emit(true)"
(blur)="focused.emit(false)"
(keyup)="onKeyup($event)"
/>
<div class="searchbox-hint-wrapper">
<input
id="searchbox"
class="searchbox-input"
autocomplete="off"
#input
type="text"
[placeholder]="placeholder"
[(ngModel)]="query"
(ngModelChange)="setQuery($event, true, true)"
(focus)="clearHint(); focused.emit(true)"
(blur)="focused.emit(false)"
(keyup)="onKeyup($event)"
/>
<div *ngIf="showHint" class="searchbox-hint" (click)="focus()">{{ hint }}</div>
</div>
<button (click)="clear(); focus()" tabindex="-1" *ngIf="input.value" class="searchbox-clear-btn" type="button">
<shared-icon icon="close" [size]="32"></shared-icon>
</button>

View File

@@ -23,3 +23,11 @@
.searchbox-load-indicator {
@apply animate-spin w-14 h-14 grid items-center justify-center flex-shrink-0 flex-grow-0;
}
.searchbox-hint-wrapper {
@apply flex-grow flex items-center relative self-stretch;
}
.searchbox-hint {
@apply absolute flex items-center right-0 top-0 bottom-0 bg-white text-warning text-xl font-bold px-4 cursor-pointer;
}