#664 Add Searchbar Component incl. Data Retrieval and Navigation Logic

[Shelf Searchbar iPad] Basic Styling Setup


Add AutoFocus to search input


Add Logic For Switching Between Search and Scan Icon


#664 Add Searchbar Component incl. Data Retrieval and Navigation Logic
This commit is contained in:
Sebastian
2020-06-22 15:57:22 +02:00
parent 1ef60b1f0b
commit 1bddf2b4f1
54 changed files with 1487 additions and 943 deletions

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './article-details.component';
// end:ng42.barrel

View File

@@ -1,9 +0,0 @@
<div class="container">
<h1 class="no-padding">Abholschein oder</h1>
<h1>Kundenkarte scannen</h1>
<caption>
Nutzen Sie die Scanfunktion des iPads.
</caption>
<lib-collecting-shelf-scanner #scanner (scan)="triggerSearch($event)"></lib-collecting-shelf-scanner>
</div>

View File

@@ -1,44 +0,0 @@
.container {
display: flex;
align-items: center;
padding: 36px 0;
flex-direction: column;
background-color: rgba(255, 255, 255, 1);
border-radius: 5px;
box-shadow: 0px -2px 24px 0px rgba(220, 226, 233, 0.8);
user-select: none;
margin-bottom: 90px;
height: calc(100% - 55px);
h1 {
font-family: 'Open Sans';
font-size: 26px;
font-weight: 600;
color: rgba(0, 0, 0, 1);
margin: 0;
margin-bottom: 5px;
}
.no-padding {
margin-bottom: 1px;
}
caption {
width: 400px;
font-family: 'Open Sans';
font-size: 22px;
color: rgba(0, 0, 0, 1);
text-align: center;
margin-bottom: 25px;
}
}
lib-barcode-scanner-scandit {
width: 550px;
height: 300px;
border: #e1ebf5 2px solid;
}
.btn {
margin-top: 15px;
}

View File

@@ -1,99 +0,0 @@
import { Subject } from 'rxjs';
import { BarcodeScannerScanditComponent } from 'shared/lib/barcode-scanner';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { isNullOrUndefined } from 'util';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { BranchSelectors } from 'apps/sales/src/app/core/store/selectors/branch.selector';
import { takeUntil, take } from 'rxjs/operators';
import { SetShelfSearch, ChangeCurrentRoute } from 'apps/sales/src/app/core/store/actions/process.actions';
import { ShelfSearch } from 'apps/sales/src/app/core/models/shelf-search.modal';
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
import { OrderItemListItemDTO, ListResponseArgsOfOrderItemListItemDTO } from '@swagger/oms';
@Component({
selector: 'app-shelf-barcode-search',
templateUrl: 'barcode-search.component.html',
styleUrls: ['barcode-search.component.scss'],
})
export class ShelfBarcodeSearchComponent implements OnInit, OnDestroy {
@ViewChild('scanner', { static: false }) scanner: BarcodeScannerScanditComponent;
scanningVisible = false;
destroy$ = new Subject();
constructor(private store: Store, private router: Router, private collectingShelf: CollectingShelfService) { }
ngOnInit() { }
triggerSearch(barcode) {
if (isNullOrUndefined(barcode) || barcode.length <= 1) {
return;
}
barcode = barcode.replace('ORD:', '').trim();
const branchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
this.collectingShelf
.searchShelfHasResultsWithResult(barcode, branchNumber)
.pipe(take(1))
.subscribe((reponse: ListResponseArgsOfOrderItemListItemDTO) => {
if (reponse) {
if (reponse.hits > 1) {
this.store.dispatch(
new SetShelfSearch(<ShelfSearch>{
input: barcode,
branchnumber: branchNumber,
})
);
sessionStorage.removeItem(SHELF_SCROLL_INDEX);
this.navigateToRoute('/shelf/results', `${barcode} (${reponse.hits} Ergebnisse)`);
} else if (reponse.hits === 1) {
this.details(reponse.result[0]);
}
}
});
}
details(order: OrderItemListItemDTO) {
if (order && (order.compartmentCode || order.orderId)) {
const path = `/shelf/details/${order.compartmentCode ? order.compartmentCode : order.orderId}/${order.orderId}/${
order.processingStatus
}/${order.compartmentCode ? 'c' : 'o'}`;
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: order.firstName + ' ' + order.lastName,
path: path,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(path));
this.router.navigate([path]);
}
}
private navigateToRoute(route: string, breadcrumbName: string) {
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: breadcrumbName,
path: route,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(route));
this.router.navigate([route]);
}
toggleScan() {
this.scanningVisible = !this.scanningVisible;
}
ngOnDestroy() {
this.destroy$.next();
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './customer-order.component';
// end:ng42.barrel

View File

@@ -1,7 +1,11 @@
export * from './barcode-search/barcode-search.component';
export * from './text-search/text-search.component';
export * from './customer-order/customer-order.component';
export * from './order-item/order-item.component';
export * from './order-tag/order-tag.component';
export * from './article-details/article-details.component';
export * from './partial-collection-modal/partial-collection-modal.component';
// start:ng42.barrel
export * from './article-details';
export * from './customer-order';
export * from './order-item-edit';
export * from './order-item';
export * from './order-loading';
export * from './order-overview-edit';
export * from './order-tag';
export * from './partial-collection-modal';
export * from './searchbar';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './order-item-edit.component';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './order-item.component';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './order-loading.component';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './order-overview-edit.component';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './order-tag.component';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './partial-collection-modal.component';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './searchbar.component';
export * from './searchbar.module';
// end:ng42.barrel

View File

@@ -0,0 +1,42 @@
<div class="input-wrapper">
<input
focus
autocomplete="off"
class="isa-input isa-text-input"
[class.error]="!!errorMessage.length"
[placeholder]="placeholder | truncateText: 35"
type="text"
name="shelf-search"
id="shelf-search-input"
[formControl]="searchForm"
(keyup.enter)="triggerSearch()"
/>
<span class="isa-input-error" *ngIf="!!errorMessage.length">{{
errorMessage
}}</span>
<button
class="isa-btn-close isa-input-reset"
type="reset"
*ngIf="
(!isFetchingData && searchQuery$ | async | showSearchResetPipe) ||
errorMessage
"
(click)="resetForm()"
></button>
<ng-container [ngSwitch]="isFetchingData">
<div class="spinner isa-mt-16" *ngSwitchCase="true"></div>
<button
*ngSwitchDefault
class="isa-input-submit"
[class.scan]="!errorMessage && !(searchQuery$ | async)"
type="submit"
(click)="triggerSearch()"
></button>
</ng-container>
<div
class="autocomplete-results"
*ngFor="let result of autocompleteResults$ | async"
>
<!-- Create own component using ListKey CDK -->
</div>
</div>

View File

@@ -0,0 +1,16 @@
@import '../../../../../assets/scss/variables';
app-search {
width: 80vw;
max-width: 550px;
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
width: 80vw;
max-width: 550px;
border-radius: 30px;
box-shadow: 0px 6px 24px 0px rgba(214, 215, 217, 0.8);
}

View File

@@ -0,0 +1,223 @@
import {
Component,
Input,
OnInit,
OnDestroy,
Output,
EventEmitter,
ChangeDetectionStrategy,
} from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { Process } from 'apps/sales/src/app/core/models/process.model';
import { AddProcess } from 'apps/sales/src/app/core/store/actions/process.actions';
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { Subject, Observable } from 'rxjs';
import { map, first, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-shelf-searchbar',
templateUrl: './searchbar.component.html',
styleUrls: ['./searchbar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfSearchbarComponent implements OnInit, OnDestroy {
searchForm: FormControl;
searchQuery$: Observable<string>;
autocompleteResults$: Observable<any[]>;
@Input() isIPad: boolean;
@Input() isFetchingData = false;
@Input() errorMessage = '';
@Input() placeholder =
'Kundenname, Abholfach, Abholschein, Kundenkartennummer';
@Output() search = new EventEmitter<{
type: 'scan' | 'search';
value: string;
}>();
destroy$ = new Subject();
constructor(private store: Store) {}
ngOnInit() {
this.initForm();
}
ngOnDestroy() {
this.destroy$.next();
}
initForm() {
this.searchForm = new FormControl(null);
this.searchQuery$ = this.searchForm.valueChanges;
}
resetForm() {
this.searchForm.reset();
this.resetError();
}
setError(message: string) {
this.errorMessage = message;
}
triggerSearch() {
const isValidInput = this.validateSearchInputBeforeSubmit(
this.searchForm.value
);
if (!isValidInput) {
return;
}
this.search.emit({
type: this.isSearch() ? 'search' : 'scan',
value: this.searchForm.value,
});
}
private validateSearchInputBeforeSubmit(searchInput: string): boolean {
return !isNullOrUndefined(searchInput) && searchInput.length >= 1;
}
private isSearch(): boolean {
return this.searchForm.value && this.searchForm.value.length;
}
private resetError() {
this.errorMessage = '';
}
/* search2(searchParams: string) {
const branchNumber = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
this.collectingShelf
.searchShelfHasResultsWithResult(searchParams, branchNumber)
.pipe(take(1))
.subscribe((reponse: ListResponseArgsOfOrderItemListItemDTO) => {
if (reponse) {
if (
reponse.hits > 1 ||
(reponse.result && reponse.result.length > 1)
) {
this.store.dispatch(
new SetShelfSearch(<ShelfSearch>{
input: searchParams,
branchnumber: branchNumber,
})
);
sessionStorage.removeItem(SHELF_SCROLL_INDEX);
this.store
.dispatch(
new SetCachedResults({
result: this.mapper.fromOrderItemListItemDTOArrayToCollectingShelfOrder(
reponse.result,
this.collectingShelf,
true
),
hits: reponse.hits,
})
)
.toPromise()
.then(() => {
// this.searchInput.stopLoading();
this.store.dispatch(new SetCollectingShelfCacheState(true));
this.navigateToRoute(
'/shelf/results',
`${searchParams} (${
reponse.hits ? reponse.hits : reponse.result.length
} Ergebnisse)`
);
});
} else if (
reponse.hits === 1 ||
(reponse.result && reponse.result.length === 1)
) {
this.details(reponse.result[0]);
this.store.dispatch(new ClearCachedResults());
} else {
// this.searchInput.stopLoading();
this.store.dispatch(new ClearCachedResults());
this.error = 'Ergibt keine Suchergebnisse';
}
} else {
// this.searchInput.stopLoading();
this.store.dispatch(new ClearCachedResults());
this.error = 'Ergibt keine Suchergebnisse';
}
});
}
details(order: OrderItemListItemDTO) {
if (order && (order.compartmentCode || order.orderId)) {
const path = `/shelf/details/${
order.compartmentCode ? order.compartmentCode : order.orderId
}/${order.orderId}/${order.processingStatus}/${
order.compartmentCode ? 'c' : 'o'
}`;
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: order.firstName + ' ' + order.lastName,
path: path,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(path));
this.router.navigate([path]);
}
}
private navigateToRoute(route: string, breadcrumbName: string) {
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: breadcrumbName,
path: route,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(route));
this.router.navigate([route]);
}
createTab() {
const processExists =
this.store.selectSnapshot(ProcessSelectors.getProcessesCount) > 0;
if (!processExists) {
this.createProcess();
}
}
clear() {
this.error = '';
} */
private createProcess() {
const newProcess = <Process>{
id: 1,
name: 'Vorgang 1',
currentRoute: '/shelf/search',
};
this.store.dispatch(new AddProcess(newProcess));
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: 'Abholfach',
path: '/shelf/search',
},
'shelf'
)
);
}
}

View File

@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { SearchInputModule } from '@libs/ui';
import { FocusDirective } from '../../shared/directives';
import { TruncateTextPipe, ShowSearchResetPipe } from '../../shared/pipes';
import { ShelfSearchbarComponent } from './searchbar.component';
@NgModule({
imports: [CommonModule, SearchInputModule, ReactiveFormsModule],
exports: [
ShelfSearchbarComponent,
ShowSearchResetPipe,
TruncateTextPipe,
FocusDirective,
],
declarations: [
ShelfSearchbarComponent,
ShowSearchResetPipe,
TruncateTextPipe,
FocusDirective,
],
providers: [],
})
export class ShelfSearchBarModule {}

View File

@@ -1,26 +0,0 @@
<div
class="shelf-search-container"
[ngClass]="{ 'shelf-search-container-mobile': isIPad }"
>
<div class="shelf-section shelf-search">
<div class="align-center shelf-search-title-margin">
<span class="shelf-search-title">Bestellpostensuche</span>
</div>
<div class="align-center">
<span class="shelf-search-description">
Suchen Sie den Bestellposten via Name, Abholfachnummer,
Abholscheinnummer, Kundenkartennummer oder Vorgang-ID.
</span>
</div>
<div class="align-center search-container">
<app-search
#searchInput
(input)="createTab()"
(search)="search($event)"
[load]="true"
placeholder="Name, Abholfach, Abholschein, Kundenkarte"
error="{{ error }}"
></app-search>
</div>
</div>
</div>

View File

@@ -1,100 +0,0 @@
@import '../../../../../assets/scss/variables';
.shelf-search-container {
background-image: linear-gradient(-180deg, #ffffff 0%, #ffffff 100%);
box-shadow: 0px -2px 24px 0px #dce2e9;
height: calc(100% + 75px);
border-radius: 4px;
}
.shelf-search-container-mobile {
height: 100%;
}
.shelf-section {
box-shadow: 0px -2px 6px 0px $hima-content-shadow-color;
-moz-box-shadow: 0px -2px 6px 0px $hima-content-shadow-color;
-webkit-box-shadow: 0px -2px 6px 0px $hima-content-shadow-color;
padding: 10px;
}
.shelf-scan {
display: grid;
grid-template-columns: auto;
height: 40px;
padding-top: 28px;
}
.shelf-scan-header {
font-size: 20px;
font-weight: bold;
color: #557596;
opacity: 0.8;
text-align: left;
line-height: 21px;
}
.shelf-scan-content {
font-size: 10pt;
}
.shelf-scan-content-div {
margin-top: 2px;
}
.shelf-search {
padding-top: 30px;
background-color: #fff;
border-radius: 4px;
display: flex;
flex-direction: column;
box-shadow: none;
}
.shelf-search-title-margin {
margin-top: 25px;
}
.shelf-search-title {
font-size: 26px;
color: #000000;
font-weight: bold;
}
.shelf-search-description {
font-size: 22px;
width: 530px;
margin: 0 auto;
display: block;
}
.search-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 30px;
}
/*
##Device = Big Desktops
*/
@media (min-width: 1281px) {
app-search {
width: 718px;
}
}
/*
##Device = Laptops, Desktops, Ipad pro
*/
@media (min-width: 1025px) and (max-width: 1280px) {
app-search {
width: 718px;
}
}
/*
##Device = Tablets, Ipads
*/
@media (min-width: 768px) and (max-width: 1024px) {
app-search {
width: 564px;
}
}

View File

@@ -1,191 +0,0 @@
import { AfterViewInit, Component, Input, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { SearchInputComponent } from '@libs/ui';
import { Store } from '@ngxs/store';
import { Process } from 'apps/sales/src/app/core/models/process.model';
import { AddProcess, SetShelfSearch, ChangeCurrentRoute } from 'apps/sales/src/app/core/store/actions/process.actions';
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
import { ProcessSelectors } from 'apps/sales/src/app/core/store/selectors/process.selectors';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { BranchSelectors } from 'apps/sales/src/app/core/store/selectors/branch.selector';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { OrderItemListItemDTO, ListResponseArgsOfOrderItemListItemDTO, OrderDTO } from '@swagger/oms';
import { ShelfSearch } from 'apps/sales/src/app/core/models/shelf-search.modal';
import { isNullOrUndefined } from 'util';
import { WindowRef } from 'apps/sales/src/app/core/services/window-ref.service';
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
import {
ClearCachedResults,
SetCachedResults,
SetCollectingShelfCacheState,
} from 'apps/sales/src/app/core/store/actions/collecting-shelf.action';
import { ShelfMapping } from 'apps/sales/src/app/core/mappings/shelf.mapping';
@Component({
selector: 'app-shelf-text-search',
templateUrl: './text-search.component.html',
styleUrls: ['./text-search.component.scss'],
})
export class ShelfTextSearchComponent implements OnInit, AfterViewInit, OnDestroy {
timer: any;
error = '';
autoCompleteEnabled = true;
isIPad: boolean;
private iPadDetected = false;
private iPadEventRecieved = false;
@ViewChild('searchInput', { static: true }) searchInput: SearchInputComponent;
destroy$ = new Subject();
constructor(
private store: Store,
private router: Router,
private mapper: ShelfMapping,
private collectingShelf: CollectingShelfService,
private windowRef: WindowRef
) { }
ngOnInit() {
this.isIPad = this.isIPadEnv();
}
ngOnDestroy() {
this.destroy$.next();
}
ngAfterViewInit() {
this.searchInput.inputChange.subscribe((data) => {
if (!data && this.error) {
this.error = '';
}
});
this.searchInput.focus();
}
search(searchParams: string) {
searchParams = searchParams.trim();
if (isNullOrUndefined(searchParams) || searchParams.length <= 1) {
return;
}
const branchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
this.collectingShelf
.searchShelfHasResultsWithResult(searchParams, branchNumber)
.pipe(take(1))
.subscribe((reponse: ListResponseArgsOfOrderItemListItemDTO) => {
if (reponse) {
if (reponse.hits > 1 || (reponse.result && reponse.result.length > 1)) {
this.store.dispatch(
new SetShelfSearch(<ShelfSearch>{
input: searchParams,
branchnumber: branchNumber,
})
);
sessionStorage.removeItem(SHELF_SCROLL_INDEX);
this.store
.dispatch(
new SetCachedResults({
result: this.mapper.fromOrderItemListItemDTOArrayToCollectingShelfOrder(reponse.result, this.collectingShelf, true),
hits: reponse.hits,
})
)
.toPromise()
.then(() => {
this.searchInput.stopLoading();
this.store.dispatch(new SetCollectingShelfCacheState(true));
this.navigateToRoute(
'/shelf/results',
`${searchParams} (${reponse.hits ? reponse.hits : reponse.result.length} Ergebnisse)`
);
});
} else if (reponse.hits === 1 || (reponse.result && reponse.result.length === 1)) {
this.details(reponse.result[0]);
this.store.dispatch(new ClearCachedResults());
} else {
this.searchInput.stopLoading();
this.store.dispatch(new ClearCachedResults());
this.error = 'Ergibt keine Suchergebnisse';
}
} else {
this.searchInput.stopLoading();
this.store.dispatch(new ClearCachedResults());
this.error = 'Ergibt keine Suchergebnisse';
}
});
}
details(order: OrderItemListItemDTO) {
if (order && (order.compartmentCode || order.orderId)) {
const path = `/shelf/details/${order.compartmentCode ? order.compartmentCode : order.orderId}/${order.orderId}/${
order.processingStatus
}/${order.compartmentCode ? 'c' : 'o'}`;
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: order.firstName + ' ' + order.lastName,
path: path,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(path));
this.router.navigate([path]);
}
}
private navigateToRoute(route: string, breadcrumbName: string) {
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: breadcrumbName,
path: route,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(route));
this.router.navigate([route]);
}
createTab() {
const processExists = this.store.selectSnapshot(ProcessSelectors.getProcessesCount) > 0;
if (!processExists) {
this.createProcess();
}
}
clear() {
this.error = '';
}
private createProcess() {
const newProcess = <Process>{
id: 1,
name: 'Vorgang 1',
currentRoute: '/shelf/search',
};
this.store.dispatch(new AddProcess(newProcess));
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: 'Abholfach',
path: '/shelf/search',
},
'shelf'
)
);
}
isIPadEnv() {
const navigator = this.windowRef.nativeWindow.navigator as Navigator;
const standalone = (navigator as any).standalone,
userAgent = navigator.userAgent.toLowerCase(),
ios = /iphone|ipod|ipad/.test(userAgent);
this.iPadDetected = ios && !standalone;
return this.iPadDetected || this.iPadEventRecieved;
}
}

View File

@@ -1,9 +1,25 @@
import { Subject, of, Observable } from 'rxjs';
import { Component, OnDestroy, OnInit, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, AfterContentChecked, ViewRef } from '@angular/core';
import {
Component,
OnDestroy,
OnInit,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
AfterContentChecked,
ViewRef,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { ActivatedRoute, Router } from '@angular/router';
import { switchMap, takeUntil, take, catchError, concatMap, filter } from 'rxjs/operators';
import {
switchMap,
takeUntil,
take,
catchError,
concatMap,
filter,
} from 'rxjs/operators';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import {
OrderDTO,
@@ -13,8 +29,11 @@ import {
OrderItemSubsetDTO,
EntityDTOContainerOfOrderItemDTO,
} from '@swagger/oms';
import { orderStatusMapper, orderPaymentType, OrderStatus } from 'apps/sales/src/app/core/mappings/shelf.mapping';
import { ShelfPartialCollectionModalComponent } from '../../components';
import {
orderStatusMapper,
orderPaymentType,
OrderStatus,
} from 'apps/sales/src/app/core/mappings/shelf.mapping';
import { SetEditOrder } from 'apps/sales/src/app/core/store/actions/collecting-shelf.action';
import {
AddBreadcrumb,
@@ -28,6 +47,7 @@ import { objectNotNull } from 'apps/sales/src/app/core/utils/app.utils';
import { isNullOrUndefined } from 'util';
import { CustomerService } from 'apps/sales/src/app/core/services/customer.service';
import { User } from 'apps/sales/src/app/core/models/user.model';
import { ShelfPartialCollectionModalComponent } from '../../components';
export const COLLECTED = 256;
export const BACK_TO_STOCK = 262144;
@@ -39,8 +59,10 @@ export const ARRIVED = 128;
styleUrls: ['./shelf-order-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterContentChecked {
@ViewChild('partialModal', { static: false }) partialModal: ShelfPartialCollectionModalComponent;
export class ShelfOrderDetailsComponent
implements OnInit, OnDestroy, AfterContentChecked {
@ViewChild('partialModal', { static: false })
partialModal: ShelfPartialCollectionModalComponent;
destroy$ = new Subject();
orderDTO: OrderDTO;
@@ -65,7 +87,13 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
changedToArrived = false;
notAvailableModel = {
comment: { value: '', disabled: true, errors: undefined },
message: { code: '', value: '', valid: false, disabled: true, errors: undefined },
message: {
code: '',
value: '',
valid: false,
disabled: true,
errors: undefined,
},
};
features: string[];
@@ -82,7 +110,7 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
private cdrf: ChangeDetectorRef,
private router: Router,
private customerService: CustomerService
) { }
) {}
ngOnInit() {
this.loadOrder();
@@ -100,7 +128,10 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
} else {
const dateEl = document.getElementById('date-span');
if (dateEl) {
this.dateWidth = status.clientWidth && dateEl.clientWidth ? status.clientWidth - dateEl.clientWidth : 25;
this.dateWidth =
status.clientWidth && dateEl.clientWidth
? status.clientWidth - dateEl.clientWidth
: 25;
}
}
this.detectChanges();
@@ -109,7 +140,9 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
}
private loadOrder() {
this.route.params.pipe(take(1), switchMap(this.orderParamSwitcher)).subscribe(this.orderSubscription$);
this.route.params
.pipe(take(1), switchMap(this.orderParamSwitcher))
.subscribe(this.orderSubscription$);
}
private orderSubscription$ = (result: OrderDTO) => {
@@ -119,7 +152,11 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
this.loadCustomerData(result.buyer.source);
}
this.order = this.orderDisplayMapper(this.orderDTO);
if (this.orderDTO && this.orderDTO.items && this.orderDTO.items.length < 1) {
if (
this.orderDTO &&
this.orderDTO.items &&
this.orderDTO.items.length < 1
) {
this.goToResults();
}
if (this.partialModal) {
@@ -160,7 +197,11 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
};
hasMultipleItems() {
if (this.orderDTO && this.orderDTO.items && this.orderDTO.items.length > 1) {
if (
this.orderDTO &&
this.orderDTO.items &&
this.orderDTO.items.length > 1
) {
return true;
} else if (
this.orderDTO &&
@@ -170,7 +211,9 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
this.orderDTO.items[0].data.subsetItems.length > 0
) {
const quantity = this.orderDTO.items[0].data.subsetItems
.filter((subItems) => subItems && subItems.data && subItems.data.quantity)
.filter(
(subItems) => subItems && subItems.data && subItems.data.quantity
)
.map((subItems) => subItems.data.quantity)
.reduce((q1, q2) => q1 + q2);
if (quantity > 1) {
@@ -184,7 +227,12 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
}
private orderParamSwitcher = (params) => {
if (params['id'] && params['orderid'] && params['status'] && params['type']) {
if (
params['id'] &&
params['orderid'] &&
params['status'] &&
params['type']
) {
if (!this.isReload) {
this.status = params['status'];
}
@@ -192,7 +240,10 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
if (!this.changedToArrived) {
this.compartmentCode = params['id'];
}
return this.shelfService.getOrdersByCompartmentNumber([this.compartmentCode], params['orderid']);
return this.shelfService.getOrdersByCompartmentNumber(
[this.compartmentCode],
params['orderid']
);
} else {
this.orderId = params['id'];
return this.shelfService.getOrderByOrderId(params['id']);
@@ -241,7 +292,12 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
subsetItems: [
...item.data.subsetItems
.map((subitem) => {
if (subitem && subitem.data && subitem.data.processingStatus.toString() === this.status) {
if (
subitem &&
subitem.data &&
subitem.data.processingStatus.toString() ===
this.status
) {
return subitem;
}
})
@@ -251,7 +307,13 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
};
return subitems;
})
.filter((f) => f && f.data && f.data.subsetItems && f.data.subsetItems.length > 0),
.filter(
(f) =>
f &&
f.data &&
f.data.subsetItems &&
f.data.subsetItems.length > 0
),
],
};
}
@@ -291,10 +353,20 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
let changed: Date;
if (orderDto.items) {
items = orderDto.items.map((itemContainer) => itemContainer.data);
if (items[0] && items[0].subsetItems && items[0].subsetItems[0].data && items[0].subsetItems[0].data.compartmentStop) {
if (
items[0] &&
items[0].subsetItems &&
items[0].subsetItems[0].data &&
items[0].subsetItems[0].data.compartmentStop
) {
pickUpDate = new Date(items[0].subsetItems[0].data.compartmentStop);
}
if (items[0] && items[0].subsetItems && items[0].subsetItems[0].data && items[0].subsetItems[0].data.processingStatus) {
if (
items[0] &&
items[0].subsetItems &&
items[0].subsetItems[0].data &&
items[0].subsetItems[0].data.processingStatus
) {
statusCode = items[0].subsetItems[0].data.processingStatus;
if (statusCode) {
status = orderStatusMapper[statusCode];
@@ -312,7 +384,8 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
items[0].subsetItems[0].data &&
items[0].subsetItems[0].data.specialComment
) {
this.notAvailableModel.comment.value = items[0].subsetItems[0].data.specialComment;
this.notAvailableModel.comment.value =
items[0].subsetItems[0].data.specialComment;
}
if (
@@ -324,7 +397,8 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
items[0].subsetItems[0].data.sscText
) {
this.notAvailableModel.message.code = items[0].subsetItems[0].data.ssc;
this.notAvailableModel.message.value = items[0].subsetItems[0].data.sscText;
this.notAvailableModel.message.value =
items[0].subsetItems[0].data.sscText;
}
if (
@@ -475,7 +549,11 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
this.collect(collection, true);
}
collect(collection: { [key: string]: number } = {}, partial = false, newStatus = COLLECTED) {
collect(
collection: { [key: string]: number } = {},
partial = false,
newStatus = COLLECTED
) {
if (this.order && this.order.items) {
const orderId = this.order.orderId;
const items = this.order.items as OrderItemDTO[];
@@ -496,8 +574,8 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
? collection[orderItem.orderItemNumber]
: 0
: orderItem && orderItem.quantity
? orderItem.quantity
: 0;
? orderItem.quantity
: 0;
if (quantity > 0 && orderId && orderItemId && orderItemSubsetId) {
observer.next({
orderId: orderId,
@@ -510,7 +588,13 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
}
});
});
this.listenForNeddedChangesToBeDone(patchingSubsetItemSequentially$, partial, items.length, collection, newStatus);
this.listenForNeddedChangesToBeDone(
patchingSubsetItemSequentially$,
partial,
items.length,
collection,
newStatus
);
}
}
@@ -548,17 +632,34 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
.subscribe((response: any) => {
itemsProcessed++;
if (response) {
if (newStatus === OrderStatus.Arrived && response && response.item1 && response.item1.compartmentCode) {
if (
newStatus === OrderStatus.Arrived &&
response &&
response.item1 &&
response.item1.compartmentCode
) {
this.compartmentCode = response.item1.compartmentCode;
this.changedToArrived = true;
}
this.isReload = true;
this.realoadIfNeeded(partial, itemLength, itemsProcessed, collection, newStatus);
this.realoadIfNeeded(
partial,
itemLength,
itemsProcessed,
collection,
newStatus
);
}
});
}
realoadIfNeeded(partial: boolean, itemLength: number, itemsProcessed: number, collection: { [key: string]: number }, status: number) {
realoadIfNeeded(
partial: boolean,
itemLength: number,
itemsProcessed: number,
collection: { [key: string]: number },
status: number
) {
if (!partial) {
if (itemsProcessed === itemLength) {
this.status = status + '';
@@ -626,7 +727,10 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
});
});
this.listenForPickUpExtensions(extendingPickUpTimeSequentially$, items.length);
this.listenForPickUpExtensions(
extendingPickUpTimeSequentially$,
items.length
);
}
}
@@ -644,7 +748,12 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
.pipe(
takeUntil(this.destroy$),
concatMap((data) => {
return this.shelfService.extendPickUpPeriod(data.orderId, data.orderItemId, data.orderItemSubsetId, data.date);
return this.shelfService.extendPickUpPeriod(
data.orderId,
data.orderItemId,
data.orderItemSubsetId,
data.date
);
}),
catchError((error) => {
console.error(error);
@@ -664,11 +773,19 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
this.store.dispatch(new SetEditOrder(this.orderDTO));
let breadcrumbName = '';
if (this.orderDTO.buyer) {
breadcrumbName = this.orderDTO.buyer.firstName + ' ' + this.orderDTO.buyer.lastName + ' Bearbeiten';
breadcrumbName =
this.orderDTO.buyer.firstName +
' ' +
this.orderDTO.buyer.lastName +
' Bearbeiten';
}
const path = `/shelf/edit/${this.order.compartmentCode ? this.order.compartmentCode : this.order.orderId}/${this.order.orderId}/${
this.status
}/${this.order.compartmentCode ? 'c' : 'o'}/shelf/0/0`;
const path = `/shelf/edit/${
this.order.compartmentCode
? this.order.compartmentCode
: this.order.orderId
}/${this.order.orderId}/${this.status}/${
this.order.compartmentCode ? 'c' : 'o'
}/shelf/0/0`;
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
@@ -738,7 +855,11 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
changeInputStatus(input: string, status: boolean) {
this.notAvailableModel[input].disabled = status;
if (input === 'message' && !isNullOrUndefined(this.notAvailableModel.message.code) && this.notAvailableModel.message.code !== '') {
if (
input === 'message' &&
!isNullOrUndefined(this.notAvailableModel.message.code) &&
this.notAvailableModel.message.code !== ''
) {
this.notAvailableModel.message.valid = true;
}
this.cdrf.detectChanges();
@@ -768,9 +889,17 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
}
this.timer = setTimeout(() => {
orderItem.subsetItems.forEach((subsetItem) => {
if (subsetItem && subsetItem.data && subsetItem.data.supplier && subsetItem.data.supplier.data) {
if (
subsetItem &&
subsetItem.data &&
subsetItem.data.supplier &&
subsetItem.data.supplier.data
) {
this.shelfService
.getMessageText(code, subsetItem.data.supplier && subsetItem.data.supplier.data.id)
.getMessageText(
code,
subsetItem.data.supplier && subsetItem.data.supplier.data.id
)
.pipe(
takeUntil(this.destroy$),
catchError((error) => {
@@ -794,7 +923,11 @@ export class ShelfOrderDetailsComponent implements OnInit, OnDestroy, AfterConte
detectChanges() {
setTimeout(() => {
if (this.cdrf !== null && this.cdrf !== undefined && !(this.cdrf as ViewRef).destroyed) {
if (
this.cdrf !== null &&
this.cdrf !== undefined &&
!(this.cdrf as ViewRef).destroyed
) {
this.cdrf.detectChanges();
}
}, 0);

View File

@@ -0,0 +1,6 @@
// start:ng42.barrel
export * from './shelf-search.component';
export * from './shelf-search.module';
export * from './stagger.animation';
// end:ng42.barrel

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './shelf-search-desktop.component';
// end:ng42.barrel

View File

@@ -0,0 +1,13 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-shelf-search-desktop',
templateUrl: './shelf-search-desktop.component.html',
styleUrls: ['./shelf-search-desktop.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfSearchDesktopComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './shelf-search-ipad.component';
export * from './shelf-search-ipad.module';
// end:ng42.barrel

View File

@@ -0,0 +1,12 @@
<div class="container">
<app-shelf-searchbar
[isFetchingData]="isFetchingData$ | async"
[errorMessage]="errorMessage$ | async"
(search)="triggerSearch($event)"
></app-shelf-searchbar>
<lib-collecting-shelf-scanner
#scanner
(scan)="triggerBarcodeSearch($event)"
></lib-collecting-shelf-scanner>
</div>

View File

@@ -0,0 +1,14 @@
.container {
display: flex;
align-items: center;
flex-direction: column;
user-select: none;
margin-bottom: 90px;
height: calc(100% - 55px);
}
lib-barcode-scanner-scandit {
width: 550px;
height: 300px;
border: #e1ebf5 2px solid;
}

View File

@@ -0,0 +1,112 @@
import {
Component,
OnDestroy,
ChangeDetectionStrategy,
ViewChild,
} from '@angular/core';
import { BarcodeScannerScanditComponent } from 'shared/lib/barcode-scanner';
import { ShelfSearchFacadeService } from '../../../shared/services/shelf-search.facade.service';
import { ListResponseArgsOfOrderItemListItemDTO } from 'apps/swagger/oms/src/lib';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { map, first, takeUntil } from 'rxjs/operators';
import {
ShelfStoreFacadeService,
ShelfNavigationService,
} from '../../../shared/services';
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
@Component({
selector: 'app-shelf-search-ipad',
templateUrl: 'shelf-search-ipad.component.html',
styleUrls: ['./shelf-search-ipad.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfSearchIpadComponent implements OnDestroy {
@ViewChild('scanner', { static: false })
scanner: BarcodeScannerScanditComponent;
destroy$ = new Subject();
isFetchingData$ = new BehaviorSubject<boolean>(false);
errorMessage$ = new BehaviorSubject<string>('');
constructor(
private shelfSearchService: ShelfSearchFacadeService,
private shelfStoreFacade: ShelfStoreFacadeService,
private shelfNavigationService: ShelfNavigationService
) {}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
triggerBarcodeSearch(barcode: string) {
this.triggerSearch({ type: 'scan', value: barcode });
}
triggerSearch({ type, value }: { type: 'search' | 'scan'; value: string }) {
let result$: Observable<ListResponseArgsOfOrderItemListItemDTO>;
this.setIsFetchingData(true);
if (type === 'search') {
result$ = this.shelfSearchService.search(value);
} else if (type === 'scan') {
result$ = this.shelfSearchService.searchWithBarcode(value);
}
result$
.pipe(takeUntil(this.destroy$), first())
.subscribe(
(result) => this.handleSearchResult(result, value),
this.handleSearchResultError
);
}
private handleSearchResult(
result: ListResponseArgsOfOrderItemListItemDTO,
value: string
) {
this.setIsFetchingData(false);
if (this.hasNoResult(result)) {
this.setErrorMessage('Ergibt keine Suchergebnisse');
} else if (this.hasMultipleSearchResults(result)) {
this.shelfStoreFacade.setShelfSearch(value);
this.shelfNavigationService.navigateToResultList({
searchQuery: value,
numberOfHits: result.hits,
});
this.resetSessionStorage();
} else {
this.shelfNavigationService.navigateToDetails(this.getDetails(result));
}
}
private handleSearchResultError() {
this.setErrorMessage('Ein Fehler ist aufgetreten');
this.setIsFetchingData(false);
}
private hasNoResult(result: ListResponseArgsOfOrderItemListItemDTO): boolean {
return !!result.result && !result.result.length;
}
private hasMultipleSearchResults(
result: ListResponseArgsOfOrderItemListItemDTO
): boolean {
return !!result.result && !!result.result.length;
}
private getDetails(result: ListResponseArgsOfOrderItemListItemDTO) {
return result.result && result.result[0];
}
private resetSessionStorage(key: string = SHELF_SCROLL_INDEX) {
sessionStorage.removeItem(key);
}
private setIsFetchingData(isFetching: boolean) {
this.isFetchingData$.next(isFetching);
}
private setErrorMessage(message: string) {
this.errorMessage$.next(message);
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CollectingShelfScannerModule } from 'shared/public_api';
import { ShelfSearchIpadComponent } from './shelf-search-ipad.component';
import { ShelfSearchBarModule } from '../../../components';
@NgModule({
imports: [CommonModule, CollectingShelfScannerModule, ShelfSearchBarModule],
exports: [ShelfSearchIpadComponent],
declarations: [ShelfSearchIpadComponent],
providers: [],
})
export class ShelfSearchIpadModule {}

View File

@@ -1,18 +1,14 @@
<div [@staggerAnimation]="active" class="headers" *ngIf="isIPad">
<app-card *ngFor="let item of navigation" (click)="switch(item)" class="header">
<span class="header-title">{{ titles[item] }}</span>
</app-card>
</div>
<div class="content" [ngSwitch]="active" *ngIf="isIPad">
<div *ngSwitchCase="'search'" [@fadeIn]="active">
<app-shelf-text-search></app-shelf-text-search>
<section class="isa-content-container mt-15">
<div class="header pt-42 mb-45">
<h2 class="isa-content-headline mt-0">Bestellpostensuche</h2>
<h6 class="isa-content-subline">
Suchen Sie den Bestellposten via Name, Abholfachnummer, Abholscheinnummer,
Kundenkartennummer, Vorgang-ID oder scannen Sie den Artikel.
</h6>
</div>
<div *ngSwitchCase="'scan'" [@fadeIn]="active">
<app-shelf-barcode-search></app-shelf-barcode-search>
<div class="content">
<app-shelf-search-ipad *ngIf="isIPad"></app-shelf-search-ipad>
<app-shelf-search-desktop *ngIf="!isIPad"></app-shelf-search-desktop>
</div>
</div>
<div class="content" [ngSwitch]="active" *ngIf="!isIPad">
<div>
<app-shelf-text-search></app-shelf-text-search>
</div>
</div>
</section>

View File

@@ -1,6 +1,4 @@
.headers {
display: block;
min-height: 60px;
.header {
}
.content {

View File

@@ -14,29 +14,20 @@ import { ClearCachedResults } from 'apps/sales/src/app/core/store/actions/collec
animations: [staggerAnimation, fadeInAnimation],
})
export class ShelfSearchComponent implements OnInit {
titles = {
search: 'Warenausgabe',
scan: 'Abholschein oder Kundenkarte scannen',
};
navigation = ['scan'];
active = 'search';
isIPad: boolean;
private iPadDetected = false;
private iPadEventRecieved = false;
constructor(private store: Store, private windowRef: WindowRef, private appService: AppService) {}
private iPadEventReceived = false;
constructor(
private store: Store,
private windowRef: WindowRef,
private appService: AppService
) {}
ngOnInit() {
this.isIPad = this.appService.isIPadEnv();
this.store.dispatch(new ClearCachedResults());
}
switch(newItem: string) {
const index = this.navigation.indexOf(newItem);
this.navigation[index] = this.active;
this.active = newItem;
this.store.dispatch(new UpdateCurrentBreadcrumbName(this.titles[this.active], 'shelf'));
}
isIPadEnv() {
const navigator = this.windowRef.nativeWindow.navigator as Navigator;
const standalone = (navigator as any).standalone,
@@ -44,6 +35,6 @@ export class ShelfSearchComponent implements OnInit {
ios = /iphone|ipod|ipad/.test(userAgent);
this.iPadDetected = ios && !standalone;
return this.iPadDetected || this.iPadEventRecieved;
return this.iPadDetected || this.iPadEventReceived;
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { ShelfSearchComponent } from './shelf-search.component';
import { ShelfSearchIpadModule } from './shelf-search-ipad';
import { CommonModule } from '@angular/common';
import { ShelfSearchDesktopComponent } from './shelf-search-desktop';
@NgModule({
imports: [CommonModule, ShelfSearchIpadModule],
exports: [ShelfSearchComponent, ShelfSearchDesktopComponent],
declarations: [ShelfSearchComponent, ShelfSearchDesktopComponent],
providers: [],
})
export class ShelfSearchModule {}

View File

@@ -0,0 +1,11 @@
import { Directive, ElementRef, AfterViewInit } from '@angular/core';
// tslint:disable-next-line: directive-selector
@Directive({ selector: 'input[focus]' })
export class FocusDirective implements AfterViewInit {
constructor(private elementRef: ElementRef<HTMLInputElement>) {}
ngAfterViewInit() {
this.elementRef.nativeElement.focus();
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './focus.directive';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './show-search-reset.pipe';
export * from './truncate.pipe';
// end:ng42.barrel

View File

@@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'showSearchResetPipe',
})
export class ShowSearchResetPipe implements PipeTransform {
transform(queryString: string, minimumNumberOfChars?: number): boolean {
console.log({ queryString });
return (
!!queryString && !!(queryString.length > (minimumNumberOfChars || 0))
);
}
}

View File

@@ -0,0 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncateText',
})
export class TruncateTextPipe implements PipeTransform {
transform(text: string, numberOfChars: number): string {
return text.slice(0, numberOfChars || text.length) + '...';
}
}

View File

@@ -0,0 +1,6 @@
// start:ng42.barrel
export * from './shelf-navigation.service';
export * from './shelf-search.facade.service';
export * from './shelf-store.facade.service';
// end:ng42.barrel

View File

@@ -0,0 +1,65 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { ChangeCurrentRoute } from 'apps/sales/src/app/core/store/actions/process.actions';
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
import { OrderItemListItemDTO } from '@swagger/oms/lib';
@Injectable({ providedIn: 'root' })
export class ShelfNavigationService {
constructor(private store: Store, private router: Router) {}
navigateToDetails(order: OrderItemListItemDTO) {
const path = this.getDetailsPath(order);
const breadcrumb = this.getDetailsBreadcrumb(order);
this.navigateToRoute(path, breadcrumb);
}
navigateToResultList({
searchQuery,
numberOfHits,
}: {
searchQuery: string;
numberOfHits: number;
}) {
const path = '/shelf/results';
const breadcrumb = this.getResultListBreadcrumb(searchQuery, numberOfHits);
console.log('navigateo');
this.navigateToRoute(path, breadcrumb);
}
private navigateToRoute(route: string, breadcrumbName: string) {
this.store.dispatch(
new AddBreadcrumb(
<Breadcrumb>{
name: breadcrumbName,
path: route,
},
'shelf'
)
);
this.store.dispatch(new ChangeCurrentRoute(route));
this.router.navigate([route]);
}
private getResultListBreadcrumb(
searchQuery: string,
numberOfHits: number
): string {
return `${searchQuery} (${numberOfHits} Ergebnisse)`;
}
getDetailsBreadcrumb(order: OrderItemListItemDTO): string {
return `${order.firstName} ${order.lastName}`;
}
private getDetailsPath(order: OrderItemListItemDTO): string {
return `/shelf/details/${
order.compartmentCode ? order.compartmentCode : order.orderId
}/${order.orderId}/${order.processingStatus}/${
order.compartmentCode ? 'c' : 'o'
}`;
}
}

View File

@@ -0,0 +1,72 @@
import { Injectable } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BranchSelectors } from 'apps/sales/src/app/core/store/selectors/branch.selector';
import { Observable } from 'rxjs';
import { filter, switchMap, map } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { ListResponseArgsOfOrderItemListItemDTO } from '@swagger/oms/lib';
@Injectable({ providedIn: 'root' })
export class ShelfSearchFacadeService {
@Select(BranchSelectors.getUserBranch) currentUserBranchId$: Observable<
string
>;
constructor(
private store: Store,
private collectingShelfService: CollectingShelfService
) {}
search(queryString: string) {
const searchParams = queryString.trim();
if (!this.isValidSearchQuery(searchParams)) {
return;
}
return this.requestSearch(searchParams).pipe(map(this.handleSearchResult));
}
searchWithBarcode(barcode: string) {
const searchParams = this.getBarcodeSearchParams(barcode);
if (!this.isValidSearchQuery(searchParams)) {
return;
}
return this.requestSearch(searchParams).pipe(map(this.handleSearchResult));
}
private requestSearch(
input: string
): Observable<ListResponseArgsOfOrderItemListItemDTO> {
return this.currentUserBranchId$.pipe(
filter((branchNumber) => !isNullOrUndefined(branchNumber)),
switchMap((branchNumber) =>
this.collectingShelfService.searchShelfHasResultsWithResult(
input,
branchNumber
)
)
);
}
private handleSearchResult(result: ListResponseArgsOfOrderItemListItemDTO) {
if (!result) {
return {
result: [],
} as ListResponseArgsOfOrderItemListItemDTO;
}
return result;
}
private isValidSearchQuery(queryString: string): boolean {
return !!queryString && !!queryString.length;
}
private getBarcodeSearchParams(barcode: string) {
return barcode.replace('ORD:', '').trim();
}
}

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { SetShelfSearch } from 'apps/sales/src/app/core/store/actions/process.actions';
import { ShelfSearch } from 'apps/sales/src/app/core/models/shelf-search.modal';
import { BranchSelectors } from 'apps/sales/src/app/core/store/selectors/branch.selector';
@Injectable({ providedIn: 'root' })
export class ShelfStoreFacadeService {
constructor(private store: Store) {}
get branchnumber(): string {
return this.store.selectSnapshot(BranchSelectors.getUserBranch);
}
setShelfSearch(input: string) {
this.store.dispatch(
new SetShelfSearch(<ShelfSearch>{
input,
branchnumber: this.branchnumber,
})
);
}
}

View File

@@ -6,20 +6,25 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ButtonModule, CardModule, IconModule, InputModule, LoadingModule, SearchInputModule, DropdownModule } from '@libs/ui';
import {
ButtonModule,
CardModule,
IconModule,
InputModule,
LoadingModule,
SearchInputModule,
DropdownModule,
} from '@libs/ui';
import { SharedModule } from '../../shared/shared.module';
import {
ShelfBarcodeSearchComponent,
ShelfArticleDetailsComponent,
ShelfTextSearchComponent,
ShelfOrderItemComponent,
ShelfCustomerOrderComponent,
ShelfOrderTagComponent,
ShelfPartialCollectionModalComponent,
} from './components';
import { ShelfSearchResultsComponent } from './pages/shelf-search-results/shelf-search-results.component';
import { ShelfSearchComponent } from './pages/shelf-search/shelf-search.component';
import { ShelfRoutingModule } from './shelf-routing.module';
import { ShelfOrderDetailsComponent } from './pages/shelf-order-details/shelf-order-details.component';
import { OrderStatusPipe } from '../../pipes/order-status.pipe';
@@ -28,12 +33,10 @@ import { ShelfEditOrderComponent } from './pages/shelf-edit-order/shelf-edit-ord
import { OrderItemEditComponent } from './components/order-item-edit/order-item-edit.component';
import { OrderOverviewEditComponent } from './components/order-overview-edit/order-overview-edit.component';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { ShelfSearchModule } from './pages/shelf-search';
@NgModule({
declarations: [
ShelfSearchComponent,
ShelfBarcodeSearchComponent,
ShelfTextSearchComponent,
ShelfSearchResultsComponent,
ShelfOrderItemComponent,
ShelfOrderTagComponent,
@@ -51,6 +54,7 @@ import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
imports: [
CommonModule,
ShelfRoutingModule,
ShelfSearchModule,
FormsModule,
ReactiveFormsModule,
RouterModule,

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="58px" height="58px" viewBox="0 0 58 58" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Group 2</title>
<g id="277-/-Warenausgang---Filter" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ISA_HIMA_Abholfach_Warenausgang_Filter_01" transform="translate(-600.000000, -467.000000)">
<g id="Group-2" transform="translate(600.000000, 467.000000)">
<rect id="CTA_BG-Copy" fill="#F70400" x="0" y="0" width="58" height="58" rx="29"></rect>
<path d="M46,37.9268293 L46,43 L40.8981132,43 L40.8981132,41.097561 L44.0861132,41.098 L44.0867925,37.9268293 L46,37.9268293 Z M14.1132075,37.9268293 L14.1138868,41.098 L17.3018868,41.097561 L17.3018868,43 L12.2,43 L12.2,37.9268293 L14.1132075,37.9268293 Z M18.5773585,22.0731707 L18.5773585,37.9268293 L16.6641509,37.9268293 L16.6641509,22.0731707 L18.5773585,22.0731707 Z M31.9698113,22.0731707 L31.9698113,37.9268293 L30.0566038,37.9268293 L30.0566038,22.0731707 L31.9698113,22.0731707 Z M40.8981132,22.0731707 L40.8981132,37.9268293 L38.9849057,37.9268293 L38.9849057,22.0731707 L40.8981132,22.0731707 Z M23.0415094,35.3902439 L23.0415094,37.9268293 L21.1283019,37.9268293 L21.1283019,35.3902439 L23.0415094,35.3902439 Z M36.4339623,35.3902439 L36.4339623,37.9268293 L34.5207547,37.9268293 L34.5207547,35.3902439 L36.4339623,35.3902439 Z M27.5056604,35.3902439 L27.5056604,37.9268293 L25.5924528,37.9268293 L25.5924528,35.3902439 L27.5056604,35.3902439 Z M23.0415094,22.0731707 L23.0415094,32.2195122 L21.1283019,32.2195122 L21.1283019,22.0731707 L23.0415094,22.0731707 Z M36.4339623,22.0731707 L36.4339623,32.2195122 L34.5207547,32.2195122 L34.5207547,22.0731707 L36.4339623,22.0731707 Z M27.5056604,22.0731707 L27.5056604,32.2195122 L25.5924528,32.2195122 L25.5924528,22.0731707 L27.5056604,22.0731707 Z M46,17 L46,22.0731707 L44.0867925,22.0731707 L44.0861132,18.902 L40.8981132,18.902439 L40.8981132,17 L46,17 Z M17.3018868,17 L17.3018868,18.902439 L14.1138868,18.902 L14.1132075,22.0731707 L12.2,22.0731707 L12.2,17 L17.3018868,17 Z" id="Icon_Scan-Copy" stroke="#FFFFFF" stroke-width="0.5" fill="#FFFFFF"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -6,7 +6,10 @@
/* MODULES */
@import 'modules/button';
@import 'modules/card';
@import 'modules/content';
@import 'modules/forms';
@import 'modules/headline';
@import 'modules/input';
/* HELPERS */
@import 'helpers/spacing';

View File

@@ -22,6 +22,20 @@ $desktop: 1200px;
$big-desktop: 1800px;
/* ***MODULES*** */
/* CONTENT */
$content-background-color: #fff;
$content-border-radius: 5px;
/* HEADLINE */
$headline-font-size-l: 26px;
$headline-font-size-m: 22px;
/* INPUT */
$input-height-size-l: 60px;
$input-font-size: 21px;
$input-letter-spacing: -0.01rem;
/* BUTTON */
$button-font-weight: bold;
$button-font-size-l: 18px;

View File

@@ -54,6 +54,14 @@
box-shadow: 0 0 30px rgba(89, 100, 112, 0.5);
}
.isa-btn-close {
background: none;
background-image: url('/assets/images/close.svg');
background-repeat: no-repeat;
background-size: cover;
border: none;
}
button:disabled.isa-btn {
background: $button-disabled-color !important;
}

View File

@@ -0,0 +1,6 @@
.isa-content-container {
width: 100%;
height: 100%;
background-color: $content-background-color;
border-radius: $content-border-radius;
}

View File

@@ -0,0 +1,15 @@
.isa-content-headline {
font-family: $font-family;
font-weight: $font-weight-bold;
font-size: $headline-font-size-l;
text-align: center;
}
.isa-content-subline {
font-family: $font-family;
font-size: $headline-font-size-m;
text-align: center;
font-weight: $font-weight;
max-width: 434px;
margin: 0 auto;
}

View File

@@ -0,0 +1,68 @@
.isa-input {
height: $input-height-size-l;
font-family: $font-family;
font-size: $input-font-size;
font-weight: $font-weight-bold;
letter-spacing: $input-letter-spacing;
}
.isa-text-input {
border: inherit;
border-radius: inherit;
padding: 0 5%;
overflow: hidden;
width: 75%;
outline: none;
caret-color: $isa-red;
&:placeholder-shown {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&::placeholder {
color: rgba(90, 114, 138, 1);
}
&.error {
width: 35%;
}
}
.isa-input-submit {
background: $content-background-color;
background-image: url('assets/images/Search_Icon.svg');
border: inherit;
border-radius: inherit;
position: absolute;
outline: none;
right: 20px;
height: 27px;
width: 27px;
background-size: cover;
top: 17px;
&.scan {
top: 0;
right: 0;
height: 58px;
width: 58px;
background-image: url('assets/images/Search_Icon_Scan.svg');
}
}
.isa-input-reset {
width: 22px;
height: 22px;
right: 65px;
top: 19px;
outline: none;
position: absolute;
}
.isa-input-error {
color: $isa-red;
font-size: 14px;
font-weight: 700;
}

View File

@@ -85,6 +85,10 @@ body {
padding-top: 39px;
}
.pt-42 {
padding-top: 42px;
}
.pr-4 {
padding-right: 4px;
}
@@ -93,6 +97,14 @@ body {
padding-bottom: 16px;
}
.mt-0 {
margin-top: 0px;
}
.mt-15 {
margin-top: 15px;
}
.mx-8 {
margin-left: 8px;
margin-right: 8px;
@@ -129,6 +141,10 @@ body {
margin-left: 16px;
}
.mb-45 {
margin-bottom: 45px;
}
.c-pointer {
cursor: pointer;
}

774
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff