mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 1472: Branch Selector Update
Branch Selector Update
This commit is contained in:
committed by
Lorenz Hilpert
parent
872db4085c
commit
fdc1dadd36
@@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BranchDTO } from '@swagger/oms';
|
||||
import { isBoolean, isNumber } from '@utils/common';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
@@ -31,7 +33,7 @@ export class ApplicationService {
|
||||
return this.activatedProcessIdSubject.asObservable();
|
||||
}
|
||||
|
||||
constructor(private store: Store) {}
|
||||
constructor(private store: Store, private _availability: DomainAvailabilityService) {}
|
||||
|
||||
getProcesses$(section?: 'customer' | 'branch') {
|
||||
const processes$ = this.store.select(selectProcesses);
|
||||
@@ -68,8 +70,8 @@ export class ApplicationService {
|
||||
this.store.dispatch(patchProcessData({ processId, data }));
|
||||
}
|
||||
|
||||
getSelectedBranchId$(processId: number): Observable<number> {
|
||||
return this.getProcessById$(processId).pipe(map((process) => process?.data?.selectedBranchId));
|
||||
getSelectedBranch$(processId: number): Observable<BranchDTO> {
|
||||
return this.getProcessById$(processId).pipe(map((process) => process?.data?.selectedBranch));
|
||||
}
|
||||
|
||||
async createProcess(process: ApplicationProcess) {
|
||||
@@ -90,6 +92,13 @@ export class ApplicationService {
|
||||
process.confirmClosing = true;
|
||||
}
|
||||
|
||||
if (process.type === 'cart') {
|
||||
const currentBranch = await this._availability.getCurrentBranch().pipe(first()).toPromise();
|
||||
process.data = {
|
||||
selectedBranch: currentBranch,
|
||||
};
|
||||
}
|
||||
|
||||
process.created = this._createTimestamp();
|
||||
process.activated = 0;
|
||||
this.store.dispatch(addProcess({ process }));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
IsAuthenticatedGuard,
|
||||
} from './guards';
|
||||
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
import { ShellComponent, ShellModule } from './shell';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
@@ -125,6 +126,13 @@ const routes: Routes = [
|
||||
},
|
||||
];
|
||||
|
||||
if (isDevMode()) {
|
||||
routes.unshift({
|
||||
path: 'preview',
|
||||
component: PreviewComponent,
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes), ShellModule, TokenLoginModule],
|
||||
exports: [RouterModule],
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from
|
||||
import { RootStateService } from './store/root-state.service';
|
||||
import * as Commands from './commands';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UserStateService } from '@swagger/isa';
|
||||
import { PreviewComponent } from './preview';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -79,6 +79,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
CoreCommandModule.forRoot(Object.values(Commands)),
|
||||
CoreLoggerModule.forRoot(),
|
||||
AppStoreModule,
|
||||
PreviewComponent,
|
||||
AuthModule.forRoot(),
|
||||
CoreApplicationModule.forRoot(),
|
||||
UiModalModule.forRoot(),
|
||||
|
||||
3
apps/isa-app/src/app/preview/index.ts
Normal file
3
apps/isa-app/src/app/preview/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// start:ng42.barrel
|
||||
export * from './preview.component';
|
||||
// end:ng42.barrel
|
||||
3
apps/isa-app/src/app/preview/preview.component.css
Normal file
3
apps/isa-app/src/app/preview/preview.component.css
Normal file
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid min-h-screen content-center justify-center;
|
||||
}
|
||||
1
apps/isa-app/src/app/preview/preview.component.html
Normal file
1
apps/isa-app/src/app/preview/preview.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<shared-branch-selector [value]="selectedBranch$ | async" (valueChange)="setNewBranch($event)"></shared-branch-selector>
|
||||
24
apps/isa-app/src/app/preview/preview.component.ts
Normal file
24
apps/isa-app/src/app/preview/preview.component.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BranchDTO } from '@swagger/oms';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-preview',
|
||||
templateUrl: 'preview.component.html',
|
||||
styleUrls: ['preview.component.css'],
|
||||
imports: [CommonModule, BranchSelectorComponent],
|
||||
standalone: true,
|
||||
})
|
||||
export class PreviewComponent implements OnInit {
|
||||
selectedBranch$ = new BehaviorSubject<BranchDTO>({});
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
setNewBranch(branch: BranchDTO) {
|
||||
this.selectedBranch$.next(branch);
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
withLatestFrom(this.filter$, this.items$, this._appService.getProcesses$('customer')),
|
||||
switchMap(([options, filter, items, processes]) => {
|
||||
const process = processes?.find((process) => process.id === this.processId);
|
||||
const stockId = process?.data?.selectedBranchId;
|
||||
const stockId = process?.data?.selectedBranch?.id;
|
||||
return this.searchRequest({
|
||||
...filter.getQueryToken(),
|
||||
skip: items.length,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']"
|
||||
><shared-branch-selector
|
||||
class="shared-branch-selector-breadcrumb"
|
||||
[value]="selectedBranchId$ | async"
|
||||
(valueChange)="patchProcessData($event)"
|
||||
></shared-branch-selector
|
||||
><shared-branch-selector [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)"></shared-branch-selector
|
||||
></shared-breadcrumb>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -5,3 +5,8 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BranchDTO } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { first, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-catalog',
|
||||
@@ -11,20 +13,28 @@ import { first, map, switchMap, tap } from 'rxjs/operators';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PageCatalogComponent implements OnInit {
|
||||
activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
activatedProcessId$: Observable<string>;
|
||||
selectedBranch$: Observable<BranchDTO>;
|
||||
|
||||
selectedBranchId$: Observable<number>;
|
||||
constructor(public application: ApplicationService, private _uiModal: UiModalService) {}
|
||||
|
||||
constructor(public application: ApplicationService) {}
|
||||
ngOnInit() {
|
||||
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedBranchId$ = this.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.application.getSelectedBranchId$(Number(processId)))
|
||||
);
|
||||
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
|
||||
}
|
||||
|
||||
async patchProcessData(selectedBranchId: number) {
|
||||
const processId = await this.activatedProcessId$.pipe(first()).toPromise();
|
||||
this.application.patchProcessData(Number(processId), { selectedBranchId });
|
||||
async patchProcessData(selectedBranch: BranchDTO) {
|
||||
try {
|
||||
const processId = await this.activatedProcessId$.pipe(first()).toPromise();
|
||||
this.application.patchProcessData(Number(processId), { selectedBranch });
|
||||
} catch (error) {
|
||||
this._uiModal.open({
|
||||
title: 'Fehler beim aktualisieren der Daten des Prozesses',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
config: { showScrollbarY: false },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { map, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-goods-out',
|
||||
@@ -9,7 +11,18 @@ import { map } from 'rxjs/operators';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class GoodsOutComponent {
|
||||
processId$ = this._activatedRoute.data.pipe(map((data) => String(data.processId)));
|
||||
// #3359 Set Default selectedBranch if navigate to goods-out
|
||||
processId$ = this._activatedRoute.data.pipe(
|
||||
withLatestFrom(this._availability.getCurrentBranch()),
|
||||
map(([data, currentBranch]) => {
|
||||
this._application.patchProcessData(Number(data.processId), { selectedBranch: currentBranch });
|
||||
return String(data.processId);
|
||||
})
|
||||
);
|
||||
|
||||
constructor(private _activatedRoute: ActivatedRoute) {}
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _application: ApplicationService,
|
||||
private _availability: DomainAvailabilityService
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<div
|
||||
[class.shared-branch-selector-expand]="autocompleteComponent?.opend"
|
||||
class="shared-branch-selector-container flex flex-row w-72 z-modal"
|
||||
>
|
||||
<ui-svg-icon class="mr-2 text-inactive-customer z-modal" icon="magnify" [size]="32"></ui-svg-icon>
|
||||
<button class="shared-branch-selector-input-container" (click)="branchInput.focus(); openComplete()">
|
||||
<button class="shared-branch-selector-input-icon">
|
||||
<ui-svg-icon class="text-inactive-customer" icon="magnify" [size]="32"></ui-svg-icon>
|
||||
</button>
|
||||
<input
|
||||
(click)="autocompleteComponent.open()"
|
||||
class="shared-branch-selector-input text-ellipsis whitespace-nowrap mr-2 font-bold text-black z-modal"
|
||||
[class.shared-branch-selector-expand]="autocompleteComponent?.opend"
|
||||
#branchInput
|
||||
class="shared-branch-selector-input"
|
||||
uiInput
|
||||
type="text"
|
||||
[placeholder]="'Filiale suchen'"
|
||||
@@ -14,19 +12,25 @@
|
||||
(ngModelChange)="onQueryChange($event)"
|
||||
(keyup)="onKeyup($event)"
|
||||
/>
|
||||
<button class="shared-branch-selector-clear-input mr-2 z-modal" *ngIf="(query$ | async).length > 0" type="button" (click)="clear()">
|
||||
<button
|
||||
class="shared-branch-selector-clear-input-icon"
|
||||
*ngIf="(query$ | async)?.length > 0"
|
||||
type="button"
|
||||
(click)="clear(); $event.stopPropagation()"
|
||||
>
|
||||
<ui-svg-icon class="text-black" icon="close" [size]="32"></ui-svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
<hr class="mt-3 mx-4 text-active-customer z-modal" *ngIf="autocompleteComponent?.opend" />
|
||||
</button>
|
||||
<ui-autocomplete class="shared-branch-selector-autocomplete z-modal w-full">
|
||||
<hr class="ml-3 text-active-customer" *ngIf="autocompleteComponent?.opend" uiAutocompleteSeparator />
|
||||
<p *ngIf="(branches$ | async)?.length > 0" class="text-base p-4 font-normal" uiAutocompleteLabel>Filialvorschläge</p>
|
||||
<button
|
||||
class="shared-branch-selector-autocomplete-option"
|
||||
(click)="setBranch(branch.id)"
|
||||
[class.shared-branch-selector-selected]="value && value.id === branch.id"
|
||||
(click)="setBranch(branch)"
|
||||
*ngFor="let branch of branches$ | async"
|
||||
[uiAutocompleteItem]="branch"
|
||||
>
|
||||
<span class="text-lg font-semibold">{{ store.formatBranchById(branch.id) }}</span>
|
||||
<span class="text-lg font-semibold">{{ store.formatBranch(branch) }}</span>
|
||||
</button>
|
||||
</ui-autocomplete>
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
.shared-branch-selector-expand {
|
||||
@apply w-full;
|
||||
.shared-branch-selector-input-container {
|
||||
@apply w-full flex flex-row items-center z-modal bg-white p-2 min-w-[21rem];
|
||||
}
|
||||
|
||||
.shared-branch-selector-input-icon {
|
||||
@apply mr-1;
|
||||
}
|
||||
|
||||
.shared-branch-selector-input {
|
||||
@apply text-ellipsis whitespace-nowrap px-4 py-1 font-bold text-black w-full;
|
||||
}
|
||||
|
||||
.shared-branch-selector-input::placeholder {
|
||||
@apply text-black;
|
||||
}
|
||||
|
||||
.shared-branch-selector-breadcrumb {
|
||||
@apply justify-self-end bg-white z-modal py-2 rounded-t-md;
|
||||
max-width: 814px;
|
||||
.shared-branch-selector-clear-input-icon {
|
||||
@apply ml-1;
|
||||
}
|
||||
|
||||
.shared-branch-selector-breadcrumb.shared-branch-selector-opend {
|
||||
@apply absolute mt-3 w-full;
|
||||
}
|
||||
|
||||
.shared-branch-selector ui-autocomplete .ui-autocomplete-output-wrapper {
|
||||
::ng-deep shared-branch-selector ui-autocomplete .ui-autocomplete-output-wrapper {
|
||||
@apply overflow-hidden overflow-y-auto max-w-content rounded-b-md;
|
||||
max-height: 500px;
|
||||
width: 100%;
|
||||
@@ -26,3 +29,7 @@
|
||||
@apply py-2;
|
||||
}
|
||||
}
|
||||
|
||||
.shared-branch-selector-autocomplete-option.shared-branch-selector-selected {
|
||||
@apply bg-customer;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// import { CommonModule } from '@angular/common';
|
||||
// import { FormsModule } from '@angular/forms';
|
||||
// import { ActivatedRoute } from '@angular/router';
|
||||
// import { DomainAvailabilityService } from '@domain/availability';
|
||||
// import { createComponentFactory, createSpyObject, mockProvider, Spectator, SpyObject } from '@ngneat/spectator';
|
||||
// import { provideComponentStore } from '@ngrx/component-store';
|
||||
// import { UiAutocompleteModule } from '@ui/autocomplete';
|
||||
// import { UiCommonModule } from '@ui/common';
|
||||
// import { IconRegistry, UiIconModule } from '@ui/icon';
|
||||
// import { from, of } from 'rxjs';
|
||||
|
||||
@@ -11,26 +14,18 @@
|
||||
|
||||
// describe('BranchSelectorComponent', () => {
|
||||
// let spectator: Spectator<BranchSelectorComponent>;
|
||||
// let activatedRouteMock: SpyObject<ActivatedRoute>;
|
||||
// let branchSelectorStoreMock: jasmine.SpyObj<BranchSelectorStore>;
|
||||
|
||||
// const createComponent = createComponentFactory({
|
||||
// component: BranchSelectorComponent,
|
||||
// imports: [CommonModule, FormsModule, UiIconModule, UiAutocompleteModule],
|
||||
// mocks: [IconRegistry],
|
||||
// componentProviders: [
|
||||
// mockProvider(BranchSelectorStore, {
|
||||
// loadBranches: async () => {},
|
||||
// query$: of('test'),
|
||||
// imports: [UiCommonModule, CommonModule, FormsModule, UiIconModule, UiAutocompleteModule],
|
||||
// mocks: [IconRegistry, DomainAvailabilityService],
|
||||
// }),
|
||||
// ],
|
||||
// });
|
||||
|
||||
// beforeEach(() => {
|
||||
// activatedRouteMock = createSpyObject(ActivatedRoute);
|
||||
// spectator = createComponent({
|
||||
// providers: [{ provide: ActivatedRoute, useValue: { params: from([{ id: 1 }]), parent: { data: from([{ processId: 123 }]) } } }],
|
||||
// });
|
||||
// spectator = createComponent();
|
||||
|
||||
// branchSelectorStoreMock = spectator.inject(BranchSelectorStore, true);
|
||||
// });
|
||||
@@ -39,62 +34,37 @@
|
||||
// expect(spectator.component).toBeTruthy();
|
||||
// });
|
||||
|
||||
// describe('ngOnInit()', () => {
|
||||
// it('should call', () => {
|
||||
// // branchSelectorStoreMock.query$;
|
||||
// // branchSelectorStoreMock.query$.and
|
||||
// // branchSelectorStoreMock.query$ = of('test')
|
||||
// // spectator.component.query$.subscribe((q) => {
|
||||
// // expect(q).toBe('test');
|
||||
// // done();
|
||||
// // });
|
||||
// });
|
||||
// // describe('ngAfterViewInit()', () => {
|
||||
// // it('should call initAutocomplete()', () => {
|
||||
// // const initAutocompleteSpy = spyOn(spectator.component, 'initAutocomplete');
|
||||
// // spectator.component.ngAfterViewInit();
|
||||
// // expect(initAutocompleteSpy).toHaveBeenCalled();
|
||||
// // });
|
||||
// // });
|
||||
|
||||
// it('should return an observable that completes when ref.dismissed$ emits', () => {
|
||||
// // snackbarRefMock.options = {
|
||||
// // duration: 50
|
||||
// // };
|
||||
// // const expectedMarble = '10ms |';
|
||||
// // const expectedValues = { };
|
||||
// // testScheduler.run(({ cold, expectObservable }) => {
|
||||
// // spectator.component['_onDestroy$'].next();
|
||||
// // const timer$ = spectator.component.createTimer$();
|
||||
// // expectObservable(timer$).toBe(expectedMarble, expectedValues);
|
||||
// // })
|
||||
// });
|
||||
// });
|
||||
// // describe('ngOnDestroy()', () => {
|
||||
// // it('should emit _onDestroy$', () => {
|
||||
// // spyOn(spectator.component['_onDestroy$'], 'next');
|
||||
// // spectator.component.ngOnDestroy();
|
||||
// // expect(spectator.component['_onDestroy$'].next).toHaveBeenCalled();
|
||||
// // });
|
||||
|
||||
// describe('ngAfterViewInit()', () => {
|
||||
// it('should call initAutocomplete()', () => {
|
||||
// const initAutocompleteSpy = spyOn(spectator.component, 'initAutocomplete');
|
||||
// spectator.component.ngAfterViewInit();
|
||||
// expect(initAutocompleteSpy).toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
// // it('should call _onDestroy$.complete()', () => {
|
||||
// // spyOn(spectator.component['_onDestroy$'], 'complete');
|
||||
// // spectator.component.ngOnDestroy();
|
||||
// // expect(spectator.component['_onDestroy$'].complete).toHaveBeenCalled();
|
||||
// // });
|
||||
// // });
|
||||
|
||||
// describe('ngOnDestroy()', () => {
|
||||
// it('should emit _onDestroy$', () => {
|
||||
// spyOn(spectator.component['_onDestroy$'], 'next');
|
||||
// spectator.component.ngOnDestroy();
|
||||
// expect(spectator.component['_onDestroy$'].next).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 call _onDestroy$.complete()', () => {
|
||||
// spyOn(spectator.component['_onDestroy$'], 'complete');
|
||||
// spectator.component.ngOnDestroy();
|
||||
// expect(spectator.component['_onDestroy$'].complete).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();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('filterAutocompleteFn()', () => {});
|
||||
// // describe('filterAutocompleteFn()', () => {});
|
||||
|
||||
// // it('should not emit search with the current query when key is ArrowUp or ArrowDown', () => {
|
||||
// // spectator.component.query = 'my query';
|
||||
|
||||
@@ -2,16 +2,17 @@ import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, FormsModule } from '@angular/forms';
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { provideComponentStore } from '@ngrx/component-store';
|
||||
import { BranchDTO } from '@swagger/oms';
|
||||
import { UiAutocompleteComponent, UiAutocompleteModule } from '@ui/autocomplete';
|
||||
@@ -26,9 +27,15 @@ import { BranchSelectorStore } from './branch-selector.store';
|
||||
templateUrl: 'branch-selector.component.html',
|
||||
styleUrls: ['branch-selector.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'shared-branch-selector', tabindex: '0' },
|
||||
providers: [provideComponentStore(BranchSelectorStore)],
|
||||
host: { tabindex: '0' },
|
||||
providers: [
|
||||
provideComponentStore(BranchSelectorStore),
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => BranchSelectorComponent),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
imports: [CommonModule, FormsModule, UiIconModule, UiAutocompleteModule, UiCommonModule],
|
||||
standalone: true,
|
||||
})
|
||||
@@ -44,23 +51,23 @@ export class BranchSelectorComponent implements AfterViewInit, ControlValueAcces
|
||||
query$ = this.store.query$;
|
||||
|
||||
@Input()
|
||||
get value(): number {
|
||||
get value(): BranchDTO {
|
||||
return this._value;
|
||||
}
|
||||
set value(value: number) {
|
||||
set value(value: BranchDTO) {
|
||||
this.setBranch(value);
|
||||
}
|
||||
private _value: number;
|
||||
private _value: BranchDTO;
|
||||
|
||||
@Output() valueChange = new EventEmitter<number>();
|
||||
@Output() valueChange = new EventEmitter<BranchDTO>();
|
||||
|
||||
@Input()
|
||||
disabled = false;
|
||||
|
||||
onChange = (value: number) => {};
|
||||
onChange = (value: BranchDTO) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(public store: BranchSelectorStore) {}
|
||||
constructor(public store: BranchSelectorStore, private _cdr: ChangeDetectorRef) {}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.value = obj;
|
||||
@@ -85,45 +92,57 @@ export class BranchSelectorComponent implements AfterViewInit, ControlValueAcces
|
||||
initAutocomplete() {
|
||||
this.branches$ = this.complete.asObservable().pipe(
|
||||
withLatestFrom(this.store.branches$),
|
||||
map(([query, branches]) => this.initAutocompleteFn({ query, branches }))
|
||||
map(([query, branches]) => this.initAutocompleteFn({ query, branches })),
|
||||
map(this.sortAutocompleteFn)
|
||||
);
|
||||
}
|
||||
|
||||
initAutocompleteFn = ({ query, branches }: { query: string; branches: BranchDTO[] }) =>
|
||||
query.length > 1 ? this.filterAutocompleteFn({ query, branches }) : [];
|
||||
query?.length > 1 ? this.filterAutocompleteFn({ query, branches }) : branches;
|
||||
|
||||
filterAutocompleteFn({ query, branches }: { query: string; branches: BranchDTO[] }): BranchDTO[] {
|
||||
return branches?.filter(
|
||||
(branch) =>
|
||||
(!!branch.key && branch.key.toLowerCase().includes(query.toLowerCase())) ||
|
||||
(!!branch.name && branch.name.toLowerCase().includes(query.toLowerCase()))
|
||||
branch?.name?.toLowerCase()?.indexOf(query.toLowerCase()) >= 0 ||
|
||||
branch?.key?.toLowerCase()?.indexOf(query.toLowerCase()) >= 0 ||
|
||||
branch?.address?.city?.toLowerCase()?.indexOf(query.toLowerCase()) >= 0 ||
|
||||
branch?.address?.zipCode?.indexOf(query) >= 0
|
||||
);
|
||||
}
|
||||
|
||||
sortAutocompleteFn = (branches: BranchDTO[]): BranchDTO[] =>
|
||||
branches?.sort((branchA, branchB) => branchA?.name?.localeCompare(branchB?.name));
|
||||
|
||||
onQueryChange(query: string) {
|
||||
this.store.setQuery(query);
|
||||
this.complete.next(query);
|
||||
}
|
||||
|
||||
setBranch(branchId?: number) {
|
||||
if (this.value !== branchId) {
|
||||
this._value = branchId;
|
||||
this.store.setSelectedBranchId(branchId);
|
||||
this.emitValues(branchId);
|
||||
openComplete() {
|
||||
this.autocompleteComponent?.open();
|
||||
this.store.setQuery('');
|
||||
this.complete.next('');
|
||||
}
|
||||
|
||||
setBranch(branch?: BranchDTO) {
|
||||
if (this.value !== branch) {
|
||||
this._value = branch;
|
||||
this.store.setSelectedBranch(branch);
|
||||
this.emitValues(branch);
|
||||
}
|
||||
this.closeAutocomplete();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.store.setSelectedBranchId();
|
||||
this.store.setSelectedBranch();
|
||||
this.emitValues();
|
||||
this.closeAndClearAutocomplete();
|
||||
}
|
||||
|
||||
emitValues(branchId?: number) {
|
||||
this.onChange(branchId);
|
||||
emitValues(branch?: BranchDTO) {
|
||||
this.onChange(branch);
|
||||
this.onTouched();
|
||||
this.valueChange.emit(branchId);
|
||||
this.valueChange.emit(branch);
|
||||
}
|
||||
|
||||
closeAndClearAutocomplete() {
|
||||
@@ -131,18 +150,11 @@ export class BranchSelectorComponent implements AfterViewInit, ControlValueAcces
|
||||
this.complete.next('');
|
||||
}
|
||||
|
||||
@HostListener('focusin', ['$event'])
|
||||
clearAutocomplete(event: FocusEvent) {
|
||||
if (!(event?.target as HTMLElement)?.classList.contains('shared-branch-selector-clear-input')) {
|
||||
this.store.setQuery('');
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('focusout', ['$event'])
|
||||
closeAutocomplete(event?: FocusEvent) {
|
||||
const isAutocompleteOption = (event?.relatedTarget as HTMLElement)?.classList.contains('shared-branch-selector-autocomplete-option');
|
||||
if (!isAutocompleteOption) {
|
||||
this.store.setQuery(this.store.formatBranchById(this.value));
|
||||
this.store.setQuery(this.store.formatBranch(this.value));
|
||||
this.closeAndClearAutocomplete();
|
||||
}
|
||||
}
|
||||
@@ -157,18 +169,18 @@ export class BranchSelectorComponent implements AfterViewInit, ControlValueAcces
|
||||
|
||||
handleEnterEvent() {
|
||||
if (this.autocompleteComponent?.opend && this.autocompleteComponent?.activeItem) {
|
||||
this.setBranch(this.autocompleteComponent?.activeItem?.item?.id);
|
||||
this.setBranch(this.autocompleteComponent?.activeItem?.item);
|
||||
}
|
||||
}
|
||||
|
||||
handleArrowUpDownEvent(event: KeyboardEvent) {
|
||||
this.autocompleteComponent?.handleKeyboardEvent(event);
|
||||
if (this.autocompleteComponent?.activeItem) {
|
||||
this.store.setQuery(this.store.formatBranchById(this.autocompleteComponent.activeItem.item.id));
|
||||
this.store.setQuery(this.store.formatBranch(this.autocompleteComponent.activeItem.item));
|
||||
}
|
||||
}
|
||||
|
||||
@HostBinding('class.shared-branch-selector-opend') get autocompleteOpend() {
|
||||
return this.autocompleteComponent?.opend;
|
||||
@HostBinding('class.shared-branch-selector-opend') get class() {
|
||||
return !!this.autocompleteComponent?.opend;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { createServiceFactory, createSpyObject, SpectatorService, SpyObject } from '@ngneat/spectator';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { BranchSelectorStore } from './branch-selector.store';
|
||||
|
||||
describe('BranchSelectorComponent', () => {
|
||||
let spectator: SpectatorService<BranchSelectorStore>;
|
||||
let availabilityServiceMock: SpyObject<DomainAvailabilityService>;
|
||||
let uiModalServiceMock: SpyObject<UiModalService>;
|
||||
|
||||
const createService = createServiceFactory({
|
||||
service: BranchSelectorStore,
|
||||
@@ -16,8 +18,13 @@ describe('BranchSelectorComponent', () => {
|
||||
availabilityServiceMock.getBranches.and.returnValue(of([]));
|
||||
availabilityServiceMock.getCurrentBranch.and.returnValue(of({}));
|
||||
|
||||
uiModalServiceMock = createSpyObject(UiModalService);
|
||||
|
||||
spectator = createService({
|
||||
providers: [{ provide: DomainAvailabilityService, useValue: availabilityServiceMock }],
|
||||
providers: [
|
||||
{ provide: DomainAvailabilityService, useValue: availabilityServiceMock },
|
||||
{ provide: UiModalService, useValue: uiModalServiceMock },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,33 +95,22 @@ describe('BranchSelectorComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('get selectedBranchId', () => {
|
||||
it('should return the selectedBranchId', () => {
|
||||
const selectedBranchId = 123;
|
||||
spectator.service.setSelectedBranchId(selectedBranchId);
|
||||
expect(spectator.service.selectedBranchId).toBe(selectedBranchId);
|
||||
describe('get selectedBranch', () => {
|
||||
it('should return the selectedBranch', () => {
|
||||
const selectedBranch = { id: 123 };
|
||||
spectator.service.setSelectedBranch(selectedBranch);
|
||||
expect(spectator.service.selectedBranch).toEqual(selectedBranch);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get selectedBranchId$', () => {
|
||||
it('should return the selectedBranchId$', () => {
|
||||
const selectedBranchId = 123;
|
||||
spectator.service.setSelectedBranchId(selectedBranchId);
|
||||
describe('get selectedBranch$', () => {
|
||||
it('should return the selectedBranch$', () => {
|
||||
const selectedBranch = { id: 123 };
|
||||
spectator.service.setSelectedBranch(selectedBranch);
|
||||
|
||||
spectator.service.selectedBranchId$
|
||||
.subscribe((sbid) => {
|
||||
expect(sbid).toBe(selectedBranchId);
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe('get currentBranch$', () => {
|
||||
it('should call _availabilityService.getCurrentBranch()', (done) => {
|
||||
spectator.service.currentBranch$
|
||||
.subscribe(() => {
|
||||
expect(availabilityServiceMock.getCurrentBranch).toHaveBeenCalled();
|
||||
done();
|
||||
spectator.service.selectedBranch$
|
||||
.subscribe((sb) => {
|
||||
expect(sb).toEqual(selectedBranch);
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
@@ -130,11 +126,9 @@ describe('BranchSelectorComponent', () => {
|
||||
|
||||
describe('loadBranches', () => {
|
||||
const branches = [{ id: 1 }, { id: 2 }];
|
||||
const currentBranch = { id: 2 };
|
||||
|
||||
beforeEach(() => {
|
||||
availabilityServiceMock.getBranches.and.returnValue(of(branches));
|
||||
availabilityServiceMock.getCurrentBranch.and.returnValue(of(currentBranch));
|
||||
});
|
||||
|
||||
it('should call setFetching(true)', () => {
|
||||
@@ -148,12 +142,12 @@ describe('BranchSelectorComponent', () => {
|
||||
expect(availabilityServiceMock.getBranches).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call loadBranchesResponseFn({ response, currentBranch, selectedBranchId })', () => {
|
||||
const selectedBranchId = 123;
|
||||
spectator.service.setSelectedBranchId(selectedBranchId);
|
||||
it('should call loadBranchesResponseFn({ response, selectedBranch })', () => {
|
||||
const selectedBranch = { id: 123 };
|
||||
spectator.service.setSelectedBranch(selectedBranch);
|
||||
const loadBranchesResponseFnSpy = spyOn(spectator.service, 'loadBranchesResponseFn');
|
||||
spectator.service.loadBranches();
|
||||
expect(loadBranchesResponseFnSpy).toHaveBeenCalledWith({ response: branches, currentBranch, selectedBranchId });
|
||||
expect(loadBranchesResponseFnSpy).toHaveBeenCalledWith({ response: branches, selectedBranch });
|
||||
});
|
||||
|
||||
it('should call loadBranchesErrorFn(error) if error got thrown', () => {
|
||||
@@ -167,83 +161,85 @@ describe('BranchSelectorComponent', () => {
|
||||
|
||||
describe('loadBranchesResponseFn()', () => {
|
||||
let branches = [];
|
||||
let currentBranch = {};
|
||||
|
||||
beforeEach(() => {
|
||||
branches = [{ id: 1 }, { id: 2 }];
|
||||
currentBranch = { id: 2 };
|
||||
});
|
||||
|
||||
it('should call _filterBranches(response, currentBranch)', () => {
|
||||
it('should call _filterBranches(response)', () => {
|
||||
const filterBranchesSpy = spyOn<any>(spectator.service, '_filterBranches');
|
||||
spectator.service.loadBranchesResponseFn({ response: branches, currentBranch });
|
||||
expect(filterBranchesSpy).toHaveBeenCalledWith(branches, currentBranch);
|
||||
spectator.service.loadBranchesResponseFn({ response: branches });
|
||||
expect(filterBranchesSpy).toHaveBeenCalledWith(branches);
|
||||
});
|
||||
|
||||
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, currentBranch });
|
||||
spectator.service.loadBranchesResponseFn({ response: branches });
|
||||
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, currentBranch });
|
||||
spectator.service.loadBranchesResponseFn({ response: branches });
|
||||
expect(setBranchesSpy).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
it('should call setSelectedBranchId(selectedBranchId) if selectedBranchId is set', () => {
|
||||
const selectedBranchId = 2;
|
||||
const setSelectedBranchIdSpy = spyOn(spectator.service, 'setSelectedBranchId');
|
||||
spectator.service.loadBranchesResponseFn({ response: branches, currentBranch, selectedBranchId });
|
||||
expect(setSelectedBranchIdSpy).toHaveBeenCalledWith(selectedBranchId);
|
||||
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 });
|
||||
expect(setSelectedBranchSpy).toHaveBeenCalledWith(selectedBranch);
|
||||
});
|
||||
|
||||
it('should not call setSelectedBranchId(selectedBranchId) if selectedBranchId is not set', () => {
|
||||
const setSelectedBranchIdSpy = spyOn(spectator.service, 'setSelectedBranchId');
|
||||
spectator.service.loadBranchesResponseFn({ response: branches, currentBranch });
|
||||
expect(setSelectedBranchIdSpy).not.toHaveBeenCalled();
|
||||
it('should not call setSelectedBranch(selectedBranch) if selectedBranch is not set', () => {
|
||||
const setSelectedBranchSpy = spyOn(spectator.service, 'setSelectedBranch');
|
||||
spectator.service.loadBranchesResponseFn({ response: branches });
|
||||
expect(setSelectedBranchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call setFetching(false)', () => {
|
||||
const setFetchingSpy = spyOn(spectator.service, 'setFetching');
|
||||
spectator.service.loadBranchesResponseFn({ response: branches, currentBranch });
|
||||
spectator.service.loadBranchesResponseFn({ response: branches });
|
||||
expect(setFetchingSpy).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadBranchesErrorFn()', () => {
|
||||
it('should call loadBranches()', () => {
|
||||
it('should call uiModalServiceMock.open with appropriate UiErrorModal data', () => {
|
||||
const error = new Error('test');
|
||||
spyOn(console, 'error');
|
||||
spectator.service.loadBranchesErrorFn(error);
|
||||
expect(console.error).toHaveBeenCalledWith('BranchSelectorStore.loadBranches()', error);
|
||||
expect(uiModalServiceMock.open).toHaveBeenCalledWith({
|
||||
title: 'Fehler beim Laden der Filialen',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
config: { showScrollbarY: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSelectedBranchId()', () => {
|
||||
it('should call patchState with empty query and undefined selectedBranchId if function call without arguments', () => {
|
||||
describe('setSelectedBranch()', () => {
|
||||
it('should call patchState with empty query and undefined selectedBranch if function call without arguments', () => {
|
||||
const patchStateSpy = spyOn(spectator.service, 'patchState');
|
||||
spectator.service.setSelectedBranchId();
|
||||
expect(patchStateSpy).toHaveBeenCalledWith({ selectedBranchId: undefined, query: '' });
|
||||
spectator.service.setSelectedBranch();
|
||||
expect(patchStateSpy).toHaveBeenCalledWith({ selectedBranch: undefined, query: '' });
|
||||
});
|
||||
|
||||
it('should call formatBranchById(selectedBranchId) if function call with selectedBranchId', () => {
|
||||
const selectedBranchId = 123;
|
||||
const formatBranchByIdSpy = spyOn(spectator.service, 'formatBranchById');
|
||||
spectator.service.setSelectedBranchId(selectedBranchId);
|
||||
expect(formatBranchByIdSpy).toHaveBeenCalledWith(selectedBranchId);
|
||||
it('should call formatBranch(selectedBranch) if function call with selectedBranch', () => {
|
||||
const selectedBranch = { id: 123 };
|
||||
const formatBranchBySpy = spyOn(spectator.service, 'formatBranch');
|
||||
spectator.service.setSelectedBranch(selectedBranch);
|
||||
expect(formatBranchBySpy).toHaveBeenCalledWith(selectedBranch);
|
||||
});
|
||||
|
||||
it('should call patchState({ selectedBranchId, query: this.formatBranchById(selectedBranchId) }) if function call with selectedBranchId', () => {
|
||||
const selectedBranchId = 123;
|
||||
it('should call patchState({ selectedBranch, query: this.formatBranch(selectedBranch) }) if function call with selectedBranch', () => {
|
||||
const selectedBranch = { id: 123 };
|
||||
const query = 'test-branch';
|
||||
spyOn(spectator.service, 'formatBranchById').and.returnValue(query);
|
||||
spyOn(spectator.service, 'formatBranch').and.returnValue(query);
|
||||
const patchStateSpy = spyOn(spectator.service, 'patchState');
|
||||
spectator.service.setSelectedBranchId(selectedBranchId);
|
||||
expect(patchStateSpy).toHaveBeenCalledWith({ selectedBranchId, query });
|
||||
spectator.service.setSelectedBranch(selectedBranch);
|
||||
expect(patchStateSpy).toHaveBeenCalledWith({ selectedBranch, query });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,41 +270,29 @@ describe('BranchSelectorComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatBranchById()', () => {
|
||||
it('should call _getBranchById with branchId', () => {
|
||||
const branchId = 123;
|
||||
const getBranchByIdSpy = spyOn<any>(spectator.service, '_getBranchById');
|
||||
spectator.service.formatBranchById(branchId);
|
||||
expect(getBranchByIdSpy).toHaveBeenCalledWith(branchId);
|
||||
describe('formatBranch()', () => {
|
||||
it('should return a formatted branch with key', () => {
|
||||
const branch = {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
key: 'T',
|
||||
};
|
||||
const formattedString = spectator.service.formatBranch(branch);
|
||||
expect(formattedString).toBe('T - Test');
|
||||
});
|
||||
|
||||
it('should call _formatBranch(branch)', () => {
|
||||
const branchId = 123;
|
||||
const branch = { id: branchId };
|
||||
spyOn<any>(spectator.service, '_getBranchById').and.returnValue(branch);
|
||||
const formatBranchSpy = spyOn<any>(spectator.service, '_formatBranch');
|
||||
spectator.service.formatBranchById(branchId);
|
||||
expect(formatBranchSpy).toHaveBeenCalledWith(branch);
|
||||
it('should return a formatted branch without key', () => {
|
||||
const branch = {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
};
|
||||
const formattedString = spectator.service.formatBranch(branch);
|
||||
expect(formattedString).toBe('Test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getBranchById()', () => {
|
||||
it('should find and return the branch by id', () => {
|
||||
const branchId = 2;
|
||||
const branches = [{ id: 1 }, { id: 2 }];
|
||||
spectator.service.setBranches(branches);
|
||||
const branch = spectator.service['_getBranchById'](branchId);
|
||||
expect(branch).toEqual(branches[1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_formatBranch()', () => {
|
||||
it('should find and return the branch by id', () => {
|
||||
const branchId = 2;
|
||||
const branches = [{ id: 1 }, { id: 2 }];
|
||||
spectator.service.setBranches(branches);
|
||||
const branch = spectator.service['_getBranchById'](branchId);
|
||||
expect(branch).toEqual(branches[1]);
|
||||
it('should return an empty string if called without branch', () => {
|
||||
const formattedString = spectator.service.formatBranch();
|
||||
expect(formattedString).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -326,11 +310,7 @@ describe('BranchSelectorComponent', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const currentBranch = {
|
||||
id: 1,
|
||||
};
|
||||
|
||||
expect(spectator.service['_filterBranches'](branches, currentBranch)).toEqual([branches[1]]);
|
||||
expect(spectator.service['_filterBranches'](branches)).toEqual([branches[1]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { ComponentStore, OnStateInit, tapResponse } from '@ngrx/component-store';
|
||||
import { BranchDTO } from '@swagger/oms';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
export interface BranchSelectorState {
|
||||
query: string;
|
||||
fetching: boolean;
|
||||
branches: BranchDTO[];
|
||||
selectedBranchId?: number;
|
||||
selectedBranch?: BranchDTO;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -31,17 +32,13 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
|
||||
|
||||
readonly branches$ = this.select((s) => s.branches);
|
||||
|
||||
get selectedBranchId() {
|
||||
return this.get((s) => s.selectedBranchId);
|
||||
get selectedBranch() {
|
||||
return this.get((s) => s.selectedBranch);
|
||||
}
|
||||
|
||||
readonly selectedBranchId$ = this.select((s) => s.selectedBranchId);
|
||||
readonly selectedBranch$ = this.select((s) => s.selectedBranch);
|
||||
|
||||
get currentBranch$() {
|
||||
return this._availabilityService.getCurrentBranch();
|
||||
}
|
||||
|
||||
constructor(private _availabilityService: DomainAvailabilityService) {
|
||||
constructor(private _availabilityService: DomainAvailabilityService, private _uiModal: UiModalService) {
|
||||
super({
|
||||
query: '',
|
||||
fetching: false,
|
||||
@@ -58,9 +55,9 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
|
||||
tap((_) => this.setFetching(true)),
|
||||
switchMap(() =>
|
||||
this._availabilityService.getBranches().pipe(
|
||||
withLatestFrom(this.currentBranch$, this.selectedBranchId$),
|
||||
withLatestFrom(this.selectedBranch$),
|
||||
tapResponse(
|
||||
([response, currentBranch, selectedBranchId]) => this.loadBranchesResponseFn({ response, currentBranch, selectedBranchId }),
|
||||
([response, selectedBranch]) => this.loadBranchesResponseFn({ response, selectedBranch }),
|
||||
(error: Error) => this.loadBranchesErrorFn(error)
|
||||
)
|
||||
)
|
||||
@@ -68,38 +65,36 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
|
||||
)
|
||||
);
|
||||
|
||||
loadBranchesResponseFn = ({
|
||||
response,
|
||||
currentBranch,
|
||||
selectedBranchId,
|
||||
}: {
|
||||
response: BranchDTO[];
|
||||
currentBranch: BranchDTO;
|
||||
selectedBranchId?: number;
|
||||
}) => {
|
||||
const branches = this._filterBranches(response, currentBranch);
|
||||
loadBranchesResponseFn = ({ response, selectedBranch }: { response: BranchDTO[]; selectedBranch?: BranchDTO }) => {
|
||||
const branches = this._filterBranches(response);
|
||||
this.setBranches(branches ?? []);
|
||||
if (selectedBranchId) {
|
||||
this.setSelectedBranchId(selectedBranchId);
|
||||
if (selectedBranch) {
|
||||
this.setSelectedBranch(selectedBranch);
|
||||
}
|
||||
this.setFetching(false);
|
||||
};
|
||||
|
||||
loadBranchesErrorFn = (error: Error) => console.error('BranchSelectorStore.loadBranches()', error);
|
||||
loadBranchesErrorFn = (error: Error) =>
|
||||
this._uiModal.open({
|
||||
title: 'Fehler beim Laden der Filialen',
|
||||
content: UiErrorModalComponent,
|
||||
data: error,
|
||||
config: { showScrollbarY: false },
|
||||
});
|
||||
|
||||
setBranches(branches: BranchDTO[]) {
|
||||
this.patchState({ branches });
|
||||
}
|
||||
|
||||
setSelectedBranchId(selectedBranchId?: number) {
|
||||
if (selectedBranchId) {
|
||||
setSelectedBranch(selectedBranch?: BranchDTO) {
|
||||
if (selectedBranch) {
|
||||
this.patchState({
|
||||
selectedBranchId,
|
||||
query: this.formatBranchById(selectedBranchId),
|
||||
selectedBranch,
|
||||
query: this.formatBranch(selectedBranch),
|
||||
});
|
||||
} else {
|
||||
this.patchState({
|
||||
selectedBranchId,
|
||||
selectedBranch,
|
||||
query: '',
|
||||
});
|
||||
}
|
||||
@@ -113,23 +108,12 @@ export class BranchSelectorStore extends ComponentStore<BranchSelectorState> imp
|
||||
this.patchState({ fetching });
|
||||
}
|
||||
|
||||
formatBranchById(branchId: number) {
|
||||
const branch = this._getBranchById(branchId);
|
||||
return this._formatBranch(branch);
|
||||
}
|
||||
|
||||
private _getBranchById(branchId: number) {
|
||||
return this.branches.find((branch) => branch.id === branchId);
|
||||
}
|
||||
|
||||
private _formatBranch(branch?: BranchDTO) {
|
||||
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[], currentBranch: BranchDTO): BranchDTO[] {
|
||||
return branches?.filter(
|
||||
(branch) => branch && branch?.isOnline && branch?.isShippingEnabled && branch?.isOrderingEnabled && branch.id !== currentBranch.id
|
||||
);
|
||||
private _filterBranches(branches: BranchDTO[]): BranchDTO[] {
|
||||
return branches?.filter((branch) => branch && branch?.isOnline && branch?.isShippingEnabled && branch?.isOrderingEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Highlightable } from '@angular/cdk/a11y';
|
||||
import { Component, ChangeDetectionStrategy, Input, ViewEncapsulation, HostBinding, HostListener } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, ViewEncapsulation, HostBinding, HostListener, ElementRef } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '[uiAutocompleteItem]',
|
||||
@@ -22,7 +22,7 @@ export class UiAutocompleteItemComponent implements Highlightable {
|
||||
|
||||
onClick = (item: any) => {};
|
||||
|
||||
constructor() {}
|
||||
constructor(private _elementRef: ElementRef<any>) {}
|
||||
|
||||
@HostListener('click')
|
||||
handleClickEvent() {
|
||||
@@ -40,4 +40,8 @@ export class UiAutocompleteItemComponent implements Highlightable {
|
||||
setInactiveStyles(): void {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
scrollIntoView() {
|
||||
this._elementRef?.nativeElement?.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="ui-autocomplete-output-wrapper scroll-bar" *ngIf="opend">
|
||||
<ng-content select="[uiAutocompleteSeparator]"></ng-content>
|
||||
<ng-content select="[uiAutocompleteLabel]"></ng-content>
|
||||
<ng-content select="[uiAutocompleteItem]"></ng-content>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('UiSearchboxAutocomplete', () => {
|
||||
it('should call listKeyManager.onKeyDown and not emit selectItem and not call close on ArrowDown key', () => {
|
||||
spyOn(spectator.component, 'close');
|
||||
spyOn(spectator.component.listKeyManager, 'onKeydown');
|
||||
spyOnProperty(spectator.component, 'activeItem', 'get').and.returnValue({ item: 'Test' });
|
||||
spyOnProperty(spectator.component, 'activeItem', 'get').and.returnValue({ item: 'Test', scrollIntoView: () => {} });
|
||||
|
||||
let selectItem;
|
||||
spectator.output('selectItem').subscribe((item) => (selectItem = item));
|
||||
|
||||
@@ -48,7 +48,6 @@ export class UiAutocompleteComponent implements AfterContentInit, OnDestroy {
|
||||
this.subscriptions.add(
|
||||
this.items.changes.subscribe(() => {
|
||||
this.registerItemOnClick();
|
||||
this.activateFirstItem();
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -75,6 +74,7 @@ export class UiAutocompleteComponent implements AfterContentInit, OnDestroy {
|
||||
break;
|
||||
default:
|
||||
this.listKeyManager.onKeydown(event);
|
||||
this.activeItem?.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user