mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Related work items: #982, #1024, #1162, #1164, #1166, #1167, #1176, #1177, #1182
This commit is contained in:
@@ -1,18 +1,43 @@
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectorRef, ElementRef, HostListener } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
ChangeDetectorRef,
|
||||
ElementRef,
|
||||
HostListener,
|
||||
ViewRef,
|
||||
} from '@angular/core';
|
||||
import { ProductService } from '../../../../core/services/product.service';
|
||||
import { ItemDTO, ReviewDTO } from '@swagger/cat';
|
||||
import { Observable, of, Subject, forkJoin, combineLatest } from 'rxjs';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { PhotoGalleryComponent, ButtonComponent } from '@libs/ui';
|
||||
import { map, delay, takeUntil, switchMap, catchError, tap, filter, take } from 'rxjs/operators';
|
||||
import {
|
||||
map,
|
||||
delay,
|
||||
takeUntil,
|
||||
switchMap,
|
||||
catchError,
|
||||
tap,
|
||||
filter,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import { ProductReview } from '../../../../core/models/product-review.model';
|
||||
import { ProductDisplay } from '../../../../core/models/product-display.model';
|
||||
import { AddBreadcrumb, UpdateCurrentBreadcrumbName } from '../../../../core/store/actions/breadcrumb.actions';
|
||||
import {
|
||||
AddBreadcrumb,
|
||||
UpdateCurrentBreadcrumbName,
|
||||
} from '../../../../core/store/actions/breadcrumb.actions';
|
||||
import { ProductCheckoutComponent } from '../../components/product-checkout/product-checkout.component';
|
||||
import { ProductReviewComponent } from '../../components/product-review/product-review.component';
|
||||
import { OtherFormats } from '../../../../core/models/other-formats.model';
|
||||
import { shrinkTitleAnimation, shrinkSecondaryAnimation, shrinkMainCard } from '../product-details/shrink.animation';
|
||||
import {
|
||||
shrinkTitleAnimation,
|
||||
shrinkSecondaryAnimation,
|
||||
shrinkMainCard,
|
||||
} from '../product-details/shrink.animation';
|
||||
import { ProductAvailabilityService } from '../../../../core/services/product-availability.service';
|
||||
import { AvailabilityDTO } from '@swagger/availability';
|
||||
import { BranchSelectors } from '../../../../core/store/selectors/branch.selector';
|
||||
@@ -45,16 +70,23 @@ import { allowedAvailabilityStatusCodes } from 'apps/sales/src/app/core/utils/pr
|
||||
export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('productDetailContainer', { read: ElementRef, static: false })
|
||||
private productDetailContainer: ElementRef<any>;
|
||||
@ViewChild('checkout', { static: false }) checkoutDialog: ProductCheckoutComponent;
|
||||
@ViewChild('photoGallery', { static: false }) photoGallery: PhotoGalleryComponent;
|
||||
@ViewChild('productReview', { static: false }) productReview: ProductReviewComponent;
|
||||
@ViewChild('checkout', { static: false })
|
||||
checkoutDialog: ProductCheckoutComponent;
|
||||
@ViewChild('photoGallery', { static: false })
|
||||
photoGallery: PhotoGalleryComponent;
|
||||
@ViewChild('productReview', { static: false })
|
||||
productReview: ProductReviewComponent;
|
||||
@ViewChild('branchesAvailabilityInfo', { static: false })
|
||||
branchesAvailabilityInfo: BranchesAvalabilityOverviewComponent;
|
||||
@ViewChild('printModal', { static: false }) printModal: PrinterSelectionComponent;
|
||||
@ViewChild('printModal', { static: false })
|
||||
printModal: PrinterSelectionComponent;
|
||||
@ViewChild('addtocart', { static: false }) addToCartBtn: ButtonComponent;
|
||||
@ViewChild('recommendations', { static: false }) recommendations: RecommendationsComponent;
|
||||
@ViewChild('otherformats', { static: false }) elOtherformats: ProductOtherFormatsComponent;
|
||||
@ViewChild('panformatsel', { read: ElementRef, static: false }) public formats: ElementRef<any>;
|
||||
@ViewChild('recommendations', { static: false })
|
||||
recommendations: RecommendationsComponent;
|
||||
@ViewChild('otherformats', { static: false })
|
||||
elOtherformats: ProductOtherFormatsComponent;
|
||||
@ViewChild('panformatsel', { read: ElementRef, static: false })
|
||||
public formats: ElementRef<any>;
|
||||
expanded = true;
|
||||
id: number;
|
||||
item: ItemDTO;
|
||||
@@ -83,11 +115,17 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
// availabilityStatusText: string;
|
||||
|
||||
get availabilityStatus() {
|
||||
return this.ssc.download.status || this.ssc.store.status || this.ssc.shipping.status;
|
||||
return (
|
||||
this.ssc.download.status ||
|
||||
this.ssc.store.status ||
|
||||
this.ssc.shipping.status
|
||||
);
|
||||
}
|
||||
|
||||
get availabilityStatusText() {
|
||||
return this.ssc.download.text || this.ssc.store.text || this.ssc.shipping.text;
|
||||
return (
|
||||
this.ssc.download.text || this.ssc.store.text || this.ssc.shipping.text
|
||||
);
|
||||
}
|
||||
|
||||
currentPickUpDate = '';
|
||||
@@ -117,7 +155,8 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
fullyLoaded = false;
|
||||
|
||||
private errorMessage = 'Verfügbarkeitsabfrage konnte nicht durchgeführt werden.';
|
||||
private errorMessage =
|
||||
'Verfügbarkeitsabfrage konnte nicht durchgeführt werden.';
|
||||
|
||||
get storeError$() {
|
||||
return of(this.storeError);
|
||||
@@ -154,7 +193,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get takeNowAvailable() {
|
||||
if (this.availability.filter((t) => t.type === CheckoutType.takeNow).length > 0) {
|
||||
if (
|
||||
this.availability.filter((t) => t.type === CheckoutType.takeNow).length >
|
||||
0
|
||||
) {
|
||||
return of(true);
|
||||
}
|
||||
return of(false);
|
||||
@@ -165,7 +207,8 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
this.availability.filter(
|
||||
(t) =>
|
||||
t.type === CheckoutType.store &&
|
||||
(allowedAvailabilityStatusCodes(t.status) || (t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
|
||||
(allowedAvailabilityStatusCodes(t.status) ||
|
||||
(t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
|
||||
).length > 0
|
||||
) {
|
||||
return of(true);
|
||||
@@ -174,14 +217,23 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get shippingAvailable() {
|
||||
if (this.availability.filter((t) => t.type === CheckoutType.delivery && allowedAvailabilityStatusCodes(t.status)).length > 0) {
|
||||
if (
|
||||
this.availability.filter(
|
||||
(t) =>
|
||||
t.type === CheckoutType.delivery &&
|
||||
allowedAvailabilityStatusCodes(t.status)
|
||||
).length > 0
|
||||
) {
|
||||
return of(true);
|
||||
}
|
||||
return of(false);
|
||||
}
|
||||
|
||||
get downloadAvailable() {
|
||||
if (this.availability.filter((t) => t.type === CheckoutType.donwload).length > 0) {
|
||||
if (
|
||||
this.availability.filter((t) => t.type === CheckoutType.donwload).length >
|
||||
0
|
||||
) {
|
||||
return of(true);
|
||||
}
|
||||
return of(false);
|
||||
@@ -189,8 +241,11 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
get showAvLoading() {
|
||||
if (
|
||||
((!this.storeAvLoaded || !this.shippingAvLoaded) && this.product.formatIcon !== 'EB' && this.product.formatIcon !== 'DL') ||
|
||||
(!this.downloadLoaded && (this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL'))
|
||||
((!this.storeAvLoaded || !this.shippingAvLoaded) &&
|
||||
this.product.formatIcon !== 'EB' &&
|
||||
this.product.formatIcon !== 'DL') ||
|
||||
(!this.downloadLoaded &&
|
||||
(this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL'))
|
||||
) {
|
||||
return of(true);
|
||||
}
|
||||
@@ -198,7 +253,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get isDownload() {
|
||||
if (this.product && (this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL')) {
|
||||
if (
|
||||
this.product &&
|
||||
(this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL')
|
||||
) {
|
||||
return of(true);
|
||||
}
|
||||
return of(false);
|
||||
@@ -206,11 +264,21 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
get productAvailable() {
|
||||
const availabilityLoaded =
|
||||
this.availability.length > 0 && ((this.storeAvLoaded === true && this.shippingAvLoaded === true) || this.downloadLoaded === true);
|
||||
const hasTakeNow = this.availability.find((t) => t.type === CheckoutType.takeNow) !== undefined;
|
||||
const hasStore = this.availability.find((t) => t.type === CheckoutType.store) !== undefined;
|
||||
const hasShipping = this.availability.find((t) => t.type === CheckoutType.delivery) !== undefined;
|
||||
const hasDownload = this.availability.find((t) => t.type === CheckoutType.donwload) !== undefined;
|
||||
this.availability.length > 0 &&
|
||||
((this.storeAvLoaded === true && this.shippingAvLoaded === true) ||
|
||||
this.downloadLoaded === true);
|
||||
const hasTakeNow =
|
||||
this.availability.find((t) => t.type === CheckoutType.takeNow) !==
|
||||
undefined;
|
||||
const hasStore =
|
||||
this.availability.find((t) => t.type === CheckoutType.store) !==
|
||||
undefined;
|
||||
const hasShipping =
|
||||
this.availability.find((t) => t.type === CheckoutType.delivery) !==
|
||||
undefined;
|
||||
const hasDownload =
|
||||
this.availability.find((t) => t.type === CheckoutType.donwload) !==
|
||||
undefined;
|
||||
const canBeBought = hasTakeNow || hasStore || hasShipping || hasDownload;
|
||||
if (canBeBought && availabilityLoaded) {
|
||||
return true;
|
||||
@@ -234,9 +302,17 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (this.product && !this.product.price) {
|
||||
return true;
|
||||
}
|
||||
const isDownload = this.product && (this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL');
|
||||
const isDownloadAlreadyAdded = isDownload && this.downloadIds.includes(this.item.id);
|
||||
if (this.cartHasItems && this.cartHasDownload && isDownload && isDownloadAlreadyAdded) {
|
||||
const isDownload =
|
||||
this.product &&
|
||||
(this.product.formatIcon === 'EB' || this.product.formatIcon === 'DL');
|
||||
const isDownloadAlreadyAdded =
|
||||
isDownload && this.downloadIds.includes(this.item.id);
|
||||
if (
|
||||
this.cartHasItems &&
|
||||
this.cartHasDownload &&
|
||||
isDownload &&
|
||||
isDownloadAlreadyAdded
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (isDownload && this.downloadError) {
|
||||
@@ -245,8 +321,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (
|
||||
isDownload &&
|
||||
this.downloadLoaded &&
|
||||
this.availability.filter((t) => t.type === CheckoutType.donwload && t.quantity > 0 && allowedAvailabilityStatusCodes(t.status))
|
||||
.length < 1
|
||||
this.availability.filter(
|
||||
(t) =>
|
||||
t.type === CheckoutType.donwload &&
|
||||
t.quantity > 0 &&
|
||||
allowedAvailabilityStatusCodes(t.status)
|
||||
).length < 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -255,8 +335,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
!isDownload &&
|
||||
this.storeAvLoaded &&
|
||||
this.shippingAvLoaded &&
|
||||
this.availability.filter((t) => t.type !== CheckoutType.donwload && t.quantity > 0 && allowedAvailabilityStatusCodes(t.status))
|
||||
.length < 1
|
||||
this.availability.filter(
|
||||
(t) =>
|
||||
t.type !== CheckoutType.donwload &&
|
||||
t.quantity > 0 &&
|
||||
allowedAvailabilityStatusCodes(t.status)
|
||||
).length < 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -349,14 +433,22 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
this.showFeatures = true;
|
||||
}
|
||||
|
||||
if (!this.product.fullDescription || (this.product.fullDescription && this.product.fullDescription.length < 1)) {
|
||||
if (
|
||||
!this.product.fullDescription ||
|
||||
(this.product.fullDescription &&
|
||||
this.product.fullDescription.length < 1)
|
||||
) {
|
||||
this.showNoDescription = true;
|
||||
}
|
||||
|
||||
this.processReviewData(item.reviews);
|
||||
this.loadBranches();
|
||||
|
||||
if (this.product && this.product.formatIcon !== 'EB' && this.product.formatIcon !== 'DL') {
|
||||
if (
|
||||
this.product &&
|
||||
this.product.formatIcon !== 'EB' &&
|
||||
this.product.formatIcon !== 'DL'
|
||||
) {
|
||||
setTimeout(() => {
|
||||
this.loadAvailability(item, item.product.ean);
|
||||
});
|
||||
@@ -375,7 +467,11 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private loadTempData() {
|
||||
combineLatest([this.store.select(SharedSelectors.getProcessSelectedItem), this.route.params, this.route.queryParams])
|
||||
combineLatest([
|
||||
this.store.select(SharedSelectors.getProcessSelectedItem),
|
||||
this.route.params,
|
||||
this.route.queryParams,
|
||||
])
|
||||
.pipe(takeUntil(this.destroy$), this.filterTempData)
|
||||
.subscribe((item) => {
|
||||
if (this.productDetailContainer) {
|
||||
@@ -384,7 +480,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
this.item = item;
|
||||
this.product = this.productDetailMapper(item);
|
||||
this.product.fullDescription =
|
||||
this.product.fullDescription && this.product.fullDescription.length > 0 ? this.product.fullDescription : ' ';
|
||||
this.product.fullDescription &&
|
||||
this.product.fullDescription.length > 0
|
||||
? this.product.fullDescription
|
||||
: ' ';
|
||||
|
||||
if (this.product.features.length > 0) {
|
||||
this.features = this.product.features;
|
||||
@@ -393,7 +492,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private filterTempData = (obs: Observable<[ItemDTO, Params, Params]>): Observable<ItemDTO> => {
|
||||
private filterTempData = (
|
||||
obs: Observable<[ItemDTO, Params, Params]>
|
||||
): Observable<ItemDTO> => {
|
||||
return obs.pipe(
|
||||
filter(([item, params, queryParams]) => {
|
||||
if (isNullOrUndefined(item)) {
|
||||
@@ -482,18 +583,20 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private loadBranches() {
|
||||
this.branches = this.store.selectSnapshot(BranchSelectors.getBranchesIterable);
|
||||
this.branches = this.store.selectSnapshot(
|
||||
BranchSelectors.getBranchesIterable
|
||||
);
|
||||
}
|
||||
|
||||
openBranchesAvailabilityModal() {
|
||||
this.loadBranchesInfoComponent = true;
|
||||
this.cdrf.detectChanges();
|
||||
this.detectChanges();
|
||||
this.branchesAvailabilityInfo.openDialog();
|
||||
}
|
||||
|
||||
destroyAvailabilityModal() {
|
||||
this.loadBranchesInfoComponent = false;
|
||||
this.cdrf.detectChanges();
|
||||
this.detectChanges();
|
||||
}
|
||||
|
||||
private productDetailMapper(item: ItemDTO): ProductDisplay {
|
||||
@@ -538,15 +641,21 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
// text object mapping
|
||||
if (item.texts) {
|
||||
item.texts.forEach((text) => {
|
||||
const label = text.label ? '<b>' + text.label + '</b>' + '\n' + '<br>' : '';
|
||||
const label = text.label
|
||||
? '<b>' + text.label + '</b>' + '\n' + '<br>'
|
||||
: '';
|
||||
const value = text.value ? text.value + '\n' + '<br>' : '';
|
||||
fullDescription = fullDescription ? fullDescription + label + value : label + value;
|
||||
fullDescription = fullDescription
|
||||
? fullDescription + label + value
|
||||
: label + value;
|
||||
});
|
||||
}
|
||||
|
||||
// specs object mapping
|
||||
if (item.specs) {
|
||||
genre = item.specs.find((s) => s.key === this.GENRE) ? item.specs.find((s) => s.key === this.GENRE).value : '';
|
||||
genre = item.specs.find((s) => s.key === this.GENRE)
|
||||
? item.specs.find((s) => s.key === this.GENRE).value
|
||||
: '';
|
||||
recommandedAge = item.specs.find((s) => s.key === this.RECOMMANDED_AGE)
|
||||
? item.specs.find((s) => s.key === this.RECOMMANDED_AGE).value
|
||||
: '';
|
||||
@@ -562,7 +671,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (Array.isArray(item.stockInfos) && item.stockInfos.length > 0) {
|
||||
quantity = item.stockInfos[0].inStock.toString();
|
||||
if (+quantity > 0) {
|
||||
const userBranch = this.store.selectSnapshot(BranchSelectors.getUserBranch);
|
||||
const userBranch = this.store.selectSnapshot(
|
||||
BranchSelectors.getUserBranch
|
||||
);
|
||||
for (let x = 0; x < +quantity; x++) {
|
||||
this.availability.push({
|
||||
itemId: item.id,
|
||||
@@ -578,7 +689,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (item.shelfInfos) {
|
||||
assortment = item.shelfInfos[0].assortment ? item.shelfInfos[0].assortment : item.shelfInfos[0].label;
|
||||
assortment = item.shelfInfos[0].assortment
|
||||
? item.shelfInfos[0].assortment
|
||||
: item.shelfInfos[0].label;
|
||||
}
|
||||
|
||||
if (item.family && item.family.length > 0) {
|
||||
@@ -588,7 +701,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
ean: t.product.ean,
|
||||
format: t.product.format,
|
||||
formatDetail: t.product.formatDetail,
|
||||
price: !!t.catalogAvailability ? t.catalogAvailability.price.value.value : 0,
|
||||
price: !!t.catalogAvailability
|
||||
? t.catalogAvailability.price.value.value
|
||||
: 0,
|
||||
status: !!t.catalogAvailability ? t.catalogAvailability.status : 0,
|
||||
});
|
||||
});
|
||||
@@ -649,8 +764,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async loadDownloadAvailability(item: ItemDTO, ean: string) {
|
||||
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
|
||||
const branch = this.store.selectSnapshot(BranchSelectors.getBranchesIterable).find((t) => t.branchNumber === userBranchNumber);
|
||||
const userBranchNumber = this.store.selectSnapshot(
|
||||
BranchSelectors.getUserBranch
|
||||
);
|
||||
const branch = this.store
|
||||
.selectSnapshot(BranchSelectors.getBranchesIterable)
|
||||
.find((t) => t.branchNumber === userBranchNumber);
|
||||
if (!branch) {
|
||||
return true;
|
||||
}
|
||||
@@ -658,7 +777,10 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
.getShippingAvailabilityWithCheck(item, ean, branch.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((response) => {
|
||||
if ((response as { error: boolean; message: string; type: CheckoutType }).error) {
|
||||
if (
|
||||
(response as { error: boolean; message: string; type: CheckoutType })
|
||||
.error
|
||||
) {
|
||||
this.downloadError = true;
|
||||
this.downloadLoaded = true;
|
||||
if (this.addToCartBtn) {
|
||||
@@ -671,13 +793,19 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
av: AvailabilityDTO[];
|
||||
};
|
||||
if (successfulResponse) {
|
||||
const preferredAvailability = successfulResponse.av.find((t) => t.preferred === 1 && t.supplier === 'DIG');
|
||||
const preferredAvailability = successfulResponse.av.find(
|
||||
(t) => t.preferred === 1 && t.supplier === 'DIG'
|
||||
);
|
||||
if (preferredAvailability) {
|
||||
this.ssc.download = {
|
||||
status: preferredAvailability.ssc,
|
||||
text: preferredAvailability.sscText,
|
||||
};
|
||||
if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
|
||||
if (
|
||||
preferredAvailability.price &&
|
||||
preferredAvailability.price.value &&
|
||||
preferredAvailability.price.value.value
|
||||
) {
|
||||
this.downloadPrice = preferredAvailability.price.value.value;
|
||||
}
|
||||
this.availability.push({
|
||||
@@ -713,10 +841,15 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
async loadAvailability(it: ItemDTO, ean: string): Promise<boolean> {
|
||||
const availabilityObservables: Observable<
|
||||
{ branchId: number; type: CheckoutType; av: AvailabilityDTO[] } | { error: boolean; message: string; type: CheckoutType }
|
||||
| { branchId: number; type: CheckoutType; av: AvailabilityDTO[] }
|
||||
| { error: boolean; message: string; type: CheckoutType }
|
||||
>[] = [];
|
||||
const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
|
||||
const branch = this.store.selectSnapshot(BranchSelectors.getBranchesIterable).find((t) => t.branchNumber === userBranchNumber);
|
||||
const userBranchNumber = this.store.selectSnapshot(
|
||||
BranchSelectors.getUserBranch
|
||||
);
|
||||
const branch = this.store
|
||||
.selectSnapshot(BranchSelectors.getBranchesIterable)
|
||||
.find((t) => t.branchNumber === userBranchNumber);
|
||||
if (!branch) {
|
||||
return true;
|
||||
}
|
||||
@@ -775,7 +908,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (item.type === CheckoutType.delivery) {
|
||||
this.deliveryError = null;
|
||||
}
|
||||
const preferredAvailability = this.getPreferedAvailability(item.av);
|
||||
const preferredAvailability = this.getPreferedAvailability(
|
||||
item.av
|
||||
);
|
||||
if (!preferredAvailability) {
|
||||
return;
|
||||
}
|
||||
@@ -785,19 +920,36 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
text: preferredAvailability.sscText,
|
||||
};
|
||||
this.currentPickUpDate = preferredAvailability.at
|
||||
? this.datePipe.transform(new Date(preferredAvailability.at), 'dd.MM.yy')
|
||||
? this.datePipe.transform(
|
||||
new Date(preferredAvailability.at),
|
||||
'dd.MM.yy'
|
||||
)
|
||||
: null;
|
||||
if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
|
||||
if (
|
||||
preferredAvailability.price &&
|
||||
preferredAvailability.price.value &&
|
||||
preferredAvailability.price.value.value
|
||||
) {
|
||||
this.pickUpPrice = preferredAvailability.price.value.value;
|
||||
}
|
||||
}
|
||||
if (item.type === CheckoutType.delivery && preferredAvailability.at) {
|
||||
if (
|
||||
item.type === CheckoutType.delivery &&
|
||||
preferredAvailability.at
|
||||
) {
|
||||
this.currentDeliveryDate = preferredAvailability.at
|
||||
? this.datePipe.transform(new Date(preferredAvailability.at), 'dd.MM.yy')
|
||||
? this.datePipe.transform(
|
||||
new Date(preferredAvailability.at),
|
||||
'dd.MM.yy'
|
||||
)
|
||||
: null;
|
||||
}
|
||||
if (item.type === CheckoutType.delivery) {
|
||||
if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
|
||||
if (
|
||||
preferredAvailability.price &&
|
||||
preferredAvailability.price.value &&
|
||||
preferredAvailability.price.value.value
|
||||
) {
|
||||
this.deliveryPrice = preferredAvailability.price.value.value;
|
||||
}
|
||||
this.ssc.shipping = {
|
||||
@@ -827,7 +979,7 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
this.addToCartBtn.stopLoading();
|
||||
}
|
||||
|
||||
this.cdrf.detectChanges();
|
||||
this.detectChanges();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -844,13 +996,17 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (stockInfo.length === 1) {
|
||||
return stockInfo[0].inStock;
|
||||
}
|
||||
return stockInfo ? stockInfo.map((t) => t.inStock).reduce((s1, s2) => s1 + s2) : 0;
|
||||
return stockInfo
|
||||
? stockInfo.map((t) => t.inStock).reduce((s1, s2) => s1 + s2)
|
||||
: 0;
|
||||
})
|
||||
)
|
||||
.subscribe((inStockAv) => {
|
||||
if (inStockAv) {
|
||||
this.product.quantity = inStockAv + '';
|
||||
this.availability = this.availability.filter((t) => t.type !== CheckoutType.takeNow);
|
||||
this.availability = this.availability.filter(
|
||||
(t) => t.type !== CheckoutType.takeNow
|
||||
);
|
||||
this.availability.push({
|
||||
itemId: itemId,
|
||||
branchId: branchId,
|
||||
@@ -872,7 +1028,9 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (!availability) {
|
||||
return;
|
||||
}
|
||||
const preferredAvailability = availability.find((ava: AvailabilityDTO) => ava.preferred === 1);
|
||||
const preferredAvailability = availability.find(
|
||||
(ava: AvailabilityDTO) => ava.preferred === 1
|
||||
);
|
||||
return preferredAvailability; // ? preferredAvailability : availability[0];
|
||||
}
|
||||
|
||||
@@ -920,7 +1078,12 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
expand() {
|
||||
this.expanded = !this.expanded;
|
||||
this.store.dispatch(new UpdateCurrentBreadcrumbName(this.expanded ? this.product.title : 'Empfehlungen', 'product'));
|
||||
this.store.dispatch(
|
||||
new UpdateCurrentBreadcrumbName(
|
||||
this.expanded ? this.product.title : 'Empfehlungen',
|
||||
'product'
|
||||
)
|
||||
);
|
||||
if (!this.expanded) {
|
||||
this.recommendations.loadReccomendations();
|
||||
}
|
||||
@@ -949,9 +1112,24 @@ export class ProductDetailsComponent implements OnInit, OnDestroy {
|
||||
if (event.target.scrollTop === 0) {
|
||||
this.elOtherformats.calculateTopPosition();
|
||||
}
|
||||
if (event.target.scrollTop === event.target.scrollHeight - event.target.offsetHeight) {
|
||||
if (
|
||||
event.target.scrollTop ===
|
||||
event.target.scrollHeight - event.target.offsetHeight
|
||||
) {
|
||||
this.elOtherformats.calculateTopPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detectChanges() {
|
||||
setTimeout(() => {
|
||||
if (
|
||||
this.cdrf !== null &&
|
||||
this.cdrf !== undefined &&
|
||||
!(this.cdrf as ViewRef).destroyed
|
||||
) {
|
||||
this.cdrf.detectChanges();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
</div>
|
||||
</h3>
|
||||
<div class="isa-flex isa-justify-content-space-between">
|
||||
<div class="isa-flex isa-flex-direction-column isa-container-width-380">
|
||||
<div
|
||||
class="isa-flex isa-flex-direction-column isa-container-width-380 isa-flex-grow-2"
|
||||
>
|
||||
<div class="detail">
|
||||
<div class="name">Vorgang-ID</div>
|
||||
<div class="value">{{ orderDetails?.orderNumber }}</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
.name {
|
||||
width: 175px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.value {
|
||||
|
||||
@@ -21,9 +21,12 @@
|
||||
<h4 class="isa-mb-6 isa-mt-12">Bestellung</h4>
|
||||
<div class="detail">
|
||||
<div class="name">Format</div>
|
||||
<div class="value">
|
||||
{{ orderItemListItem?.product?.format }}
|
||||
{{ orderItemListItem?.product?.formatDetail }}
|
||||
<div class="value isa-flex isa-justify-content-center">
|
||||
<lib-icon
|
||||
class="isa-mr-5 isa-mt-1"
|
||||
name="Icon_{{ orderItemListItem?.product?.format }}"
|
||||
></lib-icon>
|
||||
<span>{{ orderItemListItem?.product?.formatDetail }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.value {
|
||||
max-height: $font-line-height;
|
||||
}
|
||||
|
||||
.value,
|
||||
.btn-quantity {
|
||||
font-weight: $font-weight-bold;
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
name="shelf-search"
|
||||
id="shelf-search-input"
|
||||
[formControl]="searchForm"
|
||||
(keyup.enter)="triggerSearch('search')"
|
||||
(keyup)="onInput($event)"
|
||||
(keyup)="onKeyup($event)"
|
||||
/>
|
||||
<span class="isa-input-warning" *ngIf="!!errorMessage?.length">{{
|
||||
errorMessage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { NgxsModule } from '@ngxs/store';
|
||||
import { SearchStateFacade } from '@shelf-store';
|
||||
@@ -62,4 +62,42 @@ fdescribe('SearchbarComponent', () => {
|
||||
expect(component['focusSearchbar']).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onKeyup', () => {
|
||||
const getInput = () =>
|
||||
fixture.debugElement.query(By.css('#shelf-search-input'));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'onKeyup').and.callThrough();
|
||||
spyOn(component, 'triggerSearch').and.callFake(() => {});
|
||||
spyOn(component, 'onInput').and.callFake(() => {});
|
||||
});
|
||||
|
||||
it('should be called when the input emits the native keyup event', () => {
|
||||
fixture.detectChanges();
|
||||
const event = { keyCode: 13 };
|
||||
|
||||
getInput().triggerEventHandler('keyup', event);
|
||||
|
||||
expect(component.onKeyup).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it('should call trigger search on Enter Input', () => {
|
||||
fixture.detectChanges();
|
||||
const event = { keyCode: 13 };
|
||||
|
||||
getInput().triggerEventHandler('keyup', event);
|
||||
|
||||
expect(component.triggerSearch).toHaveBeenCalledWith('search');
|
||||
});
|
||||
|
||||
it('should call onInput if it is not a Enter Event', () => {
|
||||
fixture.detectChanges();
|
||||
const event = { keyCode: 1 };
|
||||
|
||||
getInput().triggerEventHandler('keyup', event);
|
||||
|
||||
expect(component.onInput).toHaveBeenCalledWith(event);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,6 +98,14 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
this.scan.emit();
|
||||
}
|
||||
|
||||
onKeyup(event: KeyboardEvent) {
|
||||
if (this.isEnterEvent(event)) {
|
||||
this.triggerSearch('search');
|
||||
} else {
|
||||
this.onInput(event);
|
||||
}
|
||||
}
|
||||
|
||||
onInput(event: KeyboardEvent) {
|
||||
if (!this.isNumberOrLetter(event.key)) {
|
||||
this.change.emit(this.searchForm.value);
|
||||
@@ -134,6 +142,12 @@ export class ShelfSearchbarComponent implements OnInit, OnDestroy {
|
||||
return key.includes('Digit') || key.includes('Key');
|
||||
}
|
||||
|
||||
private isEnterEvent(event: KeyboardEvent): boolean {
|
||||
// keycode check required for iPad (no Enter Event is dispatched)
|
||||
// tslint:disable-next-line: deprecation
|
||||
return event.keyCode === 13;
|
||||
}
|
||||
|
||||
private focusSearchbar() {
|
||||
if (this.searchbar && this.searchbar.nativeElement) {
|
||||
this.searchbar.nativeElement.focus();
|
||||
|
||||
@@ -29,3 +29,15 @@
|
||||
height: 130px;
|
||||
background: $isa-branch-bg;
|
||||
}
|
||||
|
||||
@media not all and (min-resolution: 0.001dpcm) {
|
||||
@supports (-webkit-appearance: none) {
|
||||
.spacing {
|
||||
height: 195px;
|
||||
}
|
||||
|
||||
app-shelf-edit-actions {
|
||||
padding-bottom: 65px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,3 +29,15 @@
|
||||
height: 130px;
|
||||
background: $isa-branch-bg;
|
||||
}
|
||||
|
||||
@media not all and (min-resolution: 0.001dpcm) {
|
||||
@supports (-webkit-appearance: none) {
|
||||
.spacing {
|
||||
height: 195px;
|
||||
}
|
||||
|
||||
app-shelf-edit-actions {
|
||||
padding-bottom: 65px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</app-shelf-details-customer-features>
|
||||
<button
|
||||
type="button"
|
||||
class="isa-btn isa-btn-secondary isa-p-0 isa-mr-n10 isa-h-min"
|
||||
class="isa-btn isa-btn-secondary isa-p-0 isa-h-min"
|
||||
(click)="navigateToDetails()"
|
||||
>
|
||||
Bearbeiten
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
distinctUntilChanged,
|
||||
shareReplay,
|
||||
withLatestFrom,
|
||||
take,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import {
|
||||
OrderItemProcessingStatusValue,
|
||||
@@ -385,10 +387,25 @@ export class ShelfOrderDetailsComponent {
|
||||
} else if (remainingItemsAfterPartialRemit.length) {
|
||||
this.refreshView();
|
||||
}
|
||||
|
||||
this.resetPartialPickup();
|
||||
}
|
||||
|
||||
refreshView() {
|
||||
this.orderItems$ = this.getOrderItems$();
|
||||
async refreshView() {
|
||||
const data = await race(
|
||||
this.orderNumber$.pipe(
|
||||
take(1),
|
||||
tap((orderNumber) => {
|
||||
this.detailsFacade.fetchOrderItemsByOrderNumber(orderNumber);
|
||||
})
|
||||
),
|
||||
this.compartmentCode$.pipe(
|
||||
take(1),
|
||||
tap((compartmentCode) => {
|
||||
this.detailsFacade.fetchOrderItemsByCompartmentCode(compartmentCode);
|
||||
})
|
||||
)
|
||||
).toPromise();
|
||||
}
|
||||
|
||||
async executeAction(
|
||||
@@ -451,7 +468,6 @@ export class ShelfOrderDetailsComponent {
|
||||
targetStatus,
|
||||
this.processingStatusChangeData
|
||||
);
|
||||
this.resetPartialPickup();
|
||||
|
||||
return results.filter((result) => !!result);
|
||||
}
|
||||
@@ -497,7 +513,7 @@ export class ShelfOrderDetailsComponent {
|
||||
this.selectedForPartialPickup.set(orderItem.orderItemId, orderItem);
|
||||
this.quantityForPartialPickup.set(
|
||||
orderItem.orderItemId,
|
||||
isNullOrUndefined(quantity) ? orderItem.overallQuantity : quantity
|
||||
isNullOrUndefined(quantity) ? orderItem.quantity : quantity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SearchStateFacade } from '@shelf-store/search/search.facade';
|
||||
import { of } from 'rxjs';
|
||||
import { ShelfNavigationService } from '../../shared/services';
|
||||
import { ShelfSearchResultsComponent } from './shelf-search-results.component';
|
||||
import { ShelfSearchResultsModule } from './shelf-search-results.module';
|
||||
|
||||
class SearchStateFacadeMock {
|
||||
get result$() {
|
||||
return of([]);
|
||||
}
|
||||
get fetching$() {
|
||||
return of(false);
|
||||
}
|
||||
get hits$() {
|
||||
return of(1);
|
||||
}
|
||||
|
||||
fetchResult() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
fdescribe('ShelfSearchResultComponent', () => {
|
||||
let fixture: ComponentFixture<ShelfSearchResultsComponent>;
|
||||
let component: ShelfSearchResultsComponent;
|
||||
|
||||
let facade: jasmine.SpyObj<SearchStateFacade>;
|
||||
let navService: jasmine.SpyObj<ShelfNavigationService>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ShelfSearchResultsModule],
|
||||
providers: [
|
||||
{
|
||||
provide: SearchStateFacade,
|
||||
useClass: SearchStateFacadeMock,
|
||||
},
|
||||
{
|
||||
provide: ShelfNavigationService,
|
||||
useValue: jasmine.createSpy('shelfNavigationService'),
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ShelfSearchResultsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
facade = TestBed.get(SearchStateFacade);
|
||||
navService = TestBed.get(ShelfNavigationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component instanceof ShelfSearchResultsComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('fetch', () => {
|
||||
it('should be called onInit with initalFetching set to true', () => {
|
||||
spyOn<any>(component, 'isFromSearchPage').and.returnValue(false);
|
||||
|
||||
spyOn(component, 'fetch').and.callFake(() => {});
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.fetch).toHaveBeenCalledWith(true, true);
|
||||
});
|
||||
|
||||
it('should call fetch without force and initialSearch argument if previous page was search', () => {
|
||||
spyOn<any>(component, 'isFromSearchPage').and.returnValue(true);
|
||||
spyOn(component, 'fetch').and.callFake(() => {});
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.fetch).not.toHaveBeenCalledWith(true, true);
|
||||
});
|
||||
|
||||
it('should call fetchResult on the facade when initializing the component (i.e. OnInit)', async () => {
|
||||
spyOn<any>(component, 'isFromSearchPage').and.returnValue(false);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
spyOn(facade, 'fetchResult').and.callThrough();
|
||||
|
||||
await component.fetch(true, true);
|
||||
|
||||
expect(facade.fetchResult).toHaveBeenCalledWith({ isNewSearch: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { SearchStateFacade } from 'apps/sales/src/app/store/customer';
|
||||
import { first, takeUntil, map, withLatestFrom } from 'rxjs/operators';
|
||||
import { groupBy } from 'apps/sales/src/app/utils';
|
||||
import { Group, groupBy } from 'apps/sales/src/app/utils';
|
||||
import { OrderItemListItemDTO } from '@swagger/oms';
|
||||
import { ShelfNavigationService } from '../../shared/services';
|
||||
import { Select } from '@ngxs/store';
|
||||
@@ -40,11 +40,9 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
|
||||
destroy$ = new Subject();
|
||||
isFetching$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
grouped$ = this.searchStateFacade.result$.pipe(
|
||||
map((results) => groupBy(results, (item) => (item ? item.buyerNumber : '')))
|
||||
);
|
||||
grouped$: Observable<Group<string, OrderItemListItemDTO>[]>;
|
||||
|
||||
fetching$ = this.searchStateFacade.fetching$;
|
||||
fetching$: Observable<boolean>;
|
||||
|
||||
scrollStorageKey = ORDER_DETAILS_PREFIX;
|
||||
|
||||
@@ -54,8 +52,26 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initFacade();
|
||||
this.initScrollContainer();
|
||||
this.fetch();
|
||||
this.initFetch();
|
||||
}
|
||||
|
||||
initFetch() {
|
||||
if (this.isFromSearchPage()) {
|
||||
this.fetch();
|
||||
} else {
|
||||
this.fetch(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
initFacade() {
|
||||
this.grouped$ = this.searchStateFacade.result$.pipe(
|
||||
map((results) =>
|
||||
groupBy(results, (item) => (item ? item.buyerNumber : ''))
|
||||
)
|
||||
);
|
||||
this.fetching$ = this.searchStateFacade.fetching$;
|
||||
}
|
||||
|
||||
initScrollContainer() {
|
||||
@@ -79,7 +95,7 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
async fetch(force = false) {
|
||||
async fetch(force = false, isInitialFetch = false) {
|
||||
const [hits, result, fetching] = await combineLatest([
|
||||
this.searchStateFacade.hits$,
|
||||
this.searchStateFacade.result$,
|
||||
@@ -88,7 +104,9 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
if (
|
||||
if (isInitialFetch) {
|
||||
await this.searchStateFacade.fetchResult({ isNewSearch: true });
|
||||
} else if (
|
||||
(force && this.resultListNotFullyLoaded(hits, result.length || 0)) ||
|
||||
!hits ||
|
||||
(!result.length && !fetching)
|
||||
@@ -111,4 +129,8 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
|
||||
): boolean {
|
||||
return numberOfFetchedResults < hits;
|
||||
}
|
||||
|
||||
private isFromSearchPage(): boolean {
|
||||
return !!window && !!window.history && !!window.history.state['fromSearch'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +337,7 @@ export class ShelfSearchInputComponent
|
||||
this.shelfNavigationService.navigateToResultList({
|
||||
searchQuery,
|
||||
numberOfHits,
|
||||
fromSearch: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<section class="isa-content-container mt-15">
|
||||
<section class="isa-content-container mt-15" [class.iPad]="isIPad">
|
||||
<div class="header pt-42 mb-45">
|
||||
<h2 class="isa-content-headline mt-0">Bestellpostensuche</h2>
|
||||
<h6 class="isa-content-subline">
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
.isa-content-container {
|
||||
&.iPad {
|
||||
height: calc(100% - 75px);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
height: calc(100% - 110px);
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
}
|
||||
@@ -7,7 +12,6 @@
|
||||
.content > * {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -163,7 +163,7 @@ export class ShelfOrderDetailsService {
|
||||
break;
|
||||
|
||||
case 268435456:
|
||||
results = await this.setSupplier(items);
|
||||
results = await this.setSupplier(items, data.quantityForPartialPickup);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -238,9 +238,10 @@ export class ShelfOrderDetailsService {
|
||||
setSupplier(
|
||||
items: (OrderItemListItemDTO & {
|
||||
receipts?: ReceiptDTO[];
|
||||
})[]
|
||||
})[],
|
||||
quantityForPartialPickup: Map<number, number>
|
||||
) {
|
||||
return this.setStatus(items, 268435456);
|
||||
return this.detailsFacade.setSupplier(items, quantityForPartialPickup);
|
||||
}
|
||||
|
||||
async setDetermineSupplier(
|
||||
|
||||
@@ -126,14 +126,16 @@ export class ShelfNavigationService {
|
||||
navigateToResultList({
|
||||
searchQuery,
|
||||
numberOfHits,
|
||||
fromSearch,
|
||||
}: {
|
||||
searchQuery: string;
|
||||
numberOfHits: number;
|
||||
fromSearch: boolean;
|
||||
}) {
|
||||
this.createTab();
|
||||
const path = '/shelf/results';
|
||||
const breadcrumb = this.getResultListBreadcrumb(searchQuery, numberOfHits);
|
||||
this.navigateToRoute(path, breadcrumb);
|
||||
this.navigateToRoute(path, breadcrumb, { fromSearch });
|
||||
}
|
||||
|
||||
navigateToHistory(orderitem: OrderItemListItemDTO) {
|
||||
@@ -202,7 +204,8 @@ export class ShelfNavigationService {
|
||||
|
||||
private async navigateToRoute(
|
||||
route: string,
|
||||
breadcrumbName: string
|
||||
breadcrumbName: string,
|
||||
state?: { [key: string]: string | boolean }
|
||||
): Promise<boolean> {
|
||||
this.store.dispatch(
|
||||
new AddBreadcrumb(
|
||||
@@ -214,7 +217,7 @@ export class ShelfNavigationService {
|
||||
)
|
||||
);
|
||||
this.store.dispatch(new ChangeCurrentRoute(route));
|
||||
return this.router.navigate([route]);
|
||||
return this.router.navigate([route], { ...(state ? { state } : {}) });
|
||||
}
|
||||
|
||||
public async navigateBackToDetails(
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
right: -1rem;
|
||||
max-height: 50px * 5;
|
||||
max-height: 488px;
|
||||
overflow: auto;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.144);
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { forwardRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import {
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
TestBed,
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
import {
|
||||
NgControl,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { UiTextInputComponent } from './text-input.component.component';
|
||||
import { UiTextInputModule } from './text-input.component.module';
|
||||
|
||||
fdescribe('UiTextInputComponent', () => {
|
||||
let fixture: ComponentFixture<UiTextInputComponent>;
|
||||
let component: UiTextInputComponent;
|
||||
let mockNgControl: NgControl;
|
||||
|
||||
const getInput = () => fixture.debugElement.query(By.css('input'));
|
||||
|
||||
beforeEach(() => {
|
||||
mockNgControl = jasmine.createSpyObj('ngControl', [
|
||||
'value',
|
||||
'valid',
|
||||
'disabled',
|
||||
'dirty',
|
||||
]);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [UiTextInputModule, ReactiveFormsModule],
|
||||
providers: [
|
||||
{
|
||||
provide: NgControl,
|
||||
useValue: mockNgControl,
|
||||
},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UiTextInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
component['ngControl'] = mockNgControl;
|
||||
component['onTouched'] = () => {};
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component instanceof UiTextInputComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('handleBlur', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'handleBlur').and.callThrough();
|
||||
spyOn<any>(component, 'onTouched').and.callThrough();
|
||||
});
|
||||
it('should be called on onBlur Event of the input element', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const input = getInput();
|
||||
input.triggerEventHandler('blur', {});
|
||||
|
||||
expect(component.handleBlur).toHaveBeenCalled();
|
||||
expect(component.focussed).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call onTouched', fakeAsync(() => {
|
||||
component.handleBlur();
|
||||
|
||||
tick(250);
|
||||
|
||||
expect(component['onTouched']).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('handleFocus', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'handleFocus').and.callThrough();
|
||||
});
|
||||
it('should be called on onFocus Event of the input element', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const input = getInput();
|
||||
input.triggerEventHandler('focus', {});
|
||||
|
||||
expect(component.handleFocus).toHaveBeenCalled();
|
||||
expect(component.focussed).toBeTruthy();
|
||||
expect(component.touched).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,13 +5,15 @@ import {
|
||||
Input,
|
||||
Injector,
|
||||
OnInit,
|
||||
ChangeDetectorRef,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
NG_VALUE_ACCESSOR,
|
||||
ControlValueAccessor,
|
||||
NgControl,
|
||||
} from '@angular/forms';
|
||||
import { asyncScheduler } from 'rxjs';
|
||||
import { asyncScheduler, Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ui-text-input',
|
||||
@@ -26,18 +28,23 @@ import { asyncScheduler } from 'rxjs';
|
||||
},
|
||||
],
|
||||
})
|
||||
export class UiTextInputComponent implements ControlValueAccessor, OnInit {
|
||||
export class UiTextInputComponent
|
||||
implements ControlValueAccessor, OnInit, OnDestroy {
|
||||
@Input() label = '';
|
||||
@Input() suffix: string;
|
||||
@Input() placeholder = '';
|
||||
@Input() fullWidth = true;
|
||||
|
||||
constructor(private readonly injector: Injector) {}
|
||||
constructor(
|
||||
private readonly injector: Injector,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
public value: string;
|
||||
public editable = true;
|
||||
public touched = false;
|
||||
public focussed = false;
|
||||
private subscriptions: Subscription = new Subscription();
|
||||
|
||||
public get isInvalid(): boolean {
|
||||
return (
|
||||
@@ -59,13 +66,23 @@ export class UiTextInputComponent implements ControlValueAccessor, OnInit {
|
||||
this.ngControl = this.injector.get(NgControl);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.cdr.detach();
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
handleInput() {
|
||||
this.onChange(this.value);
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
asyncScheduler.schedule(() => this.handleFocus(false));
|
||||
this.onTouched();
|
||||
this.subscriptions.add(
|
||||
asyncScheduler.schedule(() => {
|
||||
this.focussed = false;
|
||||
this.onTouched();
|
||||
this.cdr.detectChanges();
|
||||
}, 200)
|
||||
);
|
||||
}
|
||||
|
||||
handleFocus(isFocussed: boolean) {
|
||||
|
||||
@@ -212,6 +212,24 @@ export class DetailsFacade {
|
||||
return this.changeStatus(data);
|
||||
}
|
||||
|
||||
setSupplier(items: OrderItemListItemDTO[], quantities?: Map<number, number>) {
|
||||
const data = items.map(
|
||||
({ orderId, orderItemId, orderItemSubsetId, quantity }) => ({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
data: {
|
||||
processingStatus: 268435456,
|
||||
quantity:
|
||||
!!quantities && quantities.has(orderItemId)
|
||||
? quantities.get(orderItemId)
|
||||
: quantity,
|
||||
} as StatusValues,
|
||||
})
|
||||
);
|
||||
return this.changeStatus(data);
|
||||
}
|
||||
|
||||
async arrived(items: OrderItemListItemDTO[], compartmentInfo: string) {
|
||||
const payloads = items.map(
|
||||
({ orderId, orderItemId, orderItemSubsetId }) => ({
|
||||
|
||||
@@ -46,3 +46,11 @@
|
||||
.isa-flex-fill {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.isa-flex-grow-1 {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.isa-flex-grow-2 {
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user