Merge branch 'develop' into release/3.0

This commit is contained in:
Lorenz Hilpert
2023-08-11 15:34:45 +02:00
13 changed files with 164 additions and 81 deletions

View File

@@ -137,21 +137,23 @@
<div class="page-article-details__product-stock flex justify-end items-center">
<div class="h-5 w-16 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
<div
<button
class="flex flex-row py-4 pl-4"
type="button"
[uiOverlayTrigger]="tooltip"
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
(click)="showTooltip()"
*ngIf="!(store.fetchingTakeAwayAvailability$ | async)"
>
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
<ui-icon class="mr-2 mb-1" icon="home" size="15px"></ui-icon>
<span class="font-bold text-p3">{{ takeAwayAvailability.inStock || 0 }}x</span>
</ng-container>
</div>
</button>
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
{{ stockTooltipText$ | async }}
</ui-tooltip>
</div>
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
{{ stockTooltipText$ | async }}
</ui-tooltip>
<div class="page-article-details__product-ean-specs flex flex-col">
<div class="page-article-details__product-ean" data-name="product-ean">{{ item.product?.ean }}</div>

View File

@@ -22,6 +22,7 @@ import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-mod
import { EnvironmentService } from '@core/environment';
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
import { DomainCheckoutService } from '@domain/checkout';
import { Store } from '@ngrx/store';
@Component({
selector: 'page-article-details',
@@ -100,23 +101,6 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
switchMap((processId) => this.applicationService.getSelectedBranch$(processId))
);
stockTooltipText$ = combineLatest([this.store.branch$, this.selectedBranchId$]).pipe(
map(([defaultBranch, selectedBranch]) => {
if (defaultBranch?.branchType === 4) {
if (!selectedBranch) {
return 'Wählen Sie eine Filiale aus, um den Bestand zu sehen.';
}
return 'Sie sehen den Bestand einer anderen Filiale.';
} else {
if (selectedBranch && defaultBranch.id !== selectedBranch?.id) {
return 'Sie sehen den Bestand einer anderen Filiale.';
}
}
return '';
}),
shareReplay(1)
);
get isTablet$() {
return this._environment.matchTablet$;
}
@@ -134,6 +118,15 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
return this.detailsContainer?.nativeElement;
}
stockTooltipText$ = combineLatest([this.store.defaultBranch$, this.selectedBranchId$]).pipe(
map(([defaultBranch, selectedBranch]) => {
if (defaultBranch?.branchType !== 4 && selectedBranch && defaultBranch.id !== selectedBranch?.id) {
return 'Sie sehen den Bestand einer anderen Filiale.';
}
return '';
})
);
constructor(
public readonly applicationService: ApplicationService,
private activatedRoute: ActivatedRoute,
@@ -149,7 +142,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
private _checkoutNavigationService: CheckoutNavigationService,
private _environment: EnvironmentService,
private _router: Router,
private _domainCheckoutService: DomainCheckoutService
private _domainCheckoutService: DomainCheckoutService,
private _store: Store
) {}
ngOnInit() {
@@ -231,6 +225,14 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
});
}
async showTooltip() {
const text = await this.stockTooltipText$.pipe(first()).toPromise();
if (!text) {
// Show Tooltip attached to branch selector dropdown
this._store.dispatch({ type: 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' });
}
}
async updateBreadcrumbDesktop(item: ItemDTO) {
const crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])

View File

@@ -61,8 +61,12 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
return this.get((s) => s.branch);
}
get defaultBranch$() {
return this.domainAvailabilityService.getDefaultBranch();
}
readonly branch$ = this.select((s) => s.branch).pipe(
withLatestFrom(this.domainAvailabilityService.getDefaultBranch()),
withLatestFrom(this.defaultBranch$),
map(([selectedBranch, defaultBranch]) => selectedBranch ?? defaultBranch)
);

View File

@@ -81,33 +81,29 @@
/>
</div>
<div
class="page-search-result-item__item-stock desktop-small:text-p3 font-bold z-dropdown justify-self-start"
<button
class="page-search-result-item__item-stock desktop-small:text-p3 font-bold z-dropdown justify-self-start flex flex-row items-center justify-center"
[class.justify-self-end]="!mainOutletActive"
[uiOverlayTrigger]="tooltip"
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
type="button"
(click)="$event.stopPropagation(); $event.preventDefault(); showTooltip()"
>
<ui-icon class="mr-[0.125rem] -mt-[0.275rem]" icon="home" size="1rem"></ui-icon>
<ng-container *ngIf="isOrderBranch$ | async">
<div class="flex flex-row items-center justify-between">
<ui-icon class="-mt-[0.1875rem]" 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
>
<span>x</span>
</div>
<span
*ngIf="inStock$ | async; let stock"
[class.skeleton]="stock.inStock === undefined"
class="min-w-[0.75rem] text-right inline-block"
>{{ stock?.inStock }}</span
>
</ng-container>
<ng-container *ngIf="!(isOrderBranch$ | async)">
<div class="flex flex-row items-center justify-between z-dropdown">
<ui-icon class="block" icon="home" size="1em"></ui-icon>
<span class="min-w-[1rem] text-center inline-block">-</span>
<span>x</span>
</div>
<span class="min-w-[1rem] text-center inline-block">-</span>
</ng-container>
</div>
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
<span>x</span>
</button>
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
{{ stockTooltipText$ | async }}
</ui-tooltip>

View File

@@ -8,9 +8,10 @@ import { ItemDTO } from '@swagger/cat';
import { DateAdapter } from '@ui/common';
import { isEqual } from 'lodash';
import { combineLatest } from 'rxjs';
import { debounceTime, switchMap, map, filter } from 'rxjs/operators';
import { debounceTime, switchMap, map, filter, first } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { ProductCatalogNavigationService } from '@shared/services';
import { Store } from '@ngrx/store';
export interface SearchResultItemComponentState {
item?: ItemDTO;
@@ -104,15 +105,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranchId$]).pipe(
map(([defaultBranch, selectedBranch]) => {
if (defaultBranch?.branchType === 4) {
if (!selectedBranch) {
return 'Wählen Sie eine Filiale aus, um den Bestand zu sehen.';
}
if (defaultBranch?.branchType !== 4 && selectedBranch && defaultBranch.id !== selectedBranch?.id) {
return 'Sie sehen den Bestand einer anderen Filiale.';
} else {
if (selectedBranch && defaultBranch.id !== selectedBranch?.id) {
return 'Sie sehen den Bestand einer anderen Filiale.';
}
}
return '';
})
@@ -146,7 +140,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
private _availability: DomainAvailabilityService,
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService,
private _elRef: ElementRef<HTMLElement>
private _elRef: ElementRef<HTMLElement>,
private _store: Store
) {
super({
selected: false,
@@ -168,6 +163,14 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
// }
}
async showTooltip() {
const text = await this.stockTooltipText$.pipe(first()).toPromise();
if (!text) {
// Show Tooltip attached to branch selector dropdown
this._store.dispatch({ type: 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' });
}
}
@HostBinding('style') get class() {
return this.mainOutletActive ? { height: '6.125rem' } : '';
}

View File

@@ -1,5 +1,7 @@
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="activatedProcessId$ | async" [tags]="['catalog']">
<shared-branch-selector
[uiOverlayTrigger]="tooltip"
[overlayTriggerDisabled]="stockTooltipDisabled"
[filterCurrentBranch]="!!auth.hasRole('Store')"
[orderBy]="auth.hasRole('Store') ? 'distance' : 'name'"
[branchType]="1"
@@ -7,6 +9,9 @@
(valueChange)="patchProcessData($event)"
>
</shared-branch-selector>
<ui-tooltip #tooltip yPosition="below" xPosition="after" [xOffset]="-263" [yOffset]="4" [closeable]="true">
{{ stockTooltipText$ | async }}
</ui-tooltip>
</shared-breadcrumb>
<ng-container *ngIf="routerEvents$ | async">

View File

@@ -6,9 +6,12 @@ import { EnvironmentService } from '@core/environment';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
import { BranchDTO } from '@swagger/checkout';
import { UiOverlayTriggerDirective } from '@ui/common';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { fromEvent, Observable, Subject } from 'rxjs';
import { combineLatest, fromEvent, Observable, Subject } from 'rxjs';
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { ActionsSubject } from '@ngrx/store';
import { DomainAvailabilityService } from '@domain/availability';
@Component({
selector: 'page-catalog',
@@ -23,6 +26,8 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
activatedProcessId$: Observable<string>;
selectedBranch$: Observable<BranchDTO>;
@ViewChild(UiOverlayTriggerDirective) branchInputNoBranchSelectedTrigger: UiOverlayTriggerDirective;
get branchSelectorWidth() {
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
}
@@ -45,23 +50,52 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
showRightOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'right')));
defaultBranch$ = this._availability.getDefaultBranch();
stockTooltipText$: Observable<string>;
stockTooltipDisabled$: Observable<boolean>;
get stockTooltipDisabled() {
return this.branchInputNoBranchSelectedTrigger?.opened ? false : true;
}
constructor(
public application: ApplicationService,
private _availability: DomainAvailabilityService,
private _uiModal: UiModalService,
public auth: AuthService,
private _environmentService: EnvironmentService,
private _renderer: Renderer2,
private _activatedRoute: ActivatedRoute,
private _router: Router
private _router: Router,
private _actions: ActionsSubject
) {}
ngOnInit() {
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
this.stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranch$]).pipe(
map(([defaultBranch, selectedBranch]) => {
if (defaultBranch?.branchType === 4 && !selectedBranch) {
return 'Bitte wählen Sie eine Filiale aus, um den Bestand zu sehen.';
} else if (defaultBranch?.branchType !== 4 && !selectedBranch) {
return 'Bitte wählen Sie eine Filiale aus, um den Bestand einer anderen Filiale zu sehen';
}
return '';
})
);
}
ngAfterViewInit(): void {
this._actions.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.stockTooltipText$)).subscribe(([action, text]) => {
if (action.type === 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' && !!text) {
this.branchInputNoBranchSelectedTrigger.open();
}
});
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.isTablet$))
.subscribe(([_, isTablet]) => {

View File

@@ -6,9 +6,20 @@ import { ArticleDetailsModule } from './article-details/article-details.module';
import { ArticleSearchModule } from './article-search/article-search.module';
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
import { PageCatalogComponent } from './page-catalog.component';
import { UiCommonModule } from '@ui/common';
import { UiTooltipModule } from '@ui/tooltip';
@NgModule({
imports: [CommonModule, PageCatalogRoutingModule, ArticleSearchModule, ArticleDetailsModule, BreadcrumbModule, BranchSelectorComponent],
imports: [
CommonModule,
PageCatalogRoutingModule,
ArticleSearchModule,
ArticleDetailsModule,
BreadcrumbModule,
BranchSelectorComponent,
UiCommonModule,
UiTooltipModule,
],
exports: [],
declarations: [PageCatalogComponent],
})

View File

@@ -165,7 +165,7 @@
[disabled]="showLoader$ | async"
>
<shared-loader [loading]="showLoader$ | async" spinnerSize="32">
Weiter zur Artielsuche
Weiter zur Artikelsuche
</shared-loader>
</button>

View File

@@ -52,6 +52,8 @@ export class FilterInputOptionDateRangeComponent {
subscribeChanges() {
this.unsubscribeChanges();
if (this.uiStartOption) {
this.formGroup.patchValue({ start: (this.uiStartOption.value as any) as Date });
this.optionChangeSubscription.add(
this.uiStartOption.changes.subscribe(() => {
if (new Date(this.uiStartOption.value) !== new Date(this.formGroup.get('start').value)) {
@@ -71,6 +73,8 @@ export class FilterInputOptionDateRangeComponent {
);
}
if (this.uiStopOption) {
this.formGroup.patchValue({ stop: (this.uiStopOption.value as any) as Date });
this.optionChangeSubscription.add(
this.uiStopOption.changes.subscribe(() => {
if (new Date(this.uiStopOption.value) !== new Date(this.formGroup.get('stop').value)) {

View File

@@ -68,15 +68,19 @@ export class ShellProcessBarComponent implements OnInit {
setTimeout(() => this.scrollToEnd(), 25);
}
static REGEX_PROCESS_NAME = /^Vorgang \d+$/;
async createCartProcess() {
const processes = await this._app.getProcesses$('customer').pipe(first()).toPromise();
const count = processes.filter((x) => x.type === 'cart' && x.name.startsWith('Vorgang ')).length;
const processIds = processes.filter((x) => ShellProcessBarComponent.REGEX_PROCESS_NAME.test(x.name)).map((x) => +x.name.split(' ')[1]);
const maxId = processIds.length > 0 ? Math.max(...processIds) : 0;
const process: ApplicationProcess = {
id: Date.now(),
type: 'cart',
name: `Vorgang ${count + 1}`,
name: `Vorgang ${maxId + 1}`,
section: 'customer',
closeable: true,
};

View File

@@ -5,7 +5,6 @@ import {
Directive,
ElementRef,
EmbeddedViewRef,
HostBinding,
HostListener,
Input,
OnChanges,
@@ -13,10 +12,12 @@ import {
OnInit,
SimpleChanges,
ViewContainerRef,
Inject,
} from '@angular/core';
import { asapScheduler, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { asapScheduler, Subject, fromEvent, Subscription } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { UiOverlayTrigger } from './overlay-trigger';
import { DOCUMENT } from '@angular/common';
@Directive({ selector: '[uiOverlayTrigger]', exportAs: 'uiOverlayTrigger' })
export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
@@ -32,6 +33,7 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
private overlayRef: OverlayRef;
private viewRef: EmbeddedViewRef<any>;
private _onDestroy$ = new Subject<void>();
private _clickListenerSub: Subscription;
get opened() {
return !!this.viewRef;
@@ -42,7 +44,8 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
private viewContainerRef: ViewContainerRef,
private elementRef: ElementRef,
private overlay: Overlay,
private cdr: ChangeDetectorRef
private cdr: ChangeDetectorRef,
@Inject(DOCUMENT) private _document: Document
) {}
ngOnChanges({ position }: SimpleChanges): void {
@@ -107,6 +110,9 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
this.updatePositionStrategy();
this.viewRef = this.overlayRef.attach(dropdownPortal);
this.registerCloseOnClickListener();
this.component.close = () => this.close();
this.cdr.markForCheck();
@@ -116,6 +122,7 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
this.viewRef?.destroy();
this.overlayRef.detach();
delete this.viewRef;
this._clickListenerSub.unsubscribe();
this.cdr.markForCheck();
}
@@ -130,6 +137,18 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
.subscribe(() => this.close());
}
registerCloseOnClickListener() {
asapScheduler.schedule(() => {
this._clickListenerSub = fromEvent(this._document.body, 'click')
.pipe(take(1))
.subscribe((event) => {
if (this.viewRef && !this.overlayRef?.hostElement?.contains(event.target as HTMLElement)) {
this.close();
}
});
}, 1);
}
updatePositionStrategy() {
this.overlayRef.updatePositionStrategy(this.getPositionStrategy());
}
@@ -169,11 +188,4 @@ export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
updatePosition() {
this.overlayRef?.updatePositionStrategy(this.getPositionStrategy());
}
@HostListener('document:click', ['$event'])
documentClick(event: MouseEvent) {
if (this.viewRef && !this.overlayRef?.hostElement?.contains(event.target as HTMLElement)) {
this.close();
}
}
}

28
package-lock.json generated
View File

@@ -35,7 +35,7 @@
"moment": "^2.29.4",
"ng2-pdf-viewer": "^9.1.3",
"rxjs": "^6.6.7",
"scandit-sdk": "^5.12.1",
"scandit-sdk": "^5.13.2",
"socket.io": "^4.5.4",
"tslib": "^2.0.0",
"uglify-js": "^3.4.9",
@@ -2186,8 +2186,9 @@
}
},
"node_modules/@babel/runtime-corejs2": {
"version": "7.20.7",
"license": "MIT",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.22.6.tgz",
"integrity": "sha512-GTJVRjzQIHUBwRzuWxPII87XoWxXzILBJrQh5gqIV6q6m231Y0BBA9NKta5FV5Lbl8z5gS3+m6YSoKJp0KQJ4g==",
"dependencies": {
"core-js": "^2.6.12",
"regenerator-runtime": "^0.13.11"
@@ -13452,10 +13453,11 @@
"license": "ISC"
},
"node_modules/scandit-sdk": {
"version": "5.12.2",
"license": "SEE LICENSE IN LICENSE",
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/scandit-sdk/-/scandit-sdk-5.13.2.tgz",
"integrity": "sha512-ZrUciqc8X+wb9+JJD5gkn/JRLWBZYBpZO17RlqL/uS4TuMaVKLcj3EXH77nGacSwQowM03NdBGc5GPqykZq2Ow==",
"dependencies": {
"@babel/runtime-corejs2": "^7.20.7",
"@babel/runtime-corejs2": "^7.20.13",
"@juggle/resize-observer": "^3.4.0",
"csstype": "^3.1.1",
"eventemitter3": "^5.0.0",
@@ -13463,7 +13465,7 @@
"js-cookie": "^3.0.1",
"objectFitPolyfill": "^2.3.5",
"tslib": "^2.4.1",
"ua-parser-js": "^1.0.32"
"ua-parser-js": "^1.0.33"
},
"engines": {
"node": ">=10.18"
@@ -17702,7 +17704,9 @@
}
},
"@babel/runtime-corejs2": {
"version": "7.20.7",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.22.6.tgz",
"integrity": "sha512-GTJVRjzQIHUBwRzuWxPII87XoWxXzILBJrQh5gqIV6q6m231Y0BBA9NKta5FV5Lbl8z5gS3+m6YSoKJp0KQJ4g==",
"requires": {
"core-js": "^2.6.12",
"regenerator-runtime": "^0.13.11"
@@ -24968,9 +24972,11 @@
"dev": true
},
"scandit-sdk": {
"version": "5.12.2",
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/scandit-sdk/-/scandit-sdk-5.13.2.tgz",
"integrity": "sha512-ZrUciqc8X+wb9+JJD5gkn/JRLWBZYBpZO17RlqL/uS4TuMaVKLcj3EXH77nGacSwQowM03NdBGc5GPqykZq2Ow==",
"requires": {
"@babel/runtime-corejs2": "^7.20.7",
"@babel/runtime-corejs2": "^7.20.13",
"@juggle/resize-observer": "^3.4.0",
"csstype": "^3.1.1",
"eventemitter3": "^5.0.0",
@@ -24978,7 +24984,7 @@
"js-cookie": "^3.0.1",
"objectFitPolyfill": "^2.3.5",
"tslib": "^2.4.1",
"ua-parser-js": "^1.0.32"
"ua-parser-js": "^1.0.33"
},
"dependencies": {
"eventemitter3": {