mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
151 Commits
ipad-bugfi
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddd1b81d0d | ||
|
|
c79a1cdad1 | ||
|
|
edf978f5cf | ||
|
|
4bfe35a8b9 | ||
|
|
4b7c26b009 | ||
|
|
2a442dde85 | ||
|
|
45bb39f466 | ||
|
|
b2731432ed | ||
|
|
8c424bab43 | ||
|
|
571c748a66 | ||
|
|
c104f36255 | ||
|
|
672245c467 | ||
|
|
0ae92e34c6 | ||
|
|
ce72ce48b2 | ||
|
|
bd5ec27425 | ||
|
|
4dc98f7980 | ||
|
|
c89ee18db3 | ||
|
|
c4aa7999a6 | ||
|
|
27a83242c5 | ||
|
|
7bf23c4bcc | ||
|
|
0bc80e12aa | ||
|
|
b586e3da9e | ||
|
|
a433f2cfe4 | ||
|
|
ff368d68b7 | ||
|
|
40ebd72263 | ||
|
|
11eca1e25a | ||
|
|
b8d0153232 | ||
|
|
0a195e78e5 | ||
|
|
97c26f5fa1 | ||
|
|
8dac64d5b8 | ||
|
|
2c295b0797 | ||
|
|
696015b6a4 | ||
|
|
f2f70e1d83 | ||
|
|
4d1dbaa2f3 | ||
|
|
4344f4617c | ||
|
|
c451d2b329 | ||
|
|
96d1a4b826 | ||
|
|
595bb27d99 | ||
|
|
958a388fb5 | ||
|
|
a908767f08 | ||
|
|
3c0406031a | ||
|
|
7b72532c9e | ||
|
|
6b756fe893 | ||
|
|
54c7f51766 | ||
|
|
94b787655e | ||
|
|
259d0f1648 | ||
|
|
872d3ff383 | ||
|
|
cf1c4d37b9 | ||
|
|
ada16bac6c | ||
|
|
d68055f8a9 | ||
|
|
8d98dcf7e7 | ||
|
|
c828a69f66 | ||
|
|
eefb6062c7 | ||
|
|
470a451168 | ||
|
|
e3b018c5f7 | ||
|
|
bd695e21d4 | ||
|
|
79bb9b8c11 | ||
|
|
6311ebe467 | ||
|
|
341b202bc4 | ||
|
|
f8a5ceed97 | ||
|
|
0420bda5da | ||
|
|
6dc532f40e | ||
|
|
0560d01f30 | ||
|
|
bc67ec3287 | ||
|
|
bed35a2377 | ||
|
|
f284dc1db5 | ||
|
|
aaf156cee3 | ||
|
|
e8020ffde6 | ||
|
|
8d6bd80902 | ||
|
|
8aa870dddd | ||
|
|
26a3c76d5f | ||
|
|
18f738f2c3 | ||
|
|
bba50ccbcc | ||
|
|
3eea3b913d | ||
|
|
2dbeec831e | ||
|
|
88a06628e3 | ||
|
|
37ceb30ffb | ||
|
|
ebd0515e96 | ||
|
|
ece5d0fa0d | ||
|
|
6be214c6cd | ||
|
|
2144ec838c | ||
|
|
c470453ea4 | ||
|
|
bbe9326954 | ||
|
|
27961bb4e5 | ||
|
|
4952c090ef | ||
|
|
bff10cb2ff | ||
|
|
d303b1444b | ||
|
|
74e4016625 | ||
|
|
c389008811 | ||
|
|
e6dcf22012 | ||
|
|
d067f925b9 | ||
|
|
8b5609f765 | ||
|
|
dd04a1f2af | ||
|
|
9caabb6cc0 | ||
|
|
33b28d5f41 | ||
|
|
475f9b5e34 | ||
|
|
60f1348ea5 | ||
|
|
533b6e1fcf | ||
|
|
8961730b74 | ||
|
|
5d580714c8 | ||
|
|
daf1ead75b | ||
|
|
aef2654a39 | ||
|
|
8243cd3528 | ||
|
|
447456d7a6 | ||
|
|
241a34d7a8 | ||
|
|
4e67b2e8b9 | ||
|
|
8bc2ea8373 | ||
|
|
00a6a113c8 | ||
|
|
dc04619128 | ||
|
|
150e7965ee | ||
|
|
e066da3762 | ||
|
|
ad96278956 | ||
|
|
3fcf3d9396 | ||
|
|
83406277ad | ||
|
|
9e89348381 | ||
|
|
0fb7419598 | ||
|
|
52278b8baf | ||
|
|
1790298cb4 | ||
|
|
ffe8e39c85 | ||
|
|
67e0f4bd46 | ||
|
|
e7b3a58da3 | ||
|
|
598f9f3777 | ||
|
|
0a7dca2e12 | ||
|
|
4b342778df | ||
|
|
10e8fd904a | ||
|
|
1de342fd3b | ||
|
|
f4c1c3dd7f | ||
|
|
80bfc59356 | ||
|
|
3796f3ed5f | ||
|
|
f2e03d22d8 | ||
|
|
ef967b66e8 | ||
|
|
58ea70cc6c | ||
|
|
e4823950df | ||
|
|
8f9923ba5d | ||
|
|
72bbd2c36e | ||
|
|
9bdb902a56 | ||
|
|
bbc2e55ae3 | ||
|
|
6995bdb527 | ||
|
|
a7abc35316 | ||
|
|
772aed597b | ||
|
|
9e18825c27 | ||
|
|
27b7ffcf99 | ||
|
|
d804a744b6 | ||
|
|
fc5cf27bd1 | ||
|
|
355bba8966 | ||
|
|
0882bc2ca7 | ||
|
|
d9a2601f75 | ||
|
|
37a04cadf8 | ||
|
|
b01ce5b3b6 | ||
|
|
c7e6f00ddb | ||
|
|
85831ffe5d |
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -3,6 +3,5 @@
|
|||||||
"johnpapa.angular2",
|
"johnpapa.angular2",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"angular.ng-template",
|
"angular.ng-template",
|
||||||
"eg2.vscode-npm-script"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
200
angular.json
200
angular.json
@@ -375,37 +375,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@shell/breadcrumb": {
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "apps/shell/breadcrumb",
|
|
||||||
"sourceRoot": "apps/shell/breadcrumb/src",
|
|
||||||
"prefix": "shell",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/breadcrumb/tsconfig.lib.json",
|
|
||||||
"project": "apps/shell/breadcrumb/ng-package.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"tsConfig": "apps/shell/breadcrumb/tsconfig.lib.prod.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/breadcrumb/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"polyfills": [
|
|
||||||
"zone.js",
|
|
||||||
"zone.js/testing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@domain/defs": {
|
"@domain/defs": {
|
||||||
"projectType": "library",
|
"projectType": "library",
|
||||||
"root": "apps/domain/defs",
|
"root": "apps/domain/defs",
|
||||||
@@ -840,37 +809,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@shell/header": {
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "apps/shell/header",
|
|
||||||
"sourceRoot": "apps/shell/header/src",
|
|
||||||
"prefix": "shell",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/header/tsconfig.lib.json",
|
|
||||||
"project": "apps/shell/header/ng-package.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"tsConfig": "apps/shell/header/tsconfig.lib.prod.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/header/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"polyfills": [
|
|
||||||
"zone.js",
|
|
||||||
"zone.js/testing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@modal/reorder": {
|
"@modal/reorder": {
|
||||||
"projectType": "library",
|
"projectType": "library",
|
||||||
"root": "apps/modal/reorder",
|
"root": "apps/modal/reorder",
|
||||||
@@ -1058,74 +996,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@shell/footer": {
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "apps/shell/footer",
|
|
||||||
"sourceRoot": "apps/shell/footer/src",
|
|
||||||
"prefix": "lib",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
|
||||||
"options": {
|
|
||||||
"project": "apps/shell/footer/ng-package.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"tsConfig": "apps/shell/footer/tsconfig.lib.prod.json"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"tsConfig": "apps/shell/footer/tsconfig.lib.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/footer/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"polyfills": [
|
|
||||||
"zone.js",
|
|
||||||
"zone.js/testing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@shell/process": {
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "apps/shell/process",
|
|
||||||
"sourceRoot": "apps/shell/process/src",
|
|
||||||
"prefix": "lib",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
|
||||||
"options": {
|
|
||||||
"project": "apps/shell/process/ng-package.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"tsConfig": "apps/shell/process/tsconfig.lib.prod.json"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"tsConfig": "apps/shell/process/tsconfig.lib.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/process/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"polyfills": [
|
|
||||||
"zone.js",
|
|
||||||
"zone.js/testing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@domain/isa": {
|
"@domain/isa": {
|
||||||
"projectType": "library",
|
"projectType": "library",
|
||||||
"root": "apps/domain/isa",
|
"root": "apps/domain/isa",
|
||||||
@@ -1160,40 +1030,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@shell/filter-overlay": {
|
|
||||||
"projectType": "library",
|
|
||||||
"root": "apps/shell/filter-overlay",
|
|
||||||
"sourceRoot": "apps/shell/filter-overlay/src",
|
|
||||||
"prefix": "lib",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
|
||||||
"options": {
|
|
||||||
"project": "apps/shell/filter-overlay/ng-package.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"tsConfig": "apps/shell/filter-overlay/tsconfig.lib.prod.json"
|
|
||||||
},
|
|
||||||
"development": {
|
|
||||||
"tsConfig": "apps/shell/filter-overlay/tsconfig.lib.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultConfiguration": "production"
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "apps/shell/filter-overlay/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "karma.conf.js",
|
|
||||||
"polyfills": [
|
|
||||||
"zone.js",
|
|
||||||
"zone.js/testing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@store/search-component-store": {
|
"@store/search-component-store": {
|
||||||
"projectType": "library",
|
"projectType": "library",
|
||||||
"root": "apps/store/search-component-store",
|
"root": "apps/store/search-component-store",
|
||||||
@@ -1634,6 +1470,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"shell": {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "apps/shell",
|
||||||
|
"sourceRoot": "apps/shell/src",
|
||||||
|
"prefix": "shell",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||||
|
"options": {
|
||||||
|
"project": "apps/shell/ng-package.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"tsConfig": "apps/shell/tsconfig.lib.prod.json"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"tsConfig": "apps/shell/tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": "apps/shell/tsconfig.spec.json",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { BranchDTO } from '@swagger/checkout';
|
import { BranchDTO } from '@swagger/checkout';
|
||||||
import { isBoolean, isNumber } from '@utils/common';
|
import { isBoolean, isNumber } from '@utils/common';
|
||||||
@@ -16,25 +15,30 @@ import {
|
|||||||
selectActivatedProcess,
|
selectActivatedProcess,
|
||||||
patchProcess,
|
patchProcess,
|
||||||
patchProcessData,
|
patchProcessData,
|
||||||
|
selectTitle,
|
||||||
|
setTitle,
|
||||||
} from './store';
|
} from './store';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApplicationService {
|
export class ApplicationService {
|
||||||
/** @deprecated */
|
|
||||||
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
|
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
get activatedProcessId() {
|
get activatedProcessId() {
|
||||||
return this.activatedProcessIdSubject.value;
|
return this.activatedProcessIdSubject.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
get activatedProcessId$() {
|
get activatedProcessId$() {
|
||||||
return this.activatedProcessIdSubject.asObservable();
|
return this.activatedProcessIdSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
title$ = this.store.select(selectTitle);
|
||||||
|
|
||||||
constructor(private store: Store) {}
|
constructor(private store: Store) {}
|
||||||
|
|
||||||
|
setTitle(title: string) {
|
||||||
|
this.store.dispatch(setTitle({ title }));
|
||||||
|
}
|
||||||
|
|
||||||
getProcesses$(section?: 'customer' | 'branch') {
|
getProcesses$(section?: 'customer' | 'branch') {
|
||||||
const processes$ = this.store.select(selectProcesses);
|
const processes$ = this.store.select(selectProcesses);
|
||||||
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
|
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ApplicationProcess } from '..';
|
|||||||
|
|
||||||
const prefix = '[CORE-APPLICATION]';
|
const prefix = '[CORE-APPLICATION]';
|
||||||
|
|
||||||
|
export const setTitle = createAction(`${prefix} Set Title`, props<{ title: string }>());
|
||||||
|
|
||||||
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
|
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
|
||||||
|
|
||||||
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());
|
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import { Action, createReducer, on } from '@ngrx/store';
|
import { Action, createReducer, on } from '@ngrx/store';
|
||||||
import { setSection, addProcess, removeProcess, setActivatedProcess, patchProcess, patchProcessData } from './application.actions';
|
import {
|
||||||
|
setSection,
|
||||||
|
addProcess,
|
||||||
|
removeProcess,
|
||||||
|
setActivatedProcess,
|
||||||
|
patchProcess,
|
||||||
|
patchProcessData,
|
||||||
|
setTitle,
|
||||||
|
} from './application.actions';
|
||||||
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
|
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
|
||||||
|
|
||||||
const _applicationReducer = createReducer(
|
const _applicationReducer = createReducer(
|
||||||
INITIAL_APPLICATION_STATE,
|
INITIAL_APPLICATION_STATE,
|
||||||
|
on(setTitle, (state, { title }) => ({ ...state, title })),
|
||||||
on(setSection, (state, { section }) => ({ ...state, section })),
|
on(setSection, (state, { section }) => ({ ...state, section })),
|
||||||
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
|
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
|
||||||
on(removeProcess, (state, { processId }) => {
|
on(removeProcess, (state, { processId }) => {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { createFeatureSelector, createSelector } from '@ngrx/store';
|
|||||||
import { ApplicationState } from './application.state';
|
import { ApplicationState } from './application.state';
|
||||||
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
|
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
|
||||||
|
|
||||||
|
export const selectTitle = createSelector(selectApplicationState, (s) => s.title);
|
||||||
|
|
||||||
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
|
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
|
||||||
|
|
||||||
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);
|
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { ApplicationProcess } from '../defs';
|
import { ApplicationProcess } from '../defs';
|
||||||
|
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
|
title: string;
|
||||||
processes: ApplicationProcess[];
|
processes: ApplicationProcess[];
|
||||||
section: 'customer' | 'branch';
|
section: 'customer' | 'branch';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INITIAL_APPLICATION_STATE: ApplicationState = {
|
export const INITIAL_APPLICATION_STATE: ApplicationState = {
|
||||||
|
title: '',
|
||||||
processes: [],
|
processes: [],
|
||||||
section: 'customer',
|
section: 'customer',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ export class BreadcrumbService {
|
|||||||
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
|
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
|
getLatestBreadcrumbForSection(section: 'customer' | 'branch', predicate: (crumb: Breadcrumb) => boolean = (_) => true) {
|
||||||
return this.store
|
return this.store
|
||||||
.select(selectors.selectBreadcrumbsBySection, { section })
|
.select(selectors.selectBreadcrumbsBySection, { section })
|
||||||
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => true)));
|
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => predicate(f))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface Breadcrumb {
|
|||||||
/**
|
/**
|
||||||
* Url
|
* Url
|
||||||
*/
|
*/
|
||||||
path: string;
|
path: string | any[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query Parameter
|
* Query Parameter
|
||||||
|
|||||||
@@ -1,17 +1,76 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Platform } from '@angular/cdk/platform';
|
import { Platform } from '@angular/cdk/platform';
|
||||||
import { NativeContainerService } from 'native-container';
|
import { NativeContainerService } from 'native-container';
|
||||||
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
|
|
||||||
|
const MATCH_TABLET = '(max-width: 1024px)';
|
||||||
|
|
||||||
|
const MATCH_DESKTOP_SMALL = '(min-width: 1025px) and (max-width: 1439px)';
|
||||||
|
|
||||||
|
const MATCH_DESKTOP = '(min-width: 1280px)';
|
||||||
|
|
||||||
|
const MATCH_DESKTOP_LARGE = '(min-width: 1440px)';
|
||||||
|
|
||||||
|
const MATCH_DESKTOP_X_LARGE = '(min-width: 1920px)';
|
||||||
|
|
||||||
|
const MATCH_DESKTOP_XX_LARGE = '(min-width: 2736px)';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class EnvironmentService {
|
export class EnvironmentService {
|
||||||
constructor(private _platform: Platform, private _nativeContainer: NativeContainerService) {}
|
constructor(
|
||||||
|
private _platform: Platform,
|
||||||
|
private _nativeContainer: NativeContainerService,
|
||||||
|
private _breakpointObserver: BreakpointObserver
|
||||||
|
) {}
|
||||||
|
|
||||||
|
matchTablet(): boolean {
|
||||||
|
return this._breakpointObserver.isMatched(MATCH_TABLET);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchTablet$ = this._breakpointObserver.observe(MATCH_TABLET);
|
||||||
|
|
||||||
|
matchDesktopSmall(): boolean {
|
||||||
|
return this._breakpointObserver.isMatched(MATCH_DESKTOP_SMALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchDesktopSmall$ = this._breakpointObserver.observe(MATCH_DESKTOP_SMALL);
|
||||||
|
|
||||||
|
matchDesktop(): boolean {
|
||||||
|
return this._breakpointObserver.isMatched(MATCH_DESKTOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchDesktop$ = this._breakpointObserver.observe(MATCH_DESKTOP);
|
||||||
|
|
||||||
|
matchDesktopLarge(): boolean {
|
||||||
|
return this._breakpointObserver.isMatched(MATCH_DESKTOP_LARGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchDesktopLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_LARGE);
|
||||||
|
|
||||||
|
matchDesktopXLarge(): boolean {
|
||||||
|
return this._breakpointObserver.isMatched(MATCH_DESKTOP_X_LARGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchDesktopXLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_X_LARGE);
|
||||||
|
|
||||||
|
matchDesktopXXLarge(): boolean {
|
||||||
|
return this._breakpointObserver.isMatched(MATCH_DESKTOP_XX_LARGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchDesktopXXLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_XX_LARGE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `matchDesktopSmall` or 'matchDesktop' instead.
|
||||||
|
*/
|
||||||
isDesktop(): boolean {
|
isDesktop(): boolean {
|
||||||
return !this.isTablet();
|
return !this.isTablet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `matchTablet` instead.
|
||||||
|
*/
|
||||||
isTablet(): boolean {
|
isTablet(): boolean {
|
||||||
return this.isNative() || this.isSafari();
|
return this.isNative() || this.isSafari();
|
||||||
}
|
}
|
||||||
@@ -21,6 +80,6 @@ export class EnvironmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSafari(): boolean {
|
isSafari(): boolean {
|
||||||
return (this._platform.ANDROID || this._platform.IOS) && this._platform.SAFARI;
|
return this._platform.IOS && this._platform.SAFARI;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export class DomainAvailabilityService {
|
|||||||
private _branchService: StoreCheckoutBranchService
|
private _branchService: StoreCheckoutBranchService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@memorize({ ttl: 10000 })
|
||||||
|
memorizedAvailabilityShippingAvailability(request: Array<AvailabilityRequestDTO>) {
|
||||||
|
return this._availabilityService.AvailabilityShippingAvailability(request).pipe(shareReplay(1));
|
||||||
|
}
|
||||||
|
|
||||||
@memorize()
|
@memorize()
|
||||||
getSuppliers(): Observable<SupplierDTO[]> {
|
getSuppliers(): Observable<SupplierDTO[]> {
|
||||||
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
|
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
|
||||||
@@ -249,57 +254,53 @@ export class DomainAvailabilityService {
|
|||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
||||||
return this._availabilityService
|
return this.memorizedAvailabilityShippingAvailability([
|
||||||
.AvailabilityShippingAvailability([
|
{
|
||||||
{
|
ean: item?.ean,
|
||||||
ean: item?.ean,
|
itemId: item?.itemId ? String(item?.itemId) : null,
|
||||||
itemId: item?.itemId ? String(item?.itemId) : null,
|
price: item?.price,
|
||||||
price: item?.price,
|
qty: quantity,
|
||||||
qty: quantity,
|
},
|
||||||
},
|
]).pipe(
|
||||||
])
|
timeout(5000),
|
||||||
.pipe(
|
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
|
||||||
timeout(5000),
|
shareReplay(1)
|
||||||
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
|
);
|
||||||
shareReplay(1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
|
||||||
return this._availabilityService
|
return this.memorizedAvailabilityShippingAvailability([
|
||||||
.AvailabilityShippingAvailability([
|
{
|
||||||
{
|
qty: quantity,
|
||||||
qty: quantity,
|
ean: item?.ean,
|
||||||
ean: item?.ean,
|
itemId: item?.itemId ? String(item?.itemId) : null,
|
||||||
itemId: item?.itemId ? String(item?.itemId) : null,
|
price: item?.price,
|
||||||
price: item?.price,
|
},
|
||||||
},
|
]).pipe(
|
||||||
])
|
timeout(5000),
|
||||||
.pipe(
|
map((r) => {
|
||||||
timeout(5000),
|
const availabilities = r.result;
|
||||||
map((r) => {
|
const preferred = availabilities?.find((f) => f.preferred === 1);
|
||||||
const availabilities = r.result;
|
|
||||||
const preferred = availabilities?.find((f) => f.preferred === 1);
|
|
||||||
|
|
||||||
const availability: AvailabilityDTO = {
|
const availability: AvailabilityDTO = {
|
||||||
availabilityType: preferred?.status,
|
availabilityType: preferred?.status,
|
||||||
ssc: preferred?.ssc,
|
ssc: preferred?.ssc,
|
||||||
sscText: preferred?.sscText,
|
sscText: preferred?.sscText,
|
||||||
supplier: { id: preferred?.supplierId },
|
supplier: { id: preferred?.supplierId },
|
||||||
isPrebooked: preferred?.isPrebooked,
|
isPrebooked: preferred?.isPrebooked,
|
||||||
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
|
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
|
||||||
estimatedDelivery: preferred?.estimatedDelivery,
|
estimatedDelivery: preferred?.estimatedDelivery,
|
||||||
price: preferred?.price,
|
price: preferred?.price,
|
||||||
logistician: { id: preferred?.logisticianId },
|
logistician: { id: preferred?.logisticianId },
|
||||||
supplierProductNumber: preferred?.supplierProductNumber,
|
supplierProductNumber: preferred?.supplierProductNumber,
|
||||||
supplierInfo: preferred?.requestStatusCode,
|
supplierInfo: preferred?.requestStatusCode,
|
||||||
lastRequest: preferred?.requested,
|
lastRequest: preferred?.requested,
|
||||||
};
|
};
|
||||||
return availability;
|
return availability;
|
||||||
}),
|
}),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
@@ -333,37 +334,35 @@ export class DomainAvailabilityService {
|
|||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
|
getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
|
||||||
return this._availabilityService
|
return this.memorizedAvailabilityShippingAvailability([
|
||||||
.AvailabilityShippingAvailability([
|
{
|
||||||
{
|
ean: item?.ean,
|
||||||
ean: item?.ean,
|
itemId: item?.itemId ? String(item?.itemId) : null,
|
||||||
itemId: item?.itemId ? String(item?.itemId) : null,
|
price: item?.price,
|
||||||
price: item?.price,
|
qty: 1,
|
||||||
qty: 1,
|
},
|
||||||
},
|
]).pipe(
|
||||||
])
|
map((r) => {
|
||||||
.pipe(
|
const availabilities = r.result;
|
||||||
map((r) => {
|
const preferred = availabilities?.find((f) => f.preferred === 1);
|
||||||
const availabilities = r.result;
|
|
||||||
const preferred = availabilities?.find((f) => f.preferred === 1);
|
|
||||||
|
|
||||||
const availability: AvailabilityDTO = {
|
const availability: AvailabilityDTO = {
|
||||||
availabilityType: preferred?.status,
|
availabilityType: preferred?.status,
|
||||||
ssc: preferred?.ssc,
|
ssc: preferred?.ssc,
|
||||||
sscText: preferred?.sscText,
|
sscText: preferred?.sscText,
|
||||||
supplier: { id: preferred?.supplierId },
|
supplier: { id: preferred?.supplierId },
|
||||||
isPrebooked: preferred?.isPrebooked,
|
isPrebooked: preferred?.isPrebooked,
|
||||||
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
|
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
|
||||||
price: preferred?.price,
|
price: preferred?.price,
|
||||||
supplierProductNumber: preferred?.supplierProductNumber,
|
supplierProductNumber: preferred?.supplierProductNumber,
|
||||||
logistician: { id: preferred?.logisticianId },
|
logistician: { id: preferred?.logisticianId },
|
||||||
supplierInfo: preferred?.requestStatusCode,
|
supplierInfo: preferred?.requestStatusCode,
|
||||||
lastRequest: preferred?.requested,
|
lastRequest: preferred?.requested,
|
||||||
};
|
};
|
||||||
return availability;
|
return availability;
|
||||||
}),
|
}),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
@@ -401,7 +400,7 @@ export class DomainAvailabilityService {
|
|||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||||
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
|
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
|
||||||
timeout(20000),
|
timeout(20000),
|
||||||
map((response) => this._mapToShippingAvailability(response.result))
|
map((response) => this._mapToShippingAvailability(response.result))
|
||||||
);
|
);
|
||||||
@@ -409,7 +408,7 @@ export class DomainAvailabilityService {
|
|||||||
|
|
||||||
@memorize({ ttl: 10000 })
|
@memorize({ ttl: 10000 })
|
||||||
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
|
||||||
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
|
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
|
||||||
timeout(20000),
|
timeout(20000),
|
||||||
map((response) => this._mapToShippingAvailability(response.result))
|
map((response) => this._mapToShippingAvailability(response.result))
|
||||||
);
|
);
|
||||||
@@ -447,6 +446,9 @@ export class DomainAvailabilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAvailable({ availability }: { availability: AvailabilityDTO }) {
|
isAvailable({ availability }: { availability: AvailabilityDTO }) {
|
||||||
|
if (availability?.supplier?.id === 16 && availability?.inStock == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
|
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export class DomainInStockService {
|
|||||||
const key = this.getKey({ itemId, branchId });
|
const key = this.getKey({ itemId, branchId });
|
||||||
this._addToInStockQueue({ itemId, branchId });
|
this._addToInStockQueue({ itemId, branchId });
|
||||||
|
|
||||||
|
let _previousValue: InStock;
|
||||||
|
|
||||||
const sub = combineLatest([this._inStockMap, this._inStockFetchingMap])
|
const sub = combineLatest([this._inStockMap, this._inStockFetchingMap])
|
||||||
.pipe(distinctUntilChanged(isEqual))
|
.pipe(distinctUntilChanged(isEqual))
|
||||||
.subscribe(([inStockMap, inStockFetchingMap]) => {
|
.subscribe(([inStockMap, inStockFetchingMap]) => {
|
||||||
@@ -54,7 +56,12 @@ export class DomainInStockService {
|
|||||||
inStock: inStockMap[key],
|
inStock: inStockMap[key],
|
||||||
fetching: inStockFetchingMap[key] ?? false,
|
fetching: inStockFetchingMap[key] ?? false,
|
||||||
};
|
};
|
||||||
obs.next(inStock);
|
|
||||||
|
if (!isEqual(inStock, _previousValue)) {
|
||||||
|
obs.next(inStock);
|
||||||
|
}
|
||||||
|
|
||||||
|
_previousValue = inStock;
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
sub.unsubscribe();
|
sub.unsubscribe();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
StoreCheckoutBuyerService,
|
StoreCheckoutBuyerService,
|
||||||
StoreCheckoutPayerService,
|
StoreCheckoutPayerService,
|
||||||
StoreCheckoutBranchService,
|
StoreCheckoutBranchService,
|
||||||
|
ItemsResult,
|
||||||
} from '@swagger/checkout';
|
} from '@swagger/checkout';
|
||||||
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
|
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
|
||||||
import { isNullOrUndefined, memorize } from '@utils/common';
|
import { isNullOrUndefined, memorize } from '@utils/common';
|
||||||
@@ -198,7 +199,15 @@ export class DomainCheckoutService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAddItems({ processId, payload, orderType }: { processId: number; payload: ItemPayload[]; orderType: string }) {
|
canAddItems({
|
||||||
|
processId,
|
||||||
|
payload,
|
||||||
|
orderType,
|
||||||
|
}: {
|
||||||
|
processId: number;
|
||||||
|
payload: ItemPayload[];
|
||||||
|
orderType: string;
|
||||||
|
}): Observable<ItemsResult[]> {
|
||||||
return this.getShoppingCart({ processId }).pipe(
|
return this.getShoppingCart({ processId }).pipe(
|
||||||
first(),
|
first(),
|
||||||
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
|
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
|
||||||
@@ -217,7 +226,8 @@ export class DomainCheckoutService {
|
|||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response) => {
|
map((response) => {
|
||||||
return response.result;
|
// TODO: remove this when the API is fixed
|
||||||
|
return (response.result as unknown) as ItemsResult[];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ export class DomainRemissionService {
|
|||||||
* Create a new receipt for the given return/remission
|
* Create a new receipt for the given return/remission
|
||||||
* @param returnId Return ID
|
* @param returnId Return ID
|
||||||
* @param receiptNumber Receipt number
|
* @param receiptNumber Receipt number
|
||||||
* @returns ReturnDTO - ShippingDocument
|
* @returns ReceiptDTO
|
||||||
*/
|
*/
|
||||||
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
|
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
|
||||||
const stock = await this._getStock();
|
const stock = await this._getStock();
|
||||||
@@ -471,14 +471,41 @@ export class DomainRemissionService {
|
|||||||
return receipt;
|
return receipt;
|
||||||
}
|
}
|
||||||
|
|
||||||
async completeReceipt(returnId: number, receiptId: number, packageCode: string): Promise<ReceiptDTO> {
|
/**
|
||||||
|
* Create a new Package and assign it to a receipt
|
||||||
|
* @param returnId Return ID
|
||||||
|
* @param receiptId Receipt ID
|
||||||
|
* @param packageNumber Packagenumber
|
||||||
|
* @returns ReceiptDTO
|
||||||
|
*/
|
||||||
|
async createReceiptAndAssignPackage({
|
||||||
|
returnId,
|
||||||
|
receiptId,
|
||||||
|
packageNumber,
|
||||||
|
}: {
|
||||||
|
returnId: number;
|
||||||
|
receiptId: number;
|
||||||
|
packageNumber: string;
|
||||||
|
}): Promise<ReceiptDTO> {
|
||||||
|
const response = await this._returnService
|
||||||
|
.ReturnCreateAndAssignPackage({
|
||||||
|
returnId,
|
||||||
|
receiptId,
|
||||||
|
data: {
|
||||||
|
packageNumber,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
const receipt: ReceiptDTO = response.result;
|
||||||
|
return receipt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async completeReceipt(returnId: number, receiptId: number): Promise<ReceiptDTO> {
|
||||||
const res = await this._returnService
|
const res = await this._returnService
|
||||||
.ReturnFinalizeReceipt({
|
.ReturnFinalizeReceipt({
|
||||||
returnId,
|
returnId,
|
||||||
receiptId,
|
receiptId,
|
||||||
data: {
|
data: {},
|
||||||
packageCode,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { isDevMode, NgModule } from '@angular/core';
|
import { isDevMode, NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { DebugComponent } from './debug/debug.component';
|
|
||||||
import {
|
import {
|
||||||
CanActivateCartGuard,
|
CanActivateCartGuard,
|
||||||
CanActivateCartWithProcessIdGuard,
|
CanActivateCartWithProcessIdGuard,
|
||||||
@@ -17,9 +16,9 @@ import {
|
|||||||
} from './guards';
|
} from './guards';
|
||||||
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
|
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
|
||||||
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
|
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
|
||||||
|
import { MainComponent } from './main.component';
|
||||||
import { PreviewComponent } from './preview';
|
import { PreviewComponent } from './preview';
|
||||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||||
import { ShellComponent, ShellModule } from './shell';
|
|
||||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -40,7 +39,7 @@ const routes: Routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'kunde',
|
path: 'kunde',
|
||||||
component: ShellComponent,
|
component: MainComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
@@ -106,7 +105,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'filiale',
|
path: 'filiale',
|
||||||
component: ShellComponent,
|
component: MainComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'task-calendar',
|
path: 'task-calendar',
|
||||||
@@ -152,7 +151,7 @@ if (isDevMode()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes), ShellModule, TokenLoginModule],
|
imports: [RouterModule.forRoot(routes), TokenLoginModule],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply block box-border;
|
@apply block;
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
@apply fixed bottom-4 right-2 bg-blue-500 text-white font-bold py-2 px-4 rounded z-tooltip;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ import { IsaErrorHandler } from './providers/isa.error-handler';
|
|||||||
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
|
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
|
||||||
import { RootStateService } from './store/root-state.service';
|
import { RootStateService } from './store/root-state.service';
|
||||||
import * as Commands from './commands';
|
import * as Commands from './commands';
|
||||||
import { UiIconModule } from '@ui/icon';
|
import { UiIconModule, UI_ICON_CFG } from '@ui/icon';
|
||||||
import { PreviewComponent } from './preview';
|
import { PreviewComponent } from './preview';
|
||||||
import { NativeContainerService } from 'native-container';
|
import { NativeContainerService } from 'native-container';
|
||||||
|
import { ShellModule } from '@shared/shell';
|
||||||
|
import { MainComponent } from './main.component';
|
||||||
|
|
||||||
registerLocaleData(localeDe, localeDeExtra);
|
registerLocaleData(localeDe, localeDeExtra);
|
||||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||||
@@ -74,11 +76,12 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent, MainComponent],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
|
ShellModule.forRoot(),
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
AppSwaggerModule,
|
AppSwaggerModule,
|
||||||
AppDomainModule,
|
AppDomainModule,
|
||||||
@@ -103,12 +106,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
|||||||
ScanAdapterModule.forRoot(),
|
ScanAdapterModule.forRoot(),
|
||||||
ScanditScanAdapterModule.forRoot(),
|
ScanditScanAdapterModule.forRoot(),
|
||||||
PlatformModule,
|
PlatformModule,
|
||||||
UiIconModule.forRoot({
|
UiIconModule.forRoot(),
|
||||||
aliases: [
|
|
||||||
{ alias: 'd-account', name: 'account' },
|
|
||||||
{ alias: 'd-no-account', name: 'package-variant-closed' },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@@ -137,6 +135,11 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
|||||||
useClass: IsaErrorHandler,
|
useClass: IsaErrorHandler,
|
||||||
},
|
},
|
||||||
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||||
|
{
|
||||||
|
provide: UI_ICON_CFG,
|
||||||
|
useFactory: (config: Config) => config.get('@ui/icon'),
|
||||||
|
deps: [Config],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||||
import { DomainCheckoutService } from '@domain/checkout';
|
import { DomainCheckoutService } from '@domain/checkout';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -9,6 +10,7 @@ export class CanActivateProductGuard implements CanActivate {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly _applicationService: ApplicationService,
|
private readonly _applicationService: ApplicationService,
|
||||||
private readonly _checkoutService: DomainCheckoutService,
|
private readonly _checkoutService: DomainCheckoutService,
|
||||||
|
private readonly _navigationService: ProductCatalogNavigationService,
|
||||||
private readonly _router: Router
|
private readonly _router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ export class CanActivateProductGuard implements CanActivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!lastActivatedProcessId) {
|
if (!lastActivatedProcessId) {
|
||||||
await this.fromCartProcess(processes, route);
|
await this.fromCartProcess(processes);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
|
||||||
@@ -48,7 +50,7 @@ export class CanActivateProductGuard implements CanActivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
|
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
|
||||||
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
async fromCartProcess(processes: ApplicationProcess[]) {
|
||||||
const newProcessId = Date.now();
|
const newProcessId = Date.now();
|
||||||
await this._applicationService.createProcess({
|
await this._applicationService.createProcess({
|
||||||
id: newProcessId,
|
id: newProcessId,
|
||||||
@@ -57,7 +59,7 @@ export class CanActivateProductGuard implements CanActivate {
|
|||||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
|
await this._navigationService.navigateToProductSearch({ processId: newProcessId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
|
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
|
||||||
|
|||||||
3
apps/isa-app/src/app/main.component.html
Normal file
3
apps/isa-app/src/app/main.component.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<shell-root>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</shell-root>
|
||||||
10
apps/isa-app/src/app/main.component.ts
Normal file
10
apps/isa-app/src/app/main.component.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-main',
|
||||||
|
templateUrl: 'main.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class MainComponent {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// start:ng42.barrel
|
|
||||||
export * from './shell.component';
|
|
||||||
export * from './shell.module';
|
|
||||||
// end:ng42.barrel
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<div class="shell-header-wrapper">
|
|
||||||
<shell-header [section]="section$ | async" (sectionChange)="setSection($event)">
|
|
||||||
<a [routerLink]="['/kunde/dashboard']" routerLinkActive="active" class="dashboard-btn">
|
|
||||||
<ui-icon icon="dashboard" size="26px"></ui-icon>
|
|
||||||
</a>
|
|
||||||
<button class="notifications-btn" [disabled]="(notificationCount$ | async) === 0" (click)="openNotifications()">
|
|
||||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
|
||||||
<span class="notification-counter" *ngIf="notificationCount$ | async; let count">{{ count }}</span>
|
|
||||||
</button>
|
|
||||||
<button (click)="logout()" class="logout-btn">
|
|
||||||
<span *ngIf="currentBranch$ | async; let currentBranch">{{ currentBranch.key | uppercase }}</span>
|
|
||||||
<ui-icon icon="logout" size="26px"></ui-icon>
|
|
||||||
</button>
|
|
||||||
</shell-header>
|
|
||||||
</div>
|
|
||||||
<div class="shell-process-wrapper">
|
|
||||||
<shell-process
|
|
||||||
[label]="addProcessLabel$ | async"
|
|
||||||
[canAddProcess]="canAddProcess$ | async"
|
|
||||||
(addProcess)="addProcess(); processTabs?.last?.triggerAnimation()"
|
|
||||||
>
|
|
||||||
<shell-process-tab
|
|
||||||
#processTabs
|
|
||||||
(activateProcess)="activateProcess($event)"
|
|
||||||
(closeProcess)="closeProcess($event)"
|
|
||||||
(processAction)="processAction($event)"
|
|
||||||
*ngFor="let process of processes$ | async; trackBy: trackByIdFn"
|
|
||||||
[isActive]="(activatedProcessId$ | async) === process.id"
|
|
||||||
[process]="process"
|
|
||||||
></shell-process-tab>
|
|
||||||
</shell-process>
|
|
||||||
</div>
|
|
||||||
<div class="main-wrapper">
|
|
||||||
<main>
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="shell-footer-wrapper">
|
|
||||||
<shell-footer *ngIf="section$ | async; let section">
|
|
||||||
<ng-container *ngIf="section === 'customer'">
|
|
||||||
<a [routerLink]="[customerBasePath$ | async, 'product']" routerLinkActive="active">
|
|
||||||
<ui-icon icon="catalog" size="30px"></ui-icon>
|
|
||||||
Artikelsuche
|
|
||||||
</a>
|
|
||||||
<a [routerLink]="[customerBasePath$ | async, 'customer']" routerLinkActive="active">
|
|
||||||
<ui-icon icon="customer" size="24px"></ui-icon>
|
|
||||||
Kundensuche
|
|
||||||
</a>
|
|
||||||
<a *ifRole="'Store'" [routerLink]="[customerBasePath$ | async, 'goods', 'out']" routerLinkActive="active">
|
|
||||||
<ui-icon icon="box_out" size="24px"></ui-icon>
|
|
||||||
Warenausgabe
|
|
||||||
</a>
|
|
||||||
<a *ifRole="'CallCenter'" [routerLink]="[customerBasePath$ | async, 'order']" routerLinkActive="active">
|
|
||||||
<ui-svg-icon icon="package-variant-closed" [size]="28"></ui-svg-icon>
|
|
||||||
Kundenbestellungen
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="section === 'branch'">
|
|
||||||
<a [routerLink]="['/filiale/assortment']" routerLinkActive="active">
|
|
||||||
<ui-svg-icon icon="shape-outline" [size]="24"></ui-svg-icon>
|
|
||||||
Sortiment
|
|
||||||
</a>
|
|
||||||
<a [routerLink]="['/filiale/task-calendar']" routerLinkActive="active">
|
|
||||||
<ui-icon icon="calendar_check" size="24px"></ui-icon>
|
|
||||||
Tätigkeitskalender
|
|
||||||
</a>
|
|
||||||
<a [routerLink]="['/filiale/goods/in']" routerLinkActive="active">
|
|
||||||
<ui-icon icon="box_return" size="24px"></ui-icon>
|
|
||||||
Abholfach
|
|
||||||
</a>
|
|
||||||
<a [routerLink]="[remissionUrl$ | async]" [queryParams]="remissionQueryParams$ | async" routerLinkActive="active">
|
|
||||||
<ui-icon icon="documents_refresh" size="24px"></ui-icon>
|
|
||||||
Remission
|
|
||||||
</a>
|
|
||||||
<a [routerLink]="['/filiale/package-inspection']" routerLinkActive="active" (click)="fetchAndOpenPackages()">
|
|
||||||
<ui-svg-icon icon="clipboard-check-outline" [size]="24"></ui-svg-icon>
|
|
||||||
Wareneingang
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
</shell-footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button *ngIf="isDevelopment" class="block absolute bottom-0 right-0 z-tooltip p-4 opacity-5" (click)="debugOpen = !debugOpen">
|
|
||||||
<ui-svg-icon icon="bug-outline"></ui-svg-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<app-debug *ngIf="debugOpen" class="absolute inset-x-0 top-0 max-h-[calc(100vh-80px)]"></app-debug>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
:host {
|
|
||||||
@apply block relative min-h-screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-wrapper {
|
|
||||||
@apply fixed right-0 left-0 overflow-auto;
|
|
||||||
top: 8.375rem;
|
|
||||||
bottom: 5rem;
|
|
||||||
|
|
||||||
main {
|
|
||||||
@apply w-full max-w-content mx-auto px-4 self-stretch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell-header-wrapper {
|
|
||||||
@apply fixed top-0 left-0 right-0 bg-white;
|
|
||||||
|
|
||||||
shell-header {
|
|
||||||
@apply w-full max-w-content mx-auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.notifications-btn {
|
|
||||||
@apply relative;
|
|
||||||
|
|
||||||
.notification-counter {
|
|
||||||
@apply absolute flex items-center justify-center top-2 right-px-3 text-sm rounded-full w-6 h-6 font-semibold;
|
|
||||||
background-color: var(--shell-notification-counter-background);
|
|
||||||
color: var(--shell-notification-counter-text);
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell-process-wrapper {
|
|
||||||
@apply fixed left-0 right-0 bg-white;
|
|
||||||
top: 5.125rem;
|
|
||||||
|
|
||||||
shell-process {
|
|
||||||
@apply w-full max-w-content mx-auto;
|
|
||||||
height: 52px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shell-process {
|
|
||||||
height: 52px;
|
|
||||||
grid-area: process;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell-footer-wrapper {
|
|
||||||
@apply fixed bottom-0 left-0 right-0 bg-white z-fixed shadow-card;
|
|
||||||
|
|
||||||
shell-footer {
|
|
||||||
@apply w-full max-w-content mx-auto;
|
|
||||||
|
|
||||||
.active {
|
|
||||||
@apply font-bold;
|
|
||||||
color: var(--shell-footer-link-active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
// unit test ShellComponent with Spectator
|
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
|
||||||
import { AuthModule, AuthService } from '@core/auth';
|
|
||||||
import { Config } from '@core/config';
|
|
||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
|
||||||
import { DomainDashboardService } from '@domain/isa';
|
|
||||||
import { NotificationsHub } from '@hub/notifications';
|
|
||||||
import { ModalNotificationsComponent } from '@modal/notifications';
|
|
||||||
import { Spectator, createComponentFactory, SpyObject, createSpyObject } from '@ngneat/spectator';
|
|
||||||
import { DashboardComponent } from '@page/dashboard';
|
|
||||||
import { ShellFooterComponent } from '@shell/footer';
|
|
||||||
import { ShellHeaderComponent } from '@shell/header';
|
|
||||||
import { ShellProcessComponent, ShellProcessTabComponent } from '@shell/process';
|
|
||||||
import { IconRegistry, UiIconComponent, UiIconModule } from '@ui/icon';
|
|
||||||
import { UiModalService } from '@ui/modal';
|
|
||||||
import { EnvelopeDTO, MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
|
||||||
import { MockComponent } from 'ng-mocks';
|
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { first } from 'rxjs/operators';
|
|
||||||
import { ShellComponent } from './shell.component';
|
|
||||||
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
|
|
||||||
|
|
||||||
// DummyComponent Class
|
|
||||||
@Component({
|
|
||||||
selector: 'dummy-component',
|
|
||||||
template: '<div></div>',
|
|
||||||
})
|
|
||||||
class DummyComponent {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ShellComponent', () => {
|
|
||||||
let spectator: Spectator<ShellComponent>;
|
|
||||||
let applicationServiceMock: SpyObject<ApplicationService>;
|
|
||||||
let modalServiceMock: SpyObject<UiModalService>;
|
|
||||||
let notificationsHubMock: SpyObject<NotificationsHub>;
|
|
||||||
let router: Router;
|
|
||||||
let breadcrumbServiceMock: SpyObject<BreadcrumbService>;
|
|
||||||
let authServiceMock: SpyObject<AuthService>;
|
|
||||||
|
|
||||||
const createComponent = createComponentFactory({
|
|
||||||
component: ShellComponent,
|
|
||||||
imports: [
|
|
||||||
UiIconModule,
|
|
||||||
RouterTestingModule.withRoutes([
|
|
||||||
{ path: 'kunde', component: DummyComponent },
|
|
||||||
{ path: 'kunde/dashboard', component: DashboardComponent },
|
|
||||||
]),
|
|
||||||
AuthModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
MockComponent(ShellHeaderComponent),
|
|
||||||
MockComponent(ShellFooterComponent),
|
|
||||||
MockComponent(ShellProcessComponent),
|
|
||||||
MockComponent(ShellProcessTabComponent),
|
|
||||||
],
|
|
||||||
mocks: [
|
|
||||||
BreadcrumbService,
|
|
||||||
DomainAvailabilityService,
|
|
||||||
AuthService,
|
|
||||||
DomainDashboardService,
|
|
||||||
Config,
|
|
||||||
WrongDestinationModalService,
|
|
||||||
IconRegistry,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
applicationServiceMock = createSpyObject(ApplicationService);
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of([]));
|
|
||||||
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
|
|
||||||
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
|
|
||||||
applicationServiceMock.getLastActivatedProcessWithSectionAndType$.and.returnValue(of({}));
|
|
||||||
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
|
|
||||||
|
|
||||||
notificationsHubMock = createSpyObject(NotificationsHub);
|
|
||||||
notificationsHubMock.notifications$ = of({});
|
|
||||||
|
|
||||||
modalServiceMock = createSpyObject(UiModalService);
|
|
||||||
|
|
||||||
authServiceMock = createSpyObject(AuthService);
|
|
||||||
|
|
||||||
spectator = createComponent({
|
|
||||||
providers: [
|
|
||||||
{ provide: ApplicationService, useValue: applicationServiceMock },
|
|
||||||
{ provide: NotificationsHub, useValue: notificationsHubMock },
|
|
||||||
{ provide: UiModalService, useValue: modalServiceMock },
|
|
||||||
{ provide: AuthService, useValue: authServiceMock },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
breadcrumbServiceMock = spectator.inject(BreadcrumbService);
|
|
||||||
router = spectator.inject(Router);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(spectator.component).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('shell-header', () => {
|
|
||||||
it('should call setSection() on sectionChange event with the section argument', () => {
|
|
||||||
spyOn(spectator.component, 'setSection');
|
|
||||||
spectator.triggerEventHandler('shell-header', 'sectionChange', 'branch');
|
|
||||||
expect(spectator.component.setSection).toHaveBeenCalledWith('branch');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the header buttons', () => {
|
|
||||||
// Test verhält sich anders, wenn die größe des Browserfensters kleiner ist als 640px, da
|
|
||||||
// die Buttons dann unsichtbar werden und ins Drei-Punkt Menü verschoben werden.
|
|
||||||
if (document.body.clientWidth > 639) {
|
|
||||||
expect(spectator.query('shell-header .notifications-btn')).toBeVisible();
|
|
||||||
expect(spectator.query('shell-header .dashboard-btn')).toBeVisible();
|
|
||||||
expect(spectator.query('shell-header .logout-btn')).toBeVisible();
|
|
||||||
} else {
|
|
||||||
expect(spectator.query('shell-header .notifications-btn')).not.toBeVisible();
|
|
||||||
expect(spectator.query('shell-header .dashboard-btn')).not.toBeVisible();
|
|
||||||
expect(spectator.query('shell-header .logout-btn')).not.toBeVisible();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have a anchor tag which navigates to /kunde/dashboard', () => {
|
|
||||||
const anchor = spectator.query('shell-header a');
|
|
||||||
expect(anchor).toHaveAttribute('href', '/kunde/dashboard');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('shell-process', () => {
|
|
||||||
it('should call addProcess() on addProcess event', () => {
|
|
||||||
spyOn(spectator.component, 'addProcess');
|
|
||||||
spectator.triggerEventHandler('shell-process', 'addProcess', undefined);
|
|
||||||
expect(spectator.component.addProcess).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('shell-process-tab', () => {
|
|
||||||
it('should render for each process', () => {
|
|
||||||
const processes = [{}, {}, {}];
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
expect(spectator.queryAll('shell-process-tab')).toHaveLength(processes.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call activateProcess() on activateProcess event', () => {
|
|
||||||
const processes = [{ id: 1 }];
|
|
||||||
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
|
|
||||||
spyOn(spectator.component, 'activateProcess');
|
|
||||||
spectator.triggerEventHandler('shell-process-tab', 'activateProcess', processes[0].id);
|
|
||||||
expect(spectator.component.activateProcess).toHaveBeenCalledWith(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call closeProcess() on closeProcess event', () => {
|
|
||||||
const processes = [{ id: 1 }];
|
|
||||||
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
|
|
||||||
spyOn(spectator.component, 'closeProcess');
|
|
||||||
spectator.triggerEventHandler('shell-process-tab', 'closeProcess', processes[0].id);
|
|
||||||
expect(spectator.component.closeProcess).toHaveBeenCalledWith(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('shell-footer', () => {
|
|
||||||
it('should render when section is set', () => {
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
expect(spectator.query('shell-footer')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render when section is undefined', () => {
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of(undefined));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
expect(spectator.query('shell-footer')).not.toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
xit('should display the menu items for section customer', () => {
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
spectator.component.customerBasePath$ = of('/kunde/1');
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
|
|
||||||
authServiceMock.hasRole.and.returnValue(true);
|
|
||||||
|
|
||||||
const anchors = spectator.queryAll('shell-footer a');
|
|
||||||
expect(anchors[0]).toHaveText('Artikelsuche');
|
|
||||||
expect(anchors[0]).toHaveAttribute('href', '/kunde/1/product');
|
|
||||||
expect(anchors[1]).toHaveText('Kundensuche');
|
|
||||||
expect(anchors[1]).toHaveAttribute('href', '/kunde/1/customer');
|
|
||||||
expect(anchors[2]).toHaveText('Warenausgabe');
|
|
||||||
expect(anchors[2]).toHaveAttribute('href', '/kunde/1/goods/out');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the menu items for section branch', () => {
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('branch'));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
|
|
||||||
const anchors = spectator.queryAll('shell-footer a');
|
|
||||||
expect(anchors[0]).toHaveText('Sortiment');
|
|
||||||
expect(anchors[0]).toHaveAttribute('href', '/filiale/assortment');
|
|
||||||
expect(anchors[1]).toHaveText('Tätigkeitskalender');
|
|
||||||
expect(anchors[1]).toHaveAttribute('href', '/filiale/task-calendar');
|
|
||||||
expect(anchors[2]).toHaveText('Abholfach');
|
|
||||||
expect(anchors[2]).toHaveAttribute('href', '/filiale/goods/in');
|
|
||||||
expect(anchors[3]).toHaveText('Remission');
|
|
||||||
expect(anchors[3]).toHaveAttribute('href', '/filiale/remission');
|
|
||||||
expect(anchors[4]).toHaveText('Wareneingang');
|
|
||||||
expect(anchors[4]).toHaveAttribute('href', '/filiale/package-inspection');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('activatedProcessId$', () => {
|
|
||||||
it('should call _appService.getActivatedProcessId$() and return its value', async () => {
|
|
||||||
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(1));
|
|
||||||
const processId = await spectator.component.activatedProcessId$.pipe(first()).toPromise();
|
|
||||||
expect(processId).toBe(1);
|
|
||||||
expect(applicationServiceMock.getActivatedProcessId$).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('section$', () => {
|
|
||||||
it('should call _appService.getSection$() and return its value', async () => {
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('branch'));
|
|
||||||
const section = await spectator.component.section$.pipe(first()).toPromise();
|
|
||||||
expect(section).toBe('branch');
|
|
||||||
expect(applicationServiceMock.getSection$).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('processes$', () => {
|
|
||||||
it('should call _appService.processes$() and return its value', async () => {
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of([{}, {}]));
|
|
||||||
const processes = await spectator.component.processes$.pipe(first()).toPromise();
|
|
||||||
expect(processes).toHaveLength(2);
|
|
||||||
expect(applicationServiceMock.getProcesses$).toHaveBeenCalledWith('customer');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remissionProcess$', () => {
|
|
||||||
it('should call _appService.getProcessById$() with Remission Id and return its value', async () => {
|
|
||||||
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
|
|
||||||
await spectator.component.remissionProcess$.pipe(first()).toPromise();
|
|
||||||
expect(applicationServiceMock.getProcessById$).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remissionUrl$', () => {
|
|
||||||
it('should return the correct url if process.data.active is available', async () => {
|
|
||||||
const process = {
|
|
||||||
id: 4000,
|
|
||||||
data: {
|
|
||||||
active: 9999,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
applicationServiceMock.getProcessById$.and.returnValue(of(process));
|
|
||||||
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
|
|
||||||
expect(url).toBe('/filiale/remission/9999/list');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the correct url if process.data.active is not available', async () => {
|
|
||||||
const process = {
|
|
||||||
id: 4000,
|
|
||||||
data: {},
|
|
||||||
};
|
|
||||||
applicationServiceMock.getProcessById$.and.returnValue(of(process));
|
|
||||||
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
|
|
||||||
expect(url).toBe('/filiale/remission');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remissionQueryParams$', () => {
|
|
||||||
it('should return the correct queryParams if process.data.active and process.data.queryParams are available', async () => {
|
|
||||||
const process = {
|
|
||||||
id: 4000,
|
|
||||||
data: {
|
|
||||||
active: 9999,
|
|
||||||
queryParams: { filter: 'test' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
applicationServiceMock.getProcessById$.and.returnValue(of(process));
|
|
||||||
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
|
|
||||||
expect(queryParams).toEqual(process.data.queryParams);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the correct queryParams if process.data.active and process.data.queryParams are not available', async () => {
|
|
||||||
const process = {
|
|
||||||
id: 4000,
|
|
||||||
data: {},
|
|
||||||
};
|
|
||||||
applicationServiceMock.getProcessById$.and.returnValue(of(process));
|
|
||||||
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
|
|
||||||
expect(queryParams).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setSection()', () => {
|
|
||||||
it('should call _appService.setSection() with the argument section', async () => {
|
|
||||||
await spectator.component.setSection('customer');
|
|
||||||
expect(applicationServiceMock.setSection).toHaveBeenCalledWith('customer');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call activateProcess if getLastActivatedProcessWithSection returns a value', async () => {
|
|
||||||
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({ id: 1 }));
|
|
||||||
spyOn(spectator.component, 'activateProcess');
|
|
||||||
await spectator.component.setSection('customer');
|
|
||||||
expect(spectator.component.activateProcess).toHaveBeenCalledWith(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('logout()', () => {
|
|
||||||
it('should call _authService.logout()', () => {
|
|
||||||
spectator.component.logout();
|
|
||||||
expect(authServiceMock.logout).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('addProcess()', () => {
|
|
||||||
it('should call navigate to /kunde/{timestamp}/product', () => {
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
spyOn(Date, 'now').and.returnValue(123);
|
|
||||||
spectator.component.addProcess();
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 123, 'product']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('closeProcess()', () => {
|
|
||||||
it('should call _appService.removeProcess() with the processId argument', () => {
|
|
||||||
const processes = [{}, {}, {}];
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
|
||||||
spectator.component.closeProcess(1);
|
|
||||||
expect(applicationServiceMock.removeProcess).toHaveBeenCalledWith(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should navigate to kunde/dashboard if no process is available', async () => {
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of([]));
|
|
||||||
spectator.detectComponentChanges();
|
|
||||||
await spectator.component.closeProcess(1);
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 'dashboard']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not navigate to kunde/dashboard if processes are available', async () => {
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
const processes = [
|
|
||||||
{ id: 1, name: 'test', section: 'customer' },
|
|
||||||
{ id: 2, name: 'test', section: 'customer' },
|
|
||||||
];
|
|
||||||
|
|
||||||
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
|
||||||
await spectator.component.closeProcess(1);
|
|
||||||
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should activate the next process when it was not the last process', async () => {
|
|
||||||
spyOn(spectator.component, 'activateProcess');
|
|
||||||
|
|
||||||
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(
|
|
||||||
of({
|
|
||||||
id: 2,
|
|
||||||
name: 'test',
|
|
||||||
section: 'customer',
|
|
||||||
activated: 2,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const processes = [
|
|
||||||
{ id: 1, name: 'test', section: 'customer', activated: 1 },
|
|
||||||
{ id: 2, name: 'test', section: 'customer', activated: 2 },
|
|
||||||
];
|
|
||||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
|
||||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
|
||||||
await spectator.component.closeProcess(1);
|
|
||||||
|
|
||||||
expect(spectator.component.activateProcess).toHaveBeenCalledWith(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('activateProcess()', () => {
|
|
||||||
it('should get the last activated breadcrumb by key and if it is defined it navigates to its path with queryParams', async () => {
|
|
||||||
const crumb = { path: '/kunde/product', params: { id: 1 } };
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(crumb));
|
|
||||||
|
|
||||||
await spectator.component.activateProcess(1);
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith([crumb.path], { queryParams: crumb.params });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should navigate to /kunde if no breadcrumb for this process exists', async () => {
|
|
||||||
breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(undefined));
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
|
|
||||||
await spectator.component.activateProcess(1);
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('processAction()', () => {
|
|
||||||
it('should navigate to cart when process type is cart', () => {
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
|
|
||||||
const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'cart' };
|
|
||||||
spectator.component.processAction(process);
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['/kunde', process.id, 'cart']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not navigate to when process type is not cart', () => {
|
|
||||||
spyOn(router, 'navigate');
|
|
||||||
|
|
||||||
const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'goods-out' };
|
|
||||||
spectator.component.processAction(process);
|
|
||||||
expect(router.navigate).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('openNotifications()', () => {
|
|
||||||
it('should call modalService.open() with the ModalNotificationComponent', async () => {
|
|
||||||
const notifications: EnvelopeDTO<MessageBoardItemDTO[]> = {
|
|
||||||
data: [{}, {}, {}],
|
|
||||||
};
|
|
||||||
|
|
||||||
spectator.component.notifications$ = of(notifications);
|
|
||||||
|
|
||||||
await spectator.component.openNotifications();
|
|
||||||
expect(modalServiceMock.open).toHaveBeenCalledWith({
|
|
||||||
content: ModalNotificationsComponent,
|
|
||||||
data: notifications,
|
|
||||||
config: {
|
|
||||||
showScrollbarY: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList, TrackByFunction, NgZone } from '@angular/core';
|
|
||||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
|
||||||
import { first, map, shareReplay, switchMap, take, tap } from 'rxjs/operators';
|
|
||||||
import { NotificationsHub } from '@hub/notifications';
|
|
||||||
import { ModalNotificationsComponent } from '@modal/notifications';
|
|
||||||
import { UiModalService } from '@ui/modal';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
|
||||||
import { combineLatest } from 'rxjs';
|
|
||||||
import { AuthService } from '@core/auth';
|
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
|
||||||
import { ShellProcessTabComponent } from '@shell/process';
|
|
||||||
import { Config } from '@core/config';
|
|
||||||
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-shell',
|
|
||||||
templateUrl: 'shell.component.html',
|
|
||||||
styleUrls: ['shell.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class ShellComponent {
|
|
||||||
isDevelopment = Boolean(this._config.get('debug'));
|
|
||||||
|
|
||||||
debugOpen = false;
|
|
||||||
|
|
||||||
@ViewChildren('processTabs')
|
|
||||||
readonly processTabs: QueryList<ShellProcessTabComponent>;
|
|
||||||
|
|
||||||
notifications$ = this._notificationsHub.notifications$;
|
|
||||||
|
|
||||||
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
|
|
||||||
|
|
||||||
get activatedProcessId$() {
|
|
||||||
return this._appService.getActivatedProcessId$().pipe(
|
|
||||||
tap((activatedProcessId) => {
|
|
||||||
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
customerBasePath$ = this.activatedProcessId$.pipe(
|
|
||||||
switchMap((processId) => this._appService.getProcessById$(processId)),
|
|
||||||
map((process) => {
|
|
||||||
if (!!process && process.section === 'customer' && process.type !== 'cart-checkout') {
|
|
||||||
// Übernehme aktiven Prozess
|
|
||||||
return `/kunde/${process.id}`;
|
|
||||||
} else {
|
|
||||||
// Über Guards wird ein neuer Prozess erstellt
|
|
||||||
return '/kunde';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
get section$() {
|
|
||||||
return this._appService.getSection$().pipe(shareReplay());
|
|
||||||
}
|
|
||||||
|
|
||||||
get processes$() {
|
|
||||||
return this.section$.pipe(switchMap((section) => this._appService.getProcesses$(section)));
|
|
||||||
}
|
|
||||||
|
|
||||||
get remissionProcess$() {
|
|
||||||
return this._appService.getProcessById$(this._config.get('process.ids.remission'));
|
|
||||||
}
|
|
||||||
|
|
||||||
get remissionUrl$() {
|
|
||||||
return this.remissionProcess$.pipe(
|
|
||||||
map((process) => (process?.data?.active ? `/filiale/remission/${process.data.active}/list` : '/filiale/remission'))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get remissionQueryParams$() {
|
|
||||||
return this.remissionProcess$.pipe(
|
|
||||||
map((process) => (process?.data?.active && process?.data?.queryParams ? process.data.queryParams : {}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get addProcessLabel$() {
|
|
||||||
return combineLatest([this.section$, this.processes$]).pipe(
|
|
||||||
map(([section, processes]) => (section === 'customer' && processes.length === 0 ? 'VORGANG STARTEN' : ''))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canAddProcess$() {
|
|
||||||
return this.section$.pipe(map((section) => section === 'customer'));
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentBranch$() {
|
|
||||||
return this._availabilityService.getDefaultBranch();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _appService: ApplicationService,
|
|
||||||
private readonly _config: Config,
|
|
||||||
private readonly _notificationsHub: NotificationsHub,
|
|
||||||
private readonly _modal: UiModalService,
|
|
||||||
private readonly _router: Router,
|
|
||||||
private readonly _breadcrumbService: BreadcrumbService,
|
|
||||||
private readonly _authService: AuthService,
|
|
||||||
private readonly _availabilityService: DomainAvailabilityService,
|
|
||||||
private readonly _zone: NgZone,
|
|
||||||
private readonly _wrongDestinationModalService: WrongDestinationModalService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async setSection(section: 'customer' | 'branch') {
|
|
||||||
this._appService.setSection(section);
|
|
||||||
|
|
||||||
const lastProcessId = (await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise())?.id;
|
|
||||||
if (lastProcessId) {
|
|
||||||
this.activateProcess(lastProcessId);
|
|
||||||
} else {
|
|
||||||
this._router.navigate([section === 'customer' ? '/kunde' : '/filiale']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process werden über Guards erstellt und aktiviert. An dieser Stelle wird nur navigiert
|
|
||||||
async addProcess() {
|
|
||||||
const processId = Date.now();
|
|
||||||
await this._router.navigate(['/kunde', processId, 'product']);
|
|
||||||
}
|
|
||||||
|
|
||||||
async activateProcess(activatedProcessId: number) {
|
|
||||||
try {
|
|
||||||
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(take(1)).toPromise();
|
|
||||||
await this._zone.run(async () => {
|
|
||||||
if (latestCrumb) {
|
|
||||||
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
|
|
||||||
} else {
|
|
||||||
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
async closeProcess(processId: number) {
|
|
||||||
this._appService.removeProcess(processId);
|
|
||||||
|
|
||||||
const processes = await this.processes$.pipe(first()).toPromise();
|
|
||||||
if (processes.length === 0) {
|
|
||||||
await this._router.navigate(['/kunde', 'dashboard']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const section = await this.section$.pipe(first()).toPromise();
|
|
||||||
const lastActivatedProcess = await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise();
|
|
||||||
this.activateProcess(lastActivatedProcess?.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
processAction(process: ApplicationProcess) {
|
|
||||||
if (process?.type === 'cart') {
|
|
||||||
this._router.navigate(['/kunde', process.id, 'cart']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async logout() {
|
|
||||||
await this._authService.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
async openNotifications() {
|
|
||||||
const notifications = await this.notifications$.pipe(first()).toPromise();
|
|
||||||
this._modal.open({
|
|
||||||
content: ModalNotificationsComponent,
|
|
||||||
data: notifications,
|
|
||||||
config: {
|
|
||||||
showScrollbarY: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
trackByIdFn: TrackByFunction<ApplicationProcess> = (_, process) => process.id;
|
|
||||||
|
|
||||||
fetchAndOpenPackages = () => this._wrongDestinationModalService.fetchAndOpen();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { OverlayModule } from '@angular/cdk/overlay';
|
|
||||||
|
|
||||||
import { ShellHeaderModule } from '@shell/header';
|
|
||||||
import { ShellProcessModule } from '@shell/process';
|
|
||||||
import { ShellFooterModule } from '@shell/footer';
|
|
||||||
|
|
||||||
import { ShellComponent } from './shell.component';
|
|
||||||
import { UiIconModule } from '@ui/icon';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { AuthModule } from '@core/auth';
|
|
||||||
import { DebugComponent } from '../debug/debug.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
RouterModule,
|
|
||||||
CommonModule,
|
|
||||||
ShellHeaderModule,
|
|
||||||
ShellProcessModule,
|
|
||||||
ShellFooterModule,
|
|
||||||
UiIconModule,
|
|
||||||
OverlayModule,
|
|
||||||
AuthModule,
|
|
||||||
DebugComponent,
|
|
||||||
],
|
|
||||||
exports: [ShellComponent],
|
|
||||||
declarations: [ShellComponent],
|
|
||||||
providers: [],
|
|
||||||
})
|
|
||||||
export class ShellModule {}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'Material Symbols Outlined';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100 700;
|
|
||||||
src: url(./materials-icons-outlined.woff2) format('woff2');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Material Symbols Rounded';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100 700;
|
|
||||||
src: url(./materials-icons-rounded.woff2) format('woff2');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Material Symbols Sharp';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100 700;
|
|
||||||
src: url(./materials-icons-sharp.woff2) format('woff2');
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -8,9 +8,8 @@
|
|||||||
},
|
},
|
||||||
"@core/auth": {
|
"@core/auth": {
|
||||||
"issuer": "https://sso-test.paragon-data.de",
|
"issuer": "https://sso-test.paragon-data.de",
|
||||||
"clientId": "hug-isa",
|
"clientId": "isa-client",
|
||||||
"responseType": "id_token token",
|
"responseType": "code",
|
||||||
"oidc": true,
|
|
||||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
|
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
|
||||||
},
|
},
|
||||||
"@core/logger": {
|
"@core/logger": {
|
||||||
@@ -41,7 +40,7 @@
|
|||||||
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
|
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
|
||||||
},
|
},
|
||||||
"@swagger/remi": {
|
"@swagger/remi": {
|
||||||
"rootUrl": "https://isa-integration.paragon-data.net/inv/v1"
|
"rootUrl": "https://isa-integration.paragon-data.net/inv/v6"
|
||||||
},
|
},
|
||||||
"@swagger/wws": {
|
"@swagger/wws": {
|
||||||
"rootUrl": "https://isa-integration.paragon-data.net/wws/v1"
|
"rootUrl": "https://isa-integration.paragon-data.net/wws/v1"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -41,10 +41,10 @@
|
|||||||
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
|
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
|
||||||
},
|
},
|
||||||
"@swagger/remi": {
|
"@swagger/remi": {
|
||||||
"rootUrl": "https://isa.paragon-systems.de/inv/v1"
|
"rootUrl": "https://isa.paragon-systems.de/inv/v6"
|
||||||
},
|
},
|
||||||
"@swagger/wws": {
|
"@swagger/wws": {
|
||||||
"rootUrl": "https://isa.paragon-data.net/wws/v1"
|
"rootUrl": "https://isa.paragon-systems.de/wws/v1"
|
||||||
},
|
},
|
||||||
"hubs": {
|
"hubs": {
|
||||||
"notifications": {
|
"notifications": {
|
||||||
@@ -69,6 +69,6 @@
|
|||||||
},
|
},
|
||||||
"checkForUpdates": 3600000,
|
"checkForUpdates": 3600000,
|
||||||
"licence": {
|
"licence": {
|
||||||
"scandit": ""
|
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
|
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
|
||||||
},
|
},
|
||||||
"@swagger/remi": {
|
"@swagger/remi": {
|
||||||
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v1"
|
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v6"
|
||||||
},
|
},
|
||||||
"@swagger/wws": {
|
"@swagger/wws": {
|
||||||
"rootUrl": "https://isa-staging.paragon-systems.de/wws/v1"
|
"rootUrl": "https://isa-staging.paragon-systems.de/wws/v1"
|
||||||
@@ -69,6 +69,6 @@
|
|||||||
},
|
},
|
||||||
"checkForUpdates": 3600000,
|
"checkForUpdates": 3600000,
|
||||||
"licence": {
|
"licence": {
|
||||||
"scandit": ""
|
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,6 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
|
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
|
||||||
<link href="/assets/icons/icons.css" rel="stylesheet" />
|
|
||||||
<link rel="manifest" href="manifest.webmanifest" />
|
<link rel="manifest" href="manifest.webmanifest" />
|
||||||
<meta name="theme-color" content="#1976d2" />
|
<meta name="theme-color" content="#1976d2" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--bg-color);
|
@apply bg-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
import { ApplicationService } from '@core/application';
|
||||||
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
|
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
|
||||||
import { Config } from '@core/config';
|
import { Config } from '@core/config';
|
||||||
|
|
||||||
@@ -13,10 +14,11 @@ export class AssortmentComponent implements OnInit {
|
|||||||
return this._config.get('process.ids.assortment');
|
return this._config.get('process.ids.assortment');
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private _config: Config, private _breadcrumb: BreadcrumbService) {}
|
constructor(private _config: Config, private _breadcrumb: BreadcrumbService, private _app: ApplicationService) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.createBreadcrumbIfNotExists();
|
this.createBreadcrumbIfNotExists();
|
||||||
|
this._app.setTitle('Sortiment');
|
||||||
}
|
}
|
||||||
|
|
||||||
async createBreadcrumbIfNotExists(): Promise<void> {
|
async createBreadcrumbIfNotExists(): Promise<void> {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<div class="page-price-update-item__item-details">
|
<div class="page-price-update-item__item-details">
|
||||||
<div class="page-price-update-item__item-contributors flex flex-row">
|
<div class="page-price-update-item__item-contributors flex flex-row">
|
||||||
{{ environment.isTablet() ? (item?.product?.contributors | substr: 42) : item?.product?.contributors }}
|
{{ environment.isTablet() ? (item?.product?.contributors | substr: 38) : item?.product?.contributors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||||
[alt]="item?.product?.formatDetail"
|
[alt]="item?.product?.formatDetail"
|
||||||
/>
|
/>
|
||||||
{{ item?.product?.formatDetail }}
|
{{ environment.isTablet() ? (item?.product?.formatDetail | substr: 25) : item?.product?.formatDetail }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { EnvironmentService } from '@core/environment';
|
|||||||
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
||||||
import { ProductListItemDTO } from '@swagger/wws';
|
import { ProductListItemDTO } from '@swagger/wws';
|
||||||
import { DateAdapter } from '@ui/common';
|
import { DateAdapter } from '@ui/common';
|
||||||
import { debounceTime, map, shareReplay, switchMap } from 'rxjs/operators';
|
import { debounceTime, filter, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||||
import { PriceUpdateComponentStore } from '../price-update.component.store';
|
import { PriceUpdateComponentStore } from '../price-update.component.store';
|
||||||
|
import { ReplaySubject, combineLatest } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-price-update-item',
|
selector: 'page-price-update-item',
|
||||||
@@ -16,8 +17,18 @@ import { PriceUpdateComponentStore } from '../price-update.component.store';
|
|||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
})
|
})
|
||||||
export class PriceUpdateItemComponent {
|
export class PriceUpdateItemComponent {
|
||||||
|
private _item$ = new ReplaySubject<ProductListItemDTO>(1);
|
||||||
|
|
||||||
|
private _item: ProductListItemDTO;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
item: ProductListItemDTO;
|
get item() {
|
||||||
|
return this._item;
|
||||||
|
}
|
||||||
|
set item(value) {
|
||||||
|
this._item = value;
|
||||||
|
this._item$.next(value);
|
||||||
|
}
|
||||||
|
|
||||||
get publicationDate() {
|
get publicationDate() {
|
||||||
if (!!this.item?.product?.publicationDate) {
|
if (!!this.item?.product?.publicationDate) {
|
||||||
@@ -40,21 +51,14 @@ export class PriceUpdateItemComponent {
|
|||||||
|
|
||||||
defaultBranch$ = this._availability.getDefaultBranch();
|
defaultBranch$ = this._availability.getDefaultBranch();
|
||||||
|
|
||||||
inStock$ = this.defaultBranch$.pipe(
|
inStock$ = combineLatest([this.defaultBranch$, this._item$]).pipe(
|
||||||
debounceTime(100),
|
debounceTime(100),
|
||||||
switchMap(
|
filter(([defaultBranch, item]) => !!defaultBranch && !!item),
|
||||||
(defaultBranch) =>
|
switchMap(([defaultBranch, item]) =>
|
||||||
this._stockService.getInStock$({
|
this._stockService.getInStock$({
|
||||||
itemId: Number(this.item?.product?.catalogProductNumber),
|
itemId: Number(item?.product?.catalogProductNumber),
|
||||||
branchId: defaultBranch?.id,
|
branchId: defaultBranch?.id,
|
||||||
})
|
})
|
||||||
// TODO: Bugfixing INSTOCK
|
|
||||||
// .pipe(
|
|
||||||
// map((instock) => {
|
|
||||||
// this.item.product.ean === '9783551775559' ? console.log({ item: this.item, instock }) : '';
|
|
||||||
// return instock;
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
),
|
),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { Config } from '@core/config';
|
import { Config } from '@core/config';
|
||||||
import { provideComponentStore } from '@ngrx/component-store';
|
import { provideComponentStore } from '@ngrx/component-store';
|
||||||
import { ShellFilterOverlayComponent } from '@shell/filter-overlay';
|
import { SharedFilterOverlayComponent } from '@shared/components/filter-overlay';
|
||||||
import { UiFilter, UiFilterComponent } from '@ui/filter';
|
import { UiFilter, UiFilterComponent } from '@ui/filter';
|
||||||
import { PriceUpdateComponentStore } from './price-update.component.store';
|
import { PriceUpdateComponentStore } from './price-update.component.store';
|
||||||
import { combineLatest, Subject, Subscription } from 'rxjs';
|
import { combineLatest, Subject, Subscription } from 'rxjs';
|
||||||
@@ -26,8 +26,8 @@ export class PriceUpdateComponent implements OnInit {
|
|||||||
|
|
||||||
hint$ = new Subject<string>();
|
hint$ = new Subject<string>();
|
||||||
|
|
||||||
@ViewChild(ShellFilterOverlayComponent)
|
@ViewChild(SharedFilterOverlayComponent)
|
||||||
filterOverlay: ShellFilterOverlayComponent;
|
filterOverlay: SharedFilterOverlayComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeigt die liste an, wenn entweder keine items geladen werden oder wenn items geladen wurden
|
* Zeigt die liste an, wenn entweder keine items geladen werden oder wenn items geladen wurden
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
|
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||||
import { UiFilterNextModule } from '@ui/filter';
|
import { UiFilterNextModule } from '@ui/filter';
|
||||||
import { UiIconModule } from '@ui/icon';
|
import { UiIconModule } from '@ui/icon';
|
||||||
import { UiSpinnerModule } from '@ui/spinner';
|
import { UiSpinnerModule } from '@ui/spinner';
|
||||||
@@ -8,7 +8,7 @@ import { PriceUpdateListModule } from './price-update-list';
|
|||||||
import { PriceUpdateComponent } from './price-update.component';
|
import { PriceUpdateComponent } from './price-update.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
|
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
|
||||||
exports: [PriceUpdateComponent],
|
exports: [PriceUpdateComponent],
|
||||||
declarations: [PriceUpdateComponent],
|
declarations: [PriceUpdateComponent],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -1,124 +1,240 @@
|
|||||||
<ng-container *ngIf="!showRecommendations">
|
<ng-container *ngIf="!showRecommendations">
|
||||||
<div #detailsContainer class="product-card">
|
<div class="page-article-details__container px-5 relative">
|
||||||
<ng-container *ngIf="store.item$ | async; let item">
|
<ng-container *ngIf="store.item$ | async; let item">
|
||||||
<div class="product-details">
|
<div class="page-article-details__product-details mb-3">
|
||||||
<div class="product-image">
|
<div class="page-article-details__product-bookmark justify-self-end">
|
||||||
<button class="image-button" (click)="showImages()">
|
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||||
<img (load)="loadImage()" [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||||
<ui-icon *ngIf="imageLoaded$ | async" icon="search_add" size="22px"></ui-icon>
|
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||||
</button>
|
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
|
||||||
|
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
|
||||||
|
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #notAvailable>
|
||||||
|
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
|
||||||
|
</ng-template>
|
||||||
|
</ui-tooltip>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showSubscriptionBadge$ | async">
|
||||||
|
<button [uiOverlayTrigger]="subscribtionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||||
|
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
|
||||||
|
</button>
|
||||||
|
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
|
||||||
|
>Artikel ist ein Fortsetzungsartikel,<br />
|
||||||
|
Artikel muss über eine Aboabteilung<br />
|
||||||
|
bestellt werden.
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
||||||
|
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||||
|
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
||||||
|
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
||||||
|
Dieser Artikel befindet sich im Prämienkatalog.
|
||||||
|
</ui-tooltip>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button (click)="showReviews()" class="recessions" *ngIf="item.reviews?.length > 0">
|
<div class="page-article-details__product-image-recessions flex flex-col items-center">
|
||||||
|
<div class="page-article-details__product-image">
|
||||||
|
<button class="border-none outline-none bg-transparent relative" (click)="showImages()">
|
||||||
|
<img
|
||||||
|
class="max-h-[19.6875rem] max-w-[12.1875rem] rounded-card"
|
||||||
|
(load)="loadImage()"
|
||||||
|
[src]="item.imageId | productImage: 195:315:true"
|
||||||
|
alt="product image"
|
||||||
|
/>
|
||||||
|
<ui-icon
|
||||||
|
class="absolute text-[#A7B9CB] inline-block bottom-[14px] right-[18px]"
|
||||||
|
*ngIf="imageLoaded$ | async"
|
||||||
|
icon="search_add"
|
||||||
|
size="25px"
|
||||||
|
></ui-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
(click)="showReviews()"
|
||||||
|
class="page-article-details__product-recessions flex flex-col mt-2 items-center bg-transparent border-none outline-none"
|
||||||
|
*ngIf="item.reviews?.length > 0"
|
||||||
|
>
|
||||||
<ui-stars [rating]="store.reviewRating$ | async"></ui-stars>
|
<ui-stars [rating]="store.reviewRating$ | async"></ui-stars>
|
||||||
|
|
||||||
<div class="cta-recessions">{{ item.reviews.length }} Rezensionen</div>
|
<div class="text-regular text-[#0556B4] font-bold">{{ item.reviews.length }} Rezensionen</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-info">
|
<div class="page-article-details__product-contributors">
|
||||||
<div class="row" [class.bookmark-badge-gap]="isBadgeVisible$ | async">
|
<a
|
||||||
<div>
|
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||||
<a
|
class="text-[#0556B4] font-semibold no-underline text-base"
|
||||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
[routerLink]="resultsPath"
|
||||||
class="autor"
|
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search', 'results']"
|
>
|
||||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
{{ contributor }}{{ last ? '' : ';' }}
|
||||||
>
|
</a>
|
||||||
{{ contributor }}{{ last ? '' : ';' }}
|
</div>
|
||||||
</a>
|
|
||||||
|
<div class="page-article-details__product-print justify-self-end" [class.mt-4]="isBadgeVisible$ | async">
|
||||||
|
<button class="bg-transparent text-brand font-bold text-lg outline-none border-none p-0" (click)="print()">Drucken</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-title text-2xl font-bold mb-6">
|
||||||
|
{{ item.product?.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-misc flex flex-col mb-4">
|
||||||
|
<div
|
||||||
|
class="page-article-details__product-format flex items-center font-bold text-sm"
|
||||||
|
*ngIf="item?.product?.format && item?.product?.formatDetail"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
*ngIf="item?.product?.format !== '--'"
|
||||||
|
class="flex mr-2 h-[1.125rem]"
|
||||||
|
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
|
||||||
|
[alt]="item.product?.formatDetail"
|
||||||
|
/>
|
||||||
|
{{ item.product?.formatDetail }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-volume" *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-publication">{{ publicationDate$ | async }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-price-info flex flex-col mb-4">
|
||||||
|
<div
|
||||||
|
class="page-article-details__product-price font-bold text-xl self-end"
|
||||||
|
*ngIf="item.catalogAvailability?.price?.value?.value; else retailPrice"
|
||||||
|
>
|
||||||
|
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
|
||||||
|
</div>
|
||||||
|
<ng-template #retailPrice>
|
||||||
|
<div
|
||||||
|
class="page-article-details__product-price font-bold text-xl self-end"
|
||||||
|
*ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability"
|
||||||
|
>
|
||||||
|
{{ takeAwayAvailability?.retailPrice?.value?.value | currency: takeAwayAvailability?.retailPrice?.value?.currency:'code' }}
|
||||||
</div>
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<button class="cta-print right" (click)="print()">Drucken</button>
|
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
|
||||||
</div>
|
{{ promotionPoints }} Lesepunkte
|
||||||
<div class="title">
|
|
||||||
{{ item.product?.name }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<!-- TODO: Ticket PREISGEBUNDEN -->
|
||||||
<div>
|
<div class="page-article-details__product-price-bound self-end"></div>
|
||||||
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
</div>
|
||||||
<img
|
|
||||||
*ngIf="item?.product?.format !== '--'"
|
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
|
||||||
class="format-icon"
|
<div class="page-article-details__product-manufacturer" data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
|
||||||
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
|
|
||||||
[alt]="item.product?.formatDetail"
|
<div class="page-article-details__product-language" *ngIf="item?.product?.locale" data-name="product-language">
|
||||||
/>
|
{{ item?.product?.locale }}
|
||||||
{{ item.product?.formatDetail }}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-stock flex justify-end items-center">
|
||||||
|
<div class="h-5 w-16 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
|
||||||
|
<div
|
||||||
|
class="flex flex-row py-4 pl-4"
|
||||||
|
[uiOverlayTrigger]="tooltip"
|
||||||
|
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||||
|
*ngIf="!(store.fetchingTakeAwayAvailability$ | async)"
|
||||||
|
>
|
||||||
|
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
||||||
|
<ui-icon class="mr-2 mb-1" icon="home" size="15px"></ui-icon>
|
||||||
|
<span class="font-bold text-sm">{{ takeAwayAvailability.inStock || 0 }}x</span>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||||
|
{{ stockTooltipText$ | async }}
|
||||||
|
</ui-tooltip>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-ean-specs flex flex-col">
|
||||||
|
<div class="page-article-details__product-ean" data-name="product-ean">{{ item.product?.ean }}</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-specs">
|
||||||
|
<ng-container *ngIf="item?.specs?.length > 0">
|
||||||
|
{{ (item?.specs)[0]?.value }}
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-availabilities flex flex-row items-center justify-end mt-4">
|
||||||
|
<div
|
||||||
|
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||||
|
*ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"
|
||||||
|
></div>
|
||||||
|
<ng-template #showAvailabilityTakeAwayIcon>
|
||||||
|
<div
|
||||||
|
*ngIf="store.isTakeAwayAvailabilityAvailable$ | async"
|
||||||
|
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<ui-icon class="mx-1" icon="shopping_bag" size="18px"> </ui-icon>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||||
|
*ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"
|
||||||
|
></div>
|
||||||
|
<ng-template #showAvailabilityPickUpIcon>
|
||||||
|
<div
|
||||||
|
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
|
||||||
|
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||||
|
>
|
||||||
|
<ui-icon class="mx-1" icon="box_out" size="18px"></ui-icon>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||||
|
*ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"
|
||||||
|
></div>
|
||||||
|
<ng-template #showAvailabilityDeliveryIcon>
|
||||||
|
<div
|
||||||
|
*ngIf="showDeliveryTruck$ | async"
|
||||||
|
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||||
|
>
|
||||||
|
<ui-icon class="-mb-px-5 -mt-px-5 mx-1" icon="truck" size="30px"></ui-icon>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
|
||||||
|
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
||||||
|
></div>
|
||||||
|
<ng-template #showAvailabilityDeliveryB2BIcon>
|
||||||
|
<div
|
||||||
|
*ngIf="showDeliveryB2BTruck$ | async"
|
||||||
|
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
|
||||||
|
>
|
||||||
|
<ui-icon class="-mb-px-10 -mt-px-10 mx-1" icon="truck_b2b" size="30px"> </ui-icon>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<span *ngIf="store.isDownload$ | async" class="flex flex-row items-center">
|
||||||
|
<div class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3">
|
||||||
|
<ui-icon class="mx-1" icon="download" size="18px"></ui-icon>
|
||||||
|
<span class="font-bold">Download</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-article-details__shelf-ssc">
|
||||||
|
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
|
||||||
|
<div class="w-52 h-px-20 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
|
||||||
|
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
||||||
|
<div *ngIf="store.sscText$ | async; let sscText">
|
||||||
|
{{ sscText }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
|
</ng-container>
|
||||||
|
|
||||||
<div>{{ publicationDate$ | async }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right">
|
|
||||||
<div class="price" *ngIf="item.catalogAvailability?.price?.value?.value; else retailPrice">
|
|
||||||
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
|
|
||||||
</div>
|
|
||||||
<ng-template #retailPrice>
|
|
||||||
<div class="price" *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
|
||||||
{{
|
|
||||||
takeAwayAvailability?.retailPrice?.value?.value | currency: takeAwayAvailability?.retailPrice?.value?.currency:'code'
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
<div *ngIf="store.promotionPoints$ | async; let promotionPoints">{{ promotionPoints }} Lesepunkte</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row stock">
|
<div class="page-article-details__shelfinfo" *ngIf="store.isDownload$ | async">
|
||||||
<div data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
|
|
||||||
|
|
||||||
<div class="right quantity" [uiOverlayTrigger]="tooltip" [overlayTriggerDisabled]="!(stockTooltipText$ | async)">
|
|
||||||
<div class="fetching small" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
|
|
||||||
<ng-container *ngIf="!(store.fetchingTakeAwayAvailability$ | async)">
|
|
||||||
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
|
|
||||||
<ui-icon icon="home" size="22px"></ui-icon>
|
|
||||||
{{ takeAwayAvailability.inStock || 0 }}x
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
|
||||||
{{ stockTooltipText$ | async }}
|
|
||||||
</ui-tooltip>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="item?.product?.locale" data-name="product-language">{{ item?.product?.locale }}</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div data-name="product-ean">{{ item.product?.ean }}</div>
|
|
||||||
<div class="right">
|
|
||||||
<div class="availability-icons">
|
|
||||||
<div class="fetching xsmall" *ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"></div>
|
|
||||||
<ng-template #showAvailabilityTakeAwayIcon>
|
|
||||||
<ui-icon *ngIf="store.isTakeAwayAvailabilityAvailable$ | async" icon="shopping_bag" size="18px"> </ui-icon>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div class="fetching xsmall" *ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"></div>
|
|
||||||
<ng-template #showAvailabilityPickUpIcon>
|
|
||||||
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div class="fetching xsmall" *ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"></div>
|
|
||||||
<ng-template #showAvailabilityDeliveryIcon>
|
|
||||||
<ui-icon *ngIf="showDeliveryTruck$ | async" class="truck" icon="truck" size="30px"></ui-icon>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="fetching xsmall"
|
|
||||||
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
|
|
||||||
></div>
|
|
||||||
<ng-template #showAvailabilityDeliveryB2BIcon>
|
|
||||||
<ui-icon *ngIf="showDeliveryB2BTruck$ | async" class="truck_b2b" icon="truck_b2b" size="40px"> </ui-icon>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<span *ngIf="store.isDownload$ | async" class="download-icon">
|
|
||||||
<ui-icon icon="download" size="18px"></ui-icon>
|
|
||||||
<span class="label">Download</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="shelfinfo right" *ngIf="store.isDownload$ | async">
|
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="
|
*ngIf="
|
||||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||||
@@ -145,24 +261,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="page-article-details__shelfinfo text-right" *ngIf="!(store.isDownload$ | async)">
|
||||||
<div class="row">
|
|
||||||
<div class="specs">
|
|
||||||
<ng-container *ngIf="item?.specs?.length > 0">
|
|
||||||
{{ (item?.specs)[0]?.value }}
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="right ssc">
|
|
||||||
<div class="fetching" *ngIf="fetchingAvailabilities$ | async"></div>
|
|
||||||
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
|
|
||||||
<div *ngIf="store.sscText$ | async; let sscText">
|
|
||||||
{{ sscText }}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="shelfinfo right" *ngIf="!(store.isDownload$ | async)">
|
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="
|
*ngIf="
|
||||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||||
@@ -186,109 +285,115 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bookmark">
|
<div class="page-article-details__product-formats-container mt-3" *ngIf="item.family?.length > 0">
|
||||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
<hr class="bg-[#E6EFF9] border-t-2" />
|
||||||
<button [uiOverlayTrigger]="archivTooltip" class="bookmark-badge">
|
<div class="pt-3">
|
||||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
<div class="page-article-details__product-formats">
|
||||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
|
<span class="mr-2">Auch verfügbar als</span>
|
||||||
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
|
|
||||||
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
|
<ui-slider [scrollDistance]="250">
|
||||||
</ng-container>
|
<a
|
||||||
<ng-template #notAvailable>
|
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
|
||||||
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
|
*ngFor="let format of item.family"
|
||||||
</ng-template>
|
[routerLink]="getDetailsPath(format.product.ean)"
|
||||||
</ui-tooltip>
|
[queryParamsHandling]="!(isTablet$ | async) ? 'preserve' : ''"
|
||||||
</button>
|
>
|
||||||
</div>
|
<span class="flex items-center">
|
||||||
<div *ngIf="showSubscriptionBadge$ | async">
|
<img
|
||||||
<button [uiOverlayTrigger]="subscribtionTooltip" class="bookmark-badge">
|
class="mr-2"
|
||||||
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
|
*ngIf="!!format.product?.format"
|
||||||
</button>
|
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
|
||||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
|
alt="format icon"
|
||||||
>Artikel ist ein Fortsetzungsartikel,<br />
|
/>
|
||||||
Artikel muss über eine Aboabteilung<br />
|
{{ format.product?.formatDetail }}
|
||||||
bestellt werden.
|
<span class="ml-1">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
|
||||||
</ui-tooltip>
|
</span>
|
||||||
</div>
|
</a>
|
||||||
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
|
</ui-slider>
|
||||||
<button [uiOverlayTrigger]="promotionTooltip" class="bookmark-badge">
|
|
||||||
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
|
|
||||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
|
|
||||||
Dieser Artikel befindet sich im Prämienkatalog.
|
|
||||||
</ui-tooltip>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-actions">
|
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
|
||||||
<button *ngIf="!(store.isDownload$ | async)" class="cta-availabilities" (click)="showAvailabilities()">
|
|
||||||
Vorrätig in anderer Filiale
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="cta-continue"
|
|
||||||
(click)="showPurchasingModal()"
|
|
||||||
[disabled]="
|
|
||||||
!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
In den Warenkorb
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
<div
|
||||||
<ng-container *ngIf="item.family?.length > 0">
|
#description
|
||||||
<div class="product-formats">
|
class="page-article-details__product-description flex flex-col flex-grow overflow-hidden overflow-y-scroll"
|
||||||
<span class="label">Auch verfügbar als</span>
|
*ngIf="item.texts?.length > 0"
|
||||||
|
>
|
||||||
<ui-slider [scrollDistance]="250">
|
<div class="whitespace-pre-line">
|
||||||
<a
|
|
||||||
class="product-family"
|
|
||||||
*ngFor="let format of item.family"
|
|
||||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', 'ean', format.product.ean]"
|
|
||||||
>
|
|
||||||
<span class="format-detail">
|
|
||||||
<img
|
|
||||||
*ngIf="!!format.product?.format"
|
|
||||||
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
|
|
||||||
alt="format icon"
|
|
||||||
/>
|
|
||||||
{{ format.product?.formatDetail }}
|
|
||||||
<span class="price">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</ui-slider>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div class="product-description" *ngIf="item.texts?.length > 0">
|
|
||||||
<div class="info">
|
|
||||||
{{ item.texts[0].value }}
|
{{ item.texts[0].value }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-text">
|
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" *ngIf="!showMore" (click)="showMore = !showMore">
|
||||||
|
Mehr <ui-icon class="ml-2" size="15px" icon="arrow"></ui-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
*ngIf="showMore"
|
||||||
|
class="page-article-details__product-description-text flex flex-col whitespace-pre-line mb-px-100 break-words"
|
||||||
|
>
|
||||||
<span *ngFor="let text of item.texts | slice: 1">
|
<span *ngFor="let text of item.texts | slice: 1">
|
||||||
<h3 class="header">{{ text.label }}</h3>
|
<h3 class="my-4 text-regular font-bold">{{ text.label }}</h3>
|
||||||
{{ text.value }}
|
{{ text.value }}
|
||||||
</span>
|
</span>
|
||||||
<button class="scroll-top-cta" (click)="scrollTop()">
|
|
||||||
<ui-icon class="arrow" icon="arrow" size="20px"></ui-icon>
|
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" (click)="showMore = !showMore">
|
||||||
|
<ui-icon class="transform ml-0 mr-2 rotate-180" size="15px" icon="arrow"></ui-icon> Weniger
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="page-article-details__scroll-top-cta" (click)="scrollTop(description)">
|
||||||
|
<ui-icon class="text-[#0556B4]" icon="arrow" size="20px"></ui-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!showRecommendations">
|
||||||
|
<div
|
||||||
|
*ngIf="store.item$ | async; let item"
|
||||||
|
class="page-article-details__actions w-full absolute text-center left-0 bottom-10 z-fixed"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
*ngIf="!(store.isDownload$ | async)"
|
||||||
|
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-px-30"
|
||||||
|
(click)="showAvailabilities()"
|
||||||
|
>
|
||||||
|
Bestände in anderen Filialen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="text-white bg-brand border-brand font-bold text-lg px-[1.375rem] py-4 rounded-full border-none no-underline"
|
||||||
|
(click)="showPurchasingModal()"
|
||||||
|
[disabled]="
|
||||||
|
!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
In den Warenkorb
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="page-article-details__product-recommendations -mx-5">
|
||||||
|
<button
|
||||||
|
*ngIf="store.item$ | async; let item"
|
||||||
|
class="shadow-[#dce2e9_0px_-2px_18px_0px] sticky bottom-4 border-none outline-none left-0 right-0 flex items-center px-5 h-14 min-h-[3.5rem] bg-white w-full"
|
||||||
|
(click)="showRecommendations = true"
|
||||||
|
>
|
||||||
|
<span class="uppercase text-[#0556B4] font-bold text-small">Empfehlungen</span>
|
||||||
|
<img class="absolute right-5 bottom-3 h-12" src="assets/images/recommendation_tag.png" alt="recommendation icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button *ngIf="store.item$ | async; let item" class="product-recommendations" (click)="showRecommendations = true">
|
|
||||||
<span class="label">Empfehlungen</span>
|
|
||||||
<img src="assets/images/recommendation_tag.png" alt="recommendation icon" />
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="recommendations-overlay" @slideYAnimation *ngIf="showRecommendations">
|
<div class="page-article-details__recommendations-overlay absolute top-16 rounded-t-card" @slideYAnimation *ngIf="showRecommendations">
|
||||||
<button class="product-button" (click)="showRecommendations = false">{{ (store.item$ | async)?.product?.name }}</button>
|
<button
|
||||||
|
class="h-[3.75rem] shadow-[0_-2px_24px_0_#dce2e9] flex flex-row justify-center items-center w-full text-xl bg-white text-ucla-blue font-bold border-none outline-none rounded-t-card"
|
||||||
|
(click)="showRecommendations = false"
|
||||||
|
>
|
||||||
|
{{ (store.item$ | async)?.product?.name }}
|
||||||
|
</button>
|
||||||
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
|
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,269 +1,99 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply flex flex-col;
|
@apply box-border block h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card {
|
.page-article-details__container {
|
||||||
@apply flex flex-col bg-white w-full rounded-card shadow-card;
|
@apply h-full w-full bg-white rounded-card shadow-card flex flex-col;
|
||||||
|
|
||||||
.product-details {
|
|
||||||
@apply flex flex-row p-5;
|
|
||||||
|
|
||||||
.bookmark {
|
|
||||||
@apply absolute flex;
|
|
||||||
top: 52px;
|
|
||||||
right: 25px;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookmark-badge {
|
|
||||||
@apply p-0 m-0 outline-none border-none bg-transparent relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.promotion-badge {
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookmark-badge-gap {
|
|
||||||
@apply mt-px-35;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image {
|
|
||||||
@apply flex flex-col items-center justify-start mr-5;
|
|
||||||
|
|
||||||
.recessions {
|
|
||||||
@apply flex flex-col items-center mt-4 bg-transparent border-none outline-none;
|
|
||||||
|
|
||||||
.cta-recessions {
|
|
||||||
@apply text-regular text-dark-cerulean font-bold mt-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-button {
|
|
||||||
@apply border-none outline-none bg-transparent relative;
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply absolute text-dark-cerulean inline-block;
|
|
||||||
bottom: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
@apply rounded-xl shadow-card;
|
|
||||||
box-shadow: 0 0 18px 0 #b8b3b7;
|
|
||||||
max-height: 315px;
|
|
||||||
max-width: 195px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info {
|
|
||||||
@apply w-full;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
@apply text-3xl font-bold mb-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.format,
|
|
||||||
.ssc,
|
|
||||||
.quantity {
|
|
||||||
@apply font-bold text-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock {
|
|
||||||
min-height: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quantity {
|
|
||||||
@apply flex justify-end mt-4;
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply mr-1 text-ucla-blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.format {
|
|
||||||
@apply flex items-center;
|
|
||||||
|
|
||||||
.format-icon {
|
|
||||||
@apply flex mr-2;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ssc {
|
|
||||||
@apply flex justify-end my-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
@apply font-bold text-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shelfinfo {
|
|
||||||
@apply text-ucla-blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fetching {
|
|
||||||
@apply w-52 h-px-20;
|
|
||||||
background-color: #e6eff9;
|
|
||||||
animation: load 0.75s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xsmall {
|
|
||||||
@apply w-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small {
|
|
||||||
@apply w-16;
|
|
||||||
}
|
|
||||||
|
|
||||||
.medium {
|
|
||||||
@apply w-40;
|
|
||||||
}
|
|
||||||
|
|
||||||
.availability-icons {
|
|
||||||
@apply flex flex-row items-center justify-end text-dark-cerulean mt-4;
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply mx-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truck {
|
|
||||||
@apply -mb-px-5 -mt-px-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truck_b2b {
|
|
||||||
@apply -mb-px-10 -mt-px-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
@apply font-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-icon {
|
|
||||||
@apply flex flex-row items-center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-print {
|
|
||||||
@apply bg-transparent text-brand font-bold text-xl outline-none border-none p-0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
@apply grid items-end;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
@apply text-right self-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
@apply bg-glitter h-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-description {
|
|
||||||
@apply flex flex-col flex-grow px-5 py-5;
|
|
||||||
min-height: calc(100vh - 769px);
|
|
||||||
|
|
||||||
.info {
|
|
||||||
@apply whitespace-pre-line;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-text {
|
|
||||||
@apply flex flex-col whitespace-pre-line mb-px-100 break-words;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
@apply my-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
@apply text-regular font-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-top-cta {
|
|
||||||
@apply flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
border-radius: 100%;
|
|
||||||
width: 58px;
|
|
||||||
height: 58px;
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
color: #1f466c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-actions {
|
|
||||||
@apply text-right px-5 py-4;
|
|
||||||
|
|
||||||
.cta-availabilities {
|
|
||||||
@apply text-brand border-none border-brand bg-white font-bold text-lg px-4 py-2 rounded-full;
|
|
||||||
}
|
|
||||||
.cta-continue {
|
|
||||||
@apply text-white bg-brand font-bold text-lg px-4 py-2 rounded-full border-none ml-4 no-underline;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
@apply bg-inactive-branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-formats {
|
|
||||||
@apply grid whitespace-nowrap items-center px-5 py-4;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
@apply mr-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-family {
|
|
||||||
@apply mr-4 text-active-customer font-bold no-underline px-2;
|
|
||||||
|
|
||||||
.format-detail {
|
|
||||||
@apply flex items-center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
@apply mr-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
@apply ml-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-recommendations {
|
.page-article-details__product-details {
|
||||||
@apply sticky bottom-0 border-none outline-none left-0 right-0 flex items-center px-5 h-16 bg-white w-full;
|
@apply grid gap-x-5;
|
||||||
box-shadow: #dce2e9 0px -2px 18px 0px;
|
grid-template-columns: max-content auto;
|
||||||
|
grid-template-rows: 2.1875rem repeat(11, minmax(auto, max-content));
|
||||||
.label {
|
grid-template-areas:
|
||||||
@apply uppercase text-active-customer font-bold text-small;
|
'. . . bookmark'
|
||||||
}
|
'image contributors contributors contributors'
|
||||||
|
'image title title print'
|
||||||
img {
|
'image title title .'
|
||||||
@apply absolute right-5 bottom-5 h-12;
|
'image misc misc price'
|
||||||
}
|
'image misc misc price'
|
||||||
|
'image origin origin stock'
|
||||||
|
'image origin origin stock'
|
||||||
|
'image specs availabilities availabilities'
|
||||||
|
'image specs ssc ssc'
|
||||||
|
'image . ssc ssc'
|
||||||
|
'image . ssc ssc';
|
||||||
}
|
}
|
||||||
|
|
||||||
.recommendations-overlay {
|
.page-article-details__product-bookmark {
|
||||||
@apply absolute w-full top-0 rounded-t-card;
|
grid-area: bookmark;
|
||||||
top: 56px;
|
}
|
||||||
|
|
||||||
.product-button {
|
.page-article-details__product-image-recessions {
|
||||||
@apply flex flex-row justify-center items-center w-full text-xl bg-white text-ucla-blue font-bold border-none outline-none rounded-t-card;
|
grid-area: image;
|
||||||
box-shadow: 0 -2px 24px 0 #dce2e9;
|
}
|
||||||
height: 60px;
|
|
||||||
|
.page-article-details__product-contributors {
|
||||||
|
grid-area: contributors;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-print {
|
||||||
|
grid-area: print;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-title {
|
||||||
|
grid-area: title;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-misc {
|
||||||
|
grid-area: misc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-price-info {
|
||||||
|
grid-area: price;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-origin-infos {
|
||||||
|
grid-area: origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-stock {
|
||||||
|
grid-area: stock;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-ean-specs {
|
||||||
|
grid-area: specs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-availabilities {
|
||||||
|
grid-area: availabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__shelf-ssc {
|
||||||
|
grid-area: ssc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-description-text {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__product-formats {
|
||||||
|
@apply grid whitespace-nowrap items-center max-w-full;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__scroll-top-cta {
|
||||||
|
@apply flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
|
||||||
|
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article-details__actions {
|
||||||
|
&:disabled {
|
||||||
|
@apply bg-inactive-branch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.autor {
|
|
||||||
@apply text-active-customer font-bold no-underline;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
import { DomainPrinterService } from '@domain/printer';
|
import { DomainPrinterService } from '@domain/printer';
|
||||||
import { ItemDTO as PrinterItemDTO } from '@swagger/print';
|
import { ItemDTO as PrinterItemDTO } from '@swagger/print';
|
||||||
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
||||||
import { AvailabilityDTO, BranchDTO } from '@swagger/checkout';
|
import { BranchDTO } from '@swagger/checkout';
|
||||||
import { UiModalService } from '@ui/modal';
|
import { UiModalService } from '@ui/modal';
|
||||||
import { ModalReviewsComponent } from '@modal/reviews';
|
import { ModalReviewsComponent } from '@modal/reviews';
|
||||||
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal';
|
|
||||||
import { PurchasingOptions } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal/purchasing-options-modal.store';
|
|
||||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||||
import { debounceTime, filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
|
import { debounceTime, filter, first, map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||||
import { ArticleDetailsStore } from './article-details.store';
|
import { ArticleDetailsStore } from './article-details.store';
|
||||||
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
||||||
import { ProductImageService } from 'apps/cdn/product-image/src/public-api';
|
import { ProductImageService } from 'apps/cdn/product-image/src/public-api';
|
||||||
@@ -20,7 +18,10 @@ import { BreadcrumbService } from '@core/breadcrumb';
|
|||||||
import { ItemDTO } from '@swagger/cat';
|
import { ItemDTO } from '@swagger/cat';
|
||||||
import { DateAdapter } from '@ui/common';
|
import { DateAdapter } from '@ui/common';
|
||||||
import { DatePipe } from '@angular/common';
|
import { DatePipe } from '@angular/common';
|
||||||
|
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
import { DomainAvailabilityService } from '@domain/availability';
|
||||||
|
import { EnvironmentService } from '@core/environment';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-article-details',
|
selector: 'page-article-details',
|
||||||
@@ -114,6 +115,19 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
|||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
get isTablet$() {
|
||||||
|
return this._environment.matchTablet$.pipe(
|
||||||
|
map((state) => state?.matches),
|
||||||
|
shareReplay()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get resultsPath() {
|
||||||
|
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
showMore: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly applicationService: ApplicationService,
|
public readonly applicationService: ApplicationService,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
@@ -125,7 +139,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
|||||||
private _dateAdapter: DateAdapter,
|
private _dateAdapter: DateAdapter,
|
||||||
private _datePipe: DatePipe,
|
private _datePipe: DatePipe,
|
||||||
public elementRef: ElementRef,
|
public elementRef: ElementRef,
|
||||||
private _availability: DomainAvailabilityService
|
private _purchaseOptionsModalService: PurchaseOptionsModalService,
|
||||||
|
private _availability: DomainAvailabilityService,
|
||||||
|
private _navigationService: ProductCatalogNavigationService,
|
||||||
|
private _environment: EnvironmentService,
|
||||||
|
private _router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -160,16 +178,30 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
|||||||
filter((f) => !!f)
|
filter((f) => !!f)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const more$ = this.activatedRoute.params.subscribe(() => (this.showMore = false));
|
||||||
|
|
||||||
this.subscriptions.add(processIdSubscription);
|
this.subscriptions.add(processIdSubscription);
|
||||||
|
this.subscriptions.add(more$);
|
||||||
this.subscriptions.add(this.store.loadItemById(id$));
|
this.subscriptions.add(this.store.loadItemById(id$));
|
||||||
this.subscriptions.add(this.store.loadItemByEan(ean$));
|
this.subscriptions.add(this.store.loadItemByEan(ean$));
|
||||||
this.subscriptions.add(this.store.item$.pipe(filter((item) => !!item)).subscribe((item) => this.updateBreadcrumb(item)));
|
this.subscriptions.add(
|
||||||
|
this.store.item$
|
||||||
|
.pipe(
|
||||||
|
withLatestFrom(this.isTablet$),
|
||||||
|
filter(([item, isTablet]) => !!item)
|
||||||
|
)
|
||||||
|
.subscribe(([item, isTablet]) => (isTablet ? this.updateBreadcrumb(item) : this.updateBreadcrumbDesktop(item)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.subscriptions.unsubscribe();
|
this.subscriptions.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDetailsPath(ean?: string) {
|
||||||
|
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, ean });
|
||||||
|
}
|
||||||
|
|
||||||
async updateBreadcrumb(item: ItemDTO) {
|
async updateBreadcrumb(item: ItemDTO) {
|
||||||
const crumbs = await this.breadcrumb
|
const crumbs = await this.breadcrumb
|
||||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details', `${item.id}`])
|
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details', `${item.id}`])
|
||||||
@@ -183,13 +215,41 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
|||||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||||
key: this.applicationService.activatedProcessId,
|
key: this.applicationService.activatedProcessId,
|
||||||
name: item.product?.name,
|
name: item.product?.name,
|
||||||
path: `/kunde/${this.applicationService.activatedProcessId}/product/details/${item.id}`,
|
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||||
params: this.activatedRoute.snapshot.queryParams,
|
params: this.activatedRoute.snapshot.queryParams,
|
||||||
tags: ['catalog', 'details', `${item.id}`],
|
tags: ['catalog', 'details', `${item.id}`],
|
||||||
section: 'customer',
|
section: 'customer',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateBreadcrumbDesktop(item: ItemDTO) {
|
||||||
|
const crumbs = await this.breadcrumb
|
||||||
|
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])
|
||||||
|
.pipe(first())
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
if (crumbs.length === 0) {
|
||||||
|
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||||
|
key: this.applicationService.activatedProcessId,
|
||||||
|
name: item.product?.name,
|
||||||
|
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||||
|
params: this.activatedRoute.snapshot.queryParams,
|
||||||
|
tags: ['catalog', 'details', `${item.id}`],
|
||||||
|
section: 'customer',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const crumb = crumbs.find((_) => true);
|
||||||
|
this.breadcrumb.patchBreadcrumb(crumb.id, {
|
||||||
|
key: this.applicationService.activatedProcessId,
|
||||||
|
name: item.product?.name,
|
||||||
|
path: this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: item.id }),
|
||||||
|
params: this.activatedRoute.snapshot.queryParams,
|
||||||
|
tags: ['catalog', 'details', `${item.id}`],
|
||||||
|
section: 'customer',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async print() {
|
async print() {
|
||||||
const item = await this.store.item$.pipe(first()).toPromise();
|
const item = await this.store.item$.pipe(first()).toPromise();
|
||||||
this.uiModal.open({
|
this.uiModal.open({
|
||||||
@@ -262,64 +322,47 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async showPurchasingModal(selectedBranch?: BranchDTO) {
|
async showPurchasingModal(selectedBranch?: BranchDTO) {
|
||||||
let availableOptions: PurchasingOptions[] = [];
|
const item = await this.store.item$.pipe(first()).toPromise();
|
||||||
const availabilities: { [key: string]: AvailabilityDTO } = {};
|
|
||||||
|
|
||||||
const takeNow = await this.store.isTakeAwayAvailabilityAvailable$.pipe(first()).toPromise();
|
this._purchaseOptionsModalService
|
||||||
if (takeNow) {
|
.open({
|
||||||
availableOptions.push('take-away');
|
type: 'add',
|
||||||
availabilities['take-away'] = await this.store.takeAwayAvailability$.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const download = await this.store.isDownloadAvailabilityAvailable$.pipe(first()).toPromise();
|
|
||||||
if (download) {
|
|
||||||
availableOptions.push('download');
|
|
||||||
availabilities['download'] = await this.store.downloadAvailability$.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const pickup = await this.store.isPickUpAvailabilityAvailable$.pipe(first()).toPromise();
|
|
||||||
if (pickup) {
|
|
||||||
availableOptions.push('pick-up');
|
|
||||||
availabilities['pick-up'] = await this.store.pickUpAvailability$.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const digDelivery = await this.store.isDeliveryDigAvailabilityAvailable$.pipe(first()).toPromise();
|
|
||||||
if (digDelivery) {
|
|
||||||
availableOptions.push('dig-delivery');
|
|
||||||
availabilities['dig-delivery'] = await this.store.deliveryDigAvailability$.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const b2b = await this.store.isDeliveryB2BAvailabilityAvailable$.pipe(first()).toPromise();
|
|
||||||
|
|
||||||
if (b2b) {
|
|
||||||
availableOptions.push('b2b-delivery');
|
|
||||||
availabilities['b2b-delivery'] = await this.store.deliveryB2BAvailability$.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableOptions.includes('dig-delivery') && availableOptions.includes('b2b-delivery')) {
|
|
||||||
availableOptions.push('delivery');
|
|
||||||
availabilities['delivery'] = await this.store.deliveryAvailability$.pipe(first()).toPromise();
|
|
||||||
availableOptions = availableOptions.filter((option) => !(option === 'dig-delivery' || option === 'b2b-delivery'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const branch = selectedBranch || (await this.store.branch$.pipe(first()).toPromise());
|
|
||||||
|
|
||||||
this.uiModal.open({
|
|
||||||
content: PurchasingOptionsModalComponent,
|
|
||||||
data: {
|
|
||||||
availableOptions,
|
|
||||||
option: selectedBranch ? 'take-away' : undefined,
|
|
||||||
item: await this.store.item$.pipe(first()).toPromise(),
|
|
||||||
branchId: branch?.id,
|
|
||||||
processId: this.applicationService.activatedProcessId,
|
processId: this.applicationService.activatedProcessId,
|
||||||
availabilities,
|
items: [item],
|
||||||
} as PurchasingOptionsModalData,
|
})
|
||||||
});
|
.afterClosed$.subscribe((result) => {
|
||||||
|
if (result?.data === 'continue') {
|
||||||
|
this.navigateToShoppingCart();
|
||||||
|
console.log('continue');
|
||||||
|
} else if (result?.data === 'continue-shopping') {
|
||||||
|
this.navigateToResultList();
|
||||||
|
console.log('continue-shopping');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTop() {
|
navigateToShoppingCart() {
|
||||||
const element = this.elementRef.nativeElement.closest('.main-wrapper');
|
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/cart/review`]);
|
||||||
element?.scrollTo({ top: 0, behavior: 'smooth' });
|
}
|
||||||
|
|
||||||
|
async navigateToResultList() {
|
||||||
|
let crumbs = await this.breadcrumb
|
||||||
|
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
|
||||||
|
.pipe(first())
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
|
||||||
|
|
||||||
|
const crumb = crumbs[crumbs.length - 1];
|
||||||
|
if (crumb) {
|
||||||
|
this._router.navigate([crumb.path], { queryParams: crumb.params });
|
||||||
|
} else {
|
||||||
|
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/product`]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollTop(div: HTMLDivElement) {
|
||||||
|
div?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|
||||||
loadImage() {
|
loadImage() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply flex flex-col bg-white;
|
@apply flex flex-col bg-white h-[calc(100vh-16.5rem-3.75rem)] desktop-small:h-[calc(100vh-15.1rem-3.75rem)];
|
||||||
box-shadow: 0px -2px 24px 0px #dce2e9;
|
box-shadow: 0px -2px 24px 0px #dce2e9;
|
||||||
height: calc(100vh - 342px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|||||||
@@ -1,13 +1 @@
|
|||||||
<button class="filter" [class.active]="hasFilter$ | async" (click)="filterActive$.next(true); shellFilterOverlay.open()">
|
|
||||||
<ui-icon size="20px" icon="filter_alit"></ui-icon>
|
|
||||||
<span class="label">Filter</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
||||||
<shell-filter-overlay #shellFilterOverlay>
|
|
||||||
<page-article-search-filter
|
|
||||||
*ngIf="filterActive$ | async"
|
|
||||||
(close)="filterActive$.next(false); shellFilterOverlay.close()"
|
|
||||||
></page-article-search-filter>
|
|
||||||
</shell-filter-overlay>
|
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply flex flex-col w-full box-content relative;
|
@apply flex flex-col w-full h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)] box-content relative;
|
||||||
}
|
|
||||||
|
|
||||||
.filter {
|
|
||||||
@apply font-sans flex self-end items-center mb-4 font-bold bg-wild-blue-yonder border-0 text-regular py-px-8 px-px-15 rounded-filter justify-center z-sticky;
|
|
||||||
width: 106px;
|
|
||||||
min-width: 106px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
@apply ml-px-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
@apply bg-active-customer text-white ml-px-5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
import { BreadcrumbService } from '@core/breadcrumb';
|
||||||
import { UiFilterAutocompleteProvider } from '@ui/filter';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { isEqual } from 'lodash';
|
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
|
||||||
import { filter, first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
|
||||||
import { ArticleSearchService } from './article-search.store';
|
import { ArticleSearchService } from './article-search.store';
|
||||||
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
||||||
import { ArticleSearchMainAutocompleteProvider } from './providers';
|
import { ArticleSearchMainAutocompleteProvider } from './providers';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
import { FilterAutocompleteProvider } from 'apps/shared/components/filter/src/lib';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
import { EnvironmentService } from '@core/environment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-article-search',
|
selector: 'page-article-search',
|
||||||
@@ -15,9 +17,8 @@ import { ArticleSearchMainAutocompleteProvider } from './providers';
|
|||||||
styleUrls: ['article-search.component.scss'],
|
styleUrls: ['article-search.component.scss'],
|
||||||
providers: [
|
providers: [
|
||||||
FocusSearchboxEvent,
|
FocusSearchboxEvent,
|
||||||
ArticleSearchService,
|
|
||||||
{
|
{
|
||||||
provide: UiFilterAutocompleteProvider,
|
provide: FilterAutocompleteProvider,
|
||||||
useClass: ArticleSearchMainAutocompleteProvider,
|
useClass: ArticleSearchMainAutocompleteProvider,
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
@@ -28,22 +29,16 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
|||||||
private _onDestroy$ = new Subject();
|
private _onDestroy$ = new Subject();
|
||||||
private _processId$: Observable<number>;
|
private _processId$: Observable<number>;
|
||||||
|
|
||||||
initialFilter$ = this._articleSearch.filter$.pipe(
|
get isTablet() {
|
||||||
filter((filter) => !!filter),
|
return this._environmentService.matchTablet();
|
||||||
first()
|
}
|
||||||
);
|
|
||||||
|
|
||||||
hasFilter$ = this._articleSearch.filter$.pipe(
|
|
||||||
withLatestFrom(this.initialFilter$),
|
|
||||||
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
|
|
||||||
);
|
|
||||||
|
|
||||||
filterActive$ = new BehaviorSubject<boolean>(false);
|
|
||||||
constructor(
|
constructor(
|
||||||
private _breadcrumb: BreadcrumbService,
|
private _breadcrumb: BreadcrumbService,
|
||||||
private _router: Router,
|
|
||||||
private _articleSearch: ArticleSearchService,
|
private _articleSearch: ArticleSearchService,
|
||||||
private _activatedRoute: ActivatedRoute
|
private _activatedRoute: ActivatedRoute,
|
||||||
|
private _navigationService: ProductCatalogNavigationService,
|
||||||
|
private _environmentService: EnvironmentService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -60,12 +55,17 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||||
.subscribe(([state, processId]) => {
|
.subscribe(([state, processId]) => {
|
||||||
if (state.searchState === '') {
|
if (state.searchState === '') {
|
||||||
|
const params = state.filter.getQueryParams();
|
||||||
if (state.hits === 1) {
|
if (state.hits === 1) {
|
||||||
const item = state.items.find((f) => f);
|
const item = state.items.find((f) => f);
|
||||||
this._router.navigate(['/kunde', processId, 'product', 'details', item.id]);
|
this._navigationService.navigateToDetails({
|
||||||
|
processId,
|
||||||
|
itemId: item.id,
|
||||||
|
queryParams: this.isTablet ? undefined : params,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const params = state.filter.getQueryParams();
|
this._navigationService.navigateToResults({
|
||||||
this._router.navigate(['/kunde', processId, 'product', 'search', 'results'], {
|
processId,
|
||||||
queryParams: params,
|
queryParams: params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -73,6 +73,20 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||||
|
const clean = { ...params };
|
||||||
|
|
||||||
|
for (const key in clean) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(clean, key)) {
|
||||||
|
if (clean[key] == undefined) {
|
||||||
|
delete clean[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
initProcessId() {
|
initProcessId() {
|
||||||
this._processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
this._processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||||
}
|
}
|
||||||
@@ -83,8 +97,7 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resetFilter(queryParams: Record<string, string>) {
|
resetFilter(queryParams: Record<string, string>) {
|
||||||
const currentQueryParams = this._articleSearch.filter?.getQueryParams();
|
if (Object.keys(queryParams).length === 0) {
|
||||||
if (!isEqual(currentQueryParams, queryParams)) {
|
|
||||||
this._articleSearch.resetFilter();
|
this._articleSearch.resetFilter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +110,7 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
|||||||
await this._breadcrumb.addBreadcrumbIfNotExists({
|
await this._breadcrumb.addBreadcrumbIfNotExists({
|
||||||
key: processId,
|
key: processId,
|
||||||
name: 'Artikelsuche',
|
name: 'Artikelsuche',
|
||||||
path: `/kunde/${processId}/product`,
|
path: this._navigationService.getArticleSearchBasePath(processId),
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
tags: ['catalog', 'main'],
|
tags: ['catalog', 'main'],
|
||||||
section: 'customer',
|
section: 'customer',
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import { ArticleSearchComponent } from './article-search.component';
|
|||||||
import { SearchResultsModule } from './search-results/search-results.module';
|
import { SearchResultsModule } from './search-results/search-results.module';
|
||||||
import { SearchMainModule } from './search-main/search-main.module';
|
import { SearchMainModule } from './search-main/search-main.module';
|
||||||
import { SearchFilterModule } from './search-filter/search-filter.module';
|
import { SearchFilterModule } from './search-filter/search-filter.module';
|
||||||
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
|
import { ArticleSearchService } from './article-search.store';
|
||||||
|
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, ShellFilterOverlayModule],
|
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, SharedFilterOverlayModule],
|
||||||
exports: [ArticleSearchComponent],
|
exports: [ArticleSearchComponent],
|
||||||
declarations: [ArticleSearchComponent],
|
declarations: [ArticleSearchComponent],
|
||||||
providers: [],
|
providers: [ArticleSearchService],
|
||||||
})
|
})
|
||||||
export class ArticleSearchModule {}
|
export class ArticleSearchModule {}
|
||||||
|
|||||||
@@ -3,24 +3,31 @@ import { DomainCatalogService } from '@domain/catalog';
|
|||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
import { UiFilter } from '@ui/filter';
|
|
||||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||||
import { ItemDTO, QueryTokenDTO } from '@swagger/cat';
|
import { ItemDTO, QueryTokenDTO, UISettingsDTO } from '@swagger/cat';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
import { BranchDTO } from '@swagger/checkout';
|
import { BranchDTO } from '@swagger/checkout';
|
||||||
|
import { Filter } from 'apps/shared/components/filter/src/lib';
|
||||||
|
|
||||||
export interface ArticleSearchState {
|
export interface ArticleSearchState {
|
||||||
processId: number;
|
processId: number;
|
||||||
filter: UiFilter;
|
filter: Filter;
|
||||||
searchState: '' | 'fetching' | 'empty' | 'error';
|
searchState: '' | 'fetching' | 'empty' | 'error';
|
||||||
items: ItemDTO[];
|
items: ItemDTO[];
|
||||||
hits: number;
|
hits: number;
|
||||||
selectedBranch: BranchDTO;
|
selectedBranch: BranchDTO;
|
||||||
selectedItemIds: number[];
|
selectedItemIds: number[];
|
||||||
|
defaultSettings?: UISettingsDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||||
|
get defaultSettings() {
|
||||||
|
return this.get((s) => s.defaultSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly defaultSettings$ = this.select((s) => s.defaultSettings);
|
||||||
|
|
||||||
get processId() {
|
get processId() {
|
||||||
return this.get((s) => s.processId);
|
return this.get((s) => s.processId);
|
||||||
}
|
}
|
||||||
@@ -100,19 +107,19 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
|
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
|
||||||
const filter = await this.catalog
|
const defaultSettings = await this.catalog.getSettings().toPromise();
|
||||||
.getSettings()
|
|
||||||
.pipe(map((settings) => UiFilter.create(settings)))
|
const filter = Filter.create(defaultSettings);
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (!!defaultQueryParams) {
|
if (!!defaultQueryParams) {
|
||||||
filter?.fromQueryParams(defaultQueryParams);
|
filter?.fromQueryParams(defaultQueryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setFilter(filter);
|
this.setFilter(filter);
|
||||||
|
this.patchState({ defaultSettings });
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilter(filter: UiFilter) {
|
setFilter(filter: Filter) {
|
||||||
this.patchState({ filter });
|
this.patchState({ filter });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DomainCatalogService } from '@domain/catalog';
|
import { DomainCatalogService } from '@domain/catalog';
|
||||||
import { UiFilterAutocomplete, UiFilterAutocompleteProvider, UiInput } from '@ui/filter';
|
import { FilterAutocomplete, FilterAutocompleteProvider, FilterInput } from 'apps/shared/components/filter/src/lib';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ArticleSearchMainAutocompleteProvider extends UiFilterAutocompleteProvider {
|
export class ArticleSearchMainAutocompleteProvider extends FilterAutocompleteProvider {
|
||||||
for = 'catalog';
|
for = 'catalog';
|
||||||
|
|
||||||
constructor(private domainCatalogSearch: DomainCatalogService) {
|
constructor(private domainCatalogSearch: DomainCatalogService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(input: UiInput): Observable<UiFilterAutocomplete[]> {
|
complete(input: FilterInput): Observable<FilterAutocomplete[]> {
|
||||||
const token = input?.parent?.parent?.getQueryToken();
|
const token = input?.parent?.parent?.getQueryToken();
|
||||||
const filter = token?.filter;
|
const filter = token?.filter;
|
||||||
const type = Object.keys(token?.input).join(';');
|
const type = Object.keys(token?.input).join(';');
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
<ng-container *ngIf="filter$ | async; let filter">
|
<ng-container *ngIf="filter$ | async; let filter">
|
||||||
<div class="catalog-search-filter-content">
|
<div class="catalog-search-filter-content">
|
||||||
<button class="btn-close" type="button" (click)="close.emit()">
|
<div class="w-full flex flex-row justify-end items-center">
|
||||||
<ui-icon icon="close" size="20px"></ui-icon>
|
<button (click)="clearFilter(filter)" class="text-[#0556B4] mr-[0.8125rem]">Alle Filter entfernen</button>
|
||||||
</button>
|
<button class="text-black p-4 outline-none border-none bg-transparent" type="button" (click)="closeFilter()">
|
||||||
|
<ui-icon icon="close" size="15px"></ui-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="catalog-search-filter-content-main">
|
<div class="catalog-search-filter-content-main -mt-12">
|
||||||
<h1 class="text-3xl font-bold text-center py-4">Filter</h1>
|
<h1 class="text-2xl text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||||
<ui-filter
|
<shared-filter
|
||||||
[filter]="filter"
|
[filter]="filter"
|
||||||
[loading]="fetching$ | async"
|
[loading]="fetching$ | async"
|
||||||
(search)="applyFilter(filter)"
|
(search)="applyFilter(filter)"
|
||||||
[hint]="searchboxHint$ | async"
|
[hint]="searchboxHint$ | async"
|
||||||
resizeInputOptionsToElement="page-article-search-filter .cta-wrapper"
|
|
||||||
[scanner]="true"
|
[scanner]="true"
|
||||||
></ui-filter>
|
></shared-filter>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cta-wrapper">
|
||||||
|
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
||||||
|
Filter zurücksetzen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="fetching$ | async">
|
||||||
|
<ui-spinner [show]="fetching$ | async">
|
||||||
|
Filter anwenden
|
||||||
|
</ui-spinner>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cta-wrapper">
|
|
||||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
|
|
||||||
Filter zurücksetzen
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="fetching$ | async">
|
|
||||||
<ui-spinner [show]="fetching$ | async">
|
|
||||||
Filter anwenden
|
|
||||||
</ui-spinner>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply block bg-glitter;
|
@apply block bg-white h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-search-filter-content {
|
.catalog-search-filter-content {
|
||||||
@apply relative mx-auto p-4;
|
@apply relative mx-auto p-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-close {
|
|
||||||
@apply absolute text-cool-grey top-3 p-4 right-4 outline-none border-none bg-transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-search-filter-content-main {
|
.catalog-search-filter-content-main {
|
||||||
h1.title {
|
h1.title {
|
||||||
@apply text-center;
|
@apply text-center;
|
||||||
@@ -17,14 +13,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cta-wrapper {
|
.cta-wrapper {
|
||||||
@apply fixed bottom-8 whitespace-nowrap;
|
@apply text-center whitespace-nowrap absolute bottom-8 left-0 w-full;
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-reset-filter,
|
.cta-reset-filter,
|
||||||
.cta-apply-filter {
|
.cta-apply-filter {
|
||||||
@apply text-lg font-bold px-6 py-3 rounded-full border-solid border-2 border-brand outline-none mx-2;
|
@apply text-lg font-bold px-6 py-[0.85rem] rounded-full border-solid border-2 border-brand outline-none mx-2;
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
|
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
|
||||||
@@ -38,3 +32,7 @@
|
|||||||
.cta-apply-filter {
|
.cta-apply-filter {
|
||||||
@apply text-white bg-brand;
|
@apply text-white bg-brand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep page-article-search-filter shared-filter shared-filter-input-group-main {
|
||||||
|
@apply desktop:hidden px-16;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
import { UiFilter, UiFilterComponent } from '@ui/filter';
|
import { ApplicationService } from '@core/application';
|
||||||
import { Observable } from 'rxjs';
|
import { EnvironmentService } from '@core/environment';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
import { first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||||
import { ArticleSearchService } from '../article-search.store';
|
import { ArticleSearchService } from '../article-search.store';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
import { Filter, FilterComponent } from 'apps/shared/components/filter/src/lib';
|
||||||
|
import { BreadcrumbService } from '@core/breadcrumb';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-article-search-filter',
|
selector: 'page-article-search-filter',
|
||||||
@@ -10,51 +15,104 @@ import { ArticleSearchService } from '../article-search.store';
|
|||||||
styleUrls: ['search-filter.component.scss'],
|
styleUrls: ['search-filter.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ArticleSearchFilterComponent implements OnInit {
|
export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||||
@Output()
|
@Output()
|
||||||
close = new EventEmitter();
|
close = new EventEmitter();
|
||||||
|
|
||||||
|
_processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||||
|
|
||||||
fetching$: Observable<boolean>;
|
fetching$: Observable<boolean>;
|
||||||
|
|
||||||
filter$: Observable<UiFilter>;
|
filter$: Observable<Filter>;
|
||||||
|
|
||||||
searchboxHint$ = this.articleSearch.searchboxHint$;
|
searchboxHint$ = this.articleSearch.searchboxHint$;
|
||||||
|
|
||||||
@ViewChild(UiFilterComponent, { static: false })
|
@ViewChild(FilterComponent, { static: false })
|
||||||
uiFilterComponent: UiFilterComponent;
|
uiFilterComponent: FilterComponent;
|
||||||
|
|
||||||
constructor(private articleSearch: ArticleSearchService) {}
|
get isTablet() {
|
||||||
|
return this._environment.matchTablet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDestroy$ = new Subject();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private articleSearch: ArticleSearchService,
|
||||||
|
private _environment: EnvironmentService,
|
||||||
|
private _activatedRoute: ActivatedRoute,
|
||||||
|
public application: ApplicationService,
|
||||||
|
private _navigationService: ProductCatalogNavigationService,
|
||||||
|
private _breadcrumb: BreadcrumbService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.fetching$ = this.articleSearch.fetching$;
|
this.fetching$ = this.articleSearch.fetching$;
|
||||||
this.filter$ = this.articleSearch.filter$.pipe(
|
this.filter$ = this.articleSearch.filter$.pipe(map((filter) => Filter.create(filter)));
|
||||||
map((filter) => UiFilter.create(filter))
|
|
||||||
// tap((filter) =>
|
|
||||||
// filter.fromQueryParams({
|
|
||||||
// main_qs: 'harry potter',
|
|
||||||
// filter_format: 'eb;!hc',
|
|
||||||
// filter_dbhwgr: '110;121',
|
|
||||||
// main_author: 'author',
|
|
||||||
// filter_region: '9780*|9781*;97884*',
|
|
||||||
// 'filter_reading-age': '1-10',
|
|
||||||
// main_stock: '1-',
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFilter(value: UiFilter) {
|
ngOnDestroy(): void {
|
||||||
|
this._onDestroy$.next();
|
||||||
|
this._onDestroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeFilter(): Promise<void> {
|
||||||
|
const processId = await this._processId$.pipe(first()).toPromise();
|
||||||
|
const itemId = this._navigationService.getOutletParams(this._activatedRoute)?.right?.id;
|
||||||
|
|
||||||
|
if (this.isTablet) {
|
||||||
|
return this.closeFilterTablet(processId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
if (this._navigationService.getOutletLocations(this._activatedRoute)?.left === 'search') {
|
||||||
|
return await this._navigationService.navigateToProductSearch({ processId, queryParamsHandling: 'preserve' });
|
||||||
|
}
|
||||||
|
return await this._navigationService.navigateToResults({ processId, queryParamsHandling: 'preserve' });
|
||||||
|
} else {
|
||||||
|
return await this._navigationService.navigateToDetails({ processId, itemId, queryParamsHandling: 'preserve' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeFilterTablet(processId: number): Promise<void> {
|
||||||
|
const latestBreadcrumb = await this._breadcrumb.getLastActivatedBreadcrumbByKey$(processId).pipe(first()).toPromise();
|
||||||
|
if (latestBreadcrumb?.tags?.find((tag) => tag === 'results')) {
|
||||||
|
return await this._navigationService.navigateToResults({ processId, queryParamsHandling: 'preserve' });
|
||||||
|
} else {
|
||||||
|
return await this._navigationService.navigateToProductSearch({ processId, queryParamsHandling: 'preserve' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFilter(value: Filter) {
|
||||||
this.uiFilterComponent?.cancelAutocomplete();
|
this.uiFilterComponent?.cancelAutocomplete();
|
||||||
this.articleSearch.setFilter(value);
|
this.articleSearch.setFilter(value);
|
||||||
this.articleSearch.search({ clear: true });
|
this.articleSearch.search({ clear: true });
|
||||||
this.articleSearch.searchCompleted.pipe(take(1)).subscribe((s) => {
|
this.articleSearch.searchCompleted
|
||||||
if (s.searchState === '') {
|
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||||
this.close.emit();
|
.subscribe(([state, processId]) => {
|
||||||
}
|
if (state.searchState === '') {
|
||||||
});
|
const params = state.filter.getQueryParams();
|
||||||
|
if (state.hits === 1 && this.isTablet) {
|
||||||
|
const item = state.items.find((f) => f);
|
||||||
|
this._navigationService.navigateToDetails({
|
||||||
|
processId,
|
||||||
|
itemId: item.id,
|
||||||
|
queryParams: this.isTablet ? undefined : params,
|
||||||
|
});
|
||||||
|
} else if (this.isTablet) {
|
||||||
|
this._navigationService.navigateToResults({
|
||||||
|
processId,
|
||||||
|
queryParams: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFilter(value: UiFilter) {
|
clearFilter(value: Filter) {
|
||||||
|
value.unselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFilter(value: Filter) {
|
||||||
const queryParams = { main_qs: value?.getQueryParams()?.main_qs || '' };
|
const queryParams = { main_qs: value?.getQueryParams()?.main_qs || '' };
|
||||||
this.articleSearch.setDefaultFilter(queryParams);
|
this.articleSearch.setDefaultFilter(queryParams);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { UiFilterNextModule } from '@ui/filter';
|
import { RouterModule } from '@angular/router';
|
||||||
import { UiIconModule } from '@ui/icon';
|
import { UiIconModule } from '@ui/icon';
|
||||||
import { UiSpinnerModule } from '@ui/spinner';
|
import { UiSpinnerModule } from '@ui/spinner';
|
||||||
|
|
||||||
import { ArticleSearchFilterComponent } from './search-filter.component';
|
import { ArticleSearchFilterComponent } from './search-filter.component';
|
||||||
|
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, UiFilterNextModule, UiIconModule, UiSpinnerModule],
|
imports: [CommonModule, RouterModule, FilterNextModule, UiIconModule, UiSpinnerModule],
|
||||||
exports: [ArticleSearchFilterComponent],
|
exports: [ArticleSearchFilterComponent],
|
||||||
declarations: [ArticleSearchFilterComponent],
|
declarations: [ArticleSearchFilterComponent],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -1,26 +1,50 @@
|
|||||||
<div class="card-search-article">
|
<div class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-full">
|
||||||
<h1 class="title">Artikelsuche</h1>
|
<h1 class="text-2xl text-[1.625rem] font-bold mb-[0.375rem]">Artikelsuche</h1>
|
||||||
<p class="info">
|
<p class="text-lg mb-10">
|
||||||
Welchen Artikel suchen Sie?
|
Welchen Artikel suchen Sie?
|
||||||
</p>
|
</p>
|
||||||
<ng-container *ngIf="filter$ | async; let filter">
|
<ng-container *ngIf="filter$ | async; let filter">
|
||||||
<ui-filter-filter-group-main [inputGroup]="filter?.filter | group: 'main'"></ui-filter-filter-group-main>
|
<shared-filter-filter-group-main
|
||||||
<ui-filter-input-group-main
|
class="mb-8 w-full"
|
||||||
[hint]="searchboxHint$ | async"
|
*ngIf="!(isDesktop$ | async)"
|
||||||
[loading]="fetching$ | async"
|
[inputGroup]="filter?.filter | group: 'main'"
|
||||||
[inputGroup]="filter?.input | group: 'main'"
|
></shared-filter-filter-group-main>
|
||||||
(search)="search(filter)"
|
<div class="flex flex-row px-12 justify-center desktop:px-0">
|
||||||
[showDescription]="false"
|
<shared-filter-input-group-main
|
||||||
[scanner]="true"
|
class="block w-full mr-3 desktop:mx-auto"
|
||||||
></ui-filter-input-group-main>
|
[hint]="searchboxHint$ | async"
|
||||||
|
[loading]="fetching$ | async"
|
||||||
|
[inputGroup]="filter?.input | group: 'main'"
|
||||||
|
(search)="search(filter)"
|
||||||
|
[showDescription]="false"
|
||||||
|
[scanner]="true"
|
||||||
|
></shared-filter-input-group-main>
|
||||||
|
<a
|
||||||
|
*ngIf="!(isDesktop$ | async)"
|
||||||
|
class="page-search-main__filter w-[6.75rem] h-14 rounded-card font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||||
|
[class.active]="hasFilter$ | async"
|
||||||
|
[routerLink]="filterRoute"
|
||||||
|
queryParamsHandling="preserve"
|
||||||
|
>
|
||||||
|
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||||
|
Filter
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="recent-searches-wrapper">
|
<div class="flex flex-col items-start ml-12 desktop:ml-8 py-6 bg-white">
|
||||||
<h3 class="recent-searches-header">Deine letzten Suchanfragen</h3>
|
<h3 class="text-sm font-bold mb-3">Deine letzten Suchanfragen</h3>
|
||||||
<ul>
|
<ul class="flex flex-col justify-start overflow-hidden items-start m-0 p-0 bg-white w-full">
|
||||||
<li class="recent-searches-items" *ngFor="let recentQuery of history$ | async">
|
<li class="list-none pb-3" *ngFor="let recentQuery of history$ | async">
|
||||||
<button (click)="setQueryHistory(filter, recentQuery.friendlyName)">
|
<button
|
||||||
<ui-icon icon="search" size="15px"></ui-icon>
|
class="flex flex-row items-center outline-none border-none bg-white text-black text-base m-0 p-0"
|
||||||
<p>{{ recentQuery.friendlyName }}</p>
|
(click)="setQueryHistory(filter, recentQuery.friendlyName)"
|
||||||
|
>
|
||||||
|
<ui-icon
|
||||||
|
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||||
|
icon="search"
|
||||||
|
size="0.875rem"
|
||||||
|
></ui-icon>
|
||||||
|
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ recentQuery.friendlyName }}</p>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,71 +1,13 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply flex flex-col box-border;
|
@apply flex flex-col box-border h-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.page-search-main__filter {
|
||||||
@apply text-page-heading font-bold;
|
&.active {
|
||||||
}
|
@apply bg-[#596470] text-white ml-px-5;
|
||||||
|
}
|
||||||
.info {
|
|
||||||
@apply text-2xl mt-1 mb-px-30;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-chips {
|
|
||||||
@apply flex flex-row justify-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-search-article {
|
|
||||||
@apply bg-white rounded p-4 text-center;
|
|
||||||
|
|
||||||
box-shadow: 0 -2px 24px 0 #dce2e9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-search-article {
|
|
||||||
min-height: calc(100vh - 380px);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui-filter-filter-group-main {
|
|
||||||
@apply mb-8 w-full;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep page-article-search-main ui-filter-filter-group-main .ui-filter-chip:not(.selected) {
|
::ng-deep page-article-search-main ui-filter-filter-group-main .ui-filter-chip:not(.selected) {
|
||||||
@apply bg-glitter;
|
@apply bg-glitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui-filter-input-group-main {
|
|
||||||
@apply block mx-auto;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-searches-wrapper {
|
|
||||||
@apply flex flex-col mx-auto items-start py-6 bg-white;
|
|
||||||
width: 50%;
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
.recent-searches-header {
|
|
||||||
@apply text-sm font-bold mb-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
@apply flex flex-col justify-start overflow-hidden items-start m-0 p-0 bg-white w-full;
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
.recent-searches-items {
|
|
||||||
@apply list-none pb-px-15;
|
|
||||||
|
|
||||||
button {
|
|
||||||
@apply flex flex-row items-center outline-none border-none bg-white text-black text-base m-0 p-0;
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply flex w-px-35 h-px-35 justify-center items-center mr-3 rounded-full text-black;
|
|
||||||
background-color: #e6eff9;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
@apply m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
import { BreadcrumbService } from '@core/breadcrumb';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
import { DomainCatalogService } from '@domain/catalog';
|
import { DomainCatalogService } from '@domain/catalog';
|
||||||
import { UiFilter, UiFilterInputGroupMainComponent } from '@ui/filter';
|
|
||||||
import { combineLatest, NEVER, Subscription } from 'rxjs';
|
import { combineLatest, NEVER, Subscription } from 'rxjs';
|
||||||
import { catchError, debounceTime, first, switchMap } from 'rxjs/operators';
|
import { catchError, debounceTime, first, switchMap, map, shareReplay } from 'rxjs/operators';
|
||||||
import { ArticleSearchService } from '../article-search.store';
|
import { ArticleSearchService } from '../article-search.store';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
import { EnvironmentService } from '@core/environment';
|
||||||
|
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-article-search-main',
|
selector: 'page-article-search-main',
|
||||||
@@ -26,15 +28,39 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
subscriptions = new Subscription();
|
subscriptions = new Subscription();
|
||||||
|
|
||||||
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
|
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
|
||||||
uiInputGroupMain: UiFilterInputGroupMainComponent;
|
map(([filter, defaultFilter]) => {
|
||||||
|
const filterQueryParams = filter?.getQueryParams();
|
||||||
|
return !isEqual(this.resetQueryParamsQueryAndOrderBy(filterQueryParams), Filter.create(defaultFilter).getQueryParams());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||||
|
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
|
||||||
|
|
||||||
|
get isDesktop$() {
|
||||||
|
return this._environment.matchDesktop$.pipe(
|
||||||
|
map((state) => state?.matches),
|
||||||
|
shareReplay()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get filterRoute() {
|
||||||
|
const itemId = this._navigationService?.getOutletParams(this.route)?.right?.id;
|
||||||
|
return this._navigationService.getArticleSearchResultsAndFilterPath({
|
||||||
|
processId: this.application.activatedProcessId,
|
||||||
|
itemId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private searchService: ArticleSearchService,
|
private searchService: ArticleSearchService,
|
||||||
private catalog: DomainCatalogService,
|
private catalog: DomainCatalogService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private application: ApplicationService,
|
private application: ApplicationService,
|
||||||
private breadcrumb: BreadcrumbService
|
private breadcrumb: BreadcrumbService,
|
||||||
|
private _environment: EnvironmentService,
|
||||||
|
private _navigationService: ProductCatalogNavigationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -44,7 +70,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe(async ([processId, queryParams]) => {
|
.subscribe(async ([processId, queryParams]) => {
|
||||||
const processChanged = processId !== this.searchService.processId;
|
const processChanged = processId !== this.searchService.processId;
|
||||||
|
|
||||||
if (!(this.searchService.filter instanceof UiFilter)) {
|
if (!(this.searchService.filter instanceof Filter)) {
|
||||||
await this.searchService.setDefaultFilter();
|
await this.searchService.setDefaultFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +83,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
|||||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||||
|
|
||||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||||
|
// Reset Filter on Product Search Shell Navigation click
|
||||||
await this.searchService.setDefaultFilter(queryParams);
|
await this.searchService.setDefaultFilter(queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,16 +112,30 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
|||||||
this.updateBreadcrumb(this.searchService.processId, this.searchService.filter.getQueryParams());
|
this.updateBreadcrumb(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
search(filter: UiFilter) {
|
search(filter: Filter) {
|
||||||
this.uiInputGroupMain.cancelAutocomplete();
|
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||||
this.searchService.setFilter(filter);
|
this.searchService.setFilter(filter);
|
||||||
this.searchService.search({ clear: true });
|
this.searchService.search({ clear: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
setQueryHistory(filter: UiFilter, query: string) {
|
setQueryHistory(filter: Filter, query: string) {
|
||||||
filter.fromQueryParams({ main_qs: query });
|
filter.fromQueryParams({ main_qs: query });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetQueryParamsQueryAndOrderBy(params: Record<string, string> = {}) {
|
||||||
|
const clean = { ...params };
|
||||||
|
|
||||||
|
for (const key in clean) {
|
||||||
|
if (key === 'main_qs' || key?.includes('order_by')) {
|
||||||
|
clean[key] = undefined;
|
||||||
|
} else if (key?.includes('order_by')) {
|
||||||
|
delete clean[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||||
const clean = { ...params };
|
const clean = { ...params };
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { UiFilterNextModule } from '@ui/filter';
|
|
||||||
import { UiIconModule } from '@ui/icon';
|
import { UiIconModule } from '@ui/icon';
|
||||||
import { ArticleSearchMainComponent } from './search-main.component';
|
import { ArticleSearchMainComponent } from './search-main.component';
|
||||||
|
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, UiIconModule, UiFilterNextModule],
|
imports: [CommonModule, RouterModule, UiIconModule, FilterNextModule],
|
||||||
exports: [ArticleSearchMainComponent],
|
exports: [ArticleSearchMainComponent],
|
||||||
declarations: [ArticleSearchMainComponent],
|
declarations: [ArticleSearchMainComponent],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
<div class="thumbnail animation"></div>
|
<ng-container *ngIf="!(mainOutletActive$ | async); else mainOutlet">
|
||||||
<div class="col">
|
<div class="bg-ucla-blue rounded-card w-[4.375rem] h-[5.625rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
<div class="author animation"></div>
|
<div class="flex flex-col flex-grow">
|
||||||
<div class="row">
|
<div class="h-4 bg-ucla-blue ml-4 mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
<div class="title animation"></div>
|
<div class="flex flex-row justify-between flex-grow">
|
||||||
<div class="price animation"></div>
|
<div class="h-6 bg-ucla-blue ml-4 w-[12.5rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-6 bg-ucla-blue ml-4 w-[4.6875rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow"></div>
|
||||||
|
<div class="flex flex-row justify-between flex-grow">
|
||||||
|
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue ml-4 w-[3.125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row justify-between flex-grow">
|
||||||
|
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space"></div>
|
</ng-container>
|
||||||
<div class="row">
|
|
||||||
<div class="category animation"></div>
|
<ng-template #mainOutlet>
|
||||||
<div class="stock animation"></div>
|
<div class="bg-ucla-blue rounded-card w-[3rem] h-[4.125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="flex flex-col ml-4 w-[30%]">
|
||||||
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-6 bg-ucla-blue mb-2 w-[12.5rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-6 bg-ucla-blue w-[12.5rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flex flex-col ml-14 w-[30%]">
|
||||||
<div class="manufacturer animation"></div>
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
<div class="ava animation"></div>
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex flex-col ml-10 w-[20%]">
|
||||||
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col ml-2 w-[20%] items-end">
|
||||||
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
<div class="h-4 bg-ucla-blue w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|||||||
@@ -1,61 +1,3 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply flex flex-row rounded-card bg-white mb-2 p-4;
|
@apply flex flex-row rounded-card bg-white mb-2 p-4 w-full h-[212px] desktop-small:h-[181px];
|
||||||
height: 187px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail {
|
|
||||||
width: 70px;
|
|
||||||
height: 90px;
|
|
||||||
@apply bg-ucla-blue rounded-card;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space {
|
|
||||||
@apply flex-grow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col {
|
|
||||||
@apply flex flex-col flex-grow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
@apply flex flex-row justify-between flex-grow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author {
|
|
||||||
width: 150px;
|
|
||||||
@apply h-4 bg-ucla-blue ml-4 mb-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
width: 300px;
|
|
||||||
@apply h-6 bg-ucla-blue ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
width: 100px;
|
|
||||||
@apply h-6 bg-ucla-blue ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category {
|
|
||||||
width: 200px;
|
|
||||||
@apply h-4 bg-ucla-blue ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.manufacturer {
|
|
||||||
width: 200px;
|
|
||||||
@apply h-4 bg-ucla-blue ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock {
|
|
||||||
width: 75px;
|
|
||||||
@apply h-4 bg-ucla-blue ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ava {
|
|
||||||
width: 150px;
|
|
||||||
@apply h-4 bg-ucla-blue ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animation {
|
|
||||||
animation: load 1s linear infinite;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, HostBinding } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
import { shareReplay } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-search-result-item-loading',
|
selector: 'page-search-result-item-loading',
|
||||||
@@ -7,5 +10,13 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class SearchResultItemLoadingComponent {
|
export class SearchResultItemLoadingComponent {
|
||||||
constructor() {}
|
get mainOutletActive$() {
|
||||||
|
return this._navigationService?.mainOutletActive$(this._activatedRoute).pipe(shareReplay());
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private _navigationService: ProductCatalogNavigationService, private _activatedRoute: ActivatedRoute) {}
|
||||||
|
|
||||||
|
@HostBinding('style') get class() {
|
||||||
|
return this._navigationService.mainOutletActive(this._activatedRoute) ? { height: '6.125rem' } : '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,122 @@
|
|||||||
<a class="product-list-result-content" [routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', item?.id]">
|
<a
|
||||||
<div class="item-thumbnail">
|
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded-card"
|
||||||
<img loading="lazy" *ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
[class.page-search-result-item__item-card-main]="mainOutletActive$ | async"
|
||||||
</div>
|
[routerLink]="detailsPath"
|
||||||
|
[routerLinkActive]="!isTablet && !(mainOutletActive$ | async) ? 'active' : ''"
|
||||||
<div class="item-contributors">
|
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||||
<a
|
>
|
||||||
*ngFor="let contributor of contributors; let last = last"
|
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
|
||||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search', 'results']"
|
<img
|
||||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
class="page-search-result-item__item-image w-[50px] h-[79px]"
|
||||||
(click)="$event?.stopPropagation()"
|
loading="lazy"
|
||||||
>
|
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||||
{{ contributor }}{{ last ? '' : ';' }}
|
[src]="thumbnailUrl"
|
||||||
</a>
|
[alt]="item?.product?.name"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="item-title"
|
class="page-search-result-item__item-grid-container"
|
||||||
[class.xl]="item?.product?.name?.length >= 35"
|
[class.page-search-result-item__item-grid-container-main]="mainOutletActive$ | async"
|
||||||
[class.lg]="item?.product?.name?.length >= 40"
|
|
||||||
[class.md]="item?.product?.name?.length >= 50"
|
|
||||||
[class.sm]="item?.product?.name?.length >= 60"
|
|
||||||
[class.xs]="item?.product?.name?.length >= 100"
|
|
||||||
>
|
>
|
||||||
{{ item?.product?.name }}
|
<div
|
||||||
</div>
|
class="page-search-result-item__item-contributors desktop-small:text-sm font-bold text-[#0556B4] text-ellipsis overflow-hidden max-w-[24rem] whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
*ngFor="let contributor of contributors; let last = last"
|
||||||
|
[routerLink]="resultsPath"
|
||||||
|
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||||
|
(click)="$event?.stopPropagation()"
|
||||||
|
>
|
||||||
|
{{ contributor }}{{ last ? '' : ';' }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item-price">
|
<div
|
||||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
class="page-search-result-item__item-title font-bold text-2xl"
|
||||||
</div>
|
[class.text-xl]="item?.product?.name?.length >= 35 && isTablet"
|
||||||
|
[class.text-lg]="item?.product?.name?.length >= 40 && isTablet"
|
||||||
|
[class.text-md]="item?.product?.name?.length >= 50 && isTablet"
|
||||||
|
[class.text-sm]="item?.product?.name?.length >= 60 || !isTablet"
|
||||||
|
[class.text-xs]="item?.product?.name?.length >= 100 || (!isTablet && item?.product?.name?.length >= 70)"
|
||||||
|
>
|
||||||
|
{{ item?.product?.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="selectable" class="item-data-selector">
|
<div class="page-search-result-item__item-format desktop-small:text-sm">
|
||||||
<ui-select-bullet [ngModel]="selected" (ngModelChange)="setSelected($event)"></ui-select-bullet>
|
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
|
||||||
</div>
|
<img
|
||||||
|
class="mr-3"
|
||||||
<div class="item-stock z-dropdown" [uiOverlayTrigger]="tooltip" [overlayTriggerDisabled]="!(stockTooltipText$ | async)">
|
*ngIf="item?.product?.format !== '--'"
|
||||||
<ng-container *ngIf="isOrderBranch$ | async">
|
loading="lazy"
|
||||||
<div class="flex flex-row items-center justify-between">
|
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||||
<ui-icon icon="home" size="1em"></ui-icon>
|
[alt]="item?.product?.formatDetail"
|
||||||
<span
|
/>
|
||||||
*ngIf="inStock$ | async; let stock"
|
{{ item?.product?.formatDetail | substr: 25 }}
|
||||||
[class.skeleton]="stock.inStock === undefined"
|
|
||||||
class="min-w-[1rem] text-right inline-block"
|
|
||||||
>{{ stock?.inStock }}</span
|
|
||||||
>
|
|
||||||
<span>x</span>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
|
||||||
<div class="flex flex-row items-center justify-between z-dropdown">
|
|
||||||
<ui-icon class="block" icon="home" size="1em"></ui-icon>
|
|
||||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
|
||||||
<span>x</span>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
|
||||||
{{ stockTooltipText$ | async }}
|
|
||||||
</ui-tooltip>
|
|
||||||
<!-- <div class="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div> -->
|
|
||||||
|
|
||||||
<div class="item-ssc" [class.xs]="item?.catalogAvailability?.sscText?.length >= 60">
|
<div class="page-search-result-item__item-manufacturer desktop-small:text-sm">
|
||||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
<div class="page-search-result-item__item-misc desktop-small:text-sm">
|
||||||
<img
|
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||||
*ngIf="item?.product?.format !== '--'"
|
{{ publicationDate }}
|
||||||
loading="lazy"
|
</div>
|
||||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
|
||||||
[alt]="item?.product?.formatDetail"
|
|
||||||
/>
|
|
||||||
{{ item?.product?.formatDetail }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-misc">
|
<div class="page-search-result-item__item-price desktop-small:text-sm font-bold justify-self-end">
|
||||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
</div>
|
||||||
{{ publicationDate }}
|
|
||||||
|
<div class="page-search-result-item__item-select-bullet justify-self-end">
|
||||||
|
<input
|
||||||
|
*ngIf="selectable"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
[ngModel]="selected$ | async"
|
||||||
|
(ngModelChange)="setSelected()"
|
||||||
|
class="isa-select-bullet"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="page-search-result-item__item-stock desktop-small:text-sm font-bold z-dropdown justify-self-start"
|
||||||
|
[class.justify-self-end]="!(mainOutletActive$ | async)"
|
||||||
|
[uiOverlayTrigger]="tooltip"
|
||||||
|
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||||
|
>
|
||||||
|
<ng-container *ngIf="isOrderBranch$ | async">
|
||||||
|
<div class="flex flex-row items-center justify-between">
|
||||||
|
<ui-icon icon="home" size="1em"></ui-icon>
|
||||||
|
<span
|
||||||
|
*ngIf="inStock$ | async; let stock"
|
||||||
|
[class.skeleton]="stock.inStock === undefined"
|
||||||
|
class="min-w-[1rem] text-right inline-block"
|
||||||
|
>{{ stock?.inStock }}</span
|
||||||
|
>
|
||||||
|
<span>x</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
||||||
|
<div class="flex flex-row items-center justify-between z-dropdown">
|
||||||
|
<ui-icon class="block" icon="home" size="1em"></ui-icon>
|
||||||
|
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||||
|
<span>x</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-8" [closeable]="true">
|
||||||
|
{{ stockTooltipText$ | async }}
|
||||||
|
</ui-tooltip>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="page-search-result-item__item-ssc desktop-small:text-sm justify-self-start"
|
||||||
|
[class.justify-self-end]="!(mainOutletActive$ | async)"
|
||||||
|
[class.xs]="item?.catalogAvailability?.sscText?.length >= 60"
|
||||||
|
>
|
||||||
|
<strong>{{ item?.catalogAvailability?.ssc }}</strong> -
|
||||||
|
{{ !isTablet ? item?.catalogAvailability?.sscText : (item?.catalogAvailability?.sscText | substr: 18) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,113 +1,90 @@
|
|||||||
.product-list-result-content {
|
:host {
|
||||||
@apply text-black no-underline grid;
|
@apply flex flex-col w-full h-[13.25rem] desktop-small:h-[11.3125rem];
|
||||||
grid-template-columns: 102px 50% auto;
|
}
|
||||||
grid-template-rows: auto;
|
|
||||||
|
.page-search-result-item__item-card {
|
||||||
|
@apply grid grid-flow-col;
|
||||||
|
grid-template-columns: 3.9375rem auto;
|
||||||
|
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-search-result-item__item-grid-container {
|
||||||
|
@apply grid grid-flow-row gap-[0.375rem];
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'item-thumbnail item-contributors item-contributors'
|
'contributors contributors contributors'
|
||||||
'item-thumbnail item-title item-price'
|
'title title price'
|
||||||
'item-thumbnail item-title item-data-selector'
|
'title title price'
|
||||||
'item-thumbnail item-format item-stock'
|
'title title select'
|
||||||
'item-thumbnail item-misc item-ssc';
|
'format format select'
|
||||||
|
'manufacturer manufacturer stock'
|
||||||
|
'misc ssc ssc';
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-thumbnail {
|
.page-search-result-item__item-grid-container-main {
|
||||||
grid-area: item-thumbnail;
|
@apply gap-x-4;
|
||||||
width: 70px;
|
grid-template-rows: 1.3125rem 1.3125rem auto;
|
||||||
@apply mr-8;
|
grid-template-columns: 30% 30% 20% 8.8% auto;
|
||||||
img {
|
grid-template-areas:
|
||||||
max-width: 100%;
|
'contributors format stock price .'
|
||||||
max-height: 150px;
|
'title manufacturer ssc . select'
|
||||||
@apply rounded-card shadow-cta;
|
'title misc . . .';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.page-search-result-item__item-contributors {
|
||||||
.item-contributors {
|
grid-area: contributors;
|
||||||
grid-area: item-contributors;
|
}
|
||||||
height: 22px;
|
|
||||||
text-overflow: ellipsis;
|
.page-search-result-item__item-price {
|
||||||
overflow: hidden;
|
grid-area: price;
|
||||||
max-width: 600px;
|
}
|
||||||
white-space: nowrap;
|
|
||||||
|
.page-search-result-item__item-title {
|
||||||
a {
|
grid-area: title;
|
||||||
@apply text-active-customer font-bold no-underline;
|
}
|
||||||
}
|
|
||||||
}
|
.page-search-result-item__item-format {
|
||||||
|
grid-area: format;
|
||||||
.item-title {
|
}
|
||||||
grid-area: item-title;
|
|
||||||
@apply font-bold text-2xl;
|
.page-search-result-item__item-manufacturer {
|
||||||
height: 64px;
|
grid-area: manufacturer;
|
||||||
max-height: 64px;
|
}
|
||||||
}
|
|
||||||
|
.page-search-result-item__item-misc {
|
||||||
.item-title.xl {
|
grid-area: misc;
|
||||||
@apply font-bold text-xl;
|
}
|
||||||
}
|
|
||||||
|
.page-search-result-item__item-select-bullet {
|
||||||
.item-title.lg {
|
grid-area: select;
|
||||||
@apply font-bold text-lg;
|
}
|
||||||
}
|
|
||||||
|
.page-search-result-item__item-stock {
|
||||||
.item-title.md {
|
grid-area: stock;
|
||||||
@apply font-bold text-base;
|
}
|
||||||
}
|
|
||||||
|
.page-search-result-item__item-ssc {
|
||||||
.item-title.sm {
|
grid-area: ssc;
|
||||||
@apply font-bold text-sm;
|
}
|
||||||
}
|
|
||||||
|
.page-search-result-item__item-image {
|
||||||
.item-title.xs {
|
box-shadow: 0px 6px 18px rgba(0, 0, 0, 0.197935);
|
||||||
@apply font-bold text-xs;
|
}
|
||||||
}
|
|
||||||
|
.active,
|
||||||
.item-price {
|
.hover:hover {
|
||||||
grid-area: item-price;
|
@apply bg-[#D8DFE5] border border-solid border-[#0556B4];
|
||||||
@apply font-bold text-xl text-right;
|
|
||||||
}
|
.page-search-result-item__item-select-bullet {
|
||||||
|
.isa-select-bullet::before {
|
||||||
.item-format {
|
@apply bg-[#fff];
|
||||||
grid-area: item-format;
|
}
|
||||||
@apply flex flex-row items-center font-bold text-lg whitespace-nowrap;
|
|
||||||
|
.isa-select-bullet:checked:before {
|
||||||
img {
|
@apply bg-[#596470];
|
||||||
@apply mr-2;
|
}
|
||||||
}
|
|
||||||
}
|
.isa-select-bullet:hover::before {
|
||||||
|
@apply bg-[#778490];
|
||||||
.item-stock {
|
}
|
||||||
grid-area: item-stock;
|
|
||||||
@apply flex flex-row justify-end items-baseline font-bold text-lg;
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply text-active-customer mr-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-misc {
|
|
||||||
grid-area: item-misc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-ssc {
|
|
||||||
grid-area: item-ssc;
|
|
||||||
@apply font-bold text-right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-ssc.xs {
|
|
||||||
@apply font-bold text-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-data-selector {
|
|
||||||
@apply w-full flex justify-end;
|
|
||||||
grid-area: item-data-selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui-select-bullet {
|
|
||||||
@apply p-4 -m-4 z-dropdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1025px) {
|
|
||||||
.item-contributors {
|
|
||||||
max-width: 780px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { DatePipe } from '@angular/common';
|
import { DatePipe } from '@angular/common';
|
||||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostListener, HostBinding } from '@angular/core';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
|
import { EnvironmentService } from '@core/environment';
|
||||||
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
||||||
import { ComponentStore } from '@ngrx/component-store';
|
import { ComponentStore } from '@ngrx/component-store';
|
||||||
import { ItemDTO } from '@swagger/cat';
|
import { ItemDTO } from '@swagger/cat';
|
||||||
import { DateAdapter } from '@ui/common';
|
import { DateAdapter } from '@ui/common';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { combineLatest } from 'rxjs';
|
import { combineLatest } from 'rxjs';
|
||||||
import { debounceTime, switchMap, map, tap, shareReplay } from 'rxjs/operators';
|
import { debounceTime, switchMap, map, shareReplay, filter, first } from 'rxjs/operators';
|
||||||
import { ArticleSearchService } from '../article-search.store';
|
import { ArticleSearchService } from '../article-search.store';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
export interface SearchResultItemComponentState {
|
export interface SearchResultItemComponentState {
|
||||||
item?: ItemDTO;
|
item?: ItemDTO;
|
||||||
@@ -36,16 +39,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
|||||||
|
|
||||||
readonly item$ = this.select((s) => s.item);
|
readonly item$ = this.select((s) => s.item);
|
||||||
|
|
||||||
@Input()
|
selected$ = this._articleSearchService.selectedItemIds$.pipe(map((selectedItemIds) => selectedItemIds.includes(this.item?.id)));
|
||||||
get selected() {
|
|
||||||
return this.get((s) => s.selected);
|
|
||||||
}
|
|
||||||
set selected(selected: boolean) {
|
|
||||||
if (this.selected !== selected) {
|
|
||||||
this.patchState({ selected });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readonly selected$ = this.select((s) => s.selected);
|
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
get selectable() {
|
get selectable() {
|
||||||
@@ -58,7 +52,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
selectedChange = new EventEmitter<boolean>();
|
selectedChange = new EventEmitter<ItemDTO>();
|
||||||
|
|
||||||
get contributors() {
|
get contributors() {
|
||||||
return this.item?.product?.contributors?.split(';').map((val) => val.trim());
|
return this.item?.product?.contributors?.split(';').map((val) => val.trim());
|
||||||
@@ -77,6 +71,22 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isTablet() {
|
||||||
|
return this._environment.matchTablet();
|
||||||
|
}
|
||||||
|
|
||||||
|
get detailsPath() {
|
||||||
|
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
get resultsPath() {
|
||||||
|
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mainOutletActive$() {
|
||||||
|
return this._navigationService?.mainOutletActive$(this._activatedRoute).pipe(shareReplay());
|
||||||
|
}
|
||||||
|
|
||||||
defaultBranch$ = this._availability.getDefaultBranch();
|
defaultBranch$ = this._availability.getDefaultBranch();
|
||||||
|
|
||||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||||
@@ -110,9 +120,11 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
|||||||
|
|
||||||
inStock$ = combineLatest([this.item$, this.selectedBranchId$, this.defaultBranch$]).pipe(
|
inStock$ = combineLatest([this.item$, this.selectedBranchId$, this.defaultBranch$]).pipe(
|
||||||
debounceTime(100),
|
debounceTime(100),
|
||||||
|
filter(([item, branch, defaultBranch]) => !!item && !!defaultBranch),
|
||||||
switchMap(([item, branch, defaultBranch]) =>
|
switchMap(([item, branch, defaultBranch]) =>
|
||||||
this._stockService.getInStock$({ itemId: item.id, branchId: branch?.id ?? defaultBranch?.id })
|
this._stockService.getInStock$({ itemId: item.id, branchId: branch?.id ?? defaultBranch?.id })
|
||||||
)
|
),
|
||||||
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -121,7 +133,10 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
|||||||
private _articleSearchService: ArticleSearchService,
|
private _articleSearchService: ArticleSearchService,
|
||||||
public applicationService: ApplicationService,
|
public applicationService: ApplicationService,
|
||||||
private _stockService: DomainInStockService,
|
private _stockService: DomainInStockService,
|
||||||
private _availability: DomainAvailabilityService
|
private _availability: DomainAvailabilityService,
|
||||||
|
private _environment: EnvironmentService,
|
||||||
|
private _navigationService: ProductCatalogNavigationService,
|
||||||
|
private _activatedRoute: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
selected: false,
|
selected: false,
|
||||||
@@ -129,7 +144,16 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelected(selected: boolean) {
|
setSelected() {
|
||||||
this._articleSearchService.setSelected({ selected, itemId: this.item?.id });
|
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
|
||||||
|
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
|
||||||
|
|
||||||
|
if (!this.isTablet) {
|
||||||
|
this.selectedChange.emit(this.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('style') get class() {
|
||||||
|
return this._navigationService.mainOutletActive(this._activatedRoute) ? { height: '6.125rem' } : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,77 @@
|
|||||||
<div class="filter-wrapper">
|
<div
|
||||||
<div class="hits" *ngIf="hits$ | async; let hits">{{ hits }} Titel</div>
|
class="page-search-results__header bg-background-liste flex items-end justify-between"
|
||||||
<ui-order-by-filter [orderBy]="(filter$ | async)?.orderBy" (selectedOrderByChange)="search(); updateBreadcrumbs()"> </ui-order-by-filter>
|
[class.pb-4]="!(mainOutletActive$ | async)"
|
||||||
|
[class.flex-col]="!(mainOutletActive$ | async)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row w-full desktop-small:w-min" [class.desktop:w-full]="!(mainOutletActive$ | async)">
|
||||||
|
<shared-filter-input-group-main
|
||||||
|
*ngIf="filter$ | async; let filter"
|
||||||
|
class="block mr-3 w-full desktop-small:w-[23.5rem]"
|
||||||
|
[class.desktop:w-full]="!(mainOutletActive$ | async)"
|
||||||
|
[hint]="searchboxHint$ | async"
|
||||||
|
[loading]="fetching$ | async"
|
||||||
|
[inputGroup]="filter?.input | group: 'main'"
|
||||||
|
(search)="search(filter)"
|
||||||
|
[showDescription]="false"
|
||||||
|
[scanner]="true"
|
||||||
|
></shared-filter-input-group-main>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="page-search-results__filter w-[6.75rem] h-14 rounded-card font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||||
|
[class.active]="hasFilter$ | async"
|
||||||
|
[routerLink]="filterRoute"
|
||||||
|
queryParamsHandling="preserve"
|
||||||
|
>
|
||||||
|
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
|
||||||
|
Filter
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
*ngIf="hits$ | async; let hits"
|
||||||
|
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-sm"
|
||||||
|
[class.mb-4]="mainOutletActive$ | async"
|
||||||
|
>
|
||||||
|
{{ hits ??
|
||||||
|
0 }}
|
||||||
|
Titel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-search-results__order-by" [class.page-search-results__order-by-main]="mainOutletActive$ | async">
|
||||||
|
<shared-order-by-filter
|
||||||
|
[groupBy]="(mainOutletActive$ | async) ? [2, 2, 1, 1] : []"
|
||||||
|
[orderBy]="(filter$ | async)?.orderBy"
|
||||||
|
(selectedOrderByChange)="search(); updateBreadcrumbs()"
|
||||||
|
>
|
||||||
|
</shared-order-by-filter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<cdk-virtual-scroll-viewport
|
<cdk-virtual-scroll-viewport
|
||||||
#scrollContainer
|
#scrollContainer
|
||||||
class="product-list scroll-bar scroll-bar-margin"
|
class="product-list"
|
||||||
[itemSize]="187"
|
[itemSize]="(mainOutletActive$ | async) ? 187 : 98"
|
||||||
minBufferPx="1200"
|
minBufferPx="2800"
|
||||||
maxBufferPx="1200"
|
maxBufferPx="2800"
|
||||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||||
>
|
>
|
||||||
<div class="product-list-result" *cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId">
|
<search-result-item
|
||||||
<search-result-item
|
class="page-search-results__result-item"
|
||||||
[selected]="item | searchResultSelected: searchService.selectedItemIds"
|
[class.page-search-results__result-item-main]="mainOutletActive$ | async"
|
||||||
[selectable]="isSelectable(item)"
|
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||||
[item]="item"
|
(selectedChange)="addToCart($event)"
|
||||||
></search-result-item>
|
[selectable]="isSelectable(item)"
|
||||||
</div>
|
[item]="item"
|
||||||
|
></search-result-item>
|
||||||
<page-search-result-item-loading *ngIf="fetching$ | async"></page-search-result-item-loading>
|
<page-search-result-item-loading *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||||
</cdk-virtual-scroll-viewport>
|
</cdk-virtual-scroll-viewport>
|
||||||
|
|
||||||
<div class="actions">
|
<div *ngIf="isTablet" class="actions z-fixed">
|
||||||
<button
|
<button
|
||||||
[disabled]="loading$ | async"
|
[disabled]="loading$ | async"
|
||||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||||
class="cta-cart cta-action-primary"
|
class="cta-cart cta-action-primary"
|
||||||
(click)="addSelectedItemsToCart()"
|
(click)="addToCart()"
|
||||||
>
|
>
|
||||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply box-border grid;
|
@apply box-border grid h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||||
max-height: calc(100vh - 364px);
|
grid-template-rows: auto auto 1fr;
|
||||||
height: 100vh;
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-list {
|
.product-list {
|
||||||
@apply m-0 p-0 mt-2;
|
@apply m-0 p-0 mt-px-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-list-result {
|
.page-search-results__result-item {
|
||||||
@apply list-none bg-white rounded-card p-4 mb-2;
|
@apply mb-px-10;
|
||||||
height: 187px;
|
|
||||||
max-height: 187px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-wrapper {
|
.page-search-results__result-item-main {
|
||||||
@apply block relative;
|
@apply mb-[5px];
|
||||||
|
}
|
||||||
|
|
||||||
.hits {
|
.page-search-results__order-by {
|
||||||
@apply text-inactive-branch font-semibold absolute top-2 right-0;
|
@apply bg-white rounded-t-card px-6 desktop-small:px-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui-order-by-filter {
|
.page-search-results__order-by-main {
|
||||||
@apply mx-auto;
|
@apply pl-[4.9375rem] px-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-search-results__filter {
|
||||||
|
&.active {
|
||||||
|
@apply bg-[#596470] text-white ml-px-5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
@apply fixed bottom-28 inline-grid grid-flow-col gap-7;
|
@apply fixed bottom-16 inline-grid grid-flow-col gap-7;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|
||||||
@@ -44,3 +46,24 @@
|
|||||||
@apply bg-brand text-white;
|
@apply bg-brand text-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep page-search-results .page-search-results__order-by-main shared-order-by-filter {
|
||||||
|
@apply grid grid-flow-col justify-items-start gap-x-4 justify-start;
|
||||||
|
grid-template-columns: 30% 30% 20% 8.8% auto;
|
||||||
|
|
||||||
|
.group {
|
||||||
|
@apply desktop-small:justify-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group:last-child {
|
||||||
|
@apply justify-self-end;
|
||||||
|
|
||||||
|
.order-by-filter-button {
|
||||||
|
@apply ml-12 mr-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-by-filter-button {
|
||||||
|
@apply ml-0 mr-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ViewC
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
import { BreadcrumbService } from '@core/breadcrumb';
|
||||||
|
import { EnvironmentService } from '@core/environment';
|
||||||
import { DomainCheckoutService } from '@domain/checkout';
|
import { DomainCheckoutService } from '@domain/checkout';
|
||||||
import { ItemDTO } from '@swagger/cat';
|
import { ItemDTO } from '@swagger/cat';
|
||||||
import { AddToShoppingCartDTO } from '@swagger/checkout';
|
import { AddToShoppingCartDTO } from '@swagger/checkout';
|
||||||
import { UiFilter } from '@ui/filter';
|
|
||||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||||
import { CacheService } from 'apps/core/cache/src/public-api';
|
import { CacheService } from 'apps/core/cache/src/public-api';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
|
||||||
import { debounceTime, first, map, switchMap } from 'rxjs/operators';
|
import { debounceTime, first, map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||||
import { ArticleSearchService } from '../article-search.store';
|
import { ArticleSearchService } from '../article-search.store';
|
||||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||||
import { SearchResultItemComponent } from './search-result-item.component';
|
import { SearchResultItemComponent } from './search-result-item.component';
|
||||||
|
import { ProductCatalogNavigationService } from '@shared/services';
|
||||||
|
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-search-results',
|
selector: 'page-search-results',
|
||||||
@@ -27,6 +29,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild('scrollContainer', { static: true })
|
@ViewChild('scrollContainer', { static: true })
|
||||||
scrollContainer: CdkVirtualScrollViewport;
|
scrollContainer: CdkVirtualScrollViewport;
|
||||||
|
|
||||||
|
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||||
|
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
|
||||||
|
|
||||||
results$ = this.searchService.items$;
|
results$ = this.searchService.items$;
|
||||||
fetching$ = this.searchService.fetching$;
|
fetching$ = this.searchService.fetching$;
|
||||||
hits$ = this.searchService.hits$;
|
hits$ = this.searchService.hits$;
|
||||||
@@ -35,6 +40,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
selectedItemIds$ = this.searchService.selectedItemIds$;
|
selectedItemIds$ = this.searchService.selectedItemIds$;
|
||||||
|
|
||||||
|
searchboxHint$ = this.searchService.searchboxHint$;
|
||||||
|
|
||||||
selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
|
selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
|
||||||
map(([items, selectedItemIds]) => {
|
map(([items, selectedItemIds]) => {
|
||||||
return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
|
return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
|
||||||
@@ -47,14 +54,39 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
trackByItemId: TrackByFunction<ItemDTO> = (index, item) => item.id;
|
trackByItemId: TrackByFunction<ItemDTO> = (index, item) => item.id;
|
||||||
|
|
||||||
|
get isTablet() {
|
||||||
|
return this._environment.matchTablet();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
|
||||||
|
map(([filter, defaultFilter]) => {
|
||||||
|
const filterQueryParams = filter?.getQueryParams();
|
||||||
|
return !isEqual(this.resetQueryParamsQueryAndOrderBy(filterQueryParams), Filter.create(defaultFilter).getQueryParams());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
get filterRoute() {
|
||||||
|
const itemId = this._navigationService?.getOutletParams(this.route)?.right?.id;
|
||||||
|
return this._navigationService.getArticleSearchResultsAndFilterPath({
|
||||||
|
processId: this.application.activatedProcessId,
|
||||||
|
itemId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get mainOutletActive$() {
|
||||||
|
return this._navigationService?.mainOutletActive$(this.route).pipe(shareReplay());
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public searchService: ArticleSearchService,
|
public searchService: ArticleSearchService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private application: ApplicationService,
|
public application: ApplicationService,
|
||||||
private breadcrumb: BreadcrumbService,
|
private breadcrumb: BreadcrumbService,
|
||||||
private cache: CacheService,
|
private cache: CacheService,
|
||||||
private _uiModal: UiModalService,
|
private _uiModal: UiModalService,
|
||||||
private _checkoutService: DomainCheckoutService
|
private _checkoutService: DomainCheckoutService,
|
||||||
|
private _environment: EnvironmentService,
|
||||||
|
private _navigationService: ProductCatalogNavigationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -72,7 +104,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
|
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
|
||||||
|
|
||||||
if (processChanged) {
|
if (processChanged) {
|
||||||
if (!!this.searchService.processId && this.searchService.filter instanceof UiFilter) {
|
if (!!this.searchService.processId && this.searchService.filter instanceof Filter) {
|
||||||
this.cacheCurrentData(
|
this.cacheCurrentData(
|
||||||
this.searchService.processId,
|
this.searchService.processId,
|
||||||
this.searchService.filter.getQueryParams(),
|
this.searchService.filter.getQueryParams(),
|
||||||
@@ -87,7 +119,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
this.searchService.setBranch(selectedBranch);
|
this.searchService.setBranch(selectedBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(this.searchService.filter instanceof UiFilter)) {
|
if (!(this.searchService.filter instanceof Filter)) {
|
||||||
await this.searchService.setDefaultFilter();
|
await this.searchService.setDefaultFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +149,27 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
await this.removeDetailsBreadcrumb(processId);
|
await this.removeDetailsBreadcrumb(processId);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.subscriptions.add(
|
||||||
|
this.searchService.searchCompleted.pipe(withLatestFrom(this.application.activatedProcessId$)).subscribe(([state, processId]) => {
|
||||||
|
if (state.searchState === '') {
|
||||||
|
const params = state.filter.getQueryParams();
|
||||||
|
if ((state.hits === 1 && this.isTablet) || (!this.isTablet && !this._navigationService.mainOutletActive(this.route))) {
|
||||||
|
const item = state.items.find((f) => f);
|
||||||
|
this._navigationService.navigateToDetails({
|
||||||
|
processId,
|
||||||
|
itemId: item.id,
|
||||||
|
queryParams: this.isTablet ? undefined : params,
|
||||||
|
});
|
||||||
|
} else if (this.isTablet || this._navigationService.mainOutletActive(this.route)) {
|
||||||
|
this._navigationService.navigateToResults({
|
||||||
|
processId,
|
||||||
|
queryParams: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@@ -127,7 +180,26 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
this.unselectAll();
|
this.unselectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
search() {
|
resetQueryParamsQueryAndOrderBy(params: Record<string, string> = {}) {
|
||||||
|
const clean = { ...params };
|
||||||
|
|
||||||
|
for (const key in clean) {
|
||||||
|
if (key === 'main_qs') {
|
||||||
|
clean[key] = undefined;
|
||||||
|
} else if (key?.includes('order_by')) {
|
||||||
|
delete clean[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
search(filter?: Filter) {
|
||||||
|
if (!!filter) {
|
||||||
|
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||||
|
this.searchService.setFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
this.searchService.search({ clear: true });
|
this.searchService.search({ clear: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +259,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
await this.breadcrumb.addBreadcrumbIfNotExists({
|
await this.breadcrumb.addBreadcrumbIfNotExists({
|
||||||
key: processId,
|
key: processId,
|
||||||
name,
|
name,
|
||||||
path: `/kunde/${this.application.activatedProcessId}/product/search/results`,
|
path: this._navigationService.getArticleSearchResultsPath(this.application.activatedProcessId),
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
section: 'customer',
|
section: 'customer',
|
||||||
tags: ['catalog', 'filter', 'results'],
|
tags: ['catalog', 'filter', 'results'],
|
||||||
@@ -241,6 +313,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
// Zeige Select Radio Button nicht an wenn Item Archivartikel oder Fortsetzungsartikel ist
|
// Zeige Select Radio Button nicht an wenn Item Archivartikel oder Fortsetzungsartikel ist
|
||||||
const isArchiv = item?.catalogAvailability?.status === 1;
|
const isArchiv = item?.catalogAvailability?.status === 1;
|
||||||
const isFortsetzung = item?.features?.find((i) => i?.key === 'PFO');
|
const isFortsetzung = item?.features?.find((i) => i?.key === 'PFO');
|
||||||
|
|
||||||
return !(isArchiv || isFortsetzung);
|
return !(isArchiv || isFortsetzung);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,29 +322,44 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
this.searchService.patchState({ selectedItemIds: [] });
|
this.searchService.patchState({ selectedItemIds: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async addSelectedItemsToCart() {
|
async addToCart(item?: ItemDTO) {
|
||||||
this.loading$.next(true);
|
this.loading$.next(true);
|
||||||
const selectedItems = await this.selectedItems$.pipe(first()).toPromise();
|
|
||||||
|
if (!!item) {
|
||||||
|
await this.addItemsToCart(item);
|
||||||
|
} else {
|
||||||
|
await this.addItemsToCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading$.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createShoppingCartItem(item: ItemDTO): AddToShoppingCartDTO {
|
||||||
|
return {
|
||||||
|
quantity: 1,
|
||||||
|
availability: {
|
||||||
|
availabilityType: item?.catalogAvailability?.status,
|
||||||
|
price: item?.catalogAvailability?.price,
|
||||||
|
supplierProductNumber: item?.ids?.dig ? String(item?.ids?.dig) : item?.product?.supplierProductNumber,
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
catalogProductNumber: String(item?.id),
|
||||||
|
...item?.product,
|
||||||
|
},
|
||||||
|
itemType: item?.type,
|
||||||
|
promotion: { points: item?.promoPoints },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async addItemsToCart(item?: ItemDTO) {
|
||||||
|
const selectedItems = !item ? await this.selectedItems$.pipe(first()).toPromise() : [item];
|
||||||
const items: AddToShoppingCartDTO[] = [];
|
const items: AddToShoppingCartDTO[] = [];
|
||||||
|
|
||||||
const canAddItemsPayload = [];
|
const canAddItemsPayload = [];
|
||||||
|
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
|
const shoppingCartItem = this._createShoppingCartItem(item);
|
||||||
const isDownload = item?.product?.format === 'EB' || item?.product?.format === 'DL';
|
const isDownload = item?.product?.format === 'EB' || item?.product?.format === 'DL';
|
||||||
const shoppingCartItem: AddToShoppingCartDTO = {
|
|
||||||
quantity: 1,
|
|
||||||
availability: {
|
|
||||||
availabilityType: item?.catalogAvailability?.status,
|
|
||||||
price: item?.catalogAvailability?.price,
|
|
||||||
supplierProductNumber: item?.ids?.dig ? String(item.ids?.dig) : item?.product?.supplierProductNumber,
|
|
||||||
},
|
|
||||||
product: {
|
|
||||||
catalogProductNumber: String(item?.id),
|
|
||||||
...item?.product,
|
|
||||||
},
|
|
||||||
itemType: item.type,
|
|
||||||
promotion: { points: item?.promoPoints },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isDownload) {
|
if (isDownload) {
|
||||||
shoppingCartItem.destination = { data: { target: 16 } };
|
shoppingCartItem.destination = { data: { target: 16 } };
|
||||||
canAddItemsPayload.push({
|
canAddItemsPayload.push({
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { UiCommonModule } from '@ui/common';
|
|||||||
import { UiIconModule } from '@ui/icon';
|
import { UiIconModule } from '@ui/icon';
|
||||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||||
import { UiTooltipModule } from '@ui/tooltip';
|
import { UiTooltipModule } from '@ui/tooltip';
|
||||||
import { UiOrderByFilterModule } from 'apps/ui/filter/src/lib/next/order-by-filter/order-by-filter.module';
|
|
||||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
||||||
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
|
||||||
import { StockInfosPipe } from './order-by-filter/stick-infos.pipe';
|
import { StockInfosPipe } from './order-by-filter/stick-infos.pipe';
|
||||||
@@ -16,6 +15,9 @@ import { SearchResultItemLoadingComponent } from './search-result-item-loading.c
|
|||||||
import { SearchResultItemComponent } from './search-result-item.component';
|
import { SearchResultItemComponent } from './search-result-item.component';
|
||||||
import { ArticleSearchResultsComponent } from './search-results.component';
|
import { ArticleSearchResultsComponent } from './search-results.component';
|
||||||
import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe';
|
import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe';
|
||||||
|
import { FilterAutocompleteProvider, FilterNextModule, OrderByFilterModule } from 'apps/shared/components/filter/src/lib';
|
||||||
|
import { FocusSearchboxEvent } from '../focus-searchbox.event';
|
||||||
|
import { ArticleSearchMainAutocompleteProvider } from '../providers';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -27,9 +29,10 @@ import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe
|
|||||||
UiIconModule,
|
UiIconModule,
|
||||||
UiSelectBulletModule,
|
UiSelectBulletModule,
|
||||||
UiSpinnerModule,
|
UiSpinnerModule,
|
||||||
UiOrderByFilterModule,
|
OrderByFilterModule,
|
||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
UiTooltipModule,
|
UiTooltipModule,
|
||||||
|
FilterNextModule,
|
||||||
],
|
],
|
||||||
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
|
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -40,6 +43,13 @@ import { SearchResultSelectedPipe } from './selected/search-result-selected.pipe
|
|||||||
SearchResultSelectedPipe,
|
SearchResultSelectedPipe,
|
||||||
AddedToCartModalComponent,
|
AddedToCartModalComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [
|
||||||
|
FocusSearchboxEvent,
|
||||||
|
{
|
||||||
|
provide: FilterAutocompleteProvider,
|
||||||
|
useClass: ArticleSearchMainAutocompleteProvider,
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class SearchResultsModule {}
|
export class SearchResultsModule {}
|
||||||
|
|||||||
@@ -2,10 +2,65 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { ArticleDetailsComponent } from './article-details/article-details.component';
|
import { ArticleDetailsComponent } from './article-details/article-details.component';
|
||||||
import { ArticleSearchComponent } from './article-search/article-search.component';
|
import { ArticleSearchComponent } from './article-search/article-search.component';
|
||||||
|
import { ArticleSearchFilterComponent } from './article-search/search-filter/search-filter.component';
|
||||||
import { ArticleSearchMainComponent } from './article-search/search-main/search-main.component';
|
import { ArticleSearchMainComponent } from './article-search/search-main/search-main.component';
|
||||||
import { ArticleSearchResultsComponent } from './article-search/search-results/search-results.component';
|
import { ArticleSearchResultsComponent } from './article-search/search-results/search-results.component';
|
||||||
import { PageCatalogComponent } from './page-catalog.component';
|
import { PageCatalogComponent } from './page-catalog.component';
|
||||||
|
|
||||||
|
const auxiliaryRoutes = [
|
||||||
|
{
|
||||||
|
path: 'search',
|
||||||
|
component: ArticleSearchComponent,
|
||||||
|
outlet: 'left',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ArticleSearchMainComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'filter',
|
||||||
|
component: ArticleSearchFilterComponent,
|
||||||
|
outlet: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'filter/:id',
|
||||||
|
component: ArticleSearchFilterComponent,
|
||||||
|
outlet: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'results',
|
||||||
|
component: ArticleSearchResultsComponent,
|
||||||
|
outlet: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'results',
|
||||||
|
component: ArticleSearchResultsComponent,
|
||||||
|
outlet: 'main',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'results/:id',
|
||||||
|
component: ArticleSearchResultsComponent,
|
||||||
|
outlet: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'results/ean/:ean',
|
||||||
|
component: ArticleSearchResultsComponent,
|
||||||
|
outlet: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'details/ean/:ean',
|
||||||
|
component: ArticleDetailsComponent,
|
||||||
|
outlet: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'details/:id',
|
||||||
|
component: ArticleDetailsComponent,
|
||||||
|
outlet: 'right',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -19,12 +74,16 @@ const routes: Routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
component: ArticleSearchMainComponent,
|
component: ArticleSearchMainComponent,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'results',
|
|
||||||
component: ArticleSearchResultsComponent,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'results',
|
||||||
|
component: ArticleSearchResultsComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'filter',
|
||||||
|
component: ArticleSearchFilterComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'details/ean/:ean',
|
path: 'details/ean/:ean',
|
||||||
component: ArticleDetailsComponent,
|
component: ArticleDetailsComponent,
|
||||||
@@ -33,6 +92,7 @@ const routes: Routes = [
|
|||||||
path: 'details/:id',
|
path: 'details/:id',
|
||||||
component: ArticleDetailsComponent,
|
component: ArticleDetailsComponent,
|
||||||
},
|
},
|
||||||
|
...auxiliaryRoutes,
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']">
|
<shared-breadcrumb class="my-4" [key]="activatedProcessId$ | async" [tags]="['catalog']">
|
||||||
<shared-branch-selector [branchType]="1" [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)">
|
<shared-branch-selector
|
||||||
|
[filterCurrentBranch]="!!auth.hasRole('Store')"
|
||||||
|
[orderBy]="auth.hasRole('Store') ? 'distance' : 'name'"
|
||||||
|
[branchType]="1"
|
||||||
|
[value]="selectedBranch$ | async"
|
||||||
|
(valueChange)="patchProcessData($event)"
|
||||||
|
>
|
||||||
</shared-branch-selector>
|
</shared-branch-selector>
|
||||||
</shared-breadcrumb>
|
</shared-breadcrumb>
|
||||||
<router-outlet></router-outlet>
|
|
||||||
|
<ng-container *ngIf="routerEvents$ | async">
|
||||||
|
<ng-container *ngIf="!(isDesktop$ | async); else desktop">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #desktop>
|
||||||
|
<ng-container *ngIf="showMainOutlet$ | async">
|
||||||
|
<router-outlet name="main"></router-outlet>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-[minmax(31rem,.5fr)_1fr] gap-6">
|
||||||
|
<div *ngIf="showLeftOutlet$ | async" class="block">
|
||||||
|
<router-outlet name="left"></router-outlet>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="showRightOutlet$ | async">
|
||||||
|
<router-outlet name="right"></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
:host {
|
:host {
|
||||||
@apply block relative;
|
@apply block relative h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||||
}
|
}
|
||||||
|
|
||||||
shell-breadcrumb {
|
shell-breadcrumb {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ApplicationService } from '@core/application';
|
import { ApplicationService } from '@core/application';
|
||||||
|
import { AuthService } from '@core/auth';
|
||||||
import { EnvironmentService } from '@core/environment';
|
import { EnvironmentService } from '@core/environment';
|
||||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||||
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
|
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
|
||||||
import { BranchDTO } from '@swagger/checkout';
|
import { BranchDTO } from '@swagger/checkout';
|
||||||
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||||
import { BehaviorSubject, from, fromEvent, Observable, Subject } from 'rxjs';
|
import { fromEvent, Observable, Subject } from 'rxjs';
|
||||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-catalog',
|
selector: 'page-catalog',
|
||||||
@@ -27,33 +29,62 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
_onDestroy$ = new Subject<boolean>();
|
_onDestroy$ = new Subject<boolean>();
|
||||||
|
|
||||||
|
get isTablet$() {
|
||||||
|
return this._environmentService.matchTablet$.pipe(
|
||||||
|
map((state) => state.matches),
|
||||||
|
shareReplay()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDesktop$() {
|
||||||
|
return this._environmentService.matchDesktop$.pipe(
|
||||||
|
map((state) => {
|
||||||
|
return state.matches;
|
||||||
|
}),
|
||||||
|
shareReplay()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
routerEvents$ = this._router.events.pipe(shareReplay());
|
||||||
|
|
||||||
|
showMainOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'main')));
|
||||||
|
|
||||||
|
showLeftOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'left')));
|
||||||
|
|
||||||
|
showRightOutlet$ = this.routerEvents$.pipe(map((_) => !!this._activatedRoute?.children?.find((child) => child?.outlet === 'right')));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public application: ApplicationService,
|
public application: ApplicationService,
|
||||||
private _uiModal: UiModalService,
|
private _uiModal: UiModalService,
|
||||||
|
public auth: AuthService,
|
||||||
private _environmentService: EnvironmentService,
|
private _environmentService: EnvironmentService,
|
||||||
private _renderer: Renderer2
|
private _renderer: Renderer2,
|
||||||
|
private _activatedRoute: ActivatedRoute,
|
||||||
|
private _router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||||
|
|
||||||
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
|
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
|
||||||
|
|
||||||
|
this.application.setTitle('Artikelsuche');
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
if (this._environmentService.isTablet()) {
|
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
|
||||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusin')
|
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.isTablet$))
|
||||||
.pipe(takeUntil(this._onDestroy$))
|
.subscribe(([_, isTablet]) => {
|
||||||
.subscribe((_) => {
|
if (isTablet) {
|
||||||
this._renderer.setStyle(this.branchSelectorRef?.nativeElement, 'width', this.branchSelectorWidth);
|
this._renderer.setStyle(this.branchSelectorRef?.nativeElement, 'width', this.branchSelectorWidth);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fromEvent(this.branchSelectorRef.nativeElement, 'focusout')
|
fromEvent(this.branchSelectorRef.nativeElement, 'focusout')
|
||||||
.pipe(takeUntil(this._onDestroy$))
|
.pipe(takeUntil(this._onDestroy$))
|
||||||
.subscribe((_) => {
|
.subscribe((_) => {
|
||||||
this._renderer.removeStyle(this.branchSelectorRef?.nativeElement, 'width');
|
this._renderer.removeStyle(this.branchSelectorRef?.nativeElement, 'width');
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|||||||
@@ -2,22 +2,13 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||||
import { BreadcrumbModule } from '@shared/components/breadcrumb';
|
import { BreadcrumbModule } from '@shared/components/breadcrumb';
|
||||||
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
|
|
||||||
import { ArticleDetailsModule } from './article-details/article-details.module';
|
import { ArticleDetailsModule } from './article-details/article-details.module';
|
||||||
import { ArticleSearchModule } from './article-search/article-search.module';
|
import { ArticleSearchModule } from './article-search/article-search.module';
|
||||||
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
|
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
|
||||||
import { PageCatalogComponent } from './page-catalog.component';
|
import { PageCatalogComponent } from './page-catalog.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [CommonModule, PageCatalogRoutingModule, ArticleSearchModule, ArticleDetailsModule, BreadcrumbModule, BranchSelectorComponent],
|
||||||
CommonModule,
|
|
||||||
PageCatalogRoutingModule,
|
|
||||||
ShellBreadcrumbModule,
|
|
||||||
ArticleSearchModule,
|
|
||||||
ArticleDetailsModule,
|
|
||||||
BreadcrumbModule,
|
|
||||||
BranchSelectorComponent,
|
|
||||||
],
|
|
||||||
exports: [],
|
exports: [],
|
||||||
declarations: [PageCatalogComponent],
|
declarations: [PageCatalogComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { DomainCheckoutService } from '@domain/checkout';
|
|||||||
import { AvailabilityDTO, DestinationDTO, NotificationChannel, ShoppingCartItemDTO, ShoppingCartDTO } from '@swagger/checkout';
|
import { AvailabilityDTO, DestinationDTO, NotificationChannel, ShoppingCartItemDTO, ShoppingCartDTO } from '@swagger/checkout';
|
||||||
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
|
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||||
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||||
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
|
|
||||||
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
|
|
||||||
import { AuthService } from '@core/auth';
|
import { AuthService } from '@core/auth';
|
||||||
import { first, map, shareReplay, switchMap, take, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
import { first, map, shareReplay, switchMap, take, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
|
||||||
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
|
||||||
@@ -15,13 +13,11 @@ import { DomainCatalogService } from '@domain/catalog';
|
|||||||
import { BreadcrumbService } from '@core/breadcrumb';
|
import { BreadcrumbService } from '@core/breadcrumb';
|
||||||
import { DomainPrinterService } from '@domain/printer';
|
import { DomainPrinterService } from '@domain/printer';
|
||||||
import { CheckoutDummyComponent } from '../checkout-dummy/checkout-dummy.component';
|
import { CheckoutDummyComponent } from '../checkout-dummy/checkout-dummy.component';
|
||||||
import { ResponseArgsOfItemDTO } from '@swagger/cat';
|
|
||||||
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||||
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
|
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
|
||||||
import { PurchasingOptionsListModalComponent } from '../modals/purchasing-options-list-modal';
|
|
||||||
import { PurchasingOptionsListModalData } from '../modals/purchasing-options-list-modal/purchasing-options-list-modal.data';
|
|
||||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||||
import { CheckoutDummyData } from '../checkout-dummy/checkout-dummy-data';
|
import { CheckoutDummyData } from '../checkout-dummy/checkout-dummy-data';
|
||||||
|
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||||
|
|
||||||
export interface CheckoutReviewComponentState {
|
export interface CheckoutReviewComponentState {
|
||||||
shoppingCart: ShoppingCartDTO;
|
shoppingCart: ShoppingCartDTO;
|
||||||
@@ -242,7 +238,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
private domainCatalogService: DomainCatalogService,
|
private domainCatalogService: DomainCatalogService,
|
||||||
private breadcrumb: BreadcrumbService,
|
private breadcrumb: BreadcrumbService,
|
||||||
private domainPrinterService: DomainPrinterService,
|
private domainPrinterService: DomainPrinterService,
|
||||||
private _fb: UntypedFormBuilder
|
private _fb: UntypedFormBuilder,
|
||||||
|
private _purchaseOptionsModalService: PurchaseOptionsModalService
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
shoppingCart: undefined,
|
shoppingCart: undefined,
|
||||||
@@ -274,7 +271,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
shoppingCart,
|
shoppingCart,
|
||||||
shoppingCartItems,
|
shoppingCartItems,
|
||||||
});
|
});
|
||||||
this.checkQuantityErrors(shoppingCartItems);
|
// this.checkQuantityErrors(shoppingCartItems);
|
||||||
},
|
},
|
||||||
(err) => {},
|
(err) => {},
|
||||||
() => {}
|
() => {}
|
||||||
@@ -285,15 +282,15 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
checkQuantityErrors(shoppingCartItems: ShoppingCartItemDTO[]) {
|
// checkQuantityErrors(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||||
shoppingCartItems.forEach((item) => {
|
// shoppingCartItems.forEach((item) => {
|
||||||
if (item.features?.orderType === 'Rücklage') {
|
// if (item.features?.orderType === 'Abholung') {
|
||||||
this.setQuantityError(item, item.availability, item.quantity > item.availability?.inStock);
|
// this.setQuantityError(item, item.availability, item.quantity > item.availability?.inStock);
|
||||||
} else {
|
// } else {
|
||||||
this.setQuantityError(item, item.availability, false);
|
// this.setQuantityError(item, item.availability, false);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
async updateBreadcrumb() {
|
async updateBreadcrumb() {
|
||||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||||
@@ -434,171 +431,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
}
|
}
|
||||||
|
|
||||||
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||||
this.loadingOnItemChangeById$.next(shoppingCartItem.id);
|
this._purchaseOptionsModalService.open({
|
||||||
|
processId: this.applicationService.activatedProcessId,
|
||||||
const quantity = shoppingCartItem.quantity;
|
items: [shoppingCartItem],
|
||||||
|
type: 'update',
|
||||||
const branchNo = this.auth.getClaimByKey('branch_no');
|
|
||||||
const branchId = shoppingCartItem?.destination?.data?.targetBranch?.id;
|
|
||||||
|
|
||||||
const customerFeatures = await this.customerFeatures$.pipe(first()).toPromise();
|
|
||||||
|
|
||||||
let branch = await this.domainCheckoutService
|
|
||||||
.getBranches()
|
|
||||||
.pipe(map((branches) => branches.find((branch) => (branchId ? branch.id === branchId : branch.branchNumber === branchNo))))
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (!branch) {
|
|
||||||
branch = await this.applicationService.getSelectedBranch$().pipe(take(1)).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
let catalogItem: ResponseArgsOfItemDTO;
|
|
||||||
if (Number.isInteger(shoppingCartItem?.product?.catalogProductNumber)) {
|
|
||||||
catalogItem = await this.domainCatalogService
|
|
||||||
.getDetailsById({ id: Number(shoppingCartItem.product.catalogProductNumber) })
|
|
||||||
.toPromise();
|
|
||||||
} else if (shoppingCartItem?.product?.ean) {
|
|
||||||
catalogItem = await this.domainCatalogService.getDetailsByEan({ ean: shoppingCartItem.product.ean }).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
let takeAwayAvailability: AvailabilityDTO;
|
|
||||||
if (!!catalogItem?.result?.product) {
|
|
||||||
takeAwayAvailability = await this.availabilityService
|
|
||||||
.getTakeAwayAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: catalogItem.result.id,
|
|
||||||
ean: catalogItem.result.product.ean,
|
|
||||||
price: catalogItem.result.catalogAvailability?.price,
|
|
||||||
},
|
|
||||||
quantity,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const pickupAvailability = await this.availabilityService
|
|
||||||
.getPickUpAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: Number(shoppingCartItem.product.catalogProductNumber),
|
|
||||||
ean: shoppingCartItem.product.ean,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
},
|
|
||||||
branch,
|
|
||||||
quantity,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
const digAvailability = await this.availabilityService
|
|
||||||
.getDigDeliveryAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: Number(shoppingCartItem.product.catalogProductNumber),
|
|
||||||
ean: shoppingCartItem.product.ean,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
},
|
|
||||||
quantity,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
const b2bAvailability = await this.availabilityService
|
|
||||||
.getB2bDeliveryAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: Number(shoppingCartItem.product.catalogProductNumber),
|
|
||||||
ean: shoppingCartItem.product.ean,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
},
|
|
||||||
quantity,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
const downloadAvailability = await this.availabilityService
|
|
||||||
.getDownloadAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: Number(shoppingCartItem.product.catalogProductNumber),
|
|
||||||
ean: shoppingCartItem.product.ean,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
let availableOptions: PurchasingOptions[] = [];
|
|
||||||
const availabilities: { [key: string]: AvailabilityDTO } = {};
|
|
||||||
|
|
||||||
if (takeAwayAvailability && this.availabilityService.isAvailable({ availability: takeAwayAvailability })) {
|
|
||||||
availableOptions.push('take-away');
|
|
||||||
availabilities['take-away'] = takeAwayAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadAvailability && this.availabilityService.isAvailable({ availability: downloadAvailability })) {
|
|
||||||
availableOptions.push('download');
|
|
||||||
availabilities['download'] = downloadAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pickupAvailability && this.availabilityService.isAvailable({ availability: pickupAvailability[0] })) {
|
|
||||||
if (pickupAvailability[1].availableFor) {
|
|
||||||
if ((pickupAvailability[1].availableFor & 2) === 2) {
|
|
||||||
availableOptions.push('pick-up');
|
|
||||||
availabilities['pick-up'] = pickupAvailability[0];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
availableOptions.push('pick-up');
|
|
||||||
availabilities['pick-up'] = pickupAvailability[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!customerFeatures?.webshop && this.availabilityService.isAvailable({ availability: b2bAvailability })) {
|
|
||||||
availableOptions.push('b2b-delivery');
|
|
||||||
availabilities['b2b-delivery'] = b2bAvailability;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (digAvailability && this.availabilityService.isAvailable({ availability: digAvailability }) && !customerFeatures?.b2b) {
|
|
||||||
availableOptions.push('dig-delivery');
|
|
||||||
availabilities['dig-delivery'] = digAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableOptions.includes('dig-delivery') && availableOptions.includes('b2b-delivery')) {
|
|
||||||
let shippingAvailability = await this.availabilityService
|
|
||||||
.getDeliveryAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: Number(shoppingCartItem.product.catalogProductNumber),
|
|
||||||
ean: shoppingCartItem.product.ean,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
},
|
|
||||||
quantity,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
if (shippingAvailability && this.availabilityService.isAvailable({ availability: shippingAvailability })) {
|
|
||||||
availableOptions.push('delivery');
|
|
||||||
availabilities['delivery'] = shippingAvailability;
|
|
||||||
availableOptions = availableOptions.filter((option) => !(option === 'dig-delivery' || option === 'b2b-delivery'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadingOnItemChangeById$.next(undefined);
|
|
||||||
this.cdr.markForCheck();
|
|
||||||
|
|
||||||
const itemId = Number(shoppingCartItem.product.catalogProductNumber);
|
|
||||||
const modal = this.uiModal.open({
|
|
||||||
content: PurchasingOptionsModalComponent,
|
|
||||||
data: {
|
|
||||||
availableOptions,
|
|
||||||
item: {
|
|
||||||
id: itemId,
|
|
||||||
itemId: itemId,
|
|
||||||
product: shoppingCartItem.product,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
catalogAvailability: {
|
|
||||||
status: shoppingCartItem.availability.availabilityType,
|
|
||||||
price: shoppingCartItem.availability.price,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shoppingCartItem,
|
|
||||||
branchId: branch?.id,
|
|
||||||
processId: this.applicationService.activatedProcessId,
|
|
||||||
availabilities,
|
|
||||||
} as PurchasingOptionsModalData,
|
|
||||||
});
|
|
||||||
|
|
||||||
modal.afterClosed$.pipe(takeUntil(this._orderCompleted)).subscribe(() => {
|
|
||||||
this.setQuantityError(shoppingCartItem, undefined, false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,7 +485,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
})
|
})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
|
// this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
|
||||||
break;
|
break;
|
||||||
case 'Abholung':
|
case 'Abholung':
|
||||||
availability = await this.availabilityService
|
availability = await this.availabilityService
|
||||||
@@ -727,7 +563,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
this.setQuantityError(shoppingCartItem, availability, false);
|
// this.setQuantityError(shoppingCartItem, availability, false);
|
||||||
} else if (availability) {
|
} else if (availability) {
|
||||||
// Wenn das Ergebnis der Availability Abfrage keinen Preis zurückliefert (z.B. HFI Geschenkkarte), wird der Preis aus der
|
// Wenn das Ergebnis der Availability Abfrage keinen Preis zurückliefert (z.B. HFI Geschenkkarte), wird der Preis aus der
|
||||||
// Availability vor der Abfrage verwendet
|
// Availability vor der Abfrage verwendet
|
||||||
@@ -758,16 +594,16 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
this.loadingOnQuantityChangeById$.next(undefined);
|
this.loadingOnQuantityChangeById$.next(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
setQuantityError(item: ShoppingCartItemDTO, availability: AvailabilityDTO, error: boolean) {
|
// setQuantityError(item: ShoppingCartItemDTO, availability: AvailabilityDTO, error: boolean) {
|
||||||
const quantityErrors: { [key: string]: string } = this.quantityError$.value;
|
// const quantityErrors: { [key: string]: string } = this.quantityError$.value;
|
||||||
if (error) {
|
// if (error) {
|
||||||
quantityErrors[item.product.catalogProductNumber] = `${availability.inStock} Exemplar(e) sofort lieferbar`;
|
// quantityErrors[item.product.catalogProductNumber] = `${availability.inStock} Exemplar(e) sofort lieferbar`;
|
||||||
this.quantityError$.next({ ...quantityErrors });
|
// this.quantityError$.next({ ...quantityErrors });
|
||||||
} else {
|
// } else {
|
||||||
delete quantityErrors[item.product.catalogProductNumber];
|
// delete quantityErrors[item.product.catalogProductNumber];
|
||||||
this.quantityError$.next({ ...quantityErrors });
|
// this.quantityError$.next({ ...quantityErrors });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
|
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
|
||||||
compareDeliveryAndCatalogPrice(availability: AvailabilityDTO, orderType: string, shoppingCartItemPrice: number) {
|
compareDeliveryAndCatalogPrice(availability: AvailabilityDTO, orderType: string, shoppingCartItemPrice: number) {
|
||||||
@@ -816,16 +652,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
|
|||||||
}
|
}
|
||||||
|
|
||||||
async showPurchasingListModal(shoppingCartItems: ShoppingCartItemDTO[]) {
|
async showPurchasingListModal(shoppingCartItems: ShoppingCartItemDTO[]) {
|
||||||
const customerFeatures = await this.customerFeatures$.pipe(first()).toPromise();
|
this._purchaseOptionsModalService.open({
|
||||||
this.uiModal.open({
|
processId: this.applicationService.activatedProcessId,
|
||||||
content: PurchasingOptionsListModalComponent,
|
items: shoppingCartItems,
|
||||||
title: 'Wie möchten Sie die Artikel erhalten?',
|
type: 'update',
|
||||||
config: { showScrollbarY: false },
|
|
||||||
data: {
|
|
||||||
processId: this.applicationService.activatedProcessId,
|
|
||||||
shoppingCartItems: shoppingCartItems,
|
|
||||||
customerFeatures,
|
|
||||||
} as PurchasingOptionsListModalData,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// start:ng42.barrel
|
// start:ng42.barrel
|
||||||
export * from './page-checkout.module';
|
export * from './page-checkout.module';
|
||||||
export * from './page-checkout-modals.module';
|
|
||||||
// end:ng42.barrel
|
// end:ng42.barrel
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<div class="option-icon">
|
|
||||||
<ui-icon size="50px" icon="truck"></ui-icon>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="option-chip"
|
|
||||||
[disabled]="optionChipDisabled$ | async"
|
|
||||||
(click)="optionChange('delivery')"
|
|
||||||
[class.selected]="(selectedOption$ | async) === 'delivery'"
|
|
||||||
>
|
|
||||||
Versand
|
|
||||||
</button>
|
|
||||||
<p>Möchten Sie die Artikel<br />geliefert bekommen?</p>
|
|
||||||
<p>Versandkostenfrei</p>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|
||||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'page-delivery-option-list',
|
|
||||||
templateUrl: 'delivery-option-list.component.html',
|
|
||||||
styleUrls: ['../list-options.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class DeliveryOptionListComponent {
|
|
||||||
selectedOption$ = this._store.selectedFilterOption$;
|
|
||||||
optionChipDisabled$ = this._store.fetchingAvailabilities$;
|
|
||||||
|
|
||||||
constructor(private _store: PurchasingOptionsListModalStore) {}
|
|
||||||
|
|
||||||
optionChange(option: string) {
|
|
||||||
if (this._store.selectedFilterOption === option) {
|
|
||||||
this._store.selectedFilterOption = undefined;
|
|
||||||
} else {
|
|
||||||
this._store.selectedFilterOption = option;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// start:ng42.barrel
|
|
||||||
export * from './delivery-option-list.component';
|
|
||||||
// end:ng42.barrel
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// start:ng42.barrel
|
|
||||||
export * from './delivery-option';
|
|
||||||
export * from './pick-up-option';
|
|
||||||
export * from './take-away-option';
|
|
||||||
export * from './purchasing-options-list-modal.component';
|
|
||||||
export * from './purchasing-options-list-modal.module';
|
|
||||||
// end:ng42.barrel
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
:host {
|
|
||||||
@apply block w-72;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-icon {
|
|
||||||
@apply text-ucla-blue mx-auto;
|
|
||||||
width: 40px;
|
|
||||||
|
|
||||||
.truck-b2b {
|
|
||||||
margin-top: -21px;
|
|
||||||
margin-bottom: -12px;
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-chip {
|
|
||||||
@apply rounded-full text-base px-4 py-3 bg-glitter text-inactive-customer border-none font-bold;
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
@apply bg-active-customer text-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-description {
|
|
||||||
@apply my-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-select {
|
|
||||||
@apply mt-4 mb-4 border-2 border-solid border-brand text-brand text-cta-l font-bold bg-white rounded-full py-3 px-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
@apply my-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep page-purchasing-options-list-modal ui-branch-dropdown .wrapper {
|
|
||||||
@apply mx-auto;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// start:ng42.barrel
|
|
||||||
export * from './pick-up-option-list.component';
|
|
||||||
// end:ng42.barrel
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="option-icon">
|
|
||||||
<ui-icon size="50px" icon="box_out"></ui-icon>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="option-chip"
|
|
||||||
[disabled]="optionChipDisabled$ | async"
|
|
||||||
(click)="optionChange('pick-up')"
|
|
||||||
[class.selected]="(selectedOption$ | async) === 'pick-up'"
|
|
||||||
>
|
|
||||||
Abholung
|
|
||||||
</button>
|
|
||||||
<p>Möchten Sie die Artikel<br />in einer unserer Filialen<br />abholen?</p>
|
|
||||||
|
|
||||||
<ui-branch-dropdown
|
|
||||||
[branches]="branches$ | async"
|
|
||||||
[selected]="(selectedBranch$ | async)?.name"
|
|
||||||
(selectBranch)="selectBranch($event)"
|
|
||||||
></ui-branch-dropdown>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|
||||||
import { BranchDTO } from '@swagger/checkout';
|
|
||||||
import { first } from 'rxjs/operators';
|
|
||||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'page-pick-up-option-list',
|
|
||||||
templateUrl: 'pick-up-option-list.component.html',
|
|
||||||
styleUrls: ['../list-options.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class PickUpOptionListComponent {
|
|
||||||
branches$ = this._store.branches$;
|
|
||||||
selectedBranch$ = this._store.selectedPickUpBranch$;
|
|
||||||
selectedOption$ = this._store.selectedFilterOption$;
|
|
||||||
optionChipDisabled$ = this._store.fetchingAvailabilities$;
|
|
||||||
|
|
||||||
constructor(private _store: PurchasingOptionsListModalStore) {}
|
|
||||||
|
|
||||||
optionChange(option: string) {
|
|
||||||
if (this._store.selectedFilterOption === option) {
|
|
||||||
this._store.selectedFilterOption = undefined;
|
|
||||||
} else {
|
|
||||||
this._store.selectedFilterOption = option;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectBranch(branch: BranchDTO) {
|
|
||||||
this._store.lastSelectedFilterOption$.next(undefined);
|
|
||||||
|
|
||||||
this._store.selectedPickUpBranch = branch;
|
|
||||||
|
|
||||||
const shoppingCartItems = await this._store.shoppingCartItems$.pipe(first()).toPromise();
|
|
||||||
shoppingCartItems.forEach((item) => this._store.loadPickUpAvailability({ item }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
<div class="item-thumbnail">
|
|
||||||
<img loading="lazy" *ngIf="item?.product?.ean | productImage; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-contributors">
|
|
||||||
{{ item.product.contributors }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="item-title"
|
|
||||||
[class.xl]="item?.product?.name?.length >= 35"
|
|
||||||
[class.lg]="item?.product?.name?.length >= 40"
|
|
||||||
[class.md]="item?.product?.name?.length >= 50"
|
|
||||||
[class.sm]="item?.product?.name?.length >= 60"
|
|
||||||
[class.xs]="item?.product?.name?.length >= 100"
|
|
||||||
>
|
|
||||||
{{ item?.product?.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="canAdd$ | async; let canAdd">
|
|
||||||
<div class="item-can-add" *ngIf="canAdd !== true">
|
|
||||||
{{ canAdd }}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
|
||||||
<img
|
|
||||||
*ngIf="item?.product?.format !== '--'"
|
|
||||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
|
||||||
[alt]="item?.product?.formatDetail"
|
|
||||||
/>
|
|
||||||
{{ item?.product?.formatDetail }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-info">
|
|
||||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
|
||||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
|
||||||
{{ item?.product?.publicationDate | date: 'dd. MMMM yyyy' }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-price-stock">
|
|
||||||
<div class="price">
|
|
||||||
<ng-container *ngIf="showTooltip$ | async">
|
|
||||||
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
|
|
||||||
i
|
|
||||||
</button>
|
|
||||||
<ui-tooltip #tooltipContent yPosition="above" xPosition="after" [yOffset]="-16">
|
|
||||||
Günstigerer Preis aus Hugendubel Katalog wird übernommen
|
|
||||||
</ui-tooltip>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<ui-quantity-dropdown
|
|
||||||
[disabled]="fetchingAvailabilities$ | async"
|
|
||||||
[ngModel]="item.quantity"
|
|
||||||
(ngModelChange)="changeQuantity($event)"
|
|
||||||
[range]="quantityRange$ | async"
|
|
||||||
>
|
|
||||||
</ui-quantity-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-select">
|
|
||||||
<ui-select-bullet
|
|
||||||
*ngIf="selectVisible$ | async"
|
|
||||||
[disabled]="selectDisabled$ | async"
|
|
||||||
[ngModel]="isSelected$ | async"
|
|
||||||
(ngModelChange)="selected($event)"
|
|
||||||
></ui-select-bullet>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-availability">
|
|
||||||
<div class="fetching" *ngIf="fetchingAvailabilities$ | async; else availabilities"></div>
|
|
||||||
<ng-template #availabilities>
|
|
||||||
<ng-container *ngIf="notAvailable$ | async; else available">
|
|
||||||
<span class="hint">Derzeit nicht bestellbar</span>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-template #available>
|
|
||||||
<span>Verfügbar als</span>
|
|
||||||
<div *ngIf="takeAwayAvailabilities$ | async; let takeAwayAvailabilites">
|
|
||||||
<ui-icon icon="shopping_bag" size="18px"></ui-icon>
|
|
||||||
<span class="instock">{{ takeAwayAvailabilites?.inStock }}x</span> ab sofort
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="!!(pickUpAvailabilities$ | async)">
|
|
||||||
<ui-icon icon="box_out" size="18px"></ui-icon>
|
|
||||||
{{ (pickUpAvailabilities$ | async)?.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="!!(deliveryDigAvailabilities$ | async); else b2b">
|
|
||||||
<ui-icon class="truck" icon="truck" size="30px"></ui-icon>
|
|
||||||
<ng-container *ngIf="deliveryDigAvailabilities$ | async; let deliveryDigAvailabilities">
|
|
||||||
<ng-container *ngIf="deliveryDigAvailabilities?.estimatedDelivery; else estimatedShippingDate">
|
|
||||||
{{ (deliveryDigAvailabilities?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} -
|
|
||||||
{{ (deliveryDigAvailabilities?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #estimatedShippingDate>
|
|
||||||
{{ deliveryDigAvailabilities.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
|
||||||
</ng-template>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template #b2b>
|
|
||||||
<div *ngIf="!!(deliveryB2bAvailabilities$ | async)">
|
|
||||||
<ui-icon class="truck-b2b" icon="truck_b2b" size="40px"></ui-icon>
|
|
||||||
{{ (deliveryB2bAvailabilities$ | async)?.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</ng-template>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
:host {
|
|
||||||
@apply text-black no-underline grid py-4;
|
|
||||||
grid-template-columns: 102px 60% auto;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
grid-template-areas:
|
|
||||||
'item-thumbnail item-contributors item-contributors'
|
|
||||||
'item-thumbnail item-title item-price-stock'
|
|
||||||
'item-thumbnail item-can-add item-price-stock'
|
|
||||||
'item-thumbnail item-format item-price-stock'
|
|
||||||
'item-thumbnail item-info item-select'
|
|
||||||
'item-thumbnail item-date item-select'
|
|
||||||
'item-thumbnail item-ssc item-select'
|
|
||||||
'item-thumbnail item-availability item-select';
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-thumbnail {
|
|
||||||
grid-area: item-thumbnail;
|
|
||||||
width: 70px;
|
|
||||||
@apply mr-8;
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 150px;
|
|
||||||
@apply rounded-card shadow-cta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-contributors {
|
|
||||||
@apply font-bold no-underline;
|
|
||||||
grid-area: item-contributors;
|
|
||||||
height: 22px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 600px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
grid-area: item-title;
|
|
||||||
@apply font-bold text-lg mb-4;
|
|
||||||
max-height: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title.xl {
|
|
||||||
@apply font-bold text-xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title.lg {
|
|
||||||
@apply font-bold text-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title.md {
|
|
||||||
@apply font-bold text-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title.sm {
|
|
||||||
@apply font-bold text-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title.xs {
|
|
||||||
@apply font-bold text-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-format {
|
|
||||||
grid-area: item-format;
|
|
||||||
@apply flex flex-row items-center font-bold text-lg whitespace-nowrap;
|
|
||||||
|
|
||||||
img {
|
|
||||||
@apply mr-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-price-stock {
|
|
||||||
grid-area: item-price-stock;
|
|
||||||
@apply font-bold text-xl text-right;
|
|
||||||
|
|
||||||
.price {
|
|
||||||
@apply flex flex-row justify-end items-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-tooltip-button {
|
|
||||||
@apply border-font-customer border-solid border-2 bg-white rounded-full text-base font-bold mr-3;
|
|
||||||
border-style: outset;
|
|
||||||
width: 31px;
|
|
||||||
height: 31px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quantity-btn {
|
|
||||||
@apply flex flex-row items-center p-0 w-full text-right outline-none border-none bg-transparent text-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quantity-btn-icon {
|
|
||||||
@apply inline-flex ml-2;
|
|
||||||
transition: transform 200ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui-quantity-dropdown {
|
|
||||||
@apply flex justify-end mt-2;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
@apply cursor-not-allowed bg-inactive-branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-stock {
|
|
||||||
grid-area: item-stock;
|
|
||||||
@apply flex flex-row justify-end items-baseline font-bold text-lg;
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply text-active-customer mr-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-info {
|
|
||||||
grid-area: item-info;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-availability {
|
|
||||||
@apply flex flex-row items-center mt-4 whitespace-nowrap text-sm;
|
|
||||||
grid-area: item-availability;
|
|
||||||
|
|
||||||
.fetching {
|
|
||||||
@apply w-52 h-px-20;
|
|
||||||
background-color: #e6eff9;
|
|
||||||
animation: load 0.75s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
@apply mr-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instock {
|
|
||||||
@apply mr-2 font-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui-icon {
|
|
||||||
@apply text-dark-cerulean mx-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
@apply mr-4 flex items-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truck {
|
|
||||||
@apply -mb-px-5 -mt-px-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truck-b2b {
|
|
||||||
@apply -mb-px-10 -mt-px-10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-can-add {
|
|
||||||
@apply text-xl text-dark-goldenrod font-semibold;
|
|
||||||
grid-area: item-can-add;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-select {
|
|
||||||
@apply flex items-center justify-end;
|
|
||||||
grid-area: item-select;
|
|
||||||
|
|
||||||
ui-select-bullet {
|
|
||||||
@apply cursor-pointer p-4 -m-4 z-dropdown;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
@apply cursor-not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
@apply text-xl text-dark-goldenrod font-semibold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@screen desktop {
|
|
||||||
.item-availability {
|
|
||||||
@apply text-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
|
||||||
import { AvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
|
||||||
import { combineLatest, Observable } from 'rxjs';
|
|
||||||
import { filter, map, shareReplay, withLatestFrom } from 'rxjs/operators';
|
|
||||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'page-purchasing-options-list-item',
|
|
||||||
templateUrl: 'purchasing-options-list-item.component.html',
|
|
||||||
styleUrls: ['purchasing-options-list-item.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class PurchasingOptionsListItemComponent {
|
|
||||||
@Input()
|
|
||||||
item: ShoppingCartItemDTO;
|
|
||||||
|
|
||||||
isSelected$ = this._store.selectedShoppingCartItems$.pipe(
|
|
||||||
map((selectedShoppingCartItems) => !!selectedShoppingCartItems?.find((item) => item.id === this.item.id))
|
|
||||||
);
|
|
||||||
|
|
||||||
fetchingAvailabilities$ = combineLatest([
|
|
||||||
this._store.takeAwayAvailabilities$,
|
|
||||||
this._store.pickUpAvailabilities$,
|
|
||||||
this._store.deliveryAvailabilities$,
|
|
||||||
this._store.deliveryDigAvailabilities$,
|
|
||||||
this._store.deliveryB2bAvailabilities$,
|
|
||||||
]).pipe(
|
|
||||||
map(
|
|
||||||
([takeAway, pickUp, delivery, digDelivery, b2bDelivery]) =>
|
|
||||||
!takeAway ||
|
|
||||||
takeAway[this.item.product.catalogProductNumber] === true ||
|
|
||||||
!pickUp ||
|
|
||||||
pickUp[this.item.product.catalogProductNumber] === true ||
|
|
||||||
!delivery ||
|
|
||||||
delivery[this.item.product.catalogProductNumber] === true ||
|
|
||||||
!digDelivery ||
|
|
||||||
digDelivery[this.item.product.catalogProductNumber] === true ||
|
|
||||||
!b2bDelivery ||
|
|
||||||
b2bDelivery[this.item.product.catalogProductNumber] === true
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
takeAwayAvailabilities$ = this._store.takeAwayAvailabilities$.pipe(
|
|
||||||
map((takeAwayAvailabilities) => {
|
|
||||||
if (takeAwayAvailabilities) {
|
|
||||||
const availability = takeAwayAvailabilities[this.item.product?.catalogProductNumber];
|
|
||||||
|
|
||||||
if (typeof availability === 'boolean') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return availability;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
pickUpAvailabilities$: Observable<AvailabilityDTO> = this._store.pickUpAvailabilities$.pipe(
|
|
||||||
map((pickUpAvailabilities) => {
|
|
||||||
if (pickUpAvailabilities) {
|
|
||||||
const availability = pickUpAvailabilities[this.item.product?.catalogProductNumber];
|
|
||||||
|
|
||||||
if (typeof availability === 'boolean') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return availability;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
deliveryAvailabilities$ = this._store.deliveryAvailabilities$.pipe(
|
|
||||||
map((shippingAvailabilities) => (!!shippingAvailabilities ? shippingAvailabilities[this.item.product?.catalogProductNumber] : [])),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
deliveryDigAvailabilities$: Observable<AvailabilityDTO> = this._store.deliveryDigAvailabilities$.pipe(
|
|
||||||
map((shippingAvailabilities) => {
|
|
||||||
if (shippingAvailabilities) {
|
|
||||||
const availability = shippingAvailabilities[this.item.product?.catalogProductNumber];
|
|
||||||
|
|
||||||
if (typeof availability === 'boolean') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return availability;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
deliveryB2bAvailabilities$ = this._store.deliveryB2bAvailabilities$.pipe(
|
|
||||||
map((shippingAvailabilities) => {
|
|
||||||
if (shippingAvailabilities) {
|
|
||||||
const availability = shippingAvailabilities[this.item.product?.catalogProductNumber];
|
|
||||||
|
|
||||||
if (typeof availability === 'boolean') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return availability;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
notAvailable$ = combineLatest([
|
|
||||||
this.fetchingAvailabilities$,
|
|
||||||
this.takeAwayAvailabilities$,
|
|
||||||
this.pickUpAvailabilities$,
|
|
||||||
this.deliveryAvailabilities$,
|
|
||||||
this.deliveryDigAvailabilities$,
|
|
||||||
this.deliveryB2bAvailabilities$,
|
|
||||||
]).pipe(
|
|
||||||
map(
|
|
||||||
([fetching, takeAway, store, delivery, deliveryDig, deliveryB2b]) =>
|
|
||||||
!fetching && !takeAway && !store && !delivery && !deliveryDig && !deliveryB2b
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
showTooltip$ = this._store.selectedFilterOption$.pipe(
|
|
||||||
withLatestFrom(this.deliveryAvailabilities$, this.deliveryDigAvailabilities$),
|
|
||||||
map(([option, delivery, deliveryDig]) => {
|
|
||||||
if (option === 'delivery') {
|
|
||||||
const deliveryAvailability = (deliveryDig as AvailabilityDTO) || (delivery as AvailabilityDTO);
|
|
||||||
|
|
||||||
const shippingPrice = deliveryAvailability?.price?.value?.value;
|
|
||||||
const catalogPrice = this.item?.availability?.price?.value?.value;
|
|
||||||
return catalogPrice < shippingPrice;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
price$ = combineLatest([this.fetchingAvailabilities$, this._store.selectedFilterOption$]).pipe(
|
|
||||||
filter(([fetching]) => !fetching),
|
|
||||||
withLatestFrom(
|
|
||||||
this.takeAwayAvailabilities$,
|
|
||||||
this.pickUpAvailabilities$,
|
|
||||||
this.deliveryAvailabilities$,
|
|
||||||
this.deliveryDigAvailabilities$,
|
|
||||||
this.deliveryB2bAvailabilities$
|
|
||||||
),
|
|
||||||
map(([[_, option], takeAway, pickUp, delivery, deliveryDig, deliveryB2b]) => {
|
|
||||||
let availability;
|
|
||||||
|
|
||||||
switch (option) {
|
|
||||||
case 'take-away':
|
|
||||||
availability = takeAway;
|
|
||||||
break;
|
|
||||||
case 'pick-up':
|
|
||||||
availability = pickUp;
|
|
||||||
break;
|
|
||||||
case 'delivery':
|
|
||||||
if (deliveryDig || delivery) {
|
|
||||||
availability = deliveryDig || delivery;
|
|
||||||
} else {
|
|
||||||
availability = deliveryB2b;
|
|
||||||
option = 'b2b-delivery';
|
|
||||||
availability.p;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return this.item.availability?.price ?? this.item.unitPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._availabilityService.getPriceForAvailability(option, this.item.availability, availability) ?? this.item.unitPrice;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
selectDisabled$ = this._store.selectedFilterOption$.pipe(map((selectedFilterOption) => !selectedFilterOption));
|
|
||||||
|
|
||||||
selectVisible$ = combineLatest([this._store.canAdd$, this._store.selectedShoppingCartItems$]).pipe(
|
|
||||||
withLatestFrom(
|
|
||||||
this._store.selectedFilterOption$,
|
|
||||||
this._store.deliveryAvailabilities$,
|
|
||||||
this._store.deliveryDigAvailabilities$,
|
|
||||||
this._store.deliveryB2bAvailabilities$,
|
|
||||||
this._store.fetchingAvailabilities$
|
|
||||||
),
|
|
||||||
map(([[canAdd, items], option, delivery, deliveryDig, deliveryB2b, fetching]) => {
|
|
||||||
if (!option || fetching) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select immer sichtbar bei ausgewählten Items
|
|
||||||
if (items?.find((item) => item.product?.catalogProductNumber === this.item.product?.catalogProductNumber)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select nur anzeigen, wenn ein anderes ausgewähltes Item die gleiche Verfügbarkeit hat (B2B Versand z.B.)
|
|
||||||
if (items?.length > 0 && option === 'delivery' && canAdd[this.item.product.catalogProductNumber]?.status < 2) {
|
|
||||||
if (items.every((item) => delivery[item.product?.catalogProductNumber]) && delivery[this.item.product?.catalogProductNumber]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
items.every((item) => deliveryDig[item.product?.catalogProductNumber]) &&
|
|
||||||
deliveryDig[this.item.product?.catalogProductNumber]
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
items.every((item) => deliveryB2b[item.product?.catalogProductNumber]) &&
|
|
||||||
deliveryB2b[this.item.product?.catalogProductNumber]
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return canAdd && canAdd[this.item.product.catalogProductNumber]?.status < 2;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
canAdd$ = this._store.canAdd$.pipe(
|
|
||||||
filter((canAdd) => !!this.item && !!canAdd),
|
|
||||||
map((canAdd) => !!canAdd[this.item.product.catalogProductNumber]?.message)
|
|
||||||
);
|
|
||||||
|
|
||||||
quantityRange$ = combineLatest([this._store.selectedFilterOption$, this.takeAwayAvailabilities$]).pipe(
|
|
||||||
map(([option, availability]) => (option === 'take-away' ? (availability as AvailabilityDTO)?.inStock : 999))
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(private _store: PurchasingOptionsListModalStore, private _availabilityService: DomainAvailabilityService) {}
|
|
||||||
|
|
||||||
selected(value: boolean) {
|
|
||||||
this._store.selectShoppingCartItem([this.item], value);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeQuantity(quantity: number) {
|
|
||||||
if (quantity === 0) {
|
|
||||||
this._store.removeShoppingCartItem(this.item);
|
|
||||||
} else {
|
|
||||||
this._store.updateItemQuantity({ itemId: this.item.id, quantity });
|
|
||||||
this._store.loadAvailabilities({ items: [{ ...this.item, quantity }] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<div class="options">
|
|
||||||
<page-take-away-option-list></page-take-away-option-list>
|
|
||||||
<page-pick-up-option-list></page-pick-up-option-list>
|
|
||||||
<page-delivery-option-list></page-delivery-option-list>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="items" *ngIf="shoppingCartItems$ | async; let shoppingCartItems">
|
|
||||||
<div class="item-actions">
|
|
||||||
<ng-container>
|
|
||||||
<button
|
|
||||||
*ngIf="!(allShoppingCartItemsSelected$ | async); else unselectAll"
|
|
||||||
class="cta-select-all"
|
|
||||||
[disabled]="selectAllCtaDisabled$ | async"
|
|
||||||
(click)="selectAll(shoppingCartItems, true)"
|
|
||||||
>
|
|
||||||
Alle auswählen
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-template #unselectAll>
|
|
||||||
<button class="cta-select-all" [disabled]="selectAllCtaDisabled$ | async" (click)="selectAll(shoppingCartItems, false)">
|
|
||||||
Alle abwählen
|
|
||||||
</button>
|
|
||||||
</ng-template>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
{{ (selectedShoppingCartItems$ | async)?.length || 0 }} von {{ shoppingCartItems?.length || 0 }} Artikeln
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-list scroll-bar" *ngIf="shoppingCartItems?.length > 0; else emptyMessage">
|
|
||||||
<hr />
|
|
||||||
<ng-container *ngFor="let item of shoppingCartItems">
|
|
||||||
<page-purchasing-options-list-item [item]="item"></page-purchasing-options-list-item>
|
|
||||||
<hr />
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template #emptyMessage>
|
|
||||||
<div class="empty-message">Keine Artikel für die ausgewählte Kaufoption verfügbar</div>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<button class="cta-apply" [disabled]="applyCtaDisabled$ | async" (click)="apply()">
|
|
||||||
<ui-spinner [show]="addItemsLoader$ | async">
|
|
||||||
Übernehmen
|
|
||||||
</ui-spinner>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
:host {
|
|
||||||
@apply block box-border;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
@apply flex flex-row box-border text-center justify-center mt-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
min-height: 440px;
|
|
||||||
|
|
||||||
.item-actions {
|
|
||||||
@apply text-right;
|
|
||||||
|
|
||||||
.cta-select-all {
|
|
||||||
@apply text-brand bg-transparent text-base font-bold outline-none border-none px-4 py-4 -mr-4;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
@apply text-inactive-branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-list {
|
|
||||||
@apply overflow-y-scroll overflow-x-hidden -ml-4;
|
|
||||||
max-height: calc(100vh - 580px);
|
|
||||||
width: calc(100% + 2rem);
|
|
||||||
|
|
||||||
page-purchasing-options-list-item {
|
|
||||||
@apply px-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-message {
|
|
||||||
@apply text-inactive-branch my-8 text-center font-bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
@apply flex justify-center mt-8;
|
|
||||||
|
|
||||||
.cta-apply {
|
|
||||||
@apply text-white border-2 border-solid border-brand bg-brand font-bold text-lg px-4 py-2 rounded-full;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
@apply bg-inactive-branch border-inactive-branch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
|
||||||
import { DomainCheckoutService } from '@domain/checkout';
|
|
||||||
import { ShoppingCartItemDTO, UpdateShoppingCartItemDTO } from '@swagger/checkout';
|
|
||||||
import { UiErrorModalComponent, UiModalRef, UiModalService } from '@ui/modal';
|
|
||||||
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
|
|
||||||
import { debounceTime, filter, first, map, shareReplay, takeUntil, withLatestFrom } from 'rxjs/operators';
|
|
||||||
import { PurchasingOptionsListModalData } from './purchasing-options-list-modal.data';
|
|
||||||
import { PurchasingOptionsListModalStore } from './purchasing-options-list-modal.store';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'page-purchasing-options-list-modal',
|
|
||||||
templateUrl: 'purchasing-options-list-modal.component.html',
|
|
||||||
styleUrls: ['purchasing-options-list-modal.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
providers: [PurchasingOptionsListModalStore],
|
|
||||||
})
|
|
||||||
export class PurchasingOptionsListModalComponent implements OnInit {
|
|
||||||
private _onDestroy$ = new Subject();
|
|
||||||
|
|
||||||
addItemsLoader$ = new BehaviorSubject<boolean>(false);
|
|
||||||
|
|
||||||
shoppingCartItems$ = combineLatest([
|
|
||||||
this._store.fetchingAvailabilities$,
|
|
||||||
this._store.selectedFilterOption$,
|
|
||||||
this._store.shoppingCartItems$,
|
|
||||||
]).pipe(
|
|
||||||
withLatestFrom(
|
|
||||||
this._store.takeAwayAvailabilities$,
|
|
||||||
this._store.pickUpAvailabilities$,
|
|
||||||
this._store.deliveryAvailabilities$,
|
|
||||||
this._store.deliveryDigAvailabilities$,
|
|
||||||
this._store.deliveryB2bAvailabilities$
|
|
||||||
),
|
|
||||||
map(
|
|
||||||
([
|
|
||||||
[_, selectedFilterOption, shoppingCartItems],
|
|
||||||
takeAwayAvailability,
|
|
||||||
pickUpAvailability,
|
|
||||||
deliveryAvailability,
|
|
||||||
deliveryDigAvailability,
|
|
||||||
deliveryB2bAvailability,
|
|
||||||
]) => {
|
|
||||||
if (!!takeAwayAvailability && !!pickUpAvailability && !!deliveryAvailability) {
|
|
||||||
switch (selectedFilterOption) {
|
|
||||||
case 'take-away':
|
|
||||||
return shoppingCartItems.filter((item) => !!takeAwayAvailability[item.product?.catalogProductNumber]);
|
|
||||||
case 'pick-up':
|
|
||||||
return shoppingCartItems.filter((item) => !!pickUpAvailability[item.product?.catalogProductNumber]);
|
|
||||||
case 'delivery':
|
|
||||||
return shoppingCartItems.filter(
|
|
||||||
(item) =>
|
|
||||||
!!deliveryAvailability[item.product?.catalogProductNumber] ||
|
|
||||||
!!deliveryDigAvailability[item.product?.catalogProductNumber] ||
|
|
||||||
!!deliveryB2bAvailability[item.product?.catalogProductNumber]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shoppingCartItems;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
map((shoppingCartItems) => shoppingCartItems?.sort((a, b) => a.product?.name.localeCompare(b.product?.name))),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
selectedShoppingCartItems$ = this._store.selectedShoppingCartItems$;
|
|
||||||
|
|
||||||
allShoppingCartItemsSelected$ = combineLatest([this.shoppingCartItems$, this.selectedShoppingCartItems$]).pipe(
|
|
||||||
map(
|
|
||||||
([shoppingCartItems, selectedShoppingCartItems]) =>
|
|
||||||
shoppingCartItems.every((item) => selectedShoppingCartItems.find((i) => item.id === i.id)) && shoppingCartItems?.length > 0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
canAddItems$ = this._store.canAdd$.pipe(
|
|
||||||
map((canAdd) => {
|
|
||||||
for (const key in canAdd) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(canAdd, key)) {
|
|
||||||
if (!!canAdd[key]?.message) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
shareReplay()
|
|
||||||
);
|
|
||||||
|
|
||||||
selectAllCtaDisabled$ = combineLatest([this._store.selectedFilterOption$, this.canAddItems$]).pipe(
|
|
||||||
withLatestFrom(this.shoppingCartItems$),
|
|
||||||
map(([[selectedFilterOption, canAddItems], items]) => !selectedFilterOption || items?.length === 0 || !canAddItems)
|
|
||||||
);
|
|
||||||
|
|
||||||
applyCtaDisabled$ = combineLatest([this.addItemsLoader$, this._store.selectedFilterOption$, this._store.selectedShoppingCartItems$]).pipe(
|
|
||||||
withLatestFrom(this.shoppingCartItems$),
|
|
||||||
map(
|
|
||||||
([[addItemsLoader, selectedFilterOption, selectedShoppingCartItems], shoppingCartItems]) =>
|
|
||||||
addItemsLoader || !selectedFilterOption || shoppingCartItems?.length === 0 || selectedShoppingCartItems?.length === 0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private _modalRef: UiModalRef<any, PurchasingOptionsListModalData>,
|
|
||||||
private _modal: UiModalService,
|
|
||||||
private _store: PurchasingOptionsListModalStore,
|
|
||||||
private _availability: DomainAvailabilityService,
|
|
||||||
private _checkout: DomainCheckoutService
|
|
||||||
) {
|
|
||||||
this._store.shoppingCartItems = _modalRef.data.shoppingCartItems;
|
|
||||||
this._store.customerFeatures = _modalRef.data.customerFeatures;
|
|
||||||
this._store.processId = _modalRef.data.processId;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this._store.loadBranches();
|
|
||||||
|
|
||||||
// Beim Wechsel der ausgewählten Filteroption oder der Branches die Auswahl leeren
|
|
||||||
combineLatest([this._store.selectedFilterOption$, this._store.selectedTakeAwayBranch$, this._store.selectedPickUpBranch$])
|
|
||||||
.pipe(takeUntil(this._onDestroy$))
|
|
||||||
.subscribe(() => this._store.clearSelectedShoppingCartItems());
|
|
||||||
|
|
||||||
this._store.selectedFilterOption$
|
|
||||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.shoppingCartItems$))
|
|
||||||
.subscribe(([option, items]) => this.checkCanAdd(option, items));
|
|
||||||
|
|
||||||
this._store.fetchingAvailabilities$
|
|
||||||
.pipe(
|
|
||||||
takeUntil(this._onDestroy$),
|
|
||||||
debounceTime(250),
|
|
||||||
filter((fetching) => !fetching),
|
|
||||||
withLatestFrom(this.shoppingCartItems$, this._store.selectedFilterOption$)
|
|
||||||
)
|
|
||||||
.subscribe(([_, items, option]) => this.checkCanAdd(option, items));
|
|
||||||
|
|
||||||
this.canAddItems$
|
|
||||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this.shoppingCartItems$, this._store.selectedFilterOption$))
|
|
||||||
.subscribe(([showSelectAll, items, option]) => {
|
|
||||||
if (items?.length > 0 && this._store.lastSelectedFilterOption$.value !== option) {
|
|
||||||
this.selectAll(items, showSelectAll && !!option);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nach dem Übernehmen von Items wird eine neue CanAdd Abfrage ausgeführt, in diesem Fall soll aber nicht alles ausgewählt werden
|
|
||||||
this._store.lastSelectedFilterOption$.next(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCanAdd(selectedFilterOption: string, items: ShoppingCartItemDTO[]) {
|
|
||||||
if (!!selectedFilterOption && items?.length > 0) {
|
|
||||||
this._store.checkCanAddItems(items);
|
|
||||||
} else {
|
|
||||||
this._store.patchState({ canAdd: {} });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectAll(items: ShoppingCartItemDTO[], value: boolean) {
|
|
||||||
this._store.selectShoppingCartItem(items, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async apply() {
|
|
||||||
this.addItemsLoader$.next(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const shoppingCartItems = await this._store.shoppingCartItems$.pipe(first()).toPromise();
|
|
||||||
const items = await this._store.selectedShoppingCartItems$.pipe(first()).toPromise();
|
|
||||||
const takeAwayAvailabilities = await this._store.takeAwayAvailabilities$.pipe(first()).toPromise();
|
|
||||||
const pickupAvailabilities = await this._store.pickUpAvailabilities$.pipe(first()).toPromise();
|
|
||||||
const deliveryAvailabilities = await this._store.deliveryAvailabilities$.pipe(first()).toPromise();
|
|
||||||
const deliveryB2bAvailabilities = await this._store.deliveryB2bAvailabilities$.pipe(first()).toPromise();
|
|
||||||
const deliveryDigAvailabilities = await this._store.deliveryDigAvailabilities$.pipe(first()).toPromise();
|
|
||||||
const selectedTakeAwayBranch = await this._store.selectedTakeAwayBranch$.pipe(first()).toPromise();
|
|
||||||
const selectedPickUpBranch = await this._store.selectedPickUpBranch$.pipe(first()).toPromise();
|
|
||||||
let option = this._store.selectedFilterOption;
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
let availability;
|
|
||||||
switch (this._store.selectedFilterOption) {
|
|
||||||
case 'take-away':
|
|
||||||
availability = takeAwayAvailabilities[item.product.catalogProductNumber];
|
|
||||||
break;
|
|
||||||
case 'pick-up':
|
|
||||||
availability = pickupAvailabilities[item.product.catalogProductNumber];
|
|
||||||
break;
|
|
||||||
case 'delivery':
|
|
||||||
if (
|
|
||||||
deliveryDigAvailabilities[item.product.catalogProductNumber] &&
|
|
||||||
deliveryB2bAvailabilities[item.product.catalogProductNumber] &&
|
|
||||||
deliveryAvailabilities[item.product.catalogProductNumber]
|
|
||||||
) {
|
|
||||||
availability = deliveryAvailabilities[item.product.catalogProductNumber];
|
|
||||||
} else if (deliveryDigAvailabilities[item.product.catalogProductNumber]) {
|
|
||||||
availability = deliveryDigAvailabilities[item.product.catalogProductNumber];
|
|
||||||
} else if (deliveryB2bAvailabilities[item.product.catalogProductNumber]) {
|
|
||||||
availability = deliveryB2bAvailabilities[item.product.catalogProductNumber];
|
|
||||||
option = 'b2b-delivery';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const price = this._availability.getPriceForAvailability(option, item.availability, availability);
|
|
||||||
|
|
||||||
// Negative Preise und nicht vorhandene Availability ignorieren
|
|
||||||
if (price?.value?.value < 0 || !availability) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateItem: UpdateShoppingCartItemDTO = {
|
|
||||||
quantity: item.quantity,
|
|
||||||
availability: {
|
|
||||||
...availability,
|
|
||||||
price: price ? price : item.unitPrice,
|
|
||||||
},
|
|
||||||
promotion: item?.promotion?.points ? { points: item.promotion.points } : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (this._store.selectedFilterOption) {
|
|
||||||
case 'take-away':
|
|
||||||
updateItem.destination = {
|
|
||||||
data: { target: 1, targetBranch: { id: selectedTakeAwayBranch.id } },
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'pick-up':
|
|
||||||
updateItem.destination = {
|
|
||||||
data: { target: 1, targetBranch: { id: selectedPickUpBranch.id } },
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'delivery':
|
|
||||||
case 'dig-delivery':
|
|
||||||
case 'b2b-delivery':
|
|
||||||
updateItem.destination = {
|
|
||||||
data: { target: 2, logistician: availability?.logistician },
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._checkout
|
|
||||||
.updateItemInShoppingCart({
|
|
||||||
processId: this._modalRef.data.processId,
|
|
||||||
shoppingCartItemId: item.id,
|
|
||||||
update: {
|
|
||||||
...updateItem,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
const remainingItems = shoppingCartItems.filter((i) => !items.find((j) => i.id === j.id));
|
|
||||||
this._store.shoppingCartItems = [...remainingItems];
|
|
||||||
|
|
||||||
this._store.clearSelectedShoppingCartItems();
|
|
||||||
|
|
||||||
if (remainingItems?.length === 0) {
|
|
||||||
this._modalRef.close();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
this._modal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim Hinzufügen zum Warenkorb' });
|
|
||||||
} finally {
|
|
||||||
this.addItemsLoader$.next(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const shoppingCartItems = await this.shoppingCartItems$.pipe(first()).toPromise();
|
|
||||||
if (shoppingCartItems?.length > 0) {
|
|
||||||
this._store.checkCanAddItems(shoppingCartItems);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { ShoppingCartItemDTO } from '@swagger/checkout';
|
|
||||||
|
|
||||||
export interface PurchasingOptionsListModalData {
|
|
||||||
processId: number;
|
|
||||||
shoppingCartItems?: ShoppingCartItemDTO[];
|
|
||||||
customerFeatures: { [key: string]: string };
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
import { PurchasingOptionsListModalComponent } from './purchasing-options-list-modal.component';
|
|
||||||
import { UiIconModule } from '@ui/icon';
|
|
||||||
import { ProductImageModule } from '@cdn/product-image';
|
|
||||||
import { UiCommonModule } from '@ui/common';
|
|
||||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
|
||||||
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
|
|
||||||
import { PickUpOptionListComponent } from './pick-up-option/pick-up-option-list.component';
|
|
||||||
import { TakeAwayOptionListComponent } from './take-away-option/take-away-option-list.component';
|
|
||||||
import { DeliveryOptionListComponent } from './delivery-option/delivery-option-list.component';
|
|
||||||
import { PurchasingOptionsListItemComponent } from './purchasing-options-list-item/purchasing-options-list-item.component';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { UiBranchDropdownModule } from '@ui/branch-dropdown';
|
|
||||||
import { UiTooltipModule } from '@ui/tooltip';
|
|
||||||
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
UiCommonModule,
|
|
||||||
UiIconModule,
|
|
||||||
UiSelectBulletModule,
|
|
||||||
UiQuantityDropdownModule,
|
|
||||||
ProductImageModule,
|
|
||||||
UiBranchDropdownModule,
|
|
||||||
UiTooltipModule,
|
|
||||||
UiSpinnerModule,
|
|
||||||
],
|
|
||||||
exports: [PurchasingOptionsListModalComponent],
|
|
||||||
declarations: [
|
|
||||||
PurchasingOptionsListModalComponent,
|
|
||||||
PurchasingOptionsListItemComponent,
|
|
||||||
PickUpOptionListComponent,
|
|
||||||
TakeAwayOptionListComponent,
|
|
||||||
DeliveryOptionListComponent,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class PurchasingOptionsListModalModule {}
|
|
||||||
@@ -1,598 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
|
||||||
import { AvailabilityDTO, BranchDTO, ShoppingCartItemDTO } from '@swagger/checkout';
|
|
||||||
import { map, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators';
|
|
||||||
import { DomainAvailabilityService } from '@domain/availability';
|
|
||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
|
||||||
import { DomainCheckoutService } from '@domain/checkout';
|
|
||||||
import { ApplicationService } from '@core/application';
|
|
||||||
|
|
||||||
interface PurchasingOptionsListModalState {
|
|
||||||
processId: number;
|
|
||||||
shoppingCartItems: ShoppingCartItemDTO[];
|
|
||||||
selectedFilterOption: string;
|
|
||||||
takeAwayAvailabilities: { [key: string]: AvailabilityDTO | true };
|
|
||||||
pickUpAvailabilities: { [key: string]: AvailabilityDTO | true };
|
|
||||||
deliveryAvailabilities: { [key: string]: AvailabilityDTO | true };
|
|
||||||
deliveryB2bAvailabilities: { [key: string]: AvailabilityDTO | true };
|
|
||||||
deliveryDigAvailabilities: { [key: string]: AvailabilityDTO | true };
|
|
||||||
customerFeatures: { [key: string]: string };
|
|
||||||
canAdd: { [key: string]: { message: string; status: number } };
|
|
||||||
selectedShoppingCartItems: ShoppingCartItemDTO[];
|
|
||||||
branches: BranchDTO[];
|
|
||||||
currentBranch: BranchDTO;
|
|
||||||
selectedTakeAwayBranch: BranchDTO;
|
|
||||||
selectedPickUpBranch: BranchDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PurchasingOptionsListModalStore extends ComponentStore<PurchasingOptionsListModalState> {
|
|
||||||
lastSelectedFilterOption$ = new BehaviorSubject<string>(undefined);
|
|
||||||
|
|
||||||
branches$ = this.select((s) => s.branches);
|
|
||||||
currentBranch$ = this.select((s) => s.currentBranch);
|
|
||||||
takeAwayAvailabilities$ = this.select((s) => s.takeAwayAvailabilities);
|
|
||||||
pickUpAvailabilities$ = this.select((s) => s.pickUpAvailabilities);
|
|
||||||
deliveryAvailabilities$ = this.select((s) => s.deliveryAvailabilities);
|
|
||||||
deliveryB2bAvailabilities$ = this.select((s) => s.deliveryB2bAvailabilities);
|
|
||||||
canAdd$ = this.select((s) => s.canAdd);
|
|
||||||
deliveryDigAvailabilities$ = this.select((s) => s.deliveryDigAvailabilities);
|
|
||||||
|
|
||||||
shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
|
|
||||||
|
|
||||||
set shoppingCartItems(shoppingCartItems: ShoppingCartItemDTO[]) {
|
|
||||||
shoppingCartItems = shoppingCartItems.sort((a, b) => a.product?.name.localeCompare(b.product.name));
|
|
||||||
this.patchState({ shoppingCartItems });
|
|
||||||
}
|
|
||||||
|
|
||||||
processId$ = this.select((s) => s.processId);
|
|
||||||
|
|
||||||
set processId(processId: number) {
|
|
||||||
this.patchState({ processId });
|
|
||||||
}
|
|
||||||
|
|
||||||
customerFeatures$ = this.select((s) => s.customerFeatures);
|
|
||||||
|
|
||||||
set customerFeatures(customerFeatures: { [key: string]: string }) {
|
|
||||||
this.patchState({ customerFeatures });
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedFilterOption$ = this.select((s) => s.selectedFilterOption);
|
|
||||||
|
|
||||||
set selectedFilterOption(selectedFilterOption: string) {
|
|
||||||
this.patchState({ selectedFilterOption });
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedFilterOption() {
|
|
||||||
return this.get((s) => s.selectedFilterOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedShoppingCartItems$ = this.select((s) => s.selectedShoppingCartItems);
|
|
||||||
|
|
||||||
get selectedShoppingCartItems() {
|
|
||||||
return this.get((s) => s.selectedShoppingCartItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedTakeAwayBranch$ = this.select((s) => s.selectedTakeAwayBranch);
|
|
||||||
|
|
||||||
set selectedTakeAwayBranch(selectedTakeAwayBranch: BranchDTO) {
|
|
||||||
this.patchState({ selectedTakeAwayBranch });
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedPickUpBranch$ = this.select((s) => s.selectedPickUpBranch);
|
|
||||||
|
|
||||||
set selectedPickUpBranch(selectedPickUpBranch: BranchDTO) {
|
|
||||||
this.patchState({ selectedPickUpBranch });
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchingAvailabilities$ = combineLatest([this.takeAwayAvailabilities$, this.pickUpAvailabilities$, this.deliveryAvailabilities$]).pipe(
|
|
||||||
map(([takeAway, pickUp, delivery]) => {
|
|
||||||
const fetchingCheck = (obj) => {
|
|
||||||
for (const key in obj) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
||||||
const element = obj[key];
|
|
||||||
if (typeof element === 'boolean') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
return !takeAway || fetchingCheck(takeAway) || !pickUp || fetchingCheck(pickUp) || !delivery || fetchingCheck(delivery);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private _availabilityService: DomainAvailabilityService,
|
|
||||||
private _checkoutService: DomainCheckoutService,
|
|
||||||
private _application: ApplicationService
|
|
||||||
) {
|
|
||||||
super({
|
|
||||||
processId: undefined,
|
|
||||||
shoppingCartItems: [],
|
|
||||||
selectedFilterOption: undefined,
|
|
||||||
pickUpAvailabilities: undefined,
|
|
||||||
deliveryAvailabilities: undefined,
|
|
||||||
takeAwayAvailabilities: undefined,
|
|
||||||
deliveryB2bAvailabilities: undefined,
|
|
||||||
deliveryDigAvailabilities: undefined,
|
|
||||||
selectedShoppingCartItems: [],
|
|
||||||
branches: [],
|
|
||||||
currentBranch: undefined,
|
|
||||||
selectedTakeAwayBranch: undefined,
|
|
||||||
selectedPickUpBranch: undefined,
|
|
||||||
customerFeatures: undefined,
|
|
||||||
canAdd: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAvailabilities(options: { items?: ShoppingCartItemDTO[] }) {
|
|
||||||
const shoppingCartItems = options.items ?? this.get((s) => s.shoppingCartItems);
|
|
||||||
|
|
||||||
for (const item of shoppingCartItems) {
|
|
||||||
this.loadTakeAwayAvailability({ item });
|
|
||||||
this.loadPickUpAvailability({ item });
|
|
||||||
this.loadDeliveryAvailability({ item });
|
|
||||||
this.loadDeliveryB2bAvailability({ item });
|
|
||||||
this.loadDeliveryDigAvailability({ item });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly setAvailabilityFetching = this.updater((state, { name, id, fetching }: { name: string; id: string; fetching?: boolean }) => {
|
|
||||||
const availability = { ...state[name] };
|
|
||||||
|
|
||||||
if (fetching) {
|
|
||||||
availability[id] = fetching;
|
|
||||||
} else {
|
|
||||||
delete availability[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
[name]: {
|
|
||||||
...availability,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
readonly setAvailability = this.updater((state, { name, availability }: { name: string; availability: any }) => {
|
|
||||||
const av = { ...state[name] };
|
|
||||||
|
|
||||||
if (this._availabilityService.isAvailable({ availability })) {
|
|
||||||
av[availability.itemId] = availability;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
[name]: av,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
loadPickUpAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
|
||||||
options$.pipe(
|
|
||||||
withLatestFrom(this.selectedPickUpBranch$),
|
|
||||||
mergeMap(([options, branch]) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'pickUpAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._availabilityService
|
|
||||||
.getPickUpAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: +options.item.product.catalogProductNumber,
|
|
||||||
ean: options.item.product.ean,
|
|
||||||
price: options.item.availability.price,
|
|
||||||
},
|
|
||||||
branch,
|
|
||||||
quantity: options.item.quantity,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
map((av) => {
|
|
||||||
if (av?.length > 0) {
|
|
||||||
if (av[1].availableFor) {
|
|
||||||
if ((av[1].availableFor & 2) === 2) {
|
|
||||||
return av[0];
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return av[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
tapResponse(
|
|
||||||
(availability) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'pickUpAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
});
|
|
||||||
this.setAvailability({
|
|
||||||
name: 'pickUpAvailabilities',
|
|
||||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'pickUpAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: false,
|
|
||||||
});
|
|
||||||
this.setAvailability({ name: 'pickUpAvailabilities', availability: {} });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
loadDeliveryAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
|
||||||
options$.pipe(
|
|
||||||
mergeMap((options) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._availabilityService
|
|
||||||
.getDeliveryAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: +options.item.product.catalogProductNumber,
|
|
||||||
ean: options.item.product.ean,
|
|
||||||
price: options.item.availability.price,
|
|
||||||
},
|
|
||||||
quantity: options.item.quantity,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
tapResponse(
|
|
||||||
(availability) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
});
|
|
||||||
this.setAvailability({
|
|
||||||
name: 'deliveryAvailabilities',
|
|
||||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: false,
|
|
||||||
});
|
|
||||||
this.setAvailability({ name: 'deliveryAvailabilities', availability: {} });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
loadDeliveryB2bAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
|
||||||
options$.pipe(
|
|
||||||
mergeMap((options) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryB2bAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._availabilityService
|
|
||||||
.getB2bDeliveryAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: +options.item.product.catalogProductNumber,
|
|
||||||
ean: options.item.product.ean,
|
|
||||||
price: options.item.availability.price,
|
|
||||||
},
|
|
||||||
quantity: options.item.quantity,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
tapResponse(
|
|
||||||
(availability) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryB2bAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setAvailability({
|
|
||||||
name: 'deliveryB2bAvailabilities',
|
|
||||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryB2bAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: false,
|
|
||||||
});
|
|
||||||
this.setAvailability({ name: 'deliveryB2bAvailabilities', availability: {} });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
loadDeliveryDigAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
|
||||||
options$.pipe(
|
|
||||||
mergeMap((options) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryDigAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._availabilityService
|
|
||||||
.getDigDeliveryAvailability({
|
|
||||||
item: {
|
|
||||||
itemId: +options.item.product.catalogProductNumber,
|
|
||||||
ean: options.item.product.ean,
|
|
||||||
price: options.item.availability.price,
|
|
||||||
},
|
|
||||||
quantity: options.item.quantity,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
tapResponse(
|
|
||||||
(availability) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryDigAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
});
|
|
||||||
this.setAvailability({
|
|
||||||
name: 'deliveryDigAvailabilities',
|
|
||||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'deliveryDigAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: false,
|
|
||||||
});
|
|
||||||
this.setAvailability({ name: 'deliveryDigAvailabilities', availability: {} });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
loadTakeAwayAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
|
|
||||||
options$.pipe(
|
|
||||||
withLatestFrom(this.selectedTakeAwayBranch$),
|
|
||||||
mergeMap(([options, branch]) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'takeAwayAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._availabilityService
|
|
||||||
.getTakeAwayAvailabilityByBranch({
|
|
||||||
itemId: +options.item.product.catalogProductNumber,
|
|
||||||
price: options.item.availability.price,
|
|
||||||
quantity: options.item.quantity,
|
|
||||||
branch,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
tapResponse(
|
|
||||||
(availability) => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'takeAwayAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
});
|
|
||||||
this.setAvailability({
|
|
||||||
name: 'takeAwayAvailabilities',
|
|
||||||
availability: { ...availability, itemId: options.item.product.catalogProductNumber },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.setAvailabilityFetching({
|
|
||||||
name: 'takeAwayAvailabilities',
|
|
||||||
id: options.item.product.catalogProductNumber,
|
|
||||||
fetching: false,
|
|
||||||
});
|
|
||||||
this.setAvailability({ name: 'takeAwayAvailabilities', availability: {} });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
getCurrentBranch() {
|
|
||||||
return combineLatest([this._application.getSelectedBranch$(), this._availabilityService.getDefaultBranch()]).pipe(
|
|
||||||
map(([selectedBranch, defaultBranch]) => selectedBranch || defaultBranch)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadBranches = this.effect(($) =>
|
|
||||||
$.pipe(
|
|
||||||
switchMap(() =>
|
|
||||||
this._availabilityService.getBranches().pipe(
|
|
||||||
map((branches) =>
|
|
||||||
branches.filter(
|
|
||||||
(branch) => branch.status === 1 && branch.branchType === 1 && branch.isOnline === true && branch.isShippingEnabled === true
|
|
||||||
)
|
|
||||||
),
|
|
||||||
withLatestFrom(this.getCurrentBranch()),
|
|
||||||
tapResponse(
|
|
||||||
([branches, currentBranch]) => {
|
|
||||||
this.patchState({
|
|
||||||
branches,
|
|
||||||
selectedTakeAwayBranch: currentBranch,
|
|
||||||
selectedPickUpBranch: currentBranch,
|
|
||||||
currentBranch,
|
|
||||||
});
|
|
||||||
this.loadAvailabilities({});
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
this.patchState({
|
|
||||||
branches: [],
|
|
||||||
selectedTakeAwayBranch: undefined,
|
|
||||||
selectedPickUpBranch: undefined,
|
|
||||||
currentBranch: undefined,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
checkCanAddItems = this.effect((items$: Observable<ShoppingCartItemDTO[]>) =>
|
|
||||||
items$.pipe(
|
|
||||||
withLatestFrom(
|
|
||||||
this.processId$,
|
|
||||||
this.selectedFilterOption$,
|
|
||||||
this.takeAwayAvailabilities$,
|
|
||||||
this.pickUpAvailabilities$,
|
|
||||||
this.deliveryAvailabilities$,
|
|
||||||
this.deliveryB2bAvailabilities$,
|
|
||||||
this.deliveryDigAvailabilities$
|
|
||||||
),
|
|
||||||
mergeMap(([items, processId, selectedOption, takeAway, pickUp, delivery, deliveryB2b, deliveryDig]) => {
|
|
||||||
let orderType: string;
|
|
||||||
const payload = items.map((item) => {
|
|
||||||
switch (selectedOption) {
|
|
||||||
case 'take-away':
|
|
||||||
orderType = 'Rücklage';
|
|
||||||
return {
|
|
||||||
availabilities: [this.getOlaAvailability(takeAway[item.product.catalogProductNumber], item)],
|
|
||||||
id: item.product.catalogProductNumber,
|
|
||||||
};
|
|
||||||
case 'pick-up':
|
|
||||||
orderType = 'Abholung';
|
|
||||||
return {
|
|
||||||
availabilities: [this.getOlaAvailability(pickUp[item.product.catalogProductNumber], item)],
|
|
||||||
id: item.product.catalogProductNumber,
|
|
||||||
};
|
|
||||||
case 'delivery':
|
|
||||||
orderType = 'Versand';
|
|
||||||
if (
|
|
||||||
deliveryDig[item.product.catalogProductNumber] &&
|
|
||||||
deliveryB2b[item.product.catalogProductNumber] &&
|
|
||||||
delivery[item.product.catalogProductNumber]
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
availabilities: [this.getOlaAvailability(delivery[item.product.catalogProductNumber], item)],
|
|
||||||
id: item.product.catalogProductNumber,
|
|
||||||
};
|
|
||||||
} else if (deliveryDig[item.product.catalogProductNumber]) {
|
|
||||||
return {
|
|
||||||
availabilities: [this.getOlaAvailability(deliveryDig[item.product.catalogProductNumber], item)],
|
|
||||||
id: item.product.catalogProductNumber,
|
|
||||||
};
|
|
||||||
} else if (deliveryB2b[item.product.catalogProductNumber]) {
|
|
||||||
return {
|
|
||||||
availabilities: [this.getOlaAvailability(deliveryB2b[item.product.catalogProductNumber], item)],
|
|
||||||
id: item.product.catalogProductNumber,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._checkoutService.canAddItems({ processId, payload, orderType }).pipe(
|
|
||||||
tapResponse(
|
|
||||||
(result: any) => {
|
|
||||||
const canAdd = {};
|
|
||||||
|
|
||||||
result?.forEach((r) => {
|
|
||||||
canAdd[r.id] = { message: r.message, status: r.status };
|
|
||||||
});
|
|
||||||
|
|
||||||
this.patchState({ canAdd });
|
|
||||||
},
|
|
||||||
(error: Error) => {
|
|
||||||
const canAdd = {};
|
|
||||||
|
|
||||||
items?.forEach((i) => {
|
|
||||||
canAdd[i.product?.catalogProductNumber] = { message: error?.message };
|
|
||||||
});
|
|
||||||
|
|
||||||
this.patchState({ canAdd });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
getOlaAvailability(availability: AvailabilityDTO, item: ShoppingCartItemDTO) {
|
|
||||||
return {
|
|
||||||
qty: item.quantity,
|
|
||||||
ean: item.product.ean,
|
|
||||||
itemId: item.product.catalogProductNumber,
|
|
||||||
format: item.product.format,
|
|
||||||
at: availability?.estimatedShippingDate,
|
|
||||||
isPrebooked: availability?.isPrebooked,
|
|
||||||
status: availability?.availabilityType,
|
|
||||||
logisticianId: availability?.logistician?.id,
|
|
||||||
price: availability?.price,
|
|
||||||
ssc: availability?.ssc,
|
|
||||||
sscText: availability?.sscText,
|
|
||||||
supplierId: availability?.supplier?.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly updateItemQuantity = this.updater((state, value: { itemId: number; quantity: number }) => {
|
|
||||||
const itemToUpdate = state.shoppingCartItems.find((item) => item.id === value.itemId);
|
|
||||||
const otherItems = state.shoppingCartItems.filter((item) => item.id !== value.itemId);
|
|
||||||
const updatedItem = { ...itemToUpdate, quantity: value.quantity };
|
|
||||||
const shoppingCartItems = [...otherItems, updatedItem].sort((a, b) => a.product?.name.localeCompare(b.product.name));
|
|
||||||
|
|
||||||
// Ausgewählte Items auch aktualisieren
|
|
||||||
let selectedShoppingCartItems = state.selectedShoppingCartItems;
|
|
||||||
if (state.selectedShoppingCartItems.find((item) => item.id === value.itemId)) {
|
|
||||||
const selectedItems = state.selectedShoppingCartItems.filter((item) => item.id !== value.itemId);
|
|
||||||
selectedShoppingCartItems = [...selectedItems, updatedItem].sort((a, b) => a.product?.name.localeCompare(b.product.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shoppingCartItems,
|
|
||||||
selectedShoppingCartItems,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
async removeShoppingCartItem(item: ShoppingCartItemDTO) {
|
|
||||||
const items = this.get((s) => s.shoppingCartItems);
|
|
||||||
const processId = this.get((s) => s.processId);
|
|
||||||
|
|
||||||
await this._checkoutService
|
|
||||||
.updateItemInShoppingCart({
|
|
||||||
processId,
|
|
||||||
shoppingCartItemId: item.id,
|
|
||||||
update: {
|
|
||||||
quantity: 0,
|
|
||||||
availability: null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
this.selectShoppingCartItem([item], false);
|
|
||||||
const shoppingCartItems = items.filter((i) => i.id !== item.id);
|
|
||||||
this.patchState({ shoppingCartItems });
|
|
||||||
}
|
|
||||||
|
|
||||||
selectShoppingCartItem(shoppingCartItems: ShoppingCartItemDTO[], selected: boolean) {
|
|
||||||
if (selected) {
|
|
||||||
this.patchState({
|
|
||||||
selectedShoppingCartItems: [
|
|
||||||
...this.selectedShoppingCartItems.filter((item) => !shoppingCartItems.find((i) => item.id === i.id)),
|
|
||||||
...shoppingCartItems,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.patchState({
|
|
||||||
selectedShoppingCartItems: this.selectedShoppingCartItems.filter((item) => !shoppingCartItems.find((i) => item.id === i.id)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSelectedShoppingCartItems() {
|
|
||||||
this.patchState({ selectedShoppingCartItems: [] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// start:ng42.barrel
|
|
||||||
export * from './take-away-option-list.component';
|
|
||||||
// end:ng42.barrel
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="option-icon">
|
|
||||||
<ui-icon size="50px" icon="shopping_bag"></ui-icon>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="option-chip"
|
|
||||||
[disabled]="optionChipDisabled$ | async"
|
|
||||||
(click)="optionChange('take-away')"
|
|
||||||
[class.selected]="(selectedOption$ | async) === 'take-away'"
|
|
||||||
>
|
|
||||||
Rücklage
|
|
||||||
</button>
|
|
||||||
<p>Möchten Sie die Artikel<br />zurücklegen lassen oder<br />sofort mitnehmen?</p>
|
|
||||||
|
|
||||||
<ui-branch-dropdown
|
|
||||||
[branches]="branches$ | async"
|
|
||||||
[selected]="(selectedBranch$ | async)?.name"
|
|
||||||
(selectBranch)="selectBranch($event)"
|
|
||||||
></ui-branch-dropdown>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|
||||||
import { BranchDTO } from '@swagger/checkout';
|
|
||||||
import { first } from 'rxjs/operators';
|
|
||||||
import { PurchasingOptionsListModalStore } from '../purchasing-options-list-modal.store';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'page-take-away-option-list',
|
|
||||||
templateUrl: 'take-away-option-list.component.html',
|
|
||||||
styleUrls: ['../list-options.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class TakeAwayOptionListComponent {
|
|
||||||
branches$ = this._store.branches$;
|
|
||||||
selectedBranch$ = this._store.selectedTakeAwayBranch$;
|
|
||||||
selectedOption$ = this._store.selectedFilterOption$;
|
|
||||||
optionChipDisabled$ = this._store.fetchingAvailabilities$;
|
|
||||||
|
|
||||||
constructor(private _store: PurchasingOptionsListModalStore) {}
|
|
||||||
|
|
||||||
optionChange(option: string) {
|
|
||||||
if (this._store.selectedFilterOption === option) {
|
|
||||||
this._store.selectedFilterOption = undefined;
|
|
||||||
} else {
|
|
||||||
this._store.selectedFilterOption = option;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectBranch(branch: BranchDTO) {
|
|
||||||
this._store.lastSelectedFilterOption$.next(undefined);
|
|
||||||
|
|
||||||
this._store.selectedTakeAwayBranch = branch;
|
|
||||||
|
|
||||||
const shoppingCartItems = await this._store.shoppingCartItems$.pipe(first()).toPromise();
|
|
||||||
shoppingCartItems.forEach((item) => this._store.loadTakeAwayAvailability({ item }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<ng-container *ngIf="item$ | async; let item">
|
|
||||||
<ng-container *ngIf="availability$ | async; let availability">
|
|
||||||
<div class="option-icon">
|
|
||||||
<ui-icon size="80px" icon="truck_b2b"></ui-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4>B2B Versand</h4>
|
|
||||||
<p>
|
|
||||||
Als B2B Kunde können wir Ihnen den Artikel auch liefern.
|
|
||||||
</p>
|
|
||||||
<span class="price" *ngIf="price$ | async; let price">{{ price?.value?.value | currency: price?.value?.currency:'code' }}</span>
|
|
||||||
<div class="grow"></div>
|
|
||||||
<span class="delivery">Versandkostenfrei</span>
|
|
||||||
<span class="date"
|
|
||||||
>Versanddatum <strong>{{ availability?.estimatedShippingDate | date: 'shortDate' }}</strong></span
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<button [disabled]="availability.price?.value?.value < 0" type="button" class="select-option" (click)="select()">
|
|
||||||
Auswählen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
.option-icon {
|
|
||||||
margin-top: -12px;
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
@apply font-bold;
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user