mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 1476: Branch Selector Bugfixes, BranchType Filtering, Unit Test update
Branch Selector Bugfixes, BranchType Filtering, Unit Test update
This commit is contained in:
committed by
Lorenz Hilpert
parent
6039545f9d
commit
66bf91e177
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ body {
|
||||
@layer base {
|
||||
::-webkit-scrollbar {
|
||||
width: 0; // remove scrollbar space
|
||||
height: 0;
|
||||
background: transparent; // optional: just make scrollbar invisible */
|
||||
}
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()', () => {});
|
||||
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user