mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1477: Branch Selector Changes
Branch Selector Changes
This commit is contained in:
committed by
Lorenz Hilpert
parent
6ba7c54089
commit
06fe8b3742
@@ -33,7 +33,7 @@ export class ApplicationService {
|
||||
return this.activatedProcessIdSubject.asObservable();
|
||||
}
|
||||
|
||||
constructor(private store: Store, private _availability: DomainAvailabilityService) {}
|
||||
constructor(private store: Store) {}
|
||||
|
||||
getProcesses$(section?: 'customer' | 'branch') {
|
||||
const processes$ = this.store.select(selectProcesses);
|
||||
@@ -92,13 +92,6 @@ export class ApplicationService {
|
||||
process.confirmClosing = true;
|
||||
}
|
||||
|
||||
if (process.type === 'cart') {
|
||||
const currentBranch = await this._availability.getDefaultBranch().pipe(first()).toPromise();
|
||||
process.data = {
|
||||
selectedBranch: currentBranch,
|
||||
};
|
||||
}
|
||||
|
||||
process.created = this._createTimestamp();
|
||||
process.activated = 0;
|
||||
this.store.dispatch(addProcess({ process }));
|
||||
|
||||
@@ -607,4 +607,12 @@ export class DomainAvailabilityService {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getInStock({ itemIds, branchId }: { itemIds: number[]; branchId: number }): Observable<StockInfoDTO[]> {
|
||||
return this.getStockByBranch({ id: branchId }).pipe(
|
||||
mergeMap((stock) =>
|
||||
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
109
apps/domain/availability/src/lib/in-stock.service.ts
Normal file
109
apps/domain/availability/src/lib/in-stock.service.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { StockInfoDTO } from '@swagger/remi';
|
||||
import { groupBy } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { bufferTime } from 'rxjs/operators';
|
||||
import { DomainAvailabilityService } from './availability.service';
|
||||
|
||||
export type ItemBranch = { itemId: number; branchId: number };
|
||||
export type InStock = ItemBranch & { inStock: number; fetching: boolean };
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainInStockService {
|
||||
private _inStockQueue = new Subject<ItemBranch>();
|
||||
private _inStockMap = new BehaviorSubject<Record<string, number>>({});
|
||||
private _inStockFetchingMap = new BehaviorSubject<Record<string, boolean>>({});
|
||||
|
||||
constructor(private _availability: DomainAvailabilityService) {
|
||||
// TODO: Approvement bufferWhen statt bufferTime
|
||||
this._inStockQueue.pipe(bufferTime(1000)).subscribe((itemBranchData) => {
|
||||
if (itemBranchData?.length > 0) {
|
||||
this._fetchStockData(itemBranchData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getKey({ itemId, branchId }: ItemBranch) {
|
||||
return `${itemId}_${branchId}`;
|
||||
}
|
||||
|
||||
getInStock$({ itemId, branchId }: ItemBranch): Observable<InStock> {
|
||||
return new Observable<InStock>((obs) => {
|
||||
const key = this.getKey({ itemId, branchId });
|
||||
|
||||
this.loadStock({ itemId, branchId });
|
||||
|
||||
const sub = combineLatest([this._inStockMap, this._inStockFetchingMap]).subscribe(([inStockMap, inStockFetchingMap]) => {
|
||||
const inStock: InStock = {
|
||||
itemId,
|
||||
branchId,
|
||||
inStock: inStockMap[key],
|
||||
fetching: inStockFetchingMap[key] ?? false,
|
||||
};
|
||||
|
||||
obs.next(inStock);
|
||||
});
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async loadStock({ itemId, branchId }: { itemId: number; branchId?: number }) {
|
||||
let bId = branchId;
|
||||
if (!bId) {
|
||||
const defaultBranch = await this._availability.getDefaultBranch().toPromise();
|
||||
bId = defaultBranch.id;
|
||||
}
|
||||
this._addToInStockQueue({ itemId, branchId: bId });
|
||||
}
|
||||
|
||||
private _addToInStockQueue({ itemId, branchId }: ItemBranch): void {
|
||||
this._inStockQueue.next({ itemId, branchId });
|
||||
this._setInStockFetching({ itemId, branchId }, true);
|
||||
}
|
||||
|
||||
private _setInStockFetching({ itemId, branchId }: ItemBranch, value: boolean) {
|
||||
const key = this.getKey({ itemId, branchId });
|
||||
const current = this._inStockFetchingMap.getValue();
|
||||
this._inStockFetchingMap.next({ ...current, [key]: value });
|
||||
}
|
||||
|
||||
private _setInStock({ itemId, branchId }: ItemBranch, value: number) {
|
||||
const key = this.getKey({ itemId, branchId });
|
||||
const current = this._inStockMap.getValue();
|
||||
this._inStockMap.next({ ...current, [key]: value });
|
||||
}
|
||||
|
||||
private _fetchStockData(itemBranchData: ItemBranch[]) {
|
||||
const grouped = groupBy(itemBranchData, 'branchId');
|
||||
Object.keys(grouped).forEach((key) => {
|
||||
const branchId = Number(key);
|
||||
const itemIds = itemBranchData.filter((itemBranch) => itemBranch.branchId === branchId).map((item) => item.itemId);
|
||||
this._availability.getInStock({ itemIds, branchId }).subscribe(
|
||||
(stockInfos) => this._fetchStockDataResponse({ itemIds, branchId })(stockInfos),
|
||||
(error) => this._fetchStockDataError({ itemIds, branchId })(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _fetchStockDataResponse = ({ itemIds, branchId }: { itemIds: number[]; branchId: number }) => (stockInfos: StockInfoDTO[]) => {
|
||||
itemIds.forEach((itemId) => {
|
||||
const stockInfo = stockInfos.find((stockInfo) => stockInfo.itemId === itemId && stockInfo.branchId === branchId);
|
||||
let inStock = 0;
|
||||
if (stockInfo) {
|
||||
inStock = stockInfo.inStock;
|
||||
}
|
||||
this._setInStockFetching({ itemId, branchId }, false);
|
||||
this._setInStock({ itemId, branchId }, inStock);
|
||||
});
|
||||
};
|
||||
|
||||
private _fetchStockDataError = ({ itemIds, branchId }: { itemIds: number[]; branchId: number }) => (error: Error) => {
|
||||
itemIds.forEach((itemId) => {
|
||||
this._setInStockFetching({ itemId, branchId }, false);
|
||||
this._setInStock({ itemId, branchId }, 0);
|
||||
});
|
||||
console.error('DomainInStockService._fetchStockData()', error);
|
||||
};
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
*/
|
||||
|
||||
export * from './lib/availability.service';
|
||||
export * from './lib/in-stock.service';
|
||||
export * from './lib/availability.module';
|
||||
export * from './lib/defs';
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="shell-footer-wrapper">
|
||||
<shell-footer *ngIf="section$ | async; let section">
|
||||
<ng-container *ngIf="section === 'customer'">
|
||||
<a [routerLink]="[customerBasePath$ | async, 'product']" routerLinkActive="active">
|
||||
<a (click)="resetSelectedBranch()" [routerLink]="[customerBasePath$ | async, 'product']" routerLinkActive="active">
|
||||
<ui-icon icon="catalog" size="30px"></ui-icon>
|
||||
Artikelsuche
|
||||
</a>
|
||||
|
||||
@@ -164,6 +164,13 @@ export class ShellComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async resetSelectedBranch() {
|
||||
const processId = await this.activatedProcessId$.pipe(take(1)).toPromise();
|
||||
if (!!processId) {
|
||||
this._appService.patchProcessData(processId, { selectedBranch: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
trackByIdFn: TrackByFunction<ApplicationProcess> = (_, process) => process.id;
|
||||
|
||||
fetchAndOpenPackages = () => this._wrongDestinationModalService.fetchAndOpen();
|
||||
|
||||
@@ -56,3 +56,8 @@ body {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
@apply block bg-gray-300 h-6;
|
||||
animation: load 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,14 @@
|
||||
<ui-select-bullet [ngModel]="selected" (ngModelChange)="setSelected($event)"></ui-select-bullet>
|
||||
</div>
|
||||
|
||||
<div class="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div>
|
||||
<div class="item-stock">
|
||||
<ui-icon icon="home" size="1em"></ui-icon>
|
||||
<span *ngIf="inStock$ | async; let stock" [class.skeleton]="stock.inStock === undefined" class="min-w-[1rem] text-right inline-block">{{
|
||||
stock?.inStock
|
||||
}}</span>
|
||||
x
|
||||
</div>
|
||||
<!-- <div class="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div> -->
|
||||
|
||||
<div class="item-ssc" [class.xs]="item?.catalogAvailability?.sscText?.length >= 60">
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainInStockService, InStock } from '@domain/availability';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { ItemDTO } from '@swagger/cat';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { debounceTime, switchMap } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from '../article-search.store';
|
||||
|
||||
export interface SearchResultItemComponentState {
|
||||
@@ -74,11 +77,21 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
return '';
|
||||
}
|
||||
|
||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.applicationService.getSelectedBranch$(processId))
|
||||
);
|
||||
|
||||
inStock$ = combineLatest([this.item$, this.selectedBranchId$]).pipe(
|
||||
debounceTime(100),
|
||||
switchMap(([item, branch]) => this._stockService.getInStock$({ itemId: item.id, branchId: branch?.id }))
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _dateAdapter: DateAdapter,
|
||||
private _datePipe: DatePipe,
|
||||
private _articleSearchService: ArticleSearchService,
|
||||
public applicationService: ApplicationService
|
||||
public applicationService: ApplicationService,
|
||||
private _stockService: DomainInStockService
|
||||
) {
|
||||
super({
|
||||
selected: false,
|
||||
|
||||
@@ -67,13 +67,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
)
|
||||
.subscribe(async ({ processId, queryParams, selectedBranch }) => {
|
||||
const process = await this.application.getProcessById$(processId).pipe(first()).toPromise();
|
||||
const processChanged = processId !== this.searchService.processId;
|
||||
|
||||
// Beim schließen vom aktuellen Prozess kommt die selectedBranch.id undefined zurück, da getSelectedBranch$ keinen Branch zurückliefert.
|
||||
// Da die selectedBranch.id aber auch undefined sein kann (z.B. beim Clearen des Branch-Selectors) muss zusätzlich überprüft
|
||||
// werden, ob der aktuelle Prozess noch existiert. Ansonsten wäre branchChanged true und search() würde gecallt werden
|
||||
const branchChanged = !!process && selectedBranch?.id !== this.searchService?.selectedBranch?.id;
|
||||
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
|
||||
|
||||
if (processChanged) {
|
||||
if (!!this.searchService.processId && this.searchService.filter instanceof UiFilter) {
|
||||
@@ -97,7 +93,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams())) || branchChanged) {
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
const data = this.getCachedData(processId, queryParams, selectedBranch?.id);
|
||||
this.searchService.setItems(data.items);
|
||||
|
||||
@@ -34,10 +34,6 @@ shared-branch-selector.shared-branch-selector-opend {
|
||||
@apply pl-0 border-l-0;
|
||||
}
|
||||
|
||||
::ng-deep .tablet page-catalog shared-branch-selector.shared-branch-selector-opend {
|
||||
@apply w-[736px];
|
||||
}
|
||||
|
||||
::ng-deep .tablet page-catalog shared-breadcrumb:focus-within .shared-breadcrumb__suffix {
|
||||
@apply rounded-[5px];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, from, fromEvent, Observable, Subject } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-catalog',
|
||||
@@ -12,11 +15,24 @@ import { first, map, switchMap } from 'rxjs/operators';
|
||||
providers: [],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PageCatalogComponent implements OnInit {
|
||||
export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ViewChild(BreadcrumbComponent, { read: ElementRef }) breadcrumbRef: ElementRef<HTMLElement>;
|
||||
@ViewChild(BranchSelectorComponent, { read: ElementRef }) branchSelectorRef: ElementRef<HTMLElement>;
|
||||
activatedProcessId$: Observable<string>;
|
||||
selectedBranch$: Observable<BranchDTO>;
|
||||
|
||||
constructor(public application: ApplicationService, private _uiModal: UiModalService) {}
|
||||
get branchSelectorWidth() {
|
||||
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
|
||||
}
|
||||
|
||||
_onDestroy$ = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
public application: ApplicationService,
|
||||
private _uiModal: UiModalService,
|
||||
private _environmentService: EnvironmentService,
|
||||
private _renderer: Renderer2
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
@@ -24,6 +40,27 @@ export class PageCatalogComponent implements OnInit {
|
||||
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this._environmentService.isTablet()) {
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe((_) => {
|
||||
this._renderer.setStyle(this.branchSelectorRef?.nativeElement, 'width', this.branchSelectorWidth);
|
||||
});
|
||||
|
||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusout')
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe((_) => {
|
||||
this._renderer.removeStyle(this.branchSelectorRef?.nativeElement, 'width');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
async patchProcessData(selectedBranch: BranchDTO) {
|
||||
try {
|
||||
const processId = await this.activatedProcessId$.pipe(first()).toPromise();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { map, withLatestFrom } from 'rxjs/operators';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-goods-out',
|
||||
@@ -11,18 +9,7 @@ import { map, withLatestFrom } from 'rxjs/operators';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class GoodsOutComponent {
|
||||
// #3359 Set Default selectedBranch if navigate to goods-out
|
||||
processId$ = this._activatedRoute.data.pipe(
|
||||
withLatestFrom(this._availability.getDefaultBranch()),
|
||||
map(([data, currentBranch]) => {
|
||||
this._application.patchProcessData(Number(data.processId), { selectedBranch: currentBranch });
|
||||
return String(data.processId);
|
||||
})
|
||||
);
|
||||
processId$ = this._activatedRoute.data.pipe(map((data) => String(data.processId)));
|
||||
|
||||
constructor(
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _application: ApplicationService,
|
||||
private _availability: DomainAvailabilityService
|
||||
) {}
|
||||
constructor(private _activatedRoute: ActivatedRoute) {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<button class="shared-branch-selector-input-container" (click)="branchInput.focus(); openComplete()">
|
||||
<div class="shared-branch-selector-input-container" (click)="branchInput.focus(); openComplete()">
|
||||
<button class="shared-branch-selector-input-icon">
|
||||
<ui-svg-icon class="text-[#AEB7C1]" icon="magnify" [size]="32"></ui-svg-icon>
|
||||
</button>
|
||||
@@ -16,7 +16,7 @@
|
||||
<button class="shared-branch-selector-clear-input-icon" *ngIf="(query$ | async)?.length > 0" type="button" (click)="clear()">
|
||||
<ui-svg-icon class="text-[#1F466C]" icon="close" [size]="32"></ui-svg-icon>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
<ui-autocomplete class="shared-branch-selector-autocomplete z-modal w-full">
|
||||
<hr class="ml-3 text-[#9CB1C6]" *ngIf="autocompleteComponent?.opend" uiAutocompleteSeparator />
|
||||
<p *ngIf="(branches$ | async)?.length > 0" class="text-base p-4 font-normal" uiAutocompleteLabel>Filialvorschläge</p>
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { provideComponentStore } from '@ngrx/component-store';
|
||||
import { BranchDTO, BranchType } from '@swagger/checkout';
|
||||
import { UiAutocompleteComponent, UiAutocompleteModule } from '@ui/autocomplete';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
@@ -127,8 +126,11 @@ export class BranchSelectorComponent implements OnInit, AfterViewInit, ControlVa
|
||||
branches?.sort((branchA, branchB) => branchA?.name?.localeCompare(branchB?.name));
|
||||
|
||||
onQueryChange(query: string) {
|
||||
if (query.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
this.store.setQuery(query);
|
||||
this.complete.next(query);
|
||||
this.complete.next(query.trim());
|
||||
}
|
||||
|
||||
openComplete() {
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
}
|
||||
|
||||
.shared-breadcrumb__prefix::after {
|
||||
@apply block w-4 -right-4 inset-y-0 absolute;
|
||||
@apply block w-6 -right-6 inset-y-0 absolute;
|
||||
content: '';
|
||||
background-image: linear-gradient(to right, white, transparent);
|
||||
box-shadow: 16px 0px 24px 0px white inset;
|
||||
}
|
||||
|
||||
.shared-breadcrumb__suffix {
|
||||
@@ -17,9 +17,9 @@
|
||||
}
|
||||
|
||||
.shared-breadcrumb__suffix::before {
|
||||
@apply block w-4 -left-4 inset-y-0 absolute;
|
||||
@apply block w-6 -left-6 inset-y-0 absolute;
|
||||
content: '';
|
||||
background-image: linear-gradient(to left, white, transparent);
|
||||
box-shadow: -16px 0px 24px 0px white inset;
|
||||
}
|
||||
|
||||
.shared-breadcrumb__crumbs {
|
||||
|
||||
@@ -48,6 +48,7 @@ export class UiAutocompleteComponent implements AfterContentInit, OnDestroy {
|
||||
this.subscriptions.add(
|
||||
this.items.changes.subscribe(() => {
|
||||
this.registerItemOnClick();
|
||||
this.activateFirstItem();
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -80,14 +81,13 @@ export class UiAutocompleteComponent implements AfterContentInit, OnDestroy {
|
||||
}
|
||||
|
||||
activateFirstItem() {
|
||||
if (this.items.length > 0) {
|
||||
if (this.items.length === 1) {
|
||||
this.listKeyManager.setFirstItemActive();
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.opend = true;
|
||||
this.activateFirstItem();
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user