Merge branch 'feature/189-Warenausgabe/main' of ssh.dev.azure.com:v3/hugendubel/ISA/ISA-Frontend into feature/189-Warenausgabe/main

This commit is contained in:
Lorenz Hilpert
2020-07-02 13:09:49 +02:00
43 changed files with 574 additions and 601 deletions

View File

@@ -1,11 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
NgModule,
ErrorHandler,
LOCALE_ID,
APP_INITIALIZER,
} from '@angular/core';
import { NgModule, ErrorHandler, LOCALE_ID, APP_INITIALIZER } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -32,10 +27,7 @@ import { AppState } from './core/store/state/app.state';
import { CartEntryState } from './core/store/state/cart-entry.state';
import { BranchState } from './core/store/state/branches.state';
import { SsoModule, SsoInterface } from 'sso';
import {
SsoAuthorizationInterceptor,
HttpErrorHandlerInterceptor,
} from './core/interceptors';
import { SsoAuthorizationInterceptor, HttpErrorHandlerInterceptor } from './core/interceptors';
import { DatePipe } from '@angular/common';
import { CountryState } from './core/store/state/countries.state';
import { HimaSalesErrorHandler } from './core/error/hima-sales.error-handler';
@@ -64,6 +56,7 @@ import { ModalDialogueModule } from './core/overlay/component';
import { AppModalModule } from './app-modal.module';
import { OverlaysModule } from './core/overlay/overlays.module';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
const states = [
AppState,
@@ -91,9 +84,7 @@ export function noop() {
return function () {};
}
export function remissionModuleOptionsFactory(
config: AppConfiguration
): RemissionModuleOptions {
export function remissionModuleOptionsFactory(config: AppConfiguration): RemissionModuleOptions {
return config.remissionModuleOptions;
}

View File

@@ -50,10 +50,8 @@ export class ContentHeaderService {
break;
case 'shelf':
filters$ = this.searchStore.selectedSearchProcessFilters$.pipe(
filter(
(filters) => !isNullOrUndefined(filters) && !filters.length
),
filters$ = this.searchStore.selectedFilter$.pipe(
filter((filters) => !isNullOrUndefined(filters) && !filters.length),
// To ensure if no filter is set on process that filter is inactive
startWith([])
);
@@ -63,12 +61,7 @@ export class ContentHeaderService {
return of(false);
}
return filters$.pipe(
map(
(selectedFilters) =>
selectedFilters && !!Object.entries(selectedFilters).length
)
);
return filters$.pipe(map((selectedFilters) => selectedFilters && !!Object.entries(selectedFilters).length));
})
);
}
@@ -122,18 +115,12 @@ export class ContentHeaderService {
const applicableBlacklist = this.blackList[type];
if (type === 'filter') {
const isOnWhiteList = !!applicableWhitelist.find(
(whitelistetUrl) =>
whitelistetUrl.includes(url) || url.includes(whitelistetUrl)
);
const isOnWhiteList = !!applicableWhitelist.find((whitelistetUrl) => whitelistetUrl.includes(url) || url.includes(whitelistetUrl));
return isOnWhiteList;
}
if (type === 'breadcrumbs') {
const isOnBlackList = !!applicableBlacklist.find(
(blacklistetUrl) =>
blacklistetUrl.includes(url) || url.includes(blacklistetUrl)
);
const isOnBlackList = !!applicableBlacklist.find((blacklistetUrl) => blacklistetUrl.includes(url) || url.includes(blacklistetUrl));
return !isOnBlackList;
}
}

View File

@@ -10,10 +10,7 @@ import {
import { LoadBranches, LoadUserBranch } from '../actions/branch.actions';
import { BranchSelectors } from '../selectors/branch.selector';
import { isNullOrUndefined } from 'util';
import {
RemoveProcessNewState,
ReloadProcessData,
} from '../actions/process.actions';
import { RemoveProcessNewState, ReloadProcessData } from '../actions/process.actions';
import { UserStateService } from '../../services/user-state.service';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { ReloadCustomersData } from '../actions/customer.actions';
@@ -23,12 +20,7 @@ import { ReloadProductsData } from '../actions/product.actions';
import { ReloadBreadcrumbsData } from '../actions/breadcrumb.actions';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
switchMap,
tap,
} from 'rxjs/operators';
import { debounceTime, distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
import { ModuleSwitcher } from '../../models/app-switcher.enum';
import { ModuleSwitcherService } from '../../services/module-switcher.service';
import { ReloadFiltersData } from '../actions/filter.actions';
@@ -39,7 +31,7 @@ import { ReloadRemission } from '../actions/remission.actions';
import { ReloadFormState } from '../actions/forms.actions';
import { FILIALE_LANDING_PAGE } from '../../utils/app.constants';
export const SYNC_DATA_VERSION = 209;
export const SYNC_DATA_VERSION = 210;
export class AppStateModel {
currentProcesssId: number;
@@ -109,13 +101,9 @@ export class AppState {
}
@Action(AppSetCurrentProcess)
appSetCurrentProcess(
ctx: StateContext<AppStateModel>,
{ payload }: AppSetCurrentProcess
) {
appSetCurrentProcess(ctx: StateContext<AppStateModel>, { payload }: AppSetCurrentProcess) {
const state = ctx.getState();
const processExists =
state.processIds.findIndex((t) => t === payload) !== -1;
const processExists = state.processIds.findIndex((t) => t === payload) !== -1;
if (processExists) {
ctx.patchState({
currentProcesssId: payload,
@@ -127,23 +115,18 @@ export class AppState {
@Action(AppAddProcess)
appAddProcess(ctx: StateContext<AppStateModel>, { payload }: AppAddProcess) {
const state = ctx.getState();
const processExists =
state.processIds.findIndex((t) => t === payload) !== -1;
const processExists = state.processIds.findIndex((t) => t === payload) !== -1;
if (!processExists) {
const processIds = [...state.processIds, payload];
const currentProcesssId = payload;
ctx.patchState({ currentProcesssId, processIds });
const branchesLoaded = this.store.selectSnapshot(
BranchSelectors.getBranches
);
const branchesLoaded = this.store.selectSnapshot(BranchSelectors.getBranches);
if (!branchesLoaded || Object.keys(branchesLoaded).length === 0) {
this.store.dispatch(new LoadBranches());
}
const userBranch = this.store.selectSnapshot(
BranchSelectors.getUserBranch
);
const userBranch = this.store.selectSnapshot(BranchSelectors.getUserBranch);
if (isNullOrUndefined(userBranch)) {
this.store.dispatch(new LoadUserBranch());
}
@@ -153,10 +136,7 @@ export class AppState {
}
@Action(AppDeleteProcess)
appDeleteProcess(
ctx: StateContext<AppStateModel>,
{ payload }: AppDeleteProcess
) {
appDeleteProcess(ctx: StateContext<AppStateModel>, { payload }: AppDeleteProcess) {
const state = ctx.getState();
const currentIds = state.processIds;
if (currentIds) {
@@ -167,10 +147,7 @@ export class AppState {
}
@Action(AppSwitchModule)
appSwitchModule(
ctx: StateContext<AppStateModel>,
{ payload }: AppSwitchModule
) {
appSwitchModule(ctx: StateContext<AppStateModel>, { payload }: AppSwitchModule) {
const state = ctx.getState();
ctx.patchState({ activeModule: payload });
this.syncApiState(state.processIds, payload);
@@ -180,10 +157,7 @@ export class AppState {
* Save store data on backend
*/
@Action(AppUserDataSync)
appUserDataSynced(
ctx: StateContext<AppStateModel>,
{ data, sync }: AppUserDataSync
) {
appUserDataSynced(ctx: StateContext<AppStateModel>, { data, sync }: AppUserDataSync) {
const state = ctx.getState();
let currentUserData: UserStateSyncData = {};
@@ -210,10 +184,7 @@ export class AppState {
* Initial store loading from API, triggered once on page load
*/
@Action(ReloadSavedState)
reloadSavedState(
ctx: StateContext<AppStateModel>,
{ data, sync }: ReloadSavedState
) {
reloadSavedState(ctx: StateContext<AppStateModel>, { data, sync }: ReloadSavedState) {
const state = ctx.getState();
let currentUserData: UserStateSyncData = {};
@@ -237,10 +208,7 @@ export class AppState {
if (sync && syncedData.version === SYNC_DATA_VERSION) {
this.reloadDataFromAPI(syncedData);
if (
syncedData.currentProcesssId ||
syncedData.activeModule === ModuleSwitcher.Branch
) {
if (syncedData.currentProcesssId || syncedData.activeModule === ModuleSwitcher.Branch) {
ctx.patchState({
...state,
synced: true,
@@ -267,19 +235,11 @@ export class AppState {
return;
}
if (data.customers) {
this.store.dispatch(
new ReloadCustomersData(
data.customers,
data.lastCreatedCustomerId,
data.cachedCustomerSearch
)
);
this.store.dispatch(new ReloadCustomersData(data.customers, data.lastCreatedCustomerId, data.cachedCustomerSearch));
}
if (data.products) {
this.store.dispatch(
new ReloadProductsData(data.products, data.cachedProductResults)
);
this.store.dispatch(new ReloadProductsData(data.products, data.cachedProductResults));
}
if (data.carts) {
@@ -299,9 +259,7 @@ export class AppState {
}
if (data.processes) {
this.store.dispatch(
new ReloadProcessData(data.processes, data.recentArticles)
);
this.store.dispatch(new ReloadProcessData(data.processes, data.recentArticles));
}
if (data.formsState) {
@@ -309,20 +267,11 @@ export class AppState {
}
if (data.activeModule === ModuleSwitcher.Customer) {
if (
data.processes &&
data.currentProcesssId &&
data.processes[data.currentProcesssId]
) {
if (data.processes && data.currentProcesssId && data.processes[data.currentProcesssId]) {
const currentProcesssId = data.currentProcesssId;
const currentRoute = data.processes[currentProcesssId].currentRoute;
if (currentRoute && currentRoute.length > 0) {
this.routingAvailableAction(
data,
currentProcesssId,
currentRoute,
data.activeModule
);
this.routingAvailableAction(data, currentProcesssId, currentRoute, data.activeModule);
} else {
this.router.navigate(['/dashboard']);
}
@@ -334,12 +283,7 @@ export class AppState {
const currentProcesssId = -1;
const currentRoute = data.branchProcess.currentRoute;
if (currentRoute && currentRoute.length > 0) {
this.routingAvailableAction(
data,
currentProcesssId,
currentRoute,
data.activeModule
);
this.routingAvailableAction(data, currentProcesssId, currentRoute, data.activeModule);
} else {
this.router.navigate([FILIALE_LANDING_PAGE]);
}
@@ -353,24 +297,11 @@ export class AppState {
}
if (data.activeModule === ModuleSwitcher.Branch) {
this.moduleSwitcherService.switch(
ModuleSwitcher.Branch,
data.branchProcess
);
this.moduleSwitcherService.switch(ModuleSwitcher.Branch, data.branchProcess);
}
if (
data.processesBreadcrumbs &&
data.activeCrumbs &&
(data.currentProcesssId || data.activeModule === ModuleSwitcher.Branch)
) {
this.store.dispatch(
new ReloadBreadcrumbsData(
data.processesBreadcrumbs,
data.activeCrumbs,
data.previusMenuPath
)
);
if (data.processesBreadcrumbs && data.activeCrumbs && (data.currentProcesssId || data.activeModule === ModuleSwitcher.Branch)) {
this.store.dispatch(new ReloadBreadcrumbsData(data.processesBreadcrumbs, data.activeCrumbs, data.previusMenuPath));
}
if (data.filters && data.processesSelectedFilters && data.dropdownFilters) {
@@ -396,28 +327,16 @@ export class AppState {
);
}
private routingAvailableAction(
data: UserStateSyncData,
currentProcesssId: number,
currentRoute: string,
module: ModuleSwitcher
) {
const hasActiveCrumbsAvailableForProcess =
data.activeCrumbs && data.activeCrumbs[currentProcesssId];
private routingAvailableAction(data: UserStateSyncData, currentProcesssId: number, currentRoute: string, module: ModuleSwitcher) {
const hasActiveCrumbsAvailableForProcess = data.activeCrumbs && data.activeCrumbs[currentProcesssId];
const activeCrumbs = data.activeCrumbs[currentProcesssId];
const hasProcessBreadcrumbsAvailableForProcess =
data.processesBreadcrumbs && data.processesBreadcrumbs[activeCrumbs];
const hasProcessBreadcrumbsAvailableForProcess = data.processesBreadcrumbs && data.processesBreadcrumbs[activeCrumbs];
const breadcrumb = hasProcessBreadcrumbsAvailableForProcess
? data.processesBreadcrumbs[activeCrumbs].find(
(t) => t.processId === currentProcesssId
)
? data.processesBreadcrumbs[activeCrumbs].find((t) => t.processId === currentProcesssId)
: null;
const breadcrumbPath =
breadcrumb && breadcrumb.breadcrumbs
? breadcrumb.breadcrumbs.find((t) => t && t.path === currentRoute)
: null;
const breadcrumbPath = breadcrumb && breadcrumb.breadcrumbs ? breadcrumb.breadcrumbs.find((t) => t && t.path === currentRoute) : null;
if (
hasActiveCrumbsAvailableForProcess &&

View File

@@ -0,0 +1,2 @@
export * from './search-result-group.component';
export * from './search-result-group.module';

View File

@@ -0,0 +1,50 @@
import { Pipe, PipeTransform } from '@angular/core';
import { OrderItemProcessingStatusValue } from '@swagger/oms';
@Pipe({
name: 'processingStatus',
})
export class ProcessingStatusPipe implements PipeTransform {
map = {
0: '',
1: 'bestellt',
2: 'Placed',
4: 'Accepted',
8: 'Parked',
16: 'in Bearbeitung',
32: 'Vorbereitung Versand',
64: 'versendet',
128: 'eingetroffen',
256: 'abgeholt',
512: 'storniert (Kunde)',
1024: 'storniert',
2048: 'storniert (Lieferant)',
4096: 'NotAvailable',
8192: 'nachbestellt ',
16384: 'ReturnedByBuyer',
32768: 'AvailableForDownload',
65536: 'Downloaded',
131072: 'NotFetched',
262144: 'Zurück zum Lager',
524288: 'angefragt',
1048576: 'RedirectedInternally',
2097152: 'Overdue',
4194304: 'Delivered',
8388608: 'DetermineSupplier',
16777216: 'SupplierTemporarilyOutOfStock',
33554432: 'Reserved',
67108864: 'Assembled',
134217728: 'Packed',
};
icon = {
128: 'Check',
512: 'close',
1024: 'close',
2048: 'close',
};
transform(value: OrderItemProcessingStatusValue, type: 'icon' | 'text' = 'text'): string {
return (type === 'icon' ? this.icon[value] : this.map[value]) || '';
}
}

View File

@@ -0,0 +1,47 @@
<div class="tags" *ngIf="item.features?.prebooked">
<lib-icon name="tag_icon_preorder" width="30px"></lib-icon>
</div>
<div class="isa-text-right isa-mb-9" *ngIf="item.compartmentCode">
<strong>{{ item.compartmentCode }} {{ item.compartmentInfo }} </strong>
</div>
<div class="paid isa-mt-9 isa-mb-9" *ngIf="item.features?.paid">
<lib-icon height="24px" width="24px" name="Check_green_circle" class="isa-mr-10"></lib-icon>
<strong> {{ item.features?.paid }} </strong>
</div>
<div class="grid-container">
<div class="cover">
<img class="thumbnail" src="https://produktbilder.ihugendubel.de/{{item.product.ean}}.jpg?showDummy=true"
alt="item.product.name">
</div>
<div class="title">
<strong class="product-name"> {{ [ item.product.contributors, item.product.name] | title }}</strong>
<strong class="processing-status">
<lib-icon *ngIf="item.processingStatus | processingStatus:'icon'; let icon" [name]="icon"></lib-icon>
{{ item.processingStatus | processingStatus }}
</strong>
</div>
<div class="details">
<div class="item-type">
<lib-icon name="Icon_{{item.product.format}}"></lib-icon>
<strong>{{ item.product.formatDetail }}</strong>
</div>
<div class="order-date spec">
<span>Bestelldatum</span>
<strong>{{ item.orderDate | date }}</strong>
</div>
<div class="item-number">
<strong>{{ item.product.ean }}</strong>
</div>
<div class="quantity spec">
<span>Menge</span>
<strong>{{ item.quantity }}x</strong>
</div>
<div class="price">
<strong>{{ item.price | currency:'EUR':'code' }}</strong>
</div>
<div class="target-branch spec">
<span>Zielfiliale</span>
<strong>{{ item.targetBranch }}</strong>
</div>
</div>
</div>

View File

@@ -0,0 +1,97 @@
:host {
display: block;
position: relative;
}
.grid-container {
display: grid;
grid-template-columns: 100px 1fr;
grid-template-rows: auto 1fr;
grid-template-areas: 'cover title' 'cover details';
}
.cover {
grid-area: cover;
.thumbnail {
max-width: 59px;
max-height: 111px;
box-shadow: 0 0 18px 0 #b8b3b7;
border-radius: 6px;
}
}
.title {
grid-area: title;
display: flex;
justify-content: space-between;
margin-bottom: 18px;
.product-name {
}
.processing-status {
color: #557596;
}
}
.details {
display: grid;
grid-template-columns: 1fr 340px;
grid-template-rows: 1fr 1fr 1fr;
grid-template-areas: 'item-type order-date' 'item-number quantity' 'price target-branch';
grid-area: details;
}
.item-type {
grid-area: item-type;
}
.order-date {
grid-area: order-date;
}
.item-number {
grid-area: item-number;
}
.quantity {
grid-area: quantity;
}
.price {
grid-area: price;
}
.target-branch {
grid-area: target-branch;
}
.spec {
display: flex;
*:nth-child(1) {
width: 150px;
}
// *:nth-child(2) {
// }
}
.paid {
display: flex;
align-items: center;
justify-content: flex-end;
lib-icon {
height: 24px;
}
}
.tags {
position: absolute;
top: 0;
right: 150px;
}

View File

@@ -0,0 +1,17 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { OrderItemListItemDTO } from '@swagger/oms';
@Component({
selector: 'app-search-result-group-item',
templateUrl: 'search-result-group-item.component.html',
styleUrls: ['search-result-group-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchResultGroupItemComponent implements OnInit {
@Input()
item: OrderItemListItemDTO;
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,14 @@
<div class="isa-card">
<h3 class="heading">{{ group.fullName }}</h3>
<ng-container *ngFor="let customer of group.items; let last = last">
<div class="isa-mb-12 isa-mt-12">
<div class="isa-text-right isa-mb-9">
<strong>{{ customer.orderNumber }}</strong>
</div>
<app-search-result-group-item *ngFor="let item of customer.items; let lastItem = last" [item]="item"
[class.group-item-bottom-space]="!lastItem">
</app-search-result-group-item>
</div>
<div class="divider" *ngIf="!last"></div>
</ng-container>
</div>

View File

@@ -0,0 +1,24 @@
:host {
display: block;
}
.isa-card {
padding: 20px 24px;
.heading {
margin: 0;
margin-bottom: 18px;
}
}
.divider {
display: block;
height: 2px;
background-color: #e6eff9;
margin-left: -24px;
margin-right: -24px;
}
.group-item-bottom-space {
margin-bottom: 40px;
}

View File

@@ -0,0 +1,18 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { GroupedByCustomer } from 'apps/sales/src/app/store/customer';
import { OrderItemListItemDTO } from '@swagger/oms';
@Component({
selector: 'app-search-result-group',
templateUrl: 'search-result-group.component.html',
styleUrls: ['search-result-group.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchResultGroupComponent implements OnInit {
@Input()
group: GroupedByCustomer;
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { SearchResultGroupComponent } from './search-result-group.component';
import { CommonModule } from '@angular/common';
import { SearchResultGroupItemComponent } from './search-result-group-item.component';
import { ProcessingStatusPipe } from './processing-status.pipe';
import { TitlePipe } from './title.pipe';
import { IconModule } from '@libs/ui';
@NgModule({
imports: [CommonModule, IconModule],
exports: [SearchResultGroupComponent],
declarations: [SearchResultGroupComponent, SearchResultGroupItemComponent, ProcessingStatusPipe, TitlePipe],
providers: [],
})
export class SearchResultGroupModule {}

View File

@@ -0,0 +1,35 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'title',
})
export class TitlePipe implements PipeTransform {
readonly placeholder = '...';
readonly seperator = '-';
transform(value: [string, string] = ['', ''], max: [number, number] = [24, 24]): any {
const maxAuthor = max[0];
let author = (value[0] || '').trim();
const restAuthor = Math.max(0, maxAuthor - author.length);
const maxTitle = max[1];
let title = (value[1] || '').trim();
const restTitle = Math.max(0, maxTitle - title.length);
if (author.length > maxAuthor + restTitle) {
author = author.trim().substr(0, maxAuthor + restTitle - this.placeholder.length) + this.placeholder;
}
if (title.length > maxTitle + restAuthor) {
title = title.trim().substr(0, maxTitle + restAuthor - this.placeholder.length) + this.placeholder;
}
let result = author;
if (result.length > 0) {
result += ` ${this.seperator} `;
}
result += title;
return result;
}
}

View File

@@ -1,10 +1,4 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
Output,
EventEmitter,
} from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core';
import { ShelfPrimaryFilterOptions } from '../../../defs';
import { Observable } from 'rxjs';
import { SearchStateFacade } from 'apps/sales/src/app/store/customer';
@@ -50,6 +44,6 @@ export class ShelfPrimaryFiltersComponent implements OnInit {
}
private initFilters() {
this.primaryFilters$ = this.searchStateFacade.getPrimaryFilters();
this.primaryFilters$ = this.searchStateFacade.primaryFilters$;
}
}

View File

@@ -1,27 +1,6 @@
<div class="result-container">
<ng-container>
<cdk-virtual-scroll-viewport itemSize="300" #scroller
[perfectScrollbar]="{minScrollbarLength: 20}">
<div *cdkVirtualFor="let order of ds; let last = last; let index = index">
<app-shelf-customer-order
*ngIf="order != null; else loadingComponent"
[index]="index"
[last]="last"
[order]="order"
[processId]="id"
></app-shelf-customer-order>
</div>
<ng-template #loadingComponent>
<app-order-loading></app-order-loading>
</ng-template>
</cdk-virtual-scroll-viewport>
</ng-container>
<app-loading
*ngIf="!ds || ds.loading"
[style.marginTop.px]="60"
[style.marginBottom.px]="60"
loading="true"
text="Inhalte werden geladen"
></app-loading>
</div>
<div class="result-container" #scroll>
<app-search-result-group class="isa-mb-10" *ngFor="let group of grouped$ | async; let i = index" [group]="group">
</app-search-result-group>
<app-loading *ngIf="fetching$ | async" [style.marginTop.px]="60" [style.marginBottom.px]="60" loading="true"
text="Inhalte werden geladen"></app-loading>
</div>

View File

@@ -1,7 +1,12 @@
.result-container {
height: calc(100% - 30px);
}
cdk-virtual-scroll-viewport {
height: 100%;
width: 100%;
}
.result-container {
height: calc(100% - 74px);
overflow: auto;
}
.item {
height: 500px;
}

View File

@@ -1,101 +1,61 @@
import { Subject } from 'rxjs';
import { Subject, fromEvent, combineLatest } from 'rxjs';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
import { Store } from '@ngxs/store';
import { ProcessSelectors } from 'apps/sales/src/app/core/store/selectors/process.selectors';
import { ShelfSearch } from 'apps/sales/src/app/core/models/shelf-search.modal';
import { ShelfSearchDataSource } from './shelf-search-results.datasource';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { takeUntil, distinct, distinctUntilChanged, take, filter } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { Process } from 'apps/sales/src/app/core/models/process.model';
import { ClearShelfOrdersForProcess, SetCollectingShelfCacheState } from 'apps/sales/src/app/core/store/actions/collecting-shelf.action';
import { SHELF_SCROLL_INDEX } from 'apps/sales/src/app/core/utils/app.constants';
import { ListRange } from '@angular/cdk/collections';
import { CollectingShelfSelectors } from 'apps/sales/src/app/core/store/selectors/collecting-shelf.selector';
import { Component, OnDestroy, OnInit, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core';
import { SearchStateFacade } from 'apps/sales/src/app/store/customer';
import { first, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-shelf-search-results',
templateUrl: './shelf-search-results.component.html',
styleUrls: ['./shelf-search-results.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
@ViewChild('scroller', { static: true }) scroller: CdkVirtualScrollViewport;
@ViewChild('scroll', { static: true })
scrollContainer: ElementRef;
shelfSearch: ShelfSearch;
ds: ShelfSearchDataSource;
destroy$ = new Subject();
id: number;
currentProcess: Process;
currentSearch: ShelfSearch;
loading = true;
doScroll = true;
useCache = false;
constructor(private store: Store, private collectingShelf: CollectingShelfService, private cdr: ChangeDetectorRef) { }
grouped$ = this.searchStateFacade.resultGroupedByCustomer$;
fetching$ = this.searchStateFacade.fetching$;
constructor(private searchStateFacade: SearchStateFacade) {
this.searchStateFacade.clearResult();
}
ngOnInit() {
this.store.dispatch(new ClearShelfOrdersForProcess());
this.store
.select(ProcessSelectors.getCurrentProcess)
.pipe(
filter(data => !isNullOrUndefined(data) && data.id !== this.id),
takeUntil(this.destroy$)
)
.subscribe((process: Process) => {
this.currentProcess = process;
this.currentSearch = process.shelfSearch;
this.id = process.id;
this.initScrollContainer();
this.fetch(true);
}
if (this.currentSearch) {
this.useCache = this.store.selectSnapshot(CollectingShelfSelectors.getShelfCacheState);
this.loadDataSource();
this.store.dispatch(new SetCollectingShelfCacheState(false));
initScrollContainer() {
const scrollContainer: HTMLElement = this.scrollContainer.nativeElement;
fromEvent(scrollContainer, 'scroll')
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
if (this.reachedBottom()) {
this.fetch();
}
});
}
this.scroller.renderedRangeStream
.pipe(
takeUntil(this.destroy$),
filter(data => !isNullOrUndefined(data))
)
.subscribe(range => {
if (this.doScroll) {
this.scrollToIndexCached(range);
}
});
reachedBottom() {
const scrollContainer: HTMLElement = this.scrollContainer.nativeElement;
return scrollContainer.scrollHeight - (scrollContainer.scrollTop + scrollContainer.clientHeight) - 100 <= 0;
}
async fetch(force = false) {
const [hits, result, fetching] = await combineLatest([this.searchStateFacade.hits$, this.searchStateFacade.result$, this.fetching$])
.pipe(first())
.toPromise();
if (force || (hits > result.length && !fetching)) {
this.searchStateFacade.fetchResult();
}
}
ngOnDestroy() {
this.destroy$.next();
}
loadDataSource() {
this.ds = new ShelfSearchDataSource(this.currentSearch, this.store, this.collectingShelf, this.useCache);
this.loading = false;
this.cdr.detectChanges();
}
scrollToIndexCached(range: ListRange) {
setTimeout(() => {
this.scroller.scrollToIndex(this.getIndex());
if (range.start <= this.getIndex() && range.end >= this.getIndex()) {
this.doScroll = false;
sessionStorage.removeItem(SHELF_SCROLL_INDEX);
}
}, 1);
}
getIndex() {
const index = sessionStorage.getItem(SHELF_SCROLL_INDEX);
if (!isNullOrUndefined(index)) {
const ids = index.split(':');
if (this.id === +ids[1]) {
return +ids[0];
}
}
return 0;
}
}

View File

@@ -1,141 +0,0 @@
import { DataSource, CollectionViewer } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Store } from '@ngxs/store';
import { take, takeUntil, debounceTime } from 'rxjs/operators';
import { SetShelfOrders } from 'apps/sales/src/app/core/store/actions/collecting-shelf.action';
import { ShelfSearch } from 'apps/sales/src/app/core/models/shelf-search.modal';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { CollectingShelfOrder } from 'apps/sales/src/app/core/models/collecting-shelf-order.model';
import { ShelfOrderItemLevel } from 'apps/sales/src/app/core/models/shelf-order-item-level.enum';
import { SharedSelectors } from 'apps/sales/src/app/core/store/selectors/shared.selectors';
export class ShelfSearchDataSource extends DataSource<CollectingShelfOrder | undefined> {
private pageSize = 10;
private fetchedPages = new Set<number>();
private cachedData = Array.from<CollectingShelfOrder>({ length: 10 });
private dataStream = new BehaviorSubject<(CollectingShelfOrder | undefined)[]>(this.cachedData);
destroy$ = new Subject();
public loading = true;
public results = false;
public allHits = 0;
public firstLoad = true;
constructor(
private search: ShelfSearch,
private store: Store,
private collectingShelf: CollectingShelfService,
private useCache: boolean
) {
super();
}
connect(collectionViewer: CollectionViewer): Observable<(CollectingShelfOrder | undefined)[]> {
collectionViewer.viewChange.pipe(takeUntil(this.destroy$)).subscribe(range => {
const startPage = this.getPageForIndex(range.start);
const endPage = this.getPageForIndex(range.end - 1);
for (let i = startPage; i <= endPage; i++) {
if (this.firstLoad && i === 0) {
return;
}
this.fetchPage(i);
}
});
this.dataStream
.pipe(
takeUntil(this.destroy$),
debounceTime(1000)
)
.subscribe(i => {
this.store.dispatch(new SetShelfOrders([...i]));
});
this.fetchPage(0);
return this.dataStream;
}
disconnect(): void {
this.destroy$.next();
}
public getPageForIndex(index: number): number {
return Math.floor(index / this.pageSize);
}
private fetchPage(page: number): number {
if (page === 0) {
this.results = false;
}
if (this.fetchedPages.has(page)) {
return;
}
this.fetchedPages.add(page);
this.loading = true;
const shelfSearch$ =
page === 0 && this.useCache
? this.collectingShelf.searchShelfCached()
: this.collectingShelf.searchShelf(this.search.input, this.search.branchnumber, page * this.pageSize, this.pageSize);
shelfSearch$.pipe(take(1)).subscribe(({ result, hits }: { result: CollectingShelfOrder[]; hits: number }) => {
this.loading = false;
this.results = true;
if (page === 0) {
this.cachedData = Array.from<CollectingShelfOrder>({ length: hits });
this.allHits = hits;
}
const tempComperer = this.cachedData.filter(t => t && t.orderId);
const updatedResult = result.map(or => {
const updatedOrder = <CollectingShelfOrder>{
...or,
shelfOrderItemLevel: !this.cachedData
? ShelfOrderItemLevel.Customer
: this.defineOrderLevel(or.buyerNumber, or.orderId, or.processingStatus, or.orderItemId, or.compartmentCode, tempComperer),
};
tempComperer.push(updatedOrder);
return updatedOrder;
});
this.cachedData.splice(page * this.pageSize, this.pageSize, ...updatedResult);
this.dataStream.next(this.cachedData);
if (page === 0) {
// dispatch immediately on first page load
this.firstLoad = false;
this.store.dispatch(new SetShelfOrders(this.cachedData));
}
});
}
private defineOrderLevel(
buyerNumber: string,
orderId: number,
processingStatus: number,
itemId: number,
compartmentCode: string,
currentOrders: CollectingShelfOrder[]
): ShelfOrderItemLevel {
const customerFound = currentOrders.findIndex((or: CollectingShelfOrder) => or && or.buyerNumber === buyerNumber) !== -1;
const orderFound = currentOrders.findIndex((or: CollectingShelfOrder) => or && or.orderId === orderId) !== -1;
const compartmentCodeFound =
currentOrders &&
currentOrders[currentOrders.length - 1] &&
currentOrders[currentOrders.length - 1].compartmentCode === compartmentCode;
const orderByStatus =
currentOrders &&
currentOrders[currentOrders.length - 1] &&
currentOrders[currentOrders.length - 1].processingStatus !== processingStatus;
if (customerFound && !orderFound) {
return ShelfOrderItemLevel.Order;
} else if (customerFound && orderByStatus) {
return ShelfOrderItemLevel.Order;
} else if (customerFound && !compartmentCodeFound) {
return ShelfOrderItemLevel.Order;
} else if (!customerFound) {
return ShelfOrderItemLevel.Customer;
} else {
return ShelfOrderItemLevel.Item;
}
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { ShelfSearchResultsComponent } from './shelf-search-results.component';
import { CommonModule } from '@angular/common';
import { LoadingModule } from '@libs/ui';
import { SearchResultGroupModule } from '../../components/search-result-group';
@NgModule({
imports: [CommonModule, LoadingModule, SearchResultGroupModule],
exports: [ShelfSearchResultsComponent],
declarations: [ShelfSearchResultsComponent],
providers: [],
})
export class ShelfSearchResultsModule {}

View File

@@ -2,16 +2,7 @@ import { Injectable, OnDestroy } from '@angular/core';
import { Observable, BehaviorSubject, of, Subject, combineLatest } from 'rxjs';
import { SelectFilter, Filter, SelectFilterOption } from '../../filter';
import { mockFilters } from '../shared/mockdata/filters.mock';
import {
tap,
switchMap,
startWith,
map,
withLatestFrom,
debounceTime,
filter,
first,
} from 'rxjs/operators';
import { tap, switchMap, startWith, map, withLatestFrom, debounceTime, filter, first } from 'rxjs/operators';
import { SearchStateFacade } from '../../../store/customer';
import { flatten } from '../../../shared/utils';
import { isNullOrUndefined } from 'util';
@@ -27,14 +18,12 @@ export class ShelfFilterService implements OnDestroy {
public overlayClosed$ = new Subject<void>();
constructor(private searchStateFacade: SearchStateFacade) {
this.filters$ = this.searchStateFacade.currentSearchProcessFilters$;
this.filters$ = this.searchStateFacade.currentFilter$;
this.initPendingFilters();
}
get hasSelectedFilters$(): Observable<boolean> {
return this.searchStateFacade.currentSearchProcessFilters$.pipe(
map(this.computeHasSelectedFilters)
);
return this.searchStateFacade.currentFilter$.pipe(map(this.computeHasSelectedFilters));
}
get hasSelectedPendingFilters$(): Observable<boolean> {
@@ -75,10 +64,7 @@ export class ShelfFilterService implements OnDestroy {
}
private computeHasSelectedFilters(filters: SelectFilter[]): boolean {
const flattenedFilters = (flatten(
filters,
'options'
) as unknown) as SelectFilterOption[];
const flattenedFilters = (flatten(filters, 'options') as unknown) as SelectFilterOption[];
if (flattenedFilters.some((f) => !!f.selected)) {
return true;

View File

@@ -5,15 +5,7 @@ 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 {
@@ -23,7 +15,7 @@ import {
ShelfOrderTagComponent,
ShelfPartialCollectionModalComponent,
} from './components';
import { ShelfSearchResultsComponent } from './pages/shelf-search-results/shelf-search-results.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';
@@ -32,11 +24,11 @@ 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 { ShelfSearchResultsModule } from './pages/shelf-search-results/shelf-search-results.module';
import { ShelfSearchModule } from './pages/shelf-search';
@NgModule({
declarations: [
ShelfSearchResultsComponent,
ShelfOrderItemComponent,
ShelfOrderTagComponent,
ShelfCustomerOrderComponent,
@@ -68,6 +60,7 @@ import { ShelfSearchModule } from './pages/shelf-search';
IconModule,
LoadingModule,
ButtonModule,
ShelfSearchResultsModule,
],
})
export class ShelfModule {}

View File

@@ -1,5 +1,7 @@
export interface GroupedByCustomer<T> {
import { GroupedByOrder } from './grouped-by-order';
export interface GroupedByCustomer {
buyerNumber?: string;
fullName?: string;
items?: T[];
items?: GroupedByOrder[];
}

View File

@@ -0,0 +1,6 @@
import { OrderItemListItemDTO, OrderItemProcessingStatusValue } from '@swagger/oms';
export interface GroupedByOrder {
orderNumber: string;
items: OrderItemListItemDTO[];
}

View File

@@ -0,0 +1,4 @@
export interface Group<T> {
group: string;
items: T[];
}

View File

@@ -1,2 +1,4 @@
export * from './search-process';
export * from './grouped-by-customer';
export * from './grouped-by-order';
export * from './grouped';

View File

@@ -0,0 +1,23 @@
import { GroupedByCustomer } from '../defs';
import { OrderItemListItemDTO } from '@swagger/oms';
import { addOrCreateOrderGroup } from './grouped-by-order.mapper';
export function addOrCreateCustomerGroup(list: GroupedByCustomer[], item: OrderItemListItemDTO): GroupedByCustomer[] {
const customerGroupIndex = list.findIndex((ex) => ex.buyerNumber === item.buyerNumber);
const customerGroupList = [...list];
if (customerGroupIndex === -1) {
const customerGroup = {
buyerNumber: item.buyerNumber,
fullName: `${item.firstName} ${item.lastName}`,
items: addOrCreateOrderGroup([], item),
};
customerGroupList.push(customerGroup);
} else {
const customerGroup = { ...customerGroupList[customerGroupIndex] };
customerGroup.items = addOrCreateOrderGroup(customerGroup.items, item);
customerGroupList[customerGroupIndex] = customerGroup;
}
return customerGroupList;
}

View File

@@ -0,0 +1,20 @@
import { GroupedByOrder } from '../defs';
import { OrderItemListItemDTO } from '@swagger/oms';
export function addOrCreateOrderGroup(list: GroupedByOrder[], item: OrderItemListItemDTO): GroupedByOrder[] {
const orderGroupIndex = list.findIndex((ex) => ex.orderNumber === item.orderNumber);
const orderGroupList = [...list];
if (orderGroupIndex === -1) {
const orderGroup = {
orderNumber: item.orderNumber,
items: [item],
};
orderGroupList.push(orderGroup);
} else {
const orderGroup = { ...orderGroupList[orderGroupIndex] };
orderGroup.items = [...orderGroup.items, item];
orderGroupList[orderGroupIndex] = orderGroup;
}
return orderGroupList;
}

View File

@@ -0,0 +1,2 @@
export * from './grouped-by-customer.mapper';
export * from './grouped-by-order.mapper';

View File

@@ -3,20 +3,22 @@ import { Store } from '@ngrx/store';
import { Store as NgxsStore } from '@ngxs/store';
import * as actions from './search.actions';
import * as selectors from './search.selectors';
import { map, first, take, switchMap, filter, startWith } from 'rxjs/operators';
import { from, Observable } from 'rxjs';
import { SearchProcess } from './defs';
import { isNullOrUndefined } from 'util';
import { OrderItemListItemDTO } from '@swagger/oms';
import { GroupedByCustomer } from './defs';
import { switchMap, filter, map, first } from 'rxjs/operators';
import { SharedSelectors } from 'apps/sales/src/app/core/store/selectors/shared.selectors';
import { ShelfPrimaryFilterOptions } from 'apps/sales/src/app/modules/shelf/defs/shelf-primary-filter-options';
import { Observable } from 'rxjs';
import { GroupedByCustomer } from './defs';
import { addOrCreateCustomerGroup } from './mappers';
import { isNullOrUndefined } from 'util';
import { SelectFilter } from 'apps/sales/src/app/modules/filter';
import { ShelfPrimaryFilterOptions } from 'apps/sales/src/app/modules/shelf/defs';
@Injectable({ providedIn: 'root' })
export class SearchStateFacade {
get process$() {
return this.getProcessId$().pipe(switchMap((id) => this.getProcess$(id)));
return this.getProcessId$().pipe(
switchMap((id) => this.getProcess$(id)),
filter((process) => !isNullOrUndefined(process))
);
}
get input$() {
@@ -28,9 +30,7 @@ export class SearchStateFacade {
}
get resultGroupedByCustomer$() {
return this.getProcessId$().pipe(
switchMap((id) => this.getResultGroupedByCustomer$(id))
);
return this.getProcessId$().pipe(switchMap((id) => this.getResultGroupedByCustomer$(id)));
}
get hits$() {
@@ -41,6 +41,26 @@ export class SearchStateFacade {
return this.getProcessId$().pipe(switchMap((id) => this.getFetching$(id)));
}
get primaryFilters$() {
return this.process$.pipe(switchMap((process) => this.getPrimaryFilters(process.id)));
}
get currentFilter$(): Observable<SelectFilter[]> {
return this.process$.pipe(
filter((process) => !isNullOrUndefined(process) && !isNullOrUndefined(process.filters)),
map((process) => process.filters.selectedFilters)
);
}
get selectedFilter$(): Observable<string[]> {
return this.currentFilter$.pipe(
map(() => {
// TODO extracting all selected filters possibly required for building search query
return [];
})
);
}
constructor(private store: Store<any>, private ngxsStore: NgxsStore) {}
getProcessId$() {
@@ -66,31 +86,8 @@ export class SearchStateFacade {
return this.store.select(selectors.selectResult, id);
}
getResultGroupedByCustomer$(
id: number
): Observable<GroupedByCustomer<OrderItemListItemDTO>[]> {
return this.getResult$(id).pipe(
map((result) =>
result.reduce((acc, item) => {
const existing = acc.findIndex(
(ex) => ex.buyerNumber === item.buyerNumber
);
if (existing === -1) {
return [
...acc,
{
buyerNumber: item.buyerNumber,
fullName: `${item.firstName} ${item.lastName}`,
items: [item],
},
];
} else {
acc[existing].items.push(item);
}
return acc;
}, [] as GroupedByCustomer<OrderItemListItemDTO>[])
)
);
getResultGroupedByCustomer$(id: number): Observable<GroupedByCustomer[]> {
return this.getResult$(id).pipe(map((result) => result.reduce(addOrCreateCustomerGroup, [])));
}
getHits$(id: number) {
@@ -101,32 +98,8 @@ export class SearchStateFacade {
return this.store.select(selectors.selectFetching, id);
}
get currentSearchProcess$(): Observable<SearchProcess> {
return from(this.getProcessId()).pipe(
take(1),
switchMap((processId) =>
this.store.select(selectors.selectProcess, processId)
)
);
}
get currentSearchProcessFilters$(): Observable<SelectFilter[]> {
return this.currentSearchProcess$.pipe(
filter(
(process) =>
!isNullOrUndefined(process) && !isNullOrUndefined(process.filters)
),
map((process) => process.filters.selectedFilters)
);
}
get selectedSearchProcessFilters$(): Observable<string[]> {
return this.currentSearchProcessFilters$.pipe(
map((filters) => {
// TODO extracting all selected filters possibly required for building search query
return [];
})
);
getPrimaryFilters(id: number): Observable<ShelfPrimaryFilterOptions> {
return this.store.select(selectors.selectPrimaryFilters, id);
}
async setInput(input: string, id?: number) {
@@ -149,10 +122,7 @@ export class SearchStateFacade {
this.store.dispatch(actions.setSelectedFilters({ filters, id: processId }));
}
async setPrimaryFilters(
filters: Partial<ShelfPrimaryFilterOptions>,
id?: number
) {
async setPrimaryFilters(filters: Partial<ShelfPrimaryFilterOptions>, id?: number) {
let updatedFilters: ShelfPrimaryFilterOptions;
let processId = id;
@@ -160,34 +130,14 @@ export class SearchStateFacade {
processId = await this.getProcessId();
}
const currentPrimaryFilters = await this.getCurrentPrimaryFilters(processId)
.pipe(first())
.toPromise();
const currentPrimaryFilters = await this.getPrimaryFilters(processId).pipe(first()).toPromise();
updatedFilters = {
...currentPrimaryFilters,
...filters,
} as ShelfPrimaryFilterOptions;
return this.store.dispatch(
actions.setPrimaryFilters({ filters: updatedFilters, id: processId })
);
}
getPrimaryFilters(id?: number): Observable<ShelfPrimaryFilterOptions> {
if (id) {
return this.getCurrentPrimaryFilters(id);
}
return from(this.getProcessId()).pipe(
switchMap((processId) => this.getCurrentPrimaryFilters(processId))
);
}
private getCurrentPrimaryFilters(
processId: number
): Observable<ShelfPrimaryFilterOptions> {
return this.store.select(selectors.selectPrimaryFilters, processId);
return this.store.dispatch(actions.setPrimaryFilters({ filters: updatedFilters, id: processId }));
}
async fetchResult(id?: number) {

View File

@@ -1,23 +1,12 @@
import { createReducer, Action, on } from '@ngrx/store';
import {
INITIAL_SEARCH_STATE,
SearchState,
searchStateAdapter,
INITIAL_SEARCH_PROCESS,
} from './search.state';
import { INITIAL_SEARCH_STATE, SearchState, searchStateAdapter, INITIAL_SEARCH_PROCESS } from './search.state';
import * as actions from './search.actions';
const _searchReducer = createReducer(
INITIAL_SEARCH_STATE,
on(actions.addSearchProcess, (s, a) =>
searchStateAdapter.addOne({ id: a.id, ...INITIAL_SEARCH_PROCESS }, s)
),
on(actions.removeSearchProcess, (s, a) =>
searchStateAdapter.removeOne(a.id, s)
),
on(actions.setInput, (s, a) =>
searchStateAdapter.updateOne({ id: a.id, changes: { input: a.input } }, s)
),
on(actions.addSearchProcess, (s, a) => searchStateAdapter.addOne({ ...INITIAL_SEARCH_PROCESS, id: a.id }, s)),
on(actions.removeSearchProcess, (s, a) => searchStateAdapter.removeOne(a.id, s)),
on(actions.setInput, (s, a) => searchStateAdapter.updateOne({ id: a.id, changes: { input: a.input } }, s)),
on(actions.setSelectedFilters, (s, a) =>
searchStateAdapter.updateOne(
{
@@ -43,15 +32,7 @@ const _searchReducer = createReducer(
s
)
),
on(actions.addSearchProcess, (s, a) =>
searchStateAdapter.addOne({ ...INITIAL_SEARCH_PROCESS, id: a.id }, s)
),
on(actions.removeSearchProcess, (s, a) =>
searchStateAdapter.removeOne(a.id, s)
),
on(actions.clearResults, (s, a) =>
searchStateAdapter.updateOne({ id: a.id, changes: { result: [] } }, s)
),
on(actions.clearResults, (s, a) => searchStateAdapter.updateOne({ id: a.id, changes: { result: [] } }, s)),
on(actions.addResult, (s, a) =>
searchStateAdapter.updateOne(
{
@@ -61,18 +42,10 @@ const _searchReducer = createReducer(
s
)
),
on(actions.clearHits, (s, a) =>
searchStateAdapter.updateOne({ id: a.id, changes: { hits: undefined } }, s)
),
on(actions.setHits, (s, a) =>
searchStateAdapter.updateOne({ id: a.id, changes: { hits: a.hits } }, s)
),
on(actions.fetchResult, (s, a) =>
searchStateAdapter.updateOne({ id: a.id, changes: { fetching: true } }, s)
),
on(actions.fetchResultDone, (s, a) =>
searchStateAdapter.updateOne({ id: a.id, changes: { fetching: false } }, s)
)
on(actions.clearHits, (s, a) => searchStateAdapter.updateOne({ id: a.id, changes: { hits: undefined } }, s)),
on(actions.setHits, (s, a) => searchStateAdapter.updateOne({ id: a.id, changes: { hits: a.hits } }, s)),
on(actions.fetchResult, (s, a) => searchStateAdapter.updateOne({ id: a.id, changes: { fetching: true } }, s)),
on(actions.fetchResultDone, (s, a) => searchStateAdapter.updateOne({ id: a.id, changes: { fetching: false } }, s))
);
export function searchReducer(state: SearchState, action: Action) {

View File

@@ -4,61 +4,28 @@ import { searchStateAdapter } from './search.state';
import { Dictionary } from '@ngrx/entity';
import { SearchProcess } from './defs';
export const selectSearchState = createSelector(
selectShelfState,
(s) => s.search
);
export const selectSearchState = createSelector(selectShelfState, (s) => s.search);
export const { selectAll, selectEntities } = searchStateAdapter.getSelectors();
export const { selectAll, selectEntities } = searchStateAdapter.getSelectors(selectSearchState);
export const selectAllSearchProcesses = createSelector(
selectSearchState,
selectAll
);
export const selectProcess = createSelector(selectEntities, (entities: Dictionary<SearchProcess>, id: number) => entities[id]);
export const selectAllSearchProcessEntities = createSelector(
selectSearchState,
selectEntities
);
export const selectInput = createSelector(selectEntities, (entities: Dictionary<SearchProcess>, id: number) => entities[id].input);
export const selectProcess = createSelector(
selectAllSearchProcessEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id]
);
export const selectResult = createSelector(selectEntities, (entities: Dictionary<SearchProcess>, id: number) => entities[id].result);
export const selectInput = createSelector(
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].input
);
export const selectHits = createSelector(selectEntities, (entities: Dictionary<SearchProcess>, id: number) => entities[id].hits);
export const selectResult = createSelector(
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].result
);
export const selectFetching = createSelector(selectEntities, (entities: Dictionary<SearchProcess>, id: number) => entities[id].fetching);
export const selectHits = createSelector(
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].hits
);
export const selectFetching = createSelector(
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].fetching
);
export const selectFilters = createSelector(
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].filters
);
export const selectFilters = createSelector(selectEntities, (entities: Dictionary<SearchProcess>, id: number) => entities[id].filters);
export const selectSelectedFilters = createSelector(
selectAllSearchProcessEntities,
(entities: Dictionary<SearchProcess>, id: number) =>
entities[id].filters.selectedFilters
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].filters.selectedFilters
);
export const selectPrimaryFilters = createSelector(
selectAllSearchProcessEntities,
(entities: Dictionary<SearchProcess>, id: number) =>
entities[id].filters.primaryFilters
selectEntities,
(entities: Dictionary<SearchProcess>, id: number) => entities[id].filters.primaryFilters
);

View File

@@ -29,7 +29,7 @@ export const INITIAL_FILTERS: {
export const INITIAL_SEARCH_PROCESS: SearchProcess = {
id: undefined,
result: [],
input: 'Müller',
input: '',
fetching: false,
filters: INITIAL_FILTERS,
};

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

BIN
apps/sales/src/assets/icons/icon-144x144.png Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

BIN
apps/sales/src/assets/icons/icon-192x192.png Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

BIN
apps/sales/src/assets/icons/icon-512x512.png Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
apps/sales/src/assets/icons/icon-72x72.png Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 B

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
apps/sales/src/assets/icons/icon-96x96.png Normal file → Executable file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -6,6 +6,10 @@ import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import 'hammerjs';
import { AppConfiguration } from './app/app-configuration';
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
registerLocaleData(localeDe, 'de');
if (environment.production) {
enableProdMode();

View File

@@ -12,6 +12,23 @@
font-weight: $font-weight-emphasis;
}
.isa-text-left {
text-align: left;
}
.isa-text-center {
text-align: center;
}
.isa-text-right {
text-align: right;
}
//TODO: _color.scss ausglagern
.isa-font-color-customer {
color: $isa-customer;
}
.isa-font-grey {
color: $text-grey;
}
@@ -19,7 +36,3 @@
.isa-font-lightgrey {
color: $text-lightgrey;
}
.isa-font-color-customer {
color: $isa-customer;
}

0
sales
View File