Merged PR 1476: Branch Selector Bugfixes, BranchType Filtering, Unit Test update

Branch Selector Bugfixes, BranchType Filtering, Unit Test update
This commit is contained in:
Nino Righi
2023-02-01 16:04:19 +00:00
committed by Lorenz Hilpert
parent 6039545f9d
commit 66bf91e177
17 changed files with 292 additions and 128 deletions

View File

@@ -62,7 +62,7 @@ export class DomainAvailabilityService {
getStockByBranch(branch: BranchDTO): Observable<StockDTO> {
return this._stockService.StockGetStocksByBranch({ branchId: branch.id }).pipe(
map((response) => response.result),
map((result) => result.find((_) => true)),
map((result) => result?.find((_) => true)),
shareReplay(1)
);
}

View File

@@ -22,6 +22,7 @@ body {
@layer base {
::-webkit-scrollbar {
width: 0; // remove scrollbar space
height: 0;
background: transparent; // optional: just make scrollbar invisible */
}

View File

@@ -29,7 +29,13 @@ export class ModalAvailabilitiesComponent {
map(([branches, userbranch, search]) =>
branches
.filter(
(branch) => branch && branch?.isOnline && branch?.isShippingEnabled && branch?.isOrderingEnabled && branch.id !== userbranch.id
(branch) =>
branch &&
branch?.isOnline &&
branch?.isShippingEnabled &&
branch?.isOrderingEnabled &&
branch?.id !== userbranch?.id &&
branch?.branchType === 1
)
.filter((b) => b.name?.toLowerCase()?.indexOf(search?.toLowerCase()) > -1 || b.address?.zipCode.indexOf(search) > -1)
.sort((a, b) =>

View File

@@ -1,11 +1,11 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainCatalogService } from '@domain/catalog';
import { UiFilter, UiFilterInputGroupMainComponent } from '@ui/filter';
import { combineLatest, NEVER, Subscription } from 'rxjs';
import { catchError, debounceTime, first, map, switchMap } from 'rxjs/operators';
import { catchError, debounceTime, first, switchMap } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { isEqual } from 'lodash';
@@ -34,22 +34,15 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
private catalog: DomainCatalogService,
private route: ActivatedRoute,
private application: ApplicationService,
private breadcrumb: BreadcrumbService,
private cdr: ChangeDetectorRef
private breadcrumb: BreadcrumbService
) {}
ngOnInit() {
this.subscriptions.add(
combineLatest([this.application.activatedProcessId$, this.route.queryParams])
.pipe(
debounceTime(0),
switchMap(([processId, queryParams]) =>
this.application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, queryParams, selectedBranch })))
)
)
.subscribe(async ({ processId, queryParams, selectedBranch }) => {
.pipe(debounceTime(0))
.subscribe(async ([processId, queryParams]) => {
const processChanged = processId !== this.searchService.processId;
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
if (!(this.searchService.filter instanceof UiFilter)) {
await this.searchService.setDefaultFilter();
@@ -61,10 +54,6 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
this.searchService.setProcess(processId);
}
if (branchChanged) {
this.searchService.setBranch(selectedBranch);
}
const cleanQueryParams = this.cleanupQueryParams(queryParams);
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
@@ -74,6 +63,20 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
this.removeResultsAndDetailsBreadcrumbs(processId);
})
);
this.subscriptions.add(
this.application.activatedProcessId$
.pipe(
debounceTime(0),
switchMap((processId) => this.application.getSelectedBranch$(processId))
)
.subscribe((selectedBranch) => {
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
if (branchChanged) {
this.searchService.setBranch(selectedBranch);
}
})
);
}
ngOnDestroy() {

View File

@@ -1,6 +1,6 @@
:host {
@apply box-border grid;
max-height: calc(100vh - 284px);
max-height: calc(100vh - 364px);
height: 100vh;
grid-template-rows: auto 1fr;
}

View File

@@ -1,4 +1,5 @@
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']"
><shared-branch-selector [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)"></shared-branch-selector
></shared-breadcrumb>
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']">
<shared-branch-selector *ngIf="showBranchSelector" [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)">
</shared-branch-selector>
</shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -6,7 +6,30 @@ shell-breadcrumb {
@apply sticky z-sticky top-0;
}
shared-branch-selector.shared-branch-selector-opend {
@apply absolute top-0 right-0 w-full;
max-width: 814px;
shared-branch-selector {
@apply w-[304px] transition-[width] duration-300 ease-in-out;
}
shared-branch-selector.shared-branch-selector-opend {
@apply w-[814px];
}
::ng-deep page-catalog shared-branch-selector .shared-branch-selector-input-container {
@apply rounded-r-[5px];
}
::ng-deep page-catalog shared-branch-selector .shared-branch-selector-input-container .shared-branch-selector-input-icon {
@apply pl-2 border-l border-solid border-[#AEB7C1] transition-all duration-300 ease-in-out;
}
::ng-deep
page-catalog
shared-branch-selector.shared-branch-selector-opend
.shared-branch-selector-input-container
.shared-branch-selector-input-icon {
@apply pl-0 border-l-0;
}
::ng-deep page-catalog shared-branch-selector.shared-branch-selector-opend .shared-branch-selector-input-container {
@apply rounded-br-none;
}

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { BranchDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
@@ -16,7 +17,9 @@ export class PageCatalogComponent implements OnInit {
activatedProcessId$: Observable<string>;
selectedBranch$: Observable<BranchDTO>;
constructor(public application: ApplicationService, private _uiModal: UiModalService) {}
showBranchSelector = this.environment.isDesktop();
constructor(public application: ApplicationService, private _uiModal: UiModalService, private environment: EnvironmentService) {}
ngOnInit() {
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));

View File

@@ -1,5 +1,5 @@
:host {
@apply h-12;
@apply inline-block h-12;
}
.shared-branch-selector-input-container {
@@ -7,7 +7,7 @@
}
.shared-branch-selector-input {
@apply text-ellipsis whitespace-nowrap px-2 font-bold text-lg w-[14rem] text-black outline-none;
@apply text-ellipsis whitespace-nowrap px-2 font-bold text-lg w-full text-black outline-none;
}
.shared-branch-selector-opend.shared-branch-selector-input {

View File

@@ -7,6 +7,7 @@ import { UiCommonModule } from '@ui/common';
import { IconRegistry, UiIconModule } from '@ui/icon';
import { UiModalService } from '@ui/modal';
import { of } from 'rxjs';
import { take } from 'rxjs/operators';
import { BranchSelectorComponent } from './branch-selector.component';
import { BranchSelectorStore } from './branch-selector.store';
@@ -46,6 +47,14 @@ describe('BranchSelectorComponent', () => {
});
});
describe('branchType', () => {
it('should set branchType based on Input value', () => {
const branchType = 4;
spectator.setInput('branchType', branchType);
expect(spectator.component.branchType).toBe(branchType);
});
});
describe('writeValue()', () => {
it('should set value with new value', () => {
const newValue = { id: 123 };
@@ -78,6 +87,20 @@ describe('BranchSelectorComponent', () => {
});
});
describe('ngOnInit()', () => {
it('should call initBranchType()', () => {
const initBranchTypeSpye = spyOn(spectator.component, 'initBranchType');
spectator.component.ngOnInit();
expect(initBranchTypeSpye).toHaveBeenCalled();
});
it('should call store.loadBranches()', () => {
spyOn(branchSelectorStoreMock, 'loadBranches');
spectator.component.ngOnInit();
expect(branchSelectorStoreMock.loadBranches).toHaveBeenCalled();
});
});
describe('ngAfterViewInit()', () => {
it('should call initAutocomplete()', () => {
const initAutocompleteSpy = spyOn(spectator.component, 'initAutocomplete');
@@ -86,43 +109,65 @@ describe('BranchSelectorComponent', () => {
});
});
describe('initAutocomplete()', () => {
it('should call complete.asObservable()', () => {
const completeSpy = spyOn(spectator.component.complete, 'asObservable').and.returnValue(of('test'));
spectator.component.initAutocomplete();
expect(completeSpy).toHaveBeenCalled();
describe('initBranchType()', () => {
it('should call store.setBranchType(this.branchType)', () => {
const branchType = 4;
spectator.setInput('branchType', branchType);
spyOn(branchSelectorStoreMock, 'setBranchType');
spectator.component.initBranchType();
expect(branchSelectorStoreMock.setBranchType).toHaveBeenCalledWith(branchType);
});
// it('should call initAutocompleteFn({ query, branches })', () => {
it('should call store.setBranchType(this.branchType) with 1 if input is undefined', () => {
const branchType = undefined;
spectator.setInput('branchType', branchType);
spyOn(branchSelectorStoreMock, 'setBranchType');
spectator.component.initBranchType();
expect(branchSelectorStoreMock.setBranchType).toHaveBeenCalledWith(1);
});
});
describe('initAutocomplete()', () => {
// it('should call complete.asObservable()', () => {
// const completeSpy = spyOn(spectator.component.complete, 'asObservable').and.returnValue(of('test'));
// spectator.component.initAutocomplete();
// expect(completeSpy).toHaveBeenCalled();
// });
it('should call filterBranchesFn({ query, branches })', () => {
const branches = [{ id: 1 }, { id: 2 }];
const query = 'Test';
branchSelectorStoreMock.setBranches(branches);
const filterBranchesFnSpy = spyOn(spectator.component, 'filterBranchesFn');
spectator.component.initAutocomplete();
spectator.component.branches$.pipe(take(1)).subscribe(() => {
expect(filterBranchesFnSpy).toHaveBeenCalledWith({ query, branches });
});
spectator.component.complete.next(query);
});
// it('should call sortAutocompleteFn with BranchDTO[]', () => {
// const branches = [{ id: 1 }, { id: 2 }];
// const query = 'Test';
// branchSelectorStoreMock.setBranches(branches);
// spectator.component.complete.next(query);
// const initAutocompleteFnSpy = spyOn(spectator.component, 'initAutocompleteFn');
// // branches$ subscriben und schauen ob das richtige passiert?
// // ODER funktion umschreiben um besser testbar zu machen !
// spyOn(spectator.component, 'initAutocompleteFn').and.returnValue(branches);
// const sortAutocompleteFnSpy = spyOn(spectator.component, 'sortAutocompleteFn');
// spectator.component.initAutocomplete();
// expect(initAutocompleteFnSpy).toHaveBeenCalledWith({ query, branches });
// spectator.component.complete.next(query);
// expect(sortAutocompleteFnSpy).toHaveBeenCalledWith(branches);
// });
});
// it('should call _onDestroy$.complete()', () => {
// spyOn(spectator.component['_onDestroy$'], 'complete');
// spectator.component.ngOnDestroy();
// expect(spectator.component['_onDestroy$'].complete).toHaveBeenCalled();
// });
// describe('initAutocompleteFn()', () => {
// it('should call filterAutocompleteFn({ query, branches }) if query.length > 1', () => {
// const query = 'test';
// const branches = [{ id: 1 }, { id: 2 }];
// spectator.component.initAutocompleteFn({ query, branches });
// expect(spectator.component.complete.asObservable).toHaveBeenCalled();
// });
// describe('initAutocomplete()', () => {
// it('should call complete.asObservable()', () => {
// spyOn(spectator.component.complete, 'asObservable').and.returnValue(of());
// spectator.component.initAutocomplete();
// expect(spectator.component.complete.asObservable).toHaveBeenCalled();
// });
// });
// it('should not call filterAutocompleteFn({ query, branches }) if query.length <= 1 and return branches', () => {});
// });
// describe('filterAutocompleteFn()', () => {});

View File

@@ -2,24 +2,24 @@ import { CommonModule } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
HostBinding,
HostListener,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { provideComponentStore } from '@ngrx/component-store';
import { BranchDTO } from '@swagger/checkout';
import { BranchDTO, BranchType } from '@swagger/checkout';
import { UiAutocompleteComponent, UiAutocompleteModule } from '@ui/autocomplete';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
import { Observable, Subject } from 'rxjs';
import { map, withLatestFrom } from 'rxjs/operators';
import { combineLatest, Observable, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { BranchSelectorStore } from './branch-selector.store';
@Component({
@@ -29,7 +29,7 @@ import { BranchSelectorStore } from './branch-selector.store';
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'shared-branch-selector', tabindex: '0' },
providers: [
provideComponentStore(BranchSelectorStore),
BranchSelectorStore,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BranchSelectorComponent),
@@ -39,7 +39,7 @@ import { BranchSelectorStore } from './branch-selector.store';
imports: [CommonModule, FormsModule, UiIconModule, UiAutocompleteModule, UiCommonModule],
standalone: true,
})
export class BranchSelectorComponent implements AfterViewInit, ControlValueAccessor {
export class BranchSelectorComponent implements OnInit, AfterViewInit, ControlValueAccessor {
@ViewChild(UiAutocompleteComponent, { read: UiAutocompleteComponent, static: false })
autocompleteComponent: UiAutocompleteComponent;
@@ -61,13 +61,17 @@ export class BranchSelectorComponent implements AfterViewInit, ControlValueAcces
@Output() valueChange = new EventEmitter<BranchDTO>();
// Für Zielfilialen dürfen nur BranchType === 1 Filialen in der Liste erscheinen
// Für Bestellfilialen gelten auch alle BranchTypes
@Input() branchType?: BranchType = 1;
@Input()
disabled = false;
onChange = (value: BranchDTO) => {};
onTouched = () => {};
constructor(public store: BranchSelectorStore, private _cdr: ChangeDetectorRef) {}
constructor(public store: BranchSelectorStore) {}
writeValue(obj: any): void {
this.value = obj;
@@ -85,19 +89,27 @@ export class BranchSelectorComponent implements AfterViewInit, ControlValueAcces
this.disabled = isDisabled;
}
ngOnInit(): void {
this.initBranchType();
this.store.loadBranches();
}
ngAfterViewInit() {
this.initAutocomplete();
}
initBranchType() {
this.store.setBranchType(this.branchType ?? 1);
}
initAutocomplete() {
this.branches$ = this.complete.asObservable().pipe(
withLatestFrom(this.store.branches$),
map(([query, branches]) => this.initAutocompleteFn({ query, branches })),
this.branches$ = combineLatest([this.complete, this.store.branches$]).pipe(
map(([query, branches]) => this.filterBranchesFn({ query, branches })),
map(this.sortAutocompleteFn)
);
}
initAutocompleteFn = ({ query, branches }: { query: string; branches: BranchDTO[] }) =>
filterBranchesFn = ({ query, branches }: { query: string; branches: BranchDTO[] }) =>
query?.length > 1 ? this.filterAutocompleteFn({ query, branches }) : branches;
filterAutocompleteFn({ query, branches }: { query: string; branches: BranchDTO[] }): BranchDTO[] {

View File

@@ -1,10 +1,11 @@
import { DomainAvailabilityService } from '@domain/availability';
import { createServiceFactory, createSpyObject, SpectatorService, SpyObject } from '@ngneat/spectator';
import { BranchType } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { of, throwError } from 'rxjs';
import { BranchSelectorStore } from './branch-selector.store';
describe('BranchSelectorComponent', () => {
describe('BranchSelectorStore', () => {
let spectator: SpectatorService<BranchSelectorStore>;
let availabilityServiceMock: SpyObject<DomainAvailabilityService>;
let uiModalServiceMock: SpyObject<UiModalService>;
@@ -118,11 +119,24 @@ describe('BranchSelectorComponent', () => {
});
});
describe('ngrxOnStateInit()', () => {
it('should call loadBranches()', () => {
const loadBranchesSpy = spyOn(spectator.service, 'loadBranches');
spectator.service.ngrxOnStateInit();
expect(loadBranchesSpy).toHaveBeenCalled();
describe('get branchType', () => {
it('should return the branchType', () => {
const branchType = 1;
spectator.service.setBranchType(branchType);
expect(spectator.service.branchType).toEqual(branchType);
});
});
describe('get branchType$', () => {
it('should return the branchType$', () => {
const branchType = 1;
spectator.service.setBranchType(branchType);
spectator.service.branchType$
.subscribe((bt) => {
expect(bt).toEqual(branchType);
})
.unsubscribe();
});
});
@@ -144,12 +158,14 @@ describe('BranchSelectorComponent', () => {
expect(availabilityServiceMock.getBranches).toHaveBeenCalled();
});
it('should call loadBranchesResponseFn({ response, selectedBranch })', () => {
it('should call loadBranchesResponseFn({ response, selectedBranch, branchType })', () => {
const selectedBranch = { id: 123 };
const branchType = 1;
spectator.service.setSelectedBranch(selectedBranch);
spectator.service.setBranchType(branchType);
const loadBranchesResponseFnSpy = spyOn(spectator.service, 'loadBranchesResponseFn');
spectator.service.loadBranches();
expect(loadBranchesResponseFnSpy).toHaveBeenCalledWith({ response: branches, selectedBranch });
expect(loadBranchesResponseFnSpy).toHaveBeenCalledWith({ response: branches, selectedBranch, branchType });
});
it('should call loadBranchesErrorFn(error) if error got thrown', () => {
@@ -163,47 +179,52 @@ describe('BranchSelectorComponent', () => {
describe('loadBranchesResponseFn()', () => {
let branches = [];
let branchType: BranchType = 1;
beforeEach(() => {
branches = [{ id: 1 }, { id: 2 }];
branches = [
{ id: 1, branchType: 1 },
{ id: 2, branchType: 1 },
];
branchType = 1;
});
it('should call _filterBranches(response)', () => {
it('should call _filterBranches({ branches: response, branchType })', () => {
const filterBranchesSpy = spyOn<any>(spectator.service, '_filterBranches');
spectator.service.loadBranchesResponseFn({ response: branches });
expect(filterBranchesSpy).toHaveBeenCalledWith(branches);
spectator.service.loadBranchesResponseFn({ response: branches, branchType });
expect(filterBranchesSpy).toHaveBeenCalledWith({ branches, branchType });
});
it('should call setBranches() with branches after filtering', () => {
spyOn<any>(spectator.service, '_filterBranches').and.returnValue([{ id: 1 }]);
const setBranchesSpy = spyOn(spectator.service, 'setBranches');
spectator.service.loadBranchesResponseFn({ response: branches });
spectator.service.loadBranchesResponseFn({ response: branches, branchType });
expect(setBranchesSpy).toHaveBeenCalledWith([{ id: 1 }]);
});
it('should call setBranches() with default [] if getBranches() return undefined', () => {
spyOn<any>(spectator.service, '_filterBranches').and.returnValue([]);
const setBranchesSpy = spyOn(spectator.service, 'setBranches');
spectator.service.loadBranchesResponseFn({ response: branches });
spectator.service.loadBranchesResponseFn({ response: branches, branchType });
expect(setBranchesSpy).toHaveBeenCalledWith([]);
});
it('should call setSelectedBranchId(selectedBranchId) if selectedBranch is set', () => {
const selectedBranch = { id: 123 };
const setSelectedBranchSpy = spyOn(spectator.service, 'setSelectedBranch');
spectator.service.loadBranchesResponseFn({ response: branches, selectedBranch });
spectator.service.loadBranchesResponseFn({ response: branches, selectedBranch, branchType });
expect(setSelectedBranchSpy).toHaveBeenCalledWith(selectedBranch);
});
it('should not call setSelectedBranch(selectedBranch) if selectedBranch is not set', () => {
const setSelectedBranchSpy = spyOn(spectator.service, 'setSelectedBranch');
spectator.service.loadBranchesResponseFn({ response: branches });
spectator.service.loadBranchesResponseFn({ response: branches, branchType });
expect(setSelectedBranchSpy).not.toHaveBeenCalled();
});
it('should call setFetching(false)', () => {
const setFetchingSpy = spyOn(spectator.service, 'setFetching');
spectator.service.loadBranchesResponseFn({ response: branches });
spectator.service.loadBranchesResponseFn({ response: branches, branchType });
expect(setFetchingSpy).toHaveBeenCalledWith(false);
});
});
@@ -303,16 +324,19 @@ describe('BranchSelectorComponent', () => {
const branches = [
{
id: 1,
branchType: 2 as BranchType,
},
{
id: 2,
isOnline: true,
isShippingEnabled: true,
isOrderingEnabled: true,
branchType: 1 as BranchType,
},
];
const branchType: BranchType = 1;
expect(spectator.service['_filterBranches'](branches)).toEqual([branches[1]]);
expect(spectator.service['_filterBranches']({ branches, branchType })).toEqual([branches[1]]);
});
});
});

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { DomainAvailabilityService } from '@domain/availability';
import { ComponentStore, OnStateInit, tapResponse } from '@ngrx/component-store';
import { BranchDTO } from '@swagger/checkout';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { BranchDTO, BranchType } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
@@ -10,10 +10,11 @@ export interface BranchSelectorState {
fetching: boolean;
branches: BranchDTO[];
selectedBranch?: BranchDTO;
branchType?: BranchType;
}
@Injectable()
export class BranchSelectorStore extends ComponentStore<BranchSelectorState> implements OnStateInit {
export class BranchSelectorStore extends ComponentStore<BranchSelectorState> {
get query() {
return this.get((s) => s.query);
}
@@ -38,6 +39,12 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
readonly selectedBranch$ = this.select((s) => s.selectedBranch);
get branchType() {
return this.get((s) => s.branchType);
}
readonly branchType$ = this.select((s) => s.branchType);
constructor(private _availabilityService: DomainAvailabilityService, private _uiModal: UiModalService) {
super({
query: '',
@@ -46,18 +53,14 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
});
}
ngrxOnStateInit() {
this.loadBranches();
}
loadBranches = this.effect(($) =>
$.pipe(
tap((_) => this.setFetching(true)),
switchMap(() =>
this._availabilityService.getBranches().pipe(
withLatestFrom(this.selectedBranch$),
withLatestFrom(this.selectedBranch$, this.branchType$),
tapResponse(
([response, selectedBranch]) => this.loadBranchesResponseFn({ response, selectedBranch }),
([response, selectedBranch, branchType]) => this.loadBranchesResponseFn({ response, selectedBranch, branchType }),
(error: Error) => this.loadBranchesErrorFn(error)
)
)
@@ -65,8 +68,16 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
)
);
loadBranchesResponseFn = ({ response, selectedBranch }: { response: BranchDTO[]; selectedBranch?: BranchDTO }) => {
const branches = this._filterBranches(response);
loadBranchesResponseFn = ({
response,
selectedBranch,
branchType,
}: {
response: BranchDTO[];
selectedBranch?: BranchDTO;
branchType?: BranchType;
}) => {
const branches = this._filterBranches({ branches: response, branchType });
this.setBranches(branches ?? []);
if (selectedBranch) {
this.setSelectedBranch(selectedBranch);
@@ -108,12 +119,18 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
this.patchState({ fetching });
}
setBranchType(branchType: BranchType) {
this.patchState({ branchType });
}
formatBranch(branch?: BranchDTO) {
return branch ? (branch.key ? branch.key + ' - ' + branch.name : branch.name) : '';
}
// Frontend-Seitige Filterung der Branches vom Backend (Filterkriterien wie bei PDP - Weitere Verfügbarkeiten Modal)
private _filterBranches(branches: BranchDTO[]): BranchDTO[] {
return branches?.filter((branch) => branch && branch?.isOnline && branch?.isShippingEnabled && branch?.isOrderingEnabled);
private _filterBranches({ branches, branchType }: { branches: BranchDTO[]; branchType: BranchType }): BranchDTO[] {
return branches?.filter(
(branch) => branch && branch?.isOnline && branch?.isShippingEnabled && branch?.isOrderingEnabled && branch?.branchType === branchType
);
}
}

View File

@@ -1,24 +1,35 @@
.shared-breadcrumb {
@apply bg-white rounded h-12 grid items-center text-active-branch;
grid-template-columns: auto 1fr auto;
:host {
@apply block bg-white text-active-branch h-12 w-full max-w-full relative rounded-[5px];
}
.shared-breadcrumb__button-back {
@apply px-4 self-stretch grid grid-flow-col items-center gap-1 font-bold;
.shared-breadcrumb__prefix {
@apply absolute left-0 top-0 bottom-0 bg-white rounded-l-[5px];
}
.shared-breadcrumb__prefix::after {
@apply block w-4 -right-4 inset-y-0 absolute;
content: '';
background-image: linear-gradient(to right, white, transparent);
}
.shared-breadcrumb__suffix {
@apply absolute right-0 top-0 bottom-0 bg-white rounded-r-[5px];
}
.shared-breadcrumb__suffix::before {
@apply block w-4 -left-4 inset-y-0 absolute;
content: '';
background-image: linear-gradient(to left, white, transparent);
}
.shared-breadcrumb__crumbs {
@apply grid grid-flow-col self-stretch items-stretch justify-center;
@apply grid grid-flow-col h-full items-center justify-center w-full max-w-screen-tablet overflow-x-scroll mx-auto;
}
.shared-breadcrumb__crumb {
@apply px-2 flex flex-row items-center;
@apply whitespace-nowrap max-w-[200px] truncate;
}
.shared-breadcrumb__chevron {
@apply flex flex-row items-center;
}
.shared-breadcrumb__last-crumb {
@apply font-bold;
.shared-breadcrumb__button-back {
@apply flex flex-row items-center justify-start h-full px-4;
}

View File

@@ -1,16 +1,17 @@
<button
type="button"
class="shared-breadcrumb__button-back"
*ngIf="previousBreadcrumb$ | async; else placeholder; let crumb"
[routerLink]="crumb.path"
[queryParams]="crumb.params"
>
<ui-svg-icon icon="arrow-left" [size]="18"></ui-svg-icon> Zurück
</button>
<div class="shared-breadcrumb__prefix">
<button
type="button"
class="shared-breadcrumb__button-back"
*ngIf="previousBreadcrumb$ | async; else placeholder; let crumb"
[routerLink]="crumb.path"
[queryParams]="crumb.params"
>
<ui-svg-icon icon="arrow-left" [size]="18"></ui-svg-icon> Zurück
</button>
</div>
<div class="shared-breadcrumb__crumbs">
<ng-container *ngFor="let crumb of breadcrumbs$ | async; let last = last">
<a class="shared-breadcrumb__crumb" [routerLink]="crumb.path" [queryParams]="crumb.params">
<a class="shared-breadcrumb__crumb" [class.font-bold]="last" [routerLink]="crumb.path" [queryParams]="crumb.params">
<span>
{{ crumb.name }}
</span>
@@ -20,9 +21,10 @@
</div>
</ng-container>
</div>
<div class="shared-breadcrumb__suffix">
<ng-content></ng-content>
</div>
<ng-template #placeholder>
<div class="shared-breadcrumb__placeholder"></div>
</ng-template>
<ng-content></ng-content>

View File

@@ -1,9 +1,18 @@
import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
QueryList,
ViewChildren,
ViewEncapsulation,
} from '@angular/core';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { ComponentStore } from '@ngrx/component-store';
import { isEqual } from 'lodash';
import { combineLatest, Observable } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
import { BreadcrumbComponentState, DEFAULT_BREADCRUMB_COMPONENT_STATE } from './breadcrumb.component-state';
@Component({
@@ -11,12 +20,13 @@ import { BreadcrumbComponentState, DEFAULT_BREADCRUMB_COMPONENT_STATE } from './
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
class: 'shared-breadcrumb',
},
})
export class BreadcrumbComponent extends ComponentStore<BreadcrumbComponentState> {
export class BreadcrumbComponent extends ComponentStore<BreadcrumbComponentState> implements AfterViewInit {
@ViewChildren('breadcrumb') breadcrumbElements: QueryList<ElementRef>;
/**
* Eindeutiger Schlüssel, der die Breadcrumb-Liste identifiziert.
*/
@@ -63,4 +73,10 @@ export class BreadcrumbComponent extends ComponentStore<BreadcrumbComponentState
constructor(private _breadcrumbService: BreadcrumbService) {
super(DEFAULT_BREADCRUMB_COMPONENT_STATE);
}
ngAfterViewInit(): void {
this.breadcrumbElements.changes.pipe(debounceTime(100)).subscribe((elements) => {
this.breadcrumbElements.last.nativeElement.scrollIntoView();
});
}
}

View File

@@ -125,7 +125,7 @@ export class UiFilterInputGroupMainComponent implements OnInit, OnDestroy, After
distinctUntilChanged(),
switchMap(() => this.autocompleteProvider.complete(this.uiInput).pipe(takeUntil(this._cancelComplete))),
tap((complete) => {
if (complete.length > 0) {
if (complete?.length > 0) {
this.autocompleteComponent.open();
} else {
this.autocompleteComponent.close();