mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
62 Commits
4270-4269-
...
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 | ||
|
|
cf1c4d37b9 | ||
|
|
ada16bac6c | ||
|
|
eefb6062c7 | ||
|
|
470a451168 | ||
|
|
e3b018c5f7 | ||
|
|
bd695e21d4 | ||
|
|
79bb9b8c11 | ||
|
|
341b202bc4 | ||
|
|
f284dc1db5 | ||
|
|
aaf156cee3 | ||
|
|
e8020ffde6 | ||
|
|
2144ec838c | ||
|
|
bff10cb2ff | ||
|
|
d303b1444b | ||
|
|
150e7965ee | ||
|
|
3fcf3d9396 | ||
|
|
52278b8baf |
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -3,6 +3,5 @@
|
||||
"johnpapa.angular2",
|
||||
"esbenp.prettier-vscode",
|
||||
"angular.ng-template",
|
||||
"eg2.vscode-npm-script"
|
||||
]
|
||||
}
|
||||
197
angular.json
197
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": {
|
||||
"projectType": "library",
|
||||
"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": {
|
||||
"projectType": "library",
|
||||
"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": {
|
||||
"projectType": "library",
|
||||
"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": {
|
||||
"projectType": "library",
|
||||
"root": "apps/store/search-component-store",
|
||||
@@ -1634,6 +1470,39 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
||||
@@ -11,9 +11,13 @@ export class DevScanAdapter implements ScanAdapter {
|
||||
constructor(private _modal: UiModalService, private _environmentService: EnvironmentService) {}
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(isDevMode());
|
||||
});
|
||||
if (this._environmentService.isTablet()) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(isDevMode());
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
|
||||
@@ -14,6 +14,7 @@ export class ScanAdapterService {
|
||||
async init(): Promise<void> {
|
||||
for (const adapter of this.scanAdapters) {
|
||||
const isReady = await adapter.init();
|
||||
console.log('ScanAdapterService.init', adapter.name, isReady);
|
||||
this._readyAdapters[adapter.name] = isReady;
|
||||
}
|
||||
}
|
||||
@@ -23,30 +24,42 @@ export class ScanAdapterService {
|
||||
}
|
||||
|
||||
getAdapter(name: string): ScanAdapter | undefined {
|
||||
return this._readyAdapters[name] && this.scanAdapters.find((adapter) => adapter.name === name);
|
||||
return this.scanAdapters.find((adapter) => adapter.name === name);
|
||||
}
|
||||
|
||||
// return true if at least one adapter is ready
|
||||
isReady(): boolean {
|
||||
return Object.values(this._readyAdapters).some((ready) => ready);
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
const adapterOrder = ['Native', 'Scandit', 'Dev'];
|
||||
|
||||
scan(ops: { use?: string; include?: string[]; exclude?: string[] } = { exclude: ['Dev'] }): Observable<string> {
|
||||
let adapter: ScanAdapter;
|
||||
|
||||
for (const name of adapterOrder) {
|
||||
adapter = this.getAdapter(name);
|
||||
|
||||
if (adapter) {
|
||||
break;
|
||||
}
|
||||
if (ops.use == undefined) {
|
||||
// get the first adapter that is ready to use
|
||||
adapter = this.scanAdapters
|
||||
.filter((adapter) => {
|
||||
if (ops.include?.length) {
|
||||
return ops.include.includes(adapter.name);
|
||||
} else if (ops.exclude?.length) {
|
||||
return !ops.exclude.includes(adapter.name);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.find((adapter) => this._readyAdapters[adapter.name]);
|
||||
} else {
|
||||
adapter = this.getAdapter(ops.use);
|
||||
}
|
||||
|
||||
if (!adapter) {
|
||||
return throwError('No adapter found');
|
||||
}
|
||||
|
||||
if (this._readyAdapters[adapter.name] == false) {
|
||||
return throwError('Adapter is not ready');
|
||||
}
|
||||
|
||||
return adapter.scan();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BranchDTO } from '@swagger/checkout';
|
||||
import { isBoolean, isNumber } from '@utils/common';
|
||||
@@ -16,25 +15,30 @@ import {
|
||||
selectActivatedProcess,
|
||||
patchProcess,
|
||||
patchProcessData,
|
||||
selectTitle,
|
||||
setTitle,
|
||||
} from './store';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationService {
|
||||
/** @deprecated */
|
||||
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
|
||||
|
||||
/** @deprecated */
|
||||
get activatedProcessId() {
|
||||
return this.activatedProcessIdSubject.value;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
get activatedProcessId$() {
|
||||
return this.activatedProcessIdSubject.asObservable();
|
||||
}
|
||||
|
||||
title$ = this.store.select(selectTitle);
|
||||
|
||||
constructor(private store: Store) {}
|
||||
|
||||
setTitle(title: string) {
|
||||
this.store.dispatch(setTitle({ title }));
|
||||
}
|
||||
|
||||
getProcesses$(section?: 'customer' | 'branch') {
|
||||
const processes$ = this.store.select(selectProcesses);
|
||||
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ApplicationProcess } from '..';
|
||||
|
||||
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 addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
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';
|
||||
|
||||
const _applicationReducer = createReducer(
|
||||
INITIAL_APPLICATION_STATE,
|
||||
on(setTitle, (state, { title }) => ({ ...state, title })),
|
||||
on(setSection, (state, { section }) => ({ ...state, section })),
|
||||
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
|
||||
on(removeProcess, (state, { processId }) => {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { ApplicationState } from './application.state';
|
||||
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 selectProcesses = createSelector(selectApplicationState, (s) => s.processes);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ApplicationProcess } from '../defs';
|
||||
|
||||
export interface ApplicationState {
|
||||
title: string;
|
||||
processes: ApplicationProcess[];
|
||||
section: 'customer' | 'branch';
|
||||
}
|
||||
|
||||
export const INITIAL_APPLICATION_STATE: ApplicationState = {
|
||||
title: '',
|
||||
processes: [],
|
||||
section: 'customer',
|
||||
};
|
||||
|
||||
@@ -135,9 +135,9 @@ export class BreadcrumbService {
|
||||
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
|
||||
}
|
||||
|
||||
getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
|
||||
getLatestBreadcrumbForSection(section: 'customer' | 'branch', predicate: (crumb: Breadcrumb) => boolean = (_) => true) {
|
||||
return this.store
|
||||
.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
|
||||
*/
|
||||
path: string;
|
||||
path: string | any[];
|
||||
|
||||
/**
|
||||
* Query Parameter
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { CommandService } from './command.service';
|
||||
|
||||
export abstract class ActionHandler<T = any> {
|
||||
constructor(readonly action: string) {}
|
||||
abstract handler(data: T, service?: CommandService): Promise<T>;
|
||||
abstract handler(data: T): Promise<T>;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { ModuleWithProviders, NgModule, Provider, Type } from '@angular/core';
|
||||
import { ModuleWithProviders, NgModule, Type } from '@angular/core';
|
||||
import { ActionHandler } from './action-handler.interface';
|
||||
import { CommandService } from './command.service';
|
||||
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
|
||||
|
||||
export function provideActionHandlers(actionHandlers: Type<ActionHandler>[]): Provider[] {
|
||||
return [CommandService, actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true }))];
|
||||
}
|
||||
|
||||
@NgModule({})
|
||||
export class CoreCommandModule {
|
||||
static forRoot(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class CommandService {
|
||||
throw new Error('Action Handler does not exist');
|
||||
}
|
||||
|
||||
data = await handler.handler(data, this);
|
||||
data = await handler.handler(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,76 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Platform } from '@angular/cdk/platform';
|
||||
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({
|
||||
providedIn: 'root',
|
||||
})
|
||||
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 {
|
||||
return !this.isTablet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `matchTablet` instead.
|
||||
*/
|
||||
isTablet(): boolean {
|
||||
return this.isNative() || this.isSafari();
|
||||
}
|
||||
@@ -21,6 +80,6 @@ export class EnvironmentService {
|
||||
}
|
||||
|
||||
isSafari(): boolean {
|
||||
return (this._platform.ANDROID || this._platform.IOS) && this._platform.SAFARI;
|
||||
return this._platform.IOS && this._platform.SAFARI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import { ResponseArgsOfIEnumerableOfStockInfoDTO, StockDTO, StockInfoDTO, StockS
|
||||
import { PriceDTO } from '@swagger/availability';
|
||||
import { AvailabilityByBranchDTO, ItemData } from './defs';
|
||||
import { Availability } from './defs/availability';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class DomainAvailabilityService {
|
||||
@@ -146,7 +145,6 @@ export class DomainAvailabilityService {
|
||||
);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 10000 })
|
||||
getTakeAwayAvailability({
|
||||
item,
|
||||
quantity,
|
||||
@@ -167,7 +165,7 @@ export class DomainAvailabilityService {
|
||||
),
|
||||
map(([response, supplier, defaultBranch]) => {
|
||||
const price = item?.price;
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch?.id ?? defaultBranch?.id, quantity, price });
|
||||
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id ?? defaultBranch.id, quantity, price });
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
@@ -480,10 +478,6 @@ export class DomainAvailabilityService {
|
||||
};
|
||||
}
|
||||
|
||||
private _priceIsEmpty(price: PriceDTO) {
|
||||
return isEmpty(price?.value) || isEmpty(price?.vat);
|
||||
}
|
||||
|
||||
private _mapToTakeAwayAvailability({
|
||||
response,
|
||||
supplier,
|
||||
@@ -504,7 +498,7 @@ export class DomainAvailabilityService {
|
||||
inStock: inStock,
|
||||
supplierSSC: quantity <= inStock ? '999' : '',
|
||||
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
|
||||
price: this._priceIsEmpty(price) ? stockInfo?.retailPrice : price,
|
||||
price: price ?? stockInfo?.retailPrice,
|
||||
supplier: { id: supplier?.id },
|
||||
// TODO: Change after API Update
|
||||
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice
|
||||
@@ -546,7 +540,6 @@ export class DomainAvailabilityService {
|
||||
return preferred.map((p) => {
|
||||
return [
|
||||
{
|
||||
orderDeadline: p?.orderDeadline,
|
||||
availabilityType: p?.status,
|
||||
ssc: p?.ssc,
|
||||
sscText: p?.sscText,
|
||||
@@ -568,6 +561,7 @@ export class DomainAvailabilityService {
|
||||
|
||||
private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]) {
|
||||
const preferred = availabilities.filter((f) => f.preferred === 1);
|
||||
|
||||
return preferred.map((p) => {
|
||||
return {
|
||||
availabilityType: p?.status,
|
||||
@@ -581,7 +575,6 @@ export class DomainAvailabilityService {
|
||||
supplierInfo: p?.requestStatusCode,
|
||||
lastRequest: p?.requested,
|
||||
itemId: p.itemId,
|
||||
priceMaintained: p.priceMaintained,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,13 +28,7 @@ import {
|
||||
StoreCheckoutBranchService,
|
||||
ItemsResult,
|
||||
} from '@swagger/checkout';
|
||||
import {
|
||||
DisplayOrderDTO,
|
||||
DisplayOrderItemDTO,
|
||||
OrderCheckoutService,
|
||||
ReorderValues,
|
||||
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
|
||||
} from '@swagger/oms';
|
||||
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
|
||||
import { isNullOrUndefined, memorize } from '@utils/common';
|
||||
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
|
||||
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
@@ -378,9 +372,8 @@ export class DomainCheckoutService {
|
||||
_setBuyer({ processId, buyer }: { processId: number; buyer: BuyerDTO }): Observable<CheckoutDTO> {
|
||||
return this.getCheckout({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((checkout) => {
|
||||
console.log('checkout', checkout, processId);
|
||||
return this._buyerService
|
||||
mergeMap((checkout) =>
|
||||
this._buyerService
|
||||
.StoreCheckoutBuyerSetBuyerPOST({
|
||||
checkoutId: checkout?.id,
|
||||
buyerDTO: buyer,
|
||||
@@ -388,8 +381,8 @@ export class DomainCheckoutService {
|
||||
.pipe(
|
||||
map((response) => response.result),
|
||||
tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -717,47 +710,6 @@ export class DomainCheckoutService {
|
||||
.pipe(mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$')))));
|
||||
}
|
||||
|
||||
completeKulturpassOrder({
|
||||
processId,
|
||||
orderItemSubsetId,
|
||||
}: {
|
||||
processId: number;
|
||||
orderItemSubsetId: number;
|
||||
}): Observable<ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString> {
|
||||
const refreshShoppingCart$ = this.getShoppingCart({ processId, latest: true }).pipe(first());
|
||||
const refreshCheckout$ = this.getCheckout({ processId, refresh: true }).pipe(first());
|
||||
|
||||
const setBuyer$ = this.getBuyer({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((buyer) => this._setBuyer({ processId, buyer }))
|
||||
);
|
||||
|
||||
const setPayer$ = this.getPayer({ processId }).pipe(
|
||||
first(),
|
||||
mergeMap((payer) => this._setPayer({ processId, payer }))
|
||||
);
|
||||
|
||||
const checkAvailabilities$ = this.checkAvailabilities({ processId });
|
||||
|
||||
const updateAvailabilities$ = this.updateAvailabilities({ processId });
|
||||
|
||||
return refreshShoppingCart$.pipe(
|
||||
mergeMap((_) => refreshCheckout$),
|
||||
mergeMap((_) => checkAvailabilities$),
|
||||
mergeMap((_) => updateAvailabilities$),
|
||||
mergeMap((_) => setBuyer$),
|
||||
mergeMap((_) => setPayer$),
|
||||
mergeMap((checkout) =>
|
||||
this.orderCheckoutService.OrderCheckoutCreateKulturPassOrder({
|
||||
payload: {
|
||||
checkoutId: checkout.id,
|
||||
orderItemSubsetId: String(orderItemSubsetId),
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
updateDestination({
|
||||
processId,
|
||||
destinationId,
|
||||
|
||||
@@ -21,7 +21,6 @@ export class CollectOnDeliveryNoteActionHandler extends ActionHandler<OrderItems
|
||||
const response = await this.orderService
|
||||
.OrderCollectOnDeliveryNote({
|
||||
data,
|
||||
eagerLoading: 1,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
@@ -30,7 +29,7 @@ export class CollectOnDeliveryNoteActionHandler extends ActionHandler<OrderItems
|
||||
|
||||
return {
|
||||
...context,
|
||||
receipts: response.result.map((r) => r.data),
|
||||
receipts: response.result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActionHandler } from '@core/command';
|
||||
import { OrderService } from '@swagger/oms';
|
||||
import { OrderItemsContext } from './order-items.context';
|
||||
|
||||
@Injectable()
|
||||
export class CollectWithSmallAmountinvoiceActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(private orderService: OrderService) {
|
||||
super('COLLECT_WITH_SMALLAMOUNTINVOICE');
|
||||
}
|
||||
|
||||
async handler(context: OrderItemsContext): Promise<OrderItemsContext> {
|
||||
const data: Record<number, number> = {};
|
||||
|
||||
context.items.forEach((orderItemSubsetId) => {
|
||||
data[orderItemSubsetId.orderItemSubsetId] =
|
||||
context.itemQuantity?.get(orderItemSubsetId.orderItemSubsetId) ?? orderItemSubsetId.quantity;
|
||||
});
|
||||
|
||||
const response = await this.orderService
|
||||
.OrderCollectWithSmallAmountInvoice({
|
||||
data,
|
||||
eagerLoading: 1,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
// Für korrekte Navigation nach Aufruf, da ProcessingStatus Serverseitig auf abgeholt gesetzt wird
|
||||
context.items?.forEach((i) => (i.processingStatus = 256));
|
||||
|
||||
return {
|
||||
...context,
|
||||
receipts: response.result.map((r) => r.data),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './accepted.action-handler';
|
||||
export * from './arrived.action-handler';
|
||||
export * from './assembled.action-handler';
|
||||
@@ -6,10 +7,6 @@ export * from './back-to-stock.action-handler';
|
||||
export * from './canceled-by-buyer.action-handler';
|
||||
export * from './canceled-by-retailer.action-handler';
|
||||
export * from './canceled-by-supplier.action-handler';
|
||||
export * from './change-order-item-status-base.action-handler';
|
||||
export * from './collect-on-deliverynote.action-handler';
|
||||
export * from './collect-with-smallamountinvoice.action-handler';
|
||||
export * from './create-returnitem.action-handler';
|
||||
export * from './create-shipping-note.action-handler';
|
||||
export * from './delivered.action-handler';
|
||||
export * from './determine-supplier.action-handler';
|
||||
@@ -28,15 +25,16 @@ export * from './parked.action-handler';
|
||||
export * from './placed.action-handler';
|
||||
export * from './preperation-for-shipping.action-handler';
|
||||
export * from './print-compartment-label.action-handler';
|
||||
export * from './print-pricediffqrcodelabel.action-handler';
|
||||
export * from './print-shipping-note.action-handler';
|
||||
export * from './print-smallamountinvoice.action-handler';
|
||||
export * from './re-order.action-handler';
|
||||
export * from './re-ordered.action-handler';
|
||||
export * from './re-order.action-handler';
|
||||
export * from './redirected-internally.action-handler';
|
||||
export * from './requested.action-handler';
|
||||
export * from './reserved.action-handler';
|
||||
export * from './returned-by-buyer.action-handler';
|
||||
export * from './shipping-note.action-handler';
|
||||
export * from './shop-with-kulturpass.action-handler';
|
||||
export * from './supplier-temporarily-out-of-stock.action-handler copy';
|
||||
export * from './collect-on-deliverynote.action-handler';
|
||||
export * from './create-returnitem.action-handler';
|
||||
export * from './print-pricediffqrcodelabel.action-handler';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrderItemListItemDTO, ReceiptDTO, OrderDTO } from '@swagger/oms';
|
||||
import { OrderItemListItemDTO, ReceiptDTO } from '@swagger/oms';
|
||||
|
||||
export interface OrderItemsContext {
|
||||
items: OrderItemListItemDTO[];
|
||||
@@ -12,6 +12,4 @@ export interface OrderItemsContext {
|
||||
receipts?: ReceiptDTO[];
|
||||
|
||||
shippingDelayComment?: string;
|
||||
|
||||
order?: OrderDTO;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
|
||||
printerType: 'Label',
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
|
||||
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
for (const group of groupBy(data?.receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActionHandler } from '@core/command';
|
||||
import { OrderItemsContext } from './order-items.context';
|
||||
import { OMSPrintService } from '@swagger/print';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { groupBy } from '@ui/common';
|
||||
|
||||
@Injectable()
|
||||
export class PrintSmallamountinvoiceActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(
|
||||
private uiModal: UiModalService,
|
||||
private omsPrintService: OMSPrintService,
|
||||
private nativeContainerService: NativeContainerService
|
||||
) {
|
||||
super('PRINT_SMALLAMOUNTINVOICE');
|
||||
}
|
||||
|
||||
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
|
||||
await this.uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
printImmediately: !this.nativeContainerService.isNative,
|
||||
printerType: 'Label',
|
||||
print: async (printer) => {
|
||||
try {
|
||||
const receipts = data?.receipts?.filter((r) => r?.receiptType & 128);
|
||||
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
|
||||
await this.omsPrintService
|
||||
.OMSPrintKleinbetragsrechnung({
|
||||
data: group?.items?.map((r) => r?.id),
|
||||
printer,
|
||||
})
|
||||
.toPromise();
|
||||
}
|
||||
return {
|
||||
error: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
error: true,
|
||||
message: error?.message || error,
|
||||
};
|
||||
}
|
||||
},
|
||||
} as PrintModalData,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { OrderItemsContext } from './order-items.context';
|
||||
import { ActionHandler, CommandService } from '@core/command';
|
||||
import { KulturpassOrderModalService } from '@shared/modals/kulturpass-order-modal';
|
||||
import { DisplayOrderItemSubsetDTO, OrderItemListItemDTO, ReceiptDTO } from '@swagger/oms';
|
||||
import { DomainReceiptService } from '../receipt.service';
|
||||
import { DomainGoodsService } from '../goods.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ShopWithKulturpassActionHandler extends ActionHandler<OrderItemsContext> {
|
||||
constructor(
|
||||
private _modal: KulturpassOrderModalService,
|
||||
private _receiptService: DomainReceiptService,
|
||||
private _goodsService: DomainGoodsService
|
||||
) {
|
||||
super('SHOP_WITH_KULTURPASS');
|
||||
}
|
||||
|
||||
async handler(data: OrderItemsContext, service: CommandService): Promise<OrderItemsContext> {
|
||||
const items: OrderItemListItemDTO[] = [];
|
||||
const receipts: ReceiptDTO[] = [];
|
||||
|
||||
let command: string;
|
||||
for (const item of data.items) {
|
||||
const result = await this._modal.open({ orderItemListItem: item, order: data.order }).afterClosed$.toPromise();
|
||||
|
||||
if (result.data == null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const displayOrder = result.data[0];
|
||||
command = result.data[1];
|
||||
|
||||
if (displayOrder) {
|
||||
const subsetItems = displayOrder.items.reduce((acc, item) => [...acc, ...item.subsetItems], [] as DisplayOrderItemSubsetDTO[]);
|
||||
|
||||
const orderItems = await this.getItems(displayOrder.orderNumber);
|
||||
|
||||
items.push(...orderItems);
|
||||
|
||||
const subsetItemIds = subsetItems.map((item) => item.id);
|
||||
|
||||
const r = await this.getReceipts(subsetItemIds);
|
||||
|
||||
receipts.push(...r);
|
||||
}
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
return {
|
||||
...data,
|
||||
items,
|
||||
receipts,
|
||||
};
|
||||
} else {
|
||||
return service.handleCommand(command, {
|
||||
...data,
|
||||
items,
|
||||
receipts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getReceipts(ids: number[]) {
|
||||
return this._receiptService
|
||||
.getReceipts({
|
||||
receiptType: 128,
|
||||
eagerLoading: 1,
|
||||
ids,
|
||||
})
|
||||
.pipe(map((res) => res.result.map((data) => data.item3.data).filter((data) => !!data)))
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
getItems(orderNumber: string) {
|
||||
return this._goodsService
|
||||
.getWarenausgabeItemByOrderNumber(orderNumber, false)
|
||||
.pipe(map((res) => res.result))
|
||||
.toPromise();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
AutocompleteTokenDTO,
|
||||
BranchService,
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
@@ -10,7 +11,9 @@ import {
|
||||
OrderItemSubsetDTO,
|
||||
OrderListItemDTO,
|
||||
OrderService,
|
||||
QueryTokenDTO,
|
||||
ReceiptService,
|
||||
ResponseArgsOfIEnumerableOfHistoryDTO,
|
||||
StatusValues,
|
||||
StockStatusCodeService,
|
||||
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO,
|
||||
@@ -19,7 +22,7 @@ import {
|
||||
} from '@swagger/oms';
|
||||
import { memorize } from '@utils/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { map, mergeMap, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class DomainOmsService {
|
||||
@@ -188,10 +191,6 @@ export class DomainOmsService {
|
||||
);
|
||||
}
|
||||
|
||||
getOrderSource(orderId: number): Observable<string> {
|
||||
return this.getOrder(orderId).pipe(map((order) => order?.features?.orderSource));
|
||||
}
|
||||
|
||||
updateNotifications(orderId: number, changes: { selected: NotificationChannel; email: string; mobile: string }) {
|
||||
const communicationDetails = {
|
||||
email: changes.email,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { PackageArrivalStatusDTO } from '@swagger/wws';
|
||||
|
||||
export abstract class PackageInspectionEvent {
|
||||
constructor(public readonly type: string) {}
|
||||
}
|
||||
|
||||
export class PackageStatusChangedEvent extends PackageInspectionEvent {
|
||||
constructor(public readonly packageId: string, public readonly status: PackageArrivalStatusDTO) {
|
||||
super('PackageStatusChangedEvent');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ListResponseArgsOfPackageDTO,
|
||||
ListResponseArgsOfPackageDTO2,
|
||||
PackageArrivalStatusDTO,
|
||||
PackageDetailResponseDTO,
|
||||
PackageDTO,
|
||||
PackageDTO2,
|
||||
QuerySettingsDTO,
|
||||
QueryTokenDTO,
|
||||
@@ -11,18 +13,13 @@ import {
|
||||
ResponseArgsOfQuerySettingsDTO,
|
||||
WareneingangService,
|
||||
} from '@swagger/wws';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { PackageInspectionEvent, PackageStatusChangedEvent } from './events';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DomainPackageInspectionService {
|
||||
private _events = new Subject<PackageInspectionEvent>();
|
||||
|
||||
events = this._events.asObservable();
|
||||
|
||||
constructor(private _wareneingang: WareneingangService) {}
|
||||
|
||||
getQuerySettingsResponse(): Observable<ResponseArgsOfQuerySettingsDTO> {
|
||||
@@ -50,26 +47,23 @@ export class DomainPackageInspectionService {
|
||||
}
|
||||
|
||||
changePackageStatusResponse(pkg: PackageDTO2, modifier: string): Observable<ResponseArgsOfPackageArrivalStatusDTO> {
|
||||
return this._wareneingang
|
||||
.WareneingangChangePackageStatus({
|
||||
packageId: pkg.id,
|
||||
payload: {
|
||||
id: pkg.id,
|
||||
annotation: pkg.annotation,
|
||||
area: pkg.area,
|
||||
arrivalChecked: pkg.arrivalChecked,
|
||||
arrivalStatus: pkg.arrivalStatus,
|
||||
deliveryNoteNumber: pkg.deliveryNoteNumber,
|
||||
deliveryTarget: pkg.deliveryTarget,
|
||||
estimatedDeliveryDate: pkg.estimatedDeliveryDate,
|
||||
packageNumber: pkg.packageNumber,
|
||||
supplier: pkg.supplier,
|
||||
trackingNumber: pkg.trackingNumber,
|
||||
scanId: pkg.scanId,
|
||||
},
|
||||
modifier,
|
||||
})
|
||||
.pipe(tap((res) => this._events.next(new PackageStatusChangedEvent(pkg.id, res.result))));
|
||||
return this._wareneingang.WareneingangChangePackageStatus({
|
||||
packageId: pkg.id,
|
||||
payload: {
|
||||
id: pkg.id,
|
||||
annotation: pkg.annotation,
|
||||
area: pkg.area,
|
||||
arrivalChecked: pkg.arrivalChecked,
|
||||
arrivalStatus: pkg.arrivalStatus,
|
||||
deliveryNoteNumber: pkg.deliveryNoteNumber,
|
||||
deliveryTarget: pkg.deliveryTarget,
|
||||
estimatedDeliveryDate: pkg.estimatedDeliveryDate,
|
||||
packageNumber: pkg.packageNumber,
|
||||
supplier: pkg.supplier,
|
||||
trackingNumber: pkg.trackingNumber,
|
||||
},
|
||||
modifier,
|
||||
});
|
||||
}
|
||||
|
||||
changePackageStatus(pkg: PackageDTO2, modifier: string): Observable<PackageArrivalStatusDTO> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Public API Surface of package-inspection
|
||||
*/
|
||||
export * from './lib/events';
|
||||
|
||||
export * from './lib/package-inspection.service';
|
||||
export * from './lib/package-inspection.module';
|
||||
|
||||
@@ -4,118 +4,61 @@ import { SignalrHub, SignalRHubOptions } from '@core/signalr';
|
||||
import { BehaviorSubject, merge, of } from 'rxjs';
|
||||
import { filter, map, shareReplay, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { EnvelopeDTO, MessageBoardItemDTO } from './defs';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
export const NOTIFICATIONS_HUB_OPTIONS = new InjectionToken<SignalRHubOptions>('hub.notifications.options');
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsHub extends SignalrHub {
|
||||
updateNotification$ = new BehaviorSubject<MessageBoardItemDTO>(undefined);
|
||||
|
||||
get branchNo() {
|
||||
return String(this._auth.getClaimByKey('branch_no') || this._auth.getClaimByKey('sub'));
|
||||
}
|
||||
|
||||
// get sessionStoragesessionStorageKey() {
|
||||
// return `NOTIFICATIONS_BOARD_${this.branchNo}`;
|
||||
// }
|
||||
|
||||
get sessionStoragesessionStorageKey() {
|
||||
return `NOTIFICATIONS_BOARD_AREA_${this.branchNo}`;
|
||||
return `NOTIFICATIONS_BOARD_${this.branchNo}`;
|
||||
}
|
||||
|
||||
messageBoardItems$ = new BehaviorSubject<Record<string, MessageBoardItemDTO[]>>({});
|
||||
|
||||
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions, private _auth: AuthService) {
|
||||
super(options);
|
||||
|
||||
this.messageBoardItems$.next(this._getNotifications());
|
||||
|
||||
this.messageBoardItems$.subscribe((data) => {
|
||||
this._storeNotifactions(data);
|
||||
});
|
||||
|
||||
this.listen<EnvelopeDTO<MessageBoardItemDTO[]>>('messageBoard').subscribe((envelope) => {
|
||||
if (envelope.action === 'refresh') {
|
||||
this.refreshMessageBoardItems(envelope.target.area, envelope.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refreshMessageBoardItems(targetArea: string, messages: MessageBoardItemDTO[]) {
|
||||
const current = cloneDeep(this.messageBoardItems$.value);
|
||||
|
||||
current[targetArea] = messages ?? [];
|
||||
|
||||
this.messageBoardItems$.next(current);
|
||||
}
|
||||
|
||||
notifications$ = this.messageBoardItems$.asObservable().pipe(
|
||||
map((data) => {
|
||||
const messages = { ...data };
|
||||
const keys = Object.keys(data);
|
||||
for (let key of keys) {
|
||||
if (data[key].length === 0 || data[key] === undefined) {
|
||||
delete messages[key];
|
||||
}
|
||||
notifications$ = merge(
|
||||
of(this._getNotifications()).pipe(filter((f) => !!f)),
|
||||
this.listen<EnvelopeDTO<MessageBoardItemDTO[]>>('messageBoard')
|
||||
).pipe(
|
||||
withLatestFrom(this.updateNotification$),
|
||||
map(([d, update]) => {
|
||||
const data = d;
|
||||
if (update && !!data && !data?.data?.find((message) => message?.category === 'ISA-Update')) {
|
||||
data.data.push(update);
|
||||
}
|
||||
|
||||
return messages;
|
||||
})
|
||||
return data;
|
||||
}),
|
||||
tap((data) => this._storeNotifactions(data)),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
// notifications$ = merge(
|
||||
// of(this._getNotifications()).pipe(filter((f) => !!f)),
|
||||
// this.listen<EnvelopeDTO<MessageBoardItemDTO[]>>('messageBoard')
|
||||
// ).pipe(
|
||||
// withLatestFrom(this.updateNotification$),
|
||||
// map(([d, update]) => {
|
||||
// console.log('notifications$', d, update);
|
||||
// const data = d;
|
||||
// if (update && !!data && !data?.data?.find((message) => message?.category === 'ISA-Update')) {
|
||||
// data.data.push(update);
|
||||
// }
|
||||
// return data;
|
||||
// }),
|
||||
// tap((data) => this._storeNotifactions(data)),
|
||||
// shareReplay(1)
|
||||
// );
|
||||
|
||||
// private _storeNotifactions(data: EnvelopeDTO<MessageBoardItemDTO[]>) {
|
||||
// if (data) {
|
||||
// sessionStorage.setItem(this.sessionStoragesessionStorageKey, JSON.stringify(data));
|
||||
// }
|
||||
// }
|
||||
|
||||
// private _getNotifications(): EnvelopeDTO<MessageBoardItemDTO[]> {
|
||||
// const stringData = sessionStorage.getItem(this.sessionStoragesessionStorageKey);
|
||||
// if (stringData) {
|
||||
// return JSON.parse(stringData);
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
private _getNotifications(): Record<string, MessageBoardItemDTO[]> {
|
||||
const stringData = sessionStorage.getItem(this.sessionStoragesessionStorageKey);
|
||||
if (stringData) {
|
||||
return JSON.parse(stringData);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private _storeNotifactions(data: Record<string, MessageBoardItemDTO[]>) {
|
||||
private _storeNotifactions(data: EnvelopeDTO<MessageBoardItemDTO[]>) {
|
||||
if (data) {
|
||||
delete data['messageBoard/isa-update'];
|
||||
sessionStorage.setItem(this.sessionStoragesessionStorageKey, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
private _getNotifications(): EnvelopeDTO<MessageBoardItemDTO[]> {
|
||||
const stringData = sessionStorage.getItem(this.sessionStoragesessionStorageKey);
|
||||
if (stringData) {
|
||||
return JSON.parse(stringData);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
updateNotification() {
|
||||
this.refreshMessageBoardItems('messageBoard/isa-update', [
|
||||
{
|
||||
category: 'ISA-Update',
|
||||
type: 'update',
|
||||
headline: 'Update Benachrichtigung',
|
||||
text: 'Es steht eine aktuellere Version der ISA bereit. Bitte aktualisieren Sie die Anwendung.',
|
||||
},
|
||||
]);
|
||||
this.updateNotification$.next({
|
||||
category: 'ISA-Update',
|
||||
type: 'update',
|
||||
headline: 'Update Benachrichtigung',
|
||||
text: 'Es steht eine aktuellere Version der ISA bereit. Bitte aktualisieren Sie die Anwendung.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { DebugComponent } from './debug/debug.component';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
CanActivateCartWithProcessIdGuard,
|
||||
@@ -17,9 +16,9 @@ import {
|
||||
} from './guards';
|
||||
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
|
||||
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
|
||||
import { MainComponent } from './main.component';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
import { ShellComponent, ShellModule } from './shell';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -40,7 +39,7 @@ const routes: Routes = [
|
||||
children: [
|
||||
{
|
||||
path: 'kunde',
|
||||
component: ShellComponent,
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
@@ -106,7 +105,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'filiale',
|
||||
component: ShellComponent,
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'task-calendar',
|
||||
@@ -152,7 +151,7 @@ if (isDevMode()) {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes), ShellModule, TokenLoginModule],
|
||||
imports: [RouterModule.forRoot(routes), TokenLoginModule],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
:host {
|
||||
@apply block box-border;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply fixed bottom-4 right-2 bg-blue-500 text-white font-bold py-2 px-4 rounded z-tooltip;
|
||||
@apply block;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ import { IsaErrorHandler } from './providers/isa.error-handler';
|
||||
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
|
||||
import { RootStateService } from './store/root-state.service';
|
||||
import * as Commands from './commands';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiIconModule, UI_ICON_CFG } from '@ui/icon';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { NativeContainerService } from 'native-container';
|
||||
import { ShellModule } from '@shared/shell';
|
||||
import { MainComponent } from './main.component';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -74,11 +76,12 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
declarations: [AppComponent, MainComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
ShellModule.forRoot(),
|
||||
AppRoutingModule,
|
||||
AppSwaggerModule,
|
||||
AppDomainModule,
|
||||
@@ -103,31 +106,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
ScanAdapterModule.forRoot(),
|
||||
ScanditScanAdapterModule.forRoot(),
|
||||
PlatformModule,
|
||||
UiIconModule.forRoot({
|
||||
aliases: [
|
||||
{ alias: 'd-account', name: 'account' },
|
||||
{ alias: 'd-no-account', name: 'package-variant-closed' },
|
||||
{ name: 'isa-audio', alias: 'AU' },
|
||||
{ name: 'isa-audio', alias: 'CAS' },
|
||||
{ name: 'isa-audio', alias: 'DL' },
|
||||
{ name: 'isa-audio', alias: 'KAS' },
|
||||
{ name: 'isa-hard-cover', alias: 'BUCH' },
|
||||
{ name: 'isa-hard-cover', alias: 'GEB' },
|
||||
{ name: 'isa-hard-cover', alias: 'HC' },
|
||||
{ name: 'isa-hard-cover', alias: 'KT' },
|
||||
{ name: 'isa-ebook', alias: 'EB' },
|
||||
{ name: 'isa-non-book', alias: 'GLO' },
|
||||
{ name: 'isa-non-book', alias: 'HDL' },
|
||||
{ name: 'isa-non-book', alias: 'NB' },
|
||||
{ name: 'isa-non-book', alias: 'SPL' },
|
||||
{ name: 'isa-calendar', alias: 'KA' },
|
||||
{ name: 'isa-scroll', alias: 'MA' },
|
||||
{ name: 'isa-software', alias: 'SW' },
|
||||
{ name: 'isa-soft-cover', alias: 'TB' },
|
||||
{ name: 'isa-video', alias: 'VI' },
|
||||
{ name: 'isa-news-paper', alias: 'ZS' },
|
||||
],
|
||||
}),
|
||||
UiIconModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -156,6 +135,11 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
|
||||
useClass: IsaErrorHandler,
|
||||
},
|
||||
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||
{
|
||||
provide: UI_ICON_CFG,
|
||||
useFactory: (config: Config) => config.get('@ui/icon'),
|
||||
deps: [Config],
|
||||
},
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -9,6 +10,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
private readonly _navigationService: ProductCatalogNavigationService,
|
||||
private readonly _router: Router
|
||||
) {}
|
||||
|
||||
@@ -38,7 +40,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
}
|
||||
|
||||
if (!lastActivatedProcessId) {
|
||||
await this.fromCartProcess(processes, route);
|
||||
await this.fromCartProcess(processes);
|
||||
return false;
|
||||
} else {
|
||||
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
|
||||
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
|
||||
async fromCartProcess(processes: ApplicationProcess[]) {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
@@ -57,7 +59,7 @@ export class CanActivateProductGuard implements CanActivate {
|
||||
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
|
||||
|
||||
@@ -52,7 +52,11 @@ export class IsAuthenticatedGuard implements CanActivate {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await this._scanService.scan()?.toPromise();
|
||||
const result = await this._scanService
|
||||
.scan({
|
||||
exclude: ['Dev'],
|
||||
})
|
||||
?.toPromise();
|
||||
|
||||
if (typeof result === 'string') {
|
||||
try {
|
||||
|
||||
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,176 +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((notifications) => Object.values(notifications).reduce((acc, val) => acc + val?.length ?? 0, 0))
|
||||
);
|
||||
|
||||
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 {}
|
||||
File diff suppressed because one or more lines are too long
@@ -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.
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="18px" viewBox="0 0 22 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Icon_HC</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Hugendubel_Icons" transform="translate(-29.000000, -177.000000)" fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="0.3">
|
||||
<path d="M32.725699,178.000628 C32.7430505,177.999791 32.7604307,177.999791 32.7777823,178.000628 L37.5260449,178.000628 C38.53109,178.000628 39.44018,178.547926 40.0000026,179.340692 C40.5598253,178.547926 41.4689153,178.000628 42.4739603,178.000628 L47.222223,178.000628 C47.529035,178.00066 47.7777477,178.256627 47.7777784,178.572389 L47.7777784,179.71591 L49.4444446,179.71591 C49.7512567,179.715942 49.9999694,179.971909 50,180.287671 L50,192.008763 C49.9999694,192.324524 49.7512567,192.580492 49.4444446,192.580523 L42.4739603,192.580523 C41.4766486,192.580523 40.8524541,192.949423 40.4947942,193.688309 C40.3998428,193.879608 40.208727,194 40.0000026,194 C39.7912783,194 39.6001624,193.879608 39.5052111,193.688309 C39.1475512,192.949423 38.5233566,192.580523 37.5260449,192.580523 L30.5555607,192.580523 C30.2487486,192.580492 30.0000359,192.324524 30.0000053,192.008763 L30.0000053,180.287671 C29.9987652,179.991708 30.2171687,179.743681 30.5034773,179.71591 C30.5208289,179.715072 30.5382091,179.715072 30.5555607,179.71591 L32.2222269,179.71591 L32.2222269,178.572389 C32.2209869,178.276426 32.4393904,178.028399 32.725699,178.000628 Z M33.3333377,179.14415 L33.3333377,189.43584 L36.6232674,189.43584 C37.6190746,189.43584 38.5547188,189.729711 39.2621556,190.356017 C39.3270606,190.413476 39.3849347,190.480265 39.4444472,190.543626 L39.4444472,180.957703 C39.4433388,180.936873 39.4433388,180.915996 39.4444472,180.895166 C39.4444472,180.068361 38.4768339,179.14415 37.5260449,179.14415 L33.3333377,179.14415 Z M42.4739603,179.14415 C41.5231714,179.14415 40.555558,180.068361 40.555558,180.895166 C40.5555806,180.898144 40.5555806,180.901122 40.555558,180.9041 L40.555558,190.543626 C40.6150705,190.480265 40.6729447,190.413476 40.7378497,190.356017 C41.4452864,189.729711 42.3809306,189.43584 43.3767379,189.43584 L46.6666675,189.43584 L46.6666675,179.14415 L42.4739603,179.14415 Z M31.1111161,180.859431 L31.1111161,191.437002 L37.5260449,191.437002 C38.0365968,191.437002 38.5189494,191.531483 38.9496557,191.713949 C38.8273679,191.527334 38.6888269,191.36055 38.5329891,191.222592 C38.0547048,190.799175 37.405738,190.579361 36.6232674,190.579361 L32.7777823,190.579361 C32.4709702,190.57933 32.2222575,190.323362 32.2222269,190.007601 L32.2222269,180.859431 L31.1111161,180.859431 Z M47.7777784,180.859431 L47.7777784,190.007601 C47.7777477,190.323362 47.529035,190.57933 47.222223,190.579361 L43.3767379,190.579361 C42.5942672,190.579361 41.9453004,190.799175 41.4670161,191.222592 C41.3107359,191.360944 41.1725734,191.526903 41.0503496,191.713949 C41.4810558,191.531483 41.9634085,191.437002 42.4739603,191.437002 L48.8888892,191.437002 L48.8888892,180.859431 L47.7777784,180.859431 Z M35.5,182.116677 L37.5,182.116677 C37.7761424,182.116677 38,182.340535 38,182.616677 L38,182.645847 C38,182.921989 37.7761424,183.145847 37.5,183.145847 L35.5,183.145847 C35.2238576,183.145847 35,182.921989 35,182.645847 L35,182.616677 C35,182.340535 35.2238576,182.116677 35.5,182.116677 Z M35.5,184.175016 L37.5,184.175016 C37.7761424,184.175016 38,184.398874 38,184.675016 L38,184.704185 C38,184.980328 37.7761424,185.204185 37.5,185.204185 L35.5,185.204185 C35.2238576,185.204185 35,184.980328 35,184.704185 L35,184.675016 C35,184.398874 35.2238576,184.175016 35.5,184.175016 Z M35.5,186.233355 L37.5,186.233355 C37.7761424,186.233355 38,186.457212 38,186.733355 L38,186.762524 C38,187.038666 37.7761424,187.262524 37.5,187.262524 L35.5,187.262524 C35.2238576,187.262524 35,187.038666 35,186.762524 L35,186.733355 C35,186.457212 35.2238576,186.233355 35.5,186.233355 Z M42.5,182.116677 L44.5,182.116677 C44.7761424,182.116677 45,182.340535 45,182.616677 L45,182.645847 C45,182.921989 44.7761424,183.145847 44.5,183.145847 L42.5,183.145847 C42.2238576,183.145847 42,182.921989 42,182.645847 L42,182.616677 C42,182.340535 42.2238576,182.116677 42.5,182.116677 Z M42.5,184.175016 L44.5,184.175016 C44.7761424,184.175016 45,184.398874 45,184.675016 L45,184.704185 C45,184.980328 44.7761424,185.204185 44.5,185.204185 L42.5,185.204185 C42.2238576,185.204185 42,184.980328 42,184.704185 L42,184.675016 C42,184.398874 42.2238576,184.175016 42.5,184.175016 Z M42.5,186.233355 L44.5,186.233355 C44.7761424,186.233355 45,186.457212 45,186.733355 L45,186.762524 C45,187.038666 44.7761424,187.262524 44.5,187.262524 L42.5,187.262524 C42.2238576,187.262524 42,187.038666 42,186.762524 L42,186.733355 C42,186.457212 42.2238576,186.233355 42.5,186.233355 Z" id="Icon_HC"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="18px" viewBox="0 0 22 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Icon_HC</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Hugendubel_Icons" transform="translate(-29.000000, -177.000000)" fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="0.3">
|
||||
<path d="M32.725699,178.000628 C32.7430505,177.999791 32.7604307,177.999791 32.7777823,178.000628 L37.5260449,178.000628 C38.53109,178.000628 39.44018,178.547926 40.0000026,179.340692 C40.5598253,178.547926 41.4689153,178.000628 42.4739603,178.000628 L47.222223,178.000628 C47.529035,178.00066 47.7777477,178.256627 47.7777784,178.572389 L47.7777784,179.71591 L49.4444446,179.71591 C49.7512567,179.715942 49.9999694,179.971909 50,180.287671 L50,192.008763 C49.9999694,192.324524 49.7512567,192.580492 49.4444446,192.580523 L42.4739603,192.580523 C41.4766486,192.580523 40.8524541,192.949423 40.4947942,193.688309 C40.3998428,193.879608 40.208727,194 40.0000026,194 C39.7912783,194 39.6001624,193.879608 39.5052111,193.688309 C39.1475512,192.949423 38.5233566,192.580523 37.5260449,192.580523 L30.5555607,192.580523 C30.2487486,192.580492 30.0000359,192.324524 30.0000053,192.008763 L30.0000053,180.287671 C29.9987652,179.991708 30.2171687,179.743681 30.5034773,179.71591 C30.5208289,179.715072 30.5382091,179.715072 30.5555607,179.71591 L32.2222269,179.71591 L32.2222269,178.572389 C32.2209869,178.276426 32.4393904,178.028399 32.725699,178.000628 Z M33.3333377,179.14415 L33.3333377,189.43584 L36.6232674,189.43584 C37.6190746,189.43584 38.5547188,189.729711 39.2621556,190.356017 C39.3270606,190.413476 39.3849347,190.480265 39.4444472,190.543626 L39.4444472,180.957703 C39.4433388,180.936873 39.4433388,180.915996 39.4444472,180.895166 C39.4444472,180.068361 38.4768339,179.14415 37.5260449,179.14415 L33.3333377,179.14415 Z M42.4739603,179.14415 C41.5231714,179.14415 40.555558,180.068361 40.555558,180.895166 C40.5555806,180.898144 40.5555806,180.901122 40.555558,180.9041 L40.555558,190.543626 C40.6150705,190.480265 40.6729447,190.413476 40.7378497,190.356017 C41.4452864,189.729711 42.3809306,189.43584 43.3767379,189.43584 L46.6666675,189.43584 L46.6666675,179.14415 L42.4739603,179.14415 Z M31.1111161,180.859431 L31.1111161,191.437002 L37.5260449,191.437002 C38.0365968,191.437002 38.5189494,191.531483 38.9496557,191.713949 C38.8273679,191.527334 38.6888269,191.36055 38.5329891,191.222592 C38.0547048,190.799175 37.405738,190.579361 36.6232674,190.579361 L32.7777823,190.579361 C32.4709702,190.57933 32.2222575,190.323362 32.2222269,190.007601 L32.2222269,180.859431 L31.1111161,180.859431 Z M47.7777784,180.859431 L47.7777784,190.007601 C47.7777477,190.323362 47.529035,190.57933 47.222223,190.579361 L43.3767379,190.579361 C42.5942672,190.579361 41.9453004,190.799175 41.4670161,191.222592 C41.3107359,191.360944 41.1725734,191.526903 41.0503496,191.713949 C41.4810558,191.531483 41.9634085,191.437002 42.4739603,191.437002 L48.8888892,191.437002 L48.8888892,180.859431 L47.7777784,180.859431 Z M35.5,182.116677 L37.5,182.116677 C37.7761424,182.116677 38,182.340535 38,182.616677 L38,182.645847 C38,182.921989 37.7761424,183.145847 37.5,183.145847 L35.5,183.145847 C35.2238576,183.145847 35,182.921989 35,182.645847 L35,182.616677 C35,182.340535 35.2238576,182.116677 35.5,182.116677 Z M35.5,184.175016 L37.5,184.175016 C37.7761424,184.175016 38,184.398874 38,184.675016 L38,184.704185 C38,184.980328 37.7761424,185.204185 37.5,185.204185 L35.5,185.204185 C35.2238576,185.204185 35,184.980328 35,184.704185 L35,184.675016 C35,184.398874 35.2238576,184.175016 35.5,184.175016 Z M35.5,186.233355 L37.5,186.233355 C37.7761424,186.233355 38,186.457212 38,186.733355 L38,186.762524 C38,187.038666 37.7761424,187.262524 37.5,187.262524 L35.5,187.262524 C35.2238576,187.262524 35,187.038666 35,186.762524 L35,186.733355 C35,186.457212 35.2238576,186.233355 35.5,186.233355 Z M42.5,182.116677 L44.5,182.116677 C44.7761424,182.116677 45,182.340535 45,182.616677 L45,182.645847 C45,182.921989 44.7761424,183.145847 44.5,183.145847 L42.5,183.145847 C42.2238576,183.145847 42,182.921989 42,182.645847 L42,182.616677 C42,182.340535 42.2238576,182.116677 42.5,182.116677 Z M42.5,184.175016 L44.5,184.175016 C44.7761424,184.175016 45,184.398874 45,184.675016 L45,184.704185 C45,184.980328 44.7761424,185.204185 44.5,185.204185 L42.5,185.204185 C42.2238576,185.204185 42,184.980328 42,184.704185 L42,184.675016 C42,184.398874 42.2238576,184.175016 42.5,184.175016 Z M42.5,186.233355 L44.5,186.233355 C44.7761424,186.233355 45,186.457212 45,186.733355 L45,186.762524 C45,187.038666 44.7761424,187.262524 44.5,187.262524 L42.5,187.262524 C42.2238576,187.262524 42,187.038666 42,186.762524 L42,186.733355 C42,186.457212 42.2238576,186.233355 42.5,186.233355 Z" id="Icon_HC"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="18px" viewBox="0 0 22 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Icon_HC</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Hugendubel_Icons" transform="translate(-29.000000, -177.000000)" fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="0.3">
|
||||
<path d="M32.725699,178.000628 C32.7430505,177.999791 32.7604307,177.999791 32.7777823,178.000628 L37.5260449,178.000628 C38.53109,178.000628 39.44018,178.547926 40.0000026,179.340692 C40.5598253,178.547926 41.4689153,178.000628 42.4739603,178.000628 L47.222223,178.000628 C47.529035,178.00066 47.7777477,178.256627 47.7777784,178.572389 L47.7777784,179.71591 L49.4444446,179.71591 C49.7512567,179.715942 49.9999694,179.971909 50,180.287671 L50,192.008763 C49.9999694,192.324524 49.7512567,192.580492 49.4444446,192.580523 L42.4739603,192.580523 C41.4766486,192.580523 40.8524541,192.949423 40.4947942,193.688309 C40.3998428,193.879608 40.208727,194 40.0000026,194 C39.7912783,194 39.6001624,193.879608 39.5052111,193.688309 C39.1475512,192.949423 38.5233566,192.580523 37.5260449,192.580523 L30.5555607,192.580523 C30.2487486,192.580492 30.0000359,192.324524 30.0000053,192.008763 L30.0000053,180.287671 C29.9987652,179.991708 30.2171687,179.743681 30.5034773,179.71591 C30.5208289,179.715072 30.5382091,179.715072 30.5555607,179.71591 L32.2222269,179.71591 L32.2222269,178.572389 C32.2209869,178.276426 32.4393904,178.028399 32.725699,178.000628 Z M33.3333377,179.14415 L33.3333377,189.43584 L36.6232674,189.43584 C37.6190746,189.43584 38.5547188,189.729711 39.2621556,190.356017 C39.3270606,190.413476 39.3849347,190.480265 39.4444472,190.543626 L39.4444472,180.957703 C39.4433388,180.936873 39.4433388,180.915996 39.4444472,180.895166 C39.4444472,180.068361 38.4768339,179.14415 37.5260449,179.14415 L33.3333377,179.14415 Z M42.4739603,179.14415 C41.5231714,179.14415 40.555558,180.068361 40.555558,180.895166 C40.5555806,180.898144 40.5555806,180.901122 40.555558,180.9041 L40.555558,190.543626 C40.6150705,190.480265 40.6729447,190.413476 40.7378497,190.356017 C41.4452864,189.729711 42.3809306,189.43584 43.3767379,189.43584 L46.6666675,189.43584 L46.6666675,179.14415 L42.4739603,179.14415 Z M31.1111161,180.859431 L31.1111161,191.437002 L37.5260449,191.437002 C38.0365968,191.437002 38.5189494,191.531483 38.9496557,191.713949 C38.8273679,191.527334 38.6888269,191.36055 38.5329891,191.222592 C38.0547048,190.799175 37.405738,190.579361 36.6232674,190.579361 L32.7777823,190.579361 C32.4709702,190.57933 32.2222575,190.323362 32.2222269,190.007601 L32.2222269,180.859431 L31.1111161,180.859431 Z M47.7777784,180.859431 L47.7777784,190.007601 C47.7777477,190.323362 47.529035,190.57933 47.222223,190.579361 L43.3767379,190.579361 C42.5942672,190.579361 41.9453004,190.799175 41.4670161,191.222592 C41.3107359,191.360944 41.1725734,191.526903 41.0503496,191.713949 C41.4810558,191.531483 41.9634085,191.437002 42.4739603,191.437002 L48.8888892,191.437002 L48.8888892,180.859431 L47.7777784,180.859431 Z M35.5,182.116677 L37.5,182.116677 C37.7761424,182.116677 38,182.340535 38,182.616677 L38,182.645847 C38,182.921989 37.7761424,183.145847 37.5,183.145847 L35.5,183.145847 C35.2238576,183.145847 35,182.921989 35,182.645847 L35,182.616677 C35,182.340535 35.2238576,182.116677 35.5,182.116677 Z M35.5,184.175016 L37.5,184.175016 C37.7761424,184.175016 38,184.398874 38,184.675016 L38,184.704185 C38,184.980328 37.7761424,185.204185 37.5,185.204185 L35.5,185.204185 C35.2238576,185.204185 35,184.980328 35,184.704185 L35,184.675016 C35,184.398874 35.2238576,184.175016 35.5,184.175016 Z M35.5,186.233355 L37.5,186.233355 C37.7761424,186.233355 38,186.457212 38,186.733355 L38,186.762524 C38,187.038666 37.7761424,187.262524 37.5,187.262524 L35.5,187.262524 C35.2238576,187.262524 35,187.038666 35,186.762524 L35,186.733355 C35,186.457212 35.2238576,186.233355 35.5,186.233355 Z M42.5,182.116677 L44.5,182.116677 C44.7761424,182.116677 45,182.340535 45,182.616677 L45,182.645847 C45,182.921989 44.7761424,183.145847 44.5,183.145847 L42.5,183.145847 C42.2238576,183.145847 42,182.921989 42,182.645847 L42,182.616677 C42,182.340535 42.2238576,182.116677 42.5,182.116677 Z M42.5,184.175016 L44.5,184.175016 C44.7761424,184.175016 45,184.398874 45,184.675016 L45,184.704185 C45,184.980328 44.7761424,185.204185 44.5,185.204185 L42.5,185.204185 C42.2238576,185.204185 42,184.980328 42,184.704185 L42,184.675016 C42,184.398874 42.2238576,184.175016 42.5,184.175016 Z M42.5,186.233355 L44.5,186.233355 C44.7761424,186.233355 45,186.457212 45,186.733355 L45,186.762524 C45,187.038666 44.7761424,187.262524 44.5,187.262524 L42.5,187.262524 C42.2238576,187.262524 42,187.038666 42,186.762524 L42,186.733355 C42,186.457212 42.2238576,186.233355 42.5,186.233355 Z" id="Icon_HC"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
File diff suppressed because one or more lines are too long
@@ -68,6 +68,6 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
|
||||
"scandit": "AQZyKCc+BEkNL00Y3h3FjawGLF+INUj7cVb0My91hl8ffiW873T8FTV1k4TIZJx5RwcJlYxhgsxHVcnM4AJgSwJhbAfxJmP/3XGijLlLp3XUIRjQwFtf7UlZAFZ7Vrt1/WSf7kxxrFQ2SE2AQwLqPg9DL+hHEfd4xT/15n8p2q7qUlCKLsV6jF12Pd7koFNSWNL3ZIkRtd1ma99/321dnwAJHFGXqWg5nprJ7sYtqUqNQ8Er9SlvKbhnw3AipHzKpz0O3oNfUsr6NlZivRBhMhCZLo5WpXo1m9uIU8zLEWMNDJ+wGUctcGxE3eCptP2zLXUgxxjB+0EXOUtT/GWUc/Ip61CMiyUf7Paz026E2eYil2yWgfkTP5CUgDMNGZFuAA1T5PhB9FRW51CjAIvwOKVMCvfixJiVoUsXHnWH2ZnXqtbDR/uEZBE7OKoBlaPL4G3Lvgdqym5EjROAztUXb6wOmVDiGzzqgizyZnIcxFBSKJAownGj9Vh4/Y/Ag1xzGzNtjz3ngSRfMfIIq/q2Q51uiLiv7mBVliPvPWMUTfTjnqnK/OSBlR2ID+COJqnUKpQMedPyOT3IMznmM6gQCmyYO5KE0MkfhFh6+pdNi6oJM2iZsxK1Z1V+GRSOIwrJEoajjDJkh439XjXk8NExFvplrLjK/oL/dsHIZiG6U5GVWW92kGkuXkJCeUz1CET3paxbGqwrd53r5d6gFABbC12CtcP2JeH4YYCpHYyPQacf0prj9Hdq3wDztShC9tH+4UQS/GbaDHKcS1ANIyPuTxHmBFtPuCJ9Uagy5QBEc8eAz2nfsbfaUxYzco6u/zhNsFbqp6zgQIxs5OcqDQ=="
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -69,6 +69,6 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
|
||||
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,6 @@
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
|
||||
"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" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
|
||||
<link href="/assets/icons/icons.css" rel="stylesheet" />
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<meta name="theme-color" content="#1976d2" />
|
||||
</head>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-color);
|
||||
@apply bg-background;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<div class="grid grid-cols-[1fr_auto] items-center gap-4">
|
||||
<div class="grid grid-flow-row gap-4">
|
||||
<h1 class="text-left font-bold text-lg">{{ item.headline }}</h1>
|
||||
<div class="notification-text">{{ item.text }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<button *ngIf="editButton" class="notification-edit-cta text-brand font-bold text-lg px-4 py-3" (click)="itemSelected.emit(item)">
|
||||
{{ editButtonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="notification-headline">
|
||||
<h1>{{ item.headline }}</h1>
|
||||
<button class="notification-edit-cta" (click)="itemSelected.emit(item)">
|
||||
Bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="notification-text">{{ item.text }}</div>
|
||||
|
||||
@@ -13,11 +13,5 @@ export class ModalNotificationsListItemComponent {
|
||||
@Output()
|
||||
itemSelected = new EventEmitter<MessageBoardItemDTO>();
|
||||
|
||||
@Input()
|
||||
editButton = true;
|
||||
|
||||
@Input()
|
||||
editButtonLabel = 'Bearbeiten';
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item
|
||||
(click)="itemSelected(notification)"
|
||||
[editButtonLabel]="'Packstück-Prüfung'"
|
||||
[item]="notification"
|
||||
(itemSelected)="itemSelected($event)"
|
||||
></modal-notifications-list-item>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -1,101 +0,0 @@
|
||||
import { createComponentFactory, Spectator, SpyObject } from '@ngneat/spectator';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { Router } from '@angular/router';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
import { Component } from '@angular/core';
|
||||
import { ModalNotificationsListItemComponent } from '../notifications-list-item/notifications-list-item.component';
|
||||
import { ModalNotificationsRemissionGroupComponent } from './notifications-remission-group.component';
|
||||
|
||||
// DummyComponent Class
|
||||
@Component({
|
||||
selector: 'dummy-component',
|
||||
template: '<div></div>',
|
||||
})
|
||||
class DummyComponent {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
describe('ModalNotificationsRemissionGroupComponent', () => {
|
||||
let spectator: Spectator<ModalNotificationsRemissionGroupComponent>;
|
||||
let uiFilterMock: SpyObject<UiFilter>;
|
||||
let router: Router;
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: ModalNotificationsRemissionGroupComponent,
|
||||
declarations: [ModalNotificationsListItemComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterTestingModule.withRoutes([
|
||||
{ path: 'filiale/goods/in/results', component: DummyComponent },
|
||||
{ path: 'filiale/remission/create', component: DummyComponent },
|
||||
]),
|
||||
UiIconModule,
|
||||
],
|
||||
providers: [],
|
||||
mocks: [UiFilter],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createComponent({ props: { notifications: [] } });
|
||||
router = spectator.inject(Router);
|
||||
uiFilterMock = spectator.inject(UiFilter);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('notifications input', () => {
|
||||
it('should display the right notification-counter value based on the length of the input array', () => {
|
||||
spectator.setInput({ notifications: [{}, {}, {}] });
|
||||
expect(spectator.query('.notification-counter')).toHaveText('3');
|
||||
});
|
||||
|
||||
it('should not display notification-counter if input array has length 0', () => {
|
||||
spectator.setInput({ notifications: [] });
|
||||
expect(spectator.query('.notification-counter')).toHaveText('');
|
||||
});
|
||||
|
||||
it('should render modal-notifications-list-item based on the input array', () => {
|
||||
const notifications = [{}, {}];
|
||||
spectator.setInput({ notifications });
|
||||
spectator.detectComponentChanges();
|
||||
expect(spectator.queryAll('modal-notifications-list-item')).toHaveLength(notifications.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('itemSelected()', () => {
|
||||
it('should navigate to results with queryParams from UiFilter.getQueryParamsFromQueryTokenDTO()', () => {
|
||||
const item: MessageBoardItemDTO = { queryToken: { input: { main_qs: 'test' } } };
|
||||
spyOn(UiFilter, 'getQueryParamsFromQueryTokenDTO').and.returnValue(item.queryToken.input);
|
||||
spyOn(router, 'navigate');
|
||||
spectator.component.itemSelected(item);
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/filiale/goods/in/results'], { queryParams: item.queryToken.input });
|
||||
});
|
||||
|
||||
it('should emit the navigated event after select item', () => {
|
||||
const item: MessageBoardItemDTO = { queryToken: { input: { main_qs: 'test' } } };
|
||||
spyOn(spectator.component.navigated, 'emit');
|
||||
spectator.component.itemSelected(item);
|
||||
expect(spectator.component.navigated.emit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions CTA', () => {
|
||||
it('should navigate to remission page after clicking the CTA', () => {
|
||||
const cta = spectator.query('.cta-primary');
|
||||
expect(cta).toHaveText('Zur Remission');
|
||||
expect(cta).toHaveAttribute('href', '/filiale/remission/create');
|
||||
});
|
||||
|
||||
it('should emit the navigated event after clicking the CTA', () => {
|
||||
const cta = spectator.query('.cta-primary') as HTMLAnchorElement;
|
||||
spyOn(spectator.component.navigated, 'emit');
|
||||
spectator.click(cta);
|
||||
expect(spectator.component.navigated.emit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
|
||||
@Component({
|
||||
selector: 'modal-notifications-package-inspection-group',
|
||||
templateUrl: 'notifications-package-inspection-group.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ModalNotificationsPackageInspectionGroupComponent {
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
|
||||
@Output()
|
||||
navigated = new EventEmitter<void>();
|
||||
|
||||
constructor(private _router: Router) {}
|
||||
|
||||
itemSelected(item: MessageBoardItemDTO) {
|
||||
this._router.navigate(['/filiale/package-inspection/packages']);
|
||||
this.navigated.emit();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
<div class="header">
|
||||
<div class="notification-icon">
|
||||
<span class="notification-counter">{{ notifications.length }}</span>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<h2>Remission</h2>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item [item]="notification" (itemSelected)="itemSelected($event)"></modal-notifications-list-item>
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
<div class="header">
|
||||
<div class="notification-icon">
|
||||
<div class="notification-counter">{{ notifications.length }}</div>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<h2>Reservierungsanfragen</h2>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item [item]="notification" (itemSelected)="itemSelected($event)"></modal-notifications-list-item>
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
<div class="header">
|
||||
<div class="notification-icon">
|
||||
<span class="notification-counter">{{ notifications.length }}</span>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<h2>Tätigkeitskalender</h2>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item [item]="notification" (itemSelected)="itemSelected($event)"></modal-notifications-list-item>
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
<div class="header">
|
||||
<div class="notification-icon">
|
||||
<span class="notification-counter">{{ notifications.length }}</span>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<h2>ISA-Update</h2>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<div class="notification-headline">
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
<h1>Sie haben neue Nachrichten</h1>
|
||||
|
||||
<ng-container *ngFor="let notification of notifications$ | async | keyvalue">
|
||||
<button type="button" class="notification-card" (click)="selectArea(notification.key)">
|
||||
<div class="notification-icon">
|
||||
<div class="notification-counter">{{ notification.value?.length }}</div>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
<span>{{ notification.value?.[0]?.category }}</span>
|
||||
<ng-container *ngFor="let notification of groupedNotifications$ | async">
|
||||
<button
|
||||
*ngIf="notification.group !== (activeCard$ | async)"
|
||||
type="button"
|
||||
class="notification-card"
|
||||
(click)="activeCard = notification.group"
|
||||
>
|
||||
{{ notification.group }}
|
||||
</button>
|
||||
<hr class="-mx-4" />
|
||||
<ng-container *ngIf="notification.key === selectedArea" [ngSwitch]="notification.value?.[0]?.category">
|
||||
<modal-notifications-update-group
|
||||
*ngSwitchCase="'ISA-Update'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
></modal-notifications-update-group>
|
||||
<modal-notifications-reservation-group
|
||||
*ngSwitchCase="'Reservierungsanfragen'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-reservation-group>
|
||||
<modal-notifications-remission-group
|
||||
*ngSwitchCase="'Remission'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-remission-group>
|
||||
<modal-notifications-task-calendar-group
|
||||
*ngSwitchCase="'Tätigkeitskalender'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-task-calendar-group>
|
||||
<modal-notifications-package-inspection-group
|
||||
*ngSwitchCase="'Wareneingang Lagerware'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
>
|
||||
</modal-notifications-package-inspection-group>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container [ngSwitch]="activeCard$ | async">
|
||||
<modal-notifications-update-group
|
||||
*ngSwitchCase="'ISA-Update'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
></modal-notifications-update-group>
|
||||
<modal-notifications-reservation-group
|
||||
*ngSwitchCase="'Reservierungsanfragen'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-reservation-group>
|
||||
<modal-notifications-remission-group
|
||||
*ngSwitchCase="'Remission'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-remission-group>
|
||||
<modal-notifications-task-calendar-group
|
||||
*ngSwitchCase="'Tätigkeitskalender'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-task-calendar-group>
|
||||
</ng-container>
|
||||
|
||||
@@ -2,44 +2,46 @@ modal-notifications {
|
||||
@apply flex flex-col relative h-full;
|
||||
|
||||
h1 {
|
||||
@apply text-xl font-bold text-center;
|
||||
@apply text-xl font-bold text-center mb-10;
|
||||
}
|
||||
|
||||
// .notification-card {
|
||||
// @apply text-center text-xl text-inactive-branch block bg-white rounded-t-card font-bold no-underline py-4 border-none outline-none shadow-card -ml-4;
|
||||
// width: calc(100% + 2rem);
|
||||
// }
|
||||
|
||||
.notification-card {
|
||||
@apply grid grid-flow-col items-center justify-center gap-4;
|
||||
@apply text-inactive-branch bg-white;
|
||||
@apply font-bold text-xl -mx-4 py-4;
|
||||
|
||||
.notification-icon {
|
||||
@apply relative;
|
||||
|
||||
.notification-counter {
|
||||
@apply absolute font-normal text-base -top-2 -right-1 bg-brand text-white rounded-full w-5 h-5 flex items-center justify-center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-bold text-2xl ml-4;
|
||||
}
|
||||
@apply text-center text-xl text-inactive-branch block bg-white rounded-t-card font-bold no-underline py-4 border-none outline-none shadow-card -ml-4;
|
||||
width: calc(100% + 2rem);
|
||||
}
|
||||
|
||||
modal-notifications-remission-group,
|
||||
modal-notifications-reservation-group,
|
||||
modal-notifications-task-calendar-group,
|
||||
modal-notifications-update-group,
|
||||
modal-notifications-package-inspection-group {
|
||||
modal-notifications-update-group {
|
||||
@apply flex flex-col relative pb-2;
|
||||
|
||||
.header {
|
||||
@apply flex flex-row justify-center items-center mt-5;
|
||||
|
||||
.notification-icon {
|
||||
@apply relative;
|
||||
|
||||
.notification-counter {
|
||||
@apply absolute -top-2 -right-1 bg-brand text-white rounded-full w-5 h-5 flex items-center justify-center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
@apply text-inactive-branch;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-bold text-2xl ml-4;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply bg-disabled-branch h-px-2 -ml-4 my-4;
|
||||
width: calc(100% + 2rem);
|
||||
}
|
||||
|
||||
.notification-list {
|
||||
@apply overflow-y-scroll -ml-4;
|
||||
max-height: calc(100vh - 450px);
|
||||
@@ -58,7 +60,7 @@ modal-notifications {
|
||||
|
||||
modal-notifications-list-item,
|
||||
modal-notifications-update-group {
|
||||
@apply flex flex-col relative p-4;
|
||||
@apply flex flex-col relative py-1 px-4;
|
||||
|
||||
.notification-headline {
|
||||
@apply flex flex-row justify-between items-start;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { Group, groupBy } from '@ui/common';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
import { EnvelopeDTO, MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
interface ModalNotificationComponentState {
|
||||
selectedArea: string;
|
||||
notifications: Record<string, MessageBoardItemDTO[]>;
|
||||
activeCard: string;
|
||||
groupedNotifications: Group<string, MessageBoardItemDTO>[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -15,61 +18,44 @@ interface ModalNotificationComponentState {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class ModalNotificationsComponent extends ComponentStore<ModalNotificationComponentState> {
|
||||
private _selectedAreaSelector = (state: ModalNotificationComponentState) => {
|
||||
if (state.selectedArea) {
|
||||
return state.selectedArea;
|
||||
export class ModalNotificationsComponent extends ComponentStore<ModalNotificationComponentState> implements OnInit {
|
||||
set activeCard(activeCard: string) {
|
||||
if (this.activeCard !== activeCard) {
|
||||
this.patchState({ activeCard });
|
||||
}
|
||||
|
||||
const keys = Object.keys(state.notifications);
|
||||
|
||||
for (const key of keys) {
|
||||
if (state.notifications[key]?.length > 0) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
get selectedArea() {
|
||||
return this.get(this._selectedAreaSelector);
|
||||
}
|
||||
|
||||
selectedArea$ = this.select(this._selectedAreaSelector);
|
||||
activeCard$ = this.select((s) => s.activeCard);
|
||||
|
||||
private _categorySelector = (state: ModalNotificationComponentState) => {
|
||||
const selectedArea = this._selectedAreaSelector(state);
|
||||
console.log('_categorySelector', state.notifications[selectedArea]?.[0]?.category);
|
||||
return state.notifications[selectedArea]?.[0]?.category;
|
||||
};
|
||||
|
||||
get category() {
|
||||
return this.get(this._categorySelector);
|
||||
get activeCard() {
|
||||
return this.get((s) => s.activeCard);
|
||||
}
|
||||
|
||||
category$ = this.select(this._categorySelector);
|
||||
get groupedNotifications() {
|
||||
return this.get((s) => s.groupedNotifications);
|
||||
}
|
||||
groupedNotifications$ = this.select((s) => s.groupedNotifications);
|
||||
|
||||
get notifications() {
|
||||
return this.get((s) => s.notifications);
|
||||
set groupedNotifications(groupedNotifications: Group<string, MessageBoardItemDTO>[]) {
|
||||
this.patchState({ groupedNotifications });
|
||||
}
|
||||
|
||||
notifications$ = this.select((s) => s.notifications);
|
||||
activeNotifications$ = combineLatest([this.activeCard$, this.groupedNotifications$]).pipe(
|
||||
map(([activeCard, notifications]) => notifications.find((n) => n.group === activeCard)?.items)
|
||||
);
|
||||
|
||||
constructor(private _modalRef: UiModalRef<any, Record<string, MessageBoardItemDTO[]>>) {
|
||||
constructor(private _modalRef: UiModalRef<any, EnvelopeDTO<MessageBoardItemDTO[]>>) {
|
||||
super({
|
||||
selectedArea: undefined,
|
||||
notifications: _modalRef.data,
|
||||
activeCard: undefined,
|
||||
groupedNotifications: groupBy(_modalRef.data.data, (item: MessageBoardItemDTO) => item.category),
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.patchState({ activeCard: this.groupedNotifications?.find((_) => true)?.group });
|
||||
}
|
||||
|
||||
close() {
|
||||
this._modalRef.close();
|
||||
}
|
||||
|
||||
selectArea(area: string) {
|
||||
this.patchState({
|
||||
selectedArea: area,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ModalNotificationsReservationGroupComponent } from './notifications-res
|
||||
import { ModalNotificationsTaskCalendarGroupComponent } from './notifications-task-calendar-group/notifications-task-calendar-group.component';
|
||||
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group/notifications-update-group.component';
|
||||
import { ModalNotificationsComponent } from './notifications.component';
|
||||
import { ModalNotificationsPackageInspectionGroupComponent } from './notifications-package-inspection-group/notifications-package-inspection-group.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, UiIconModule, RouterModule],
|
||||
@@ -20,7 +19,6 @@ import { ModalNotificationsPackageInspectionGroupComponent } from './notificatio
|
||||
ModalNotificationsTaskCalendarGroupComponent,
|
||||
ModalNotificationsUpdateGroupComponent,
|
||||
ModalNotificationsListItemComponent,
|
||||
ModalNotificationsPackageInspectionGroupComponent,
|
||||
],
|
||||
exports: [
|
||||
ModalNotificationsComponent,
|
||||
@@ -29,7 +27,6 @@ import { ModalNotificationsPackageInspectionGroupComponent } from './notificatio
|
||||
ModalNotificationsTaskCalendarGroupComponent,
|
||||
ModalNotificationsUpdateGroupComponent,
|
||||
ModalNotificationsListItemComponent,
|
||||
ModalNotificationsPackageInspectionGroupComponent,
|
||||
],
|
||||
})
|
||||
export class ModalNotificationsModule {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
|
||||
import { Config } from '@core/config';
|
||||
|
||||
@@ -13,10 +14,11 @@ export class AssortmentComponent implements OnInit {
|
||||
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() {
|
||||
this.createBreadcrumbIfNotExists();
|
||||
this._app.setTitle('Sortiment');
|
||||
}
|
||||
|
||||
async createBreadcrumbIfNotExists(): Promise<void> {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<div class="page-price-update-item__item-details">
|
||||
<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
|
||||
@@ -43,7 +43,7 @@
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
{{ environment.isTablet() ? (item?.product?.formatDetail | substr: 25) : item?.product?.formatDetail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ export class PriceUpdateItemComponent {
|
||||
itemId: Number(item?.product?.catalogProductNumber),
|
||||
branchId: defaultBranch?.id,
|
||||
})
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Config } from '@core/config';
|
||||
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 { PriceUpdateComponentStore } from './price-update.component.store';
|
||||
import { combineLatest, Subject, Subscription } from 'rxjs';
|
||||
@@ -26,8 +26,8 @@ export class PriceUpdateComponent implements OnInit {
|
||||
|
||||
hint$ = new Subject<string>();
|
||||
|
||||
@ViewChild(ShellFilterOverlayComponent)
|
||||
filterOverlay: ShellFilterOverlayComponent;
|
||||
@ViewChild(SharedFilterOverlayComponent)
|
||||
filterOverlay: SharedFilterOverlayComponent;
|
||||
|
||||
/**
|
||||
* 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 { NgModule } from '@angular/core';
|
||||
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
|
||||
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
@@ -8,7 +8,7 @@ import { PriceUpdateListModule } from './price-update-list';
|
||||
import { PriceUpdateComponent } from './price-update.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
|
||||
imports: [CommonModule, PriceUpdateListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
|
||||
exports: [PriceUpdateComponent],
|
||||
declarations: [PriceUpdateComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,134 +1,240 @@
|
||||
<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">
|
||||
<div class="product-details">
|
||||
<div class="product-image">
|
||||
<button class="image-button" (click)="showImages()">
|
||||
<img (load)="loadImage()" [src]="item.imageId | productImage: 195:315:true" alt="product image" />
|
||||
<ui-icon *ngIf="imageLoaded$ | async" icon="search_add" size="22px"></ui-icon>
|
||||
</button>
|
||||
<div class="page-article-details__product-details mb-3">
|
||||
<div class="page-article-details__product-bookmark justify-self-end">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
<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>
|
||||
|
||||
<div class="cta-recessions">{{ item.reviews.length }} Rezensionen</div>
|
||||
<div class="text-regular text-[#0556B4] font-bold">{{ item.reviews.length }} Rezensionen</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="product-info">
|
||||
<div class="row" [class.bookmark-badge-gap]="isBadgeVisible$ | async">
|
||||
<div>
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
class="autor"
|
||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search', 'results']"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
<div class="page-article-details__product-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
class="text-[#0556B4] font-semibold no-underline text-base"
|
||||
[routerLink]="resultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</ng-template>
|
||||
|
||||
<button class="cta-print right" (click)="print()">Drucken</button>
|
||||
</div>
|
||||
<div class="title">
|
||||
{{ item.product?.name }}
|
||||
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
|
||||
{{ promotionPoints }} Lesepunkte
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
|
||||
[alt]="item.product?.formatDetail"
|
||||
/>
|
||||
{{ item.product?.formatDetail }}
|
||||
<!-- TODO: Ticket PREISGEBUNDEN -->
|
||||
<div class="page-article-details__product-price-bound self-end"></div>
|
||||
</div>
|
||||
|
||||
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
|
||||
<div class="page-article-details__product-manufacturer" data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
|
||||
|
||||
<div class="page-article-details__product-language" *ngIf="item?.product?.locale" data-name="product-language">
|
||||
{{ item?.product?.locale }}
|
||||
</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 *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
|
||||
|
||||
<div>{{ publicationDate$ | async }}</div>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="price" *ngIf="price$ | async; let price">
|
||||
{{ price?.value?.value | currency: price?.value?.currency:'code' }}
|
||||
</div>
|
||||
<div *ngIf="store.promotionPoints$ | async; let promotionPoints">{{ promotionPoints }} Lesepunkte</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="row stock">
|
||||
<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
|
||||
[uiOverlayTrigger]="orderDeadlineTooltip"
|
||||
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
|
||||
icon="box_out"
|
||||
size="18px"
|
||||
></ui-icon>
|
||||
|
||||
<ui-tooltip
|
||||
[warning]="true"
|
||||
yPosition="above"
|
||||
xPosition="after"
|
||||
[yOffset]="-11"
|
||||
[xOffset]="8"
|
||||
#orderDeadlineTooltip
|
||||
[closeable]="true"
|
||||
>
|
||||
<b>{{ (store.pickUpAvailability$ | async)?.orderDeadline | orderDeadline }}</b>
|
||||
</ui-tooltip>
|
||||
</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">
|
||||
<div class="page-article-details__shelfinfo" *ngIf="store.isDownload$ | async">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
@@ -155,24 +261,7 @@
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<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)">
|
||||
<div class="page-article-details__shelfinfo text-right" *ngIf="!(store.isDownload$ | async)">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
|
||||
@@ -196,109 +285,115 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bookmark">
|
||||
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
|
||||
<button [uiOverlayTrigger]="archivTooltip" class="bookmark-badge">
|
||||
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
|
||||
<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="bookmark-badge">
|
||||
<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="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 class="page-article-details__product-formats-container mt-3" *ngIf="item.family?.length > 0">
|
||||
<hr class="bg-[#E6EFF9] border-t-2" />
|
||||
<div class="pt-3">
|
||||
<div class="page-article-details__product-formats">
|
||||
<span class="mr-2">Auch verfügbar als</span>
|
||||
|
||||
<ui-slider [scrollDistance]="250">
|
||||
<a
|
||||
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
|
||||
*ngFor="let format of item.family"
|
||||
[routerLink]="getDetailsPath(format.product.ean)"
|
||||
[queryParamsHandling]="!(isTablet$ | async) ? 'preserve' : ''"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<img
|
||||
class="mr-2"
|
||||
*ngIf="!!format.product?.format"
|
||||
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
{{ format.product?.formatDetail }}
|
||||
<span class="ml-1">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</ui-slider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="product-actions">
|
||||
<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 class="bg-[#E6EFF9] border-t-2 my-3" />
|
||||
|
||||
<hr />
|
||||
<ng-container *ngIf="item.family?.length > 0">
|
||||
<div class="product-formats">
|
||||
<span class="label">Auch verfügbar als</span>
|
||||
|
||||
<ui-slider [scrollDistance]="250">
|
||||
<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">
|
||||
<div
|
||||
#description
|
||||
class="page-article-details__product-description flex flex-col flex-grow overflow-hidden overflow-y-scroll"
|
||||
*ngIf="item.texts?.length > 0"
|
||||
>
|
||||
<div class="whitespace-pre-line">
|
||||
{{ item.texts[0].value }}
|
||||
</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">
|
||||
<h3 class="header">{{ text.label }}</h3>
|
||||
<h3 class="my-4 text-regular font-bold">{{ text.label }}</h3>
|
||||
{{ text.value }}
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<div class="recommendations-overlay" @slideYAnimation *ngIf="showRecommendations">
|
||||
<button class="product-button" (click)="showRecommendations = false">{{ (store.item$ | async)?.product?.name }}</button>
|
||||
<div class="page-article-details__recommendations-overlay absolute top-16 rounded-t-card" @slideYAnimation *ngIf="showRecommendations">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -1,269 +1,99 @@
|
||||
:host {
|
||||
@apply flex flex-col;
|
||||
@apply box-border block h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)];
|
||||
}
|
||||
|
||||
.product-card {
|
||||
@apply flex flex-col bg-white w-full rounded-card shadow-card;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-article-details__container {
|
||||
@apply h-full w-full bg-white rounded-card shadow-card flex flex-col;
|
||||
}
|
||||
|
||||
.product-recommendations {
|
||||
@apply sticky bottom-0 border-none outline-none left-0 right-0 flex items-center px-5 h-16 bg-white w-full;
|
||||
box-shadow: #dce2e9 0px -2px 18px 0px;
|
||||
|
||||
.label {
|
||||
@apply uppercase text-active-customer font-bold text-small;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply absolute right-5 bottom-5 h-12;
|
||||
}
|
||||
.page-article-details__product-details {
|
||||
@apply grid gap-x-5;
|
||||
grid-template-columns: max-content auto;
|
||||
grid-template-rows: 2.1875rem repeat(11, minmax(auto, max-content));
|
||||
grid-template-areas:
|
||||
'. . . bookmark'
|
||||
'image contributors contributors contributors'
|
||||
'image title title print'
|
||||
'image title title .'
|
||||
'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 {
|
||||
@apply absolute w-full top-0 rounded-t-card;
|
||||
top: 56px;
|
||||
.page-article-details__product-bookmark {
|
||||
grid-area: bookmark;
|
||||
}
|
||||
|
||||
.product-button {
|
||||
@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;
|
||||
box-shadow: 0 -2px 24px 0 #dce2e9;
|
||||
height: 60px;
|
||||
.page-article-details__product-image-recessions {
|
||||
grid-area: image;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BranchDTO } from '@swagger/checkout';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { ModalReviewsComponent } from '@modal/reviews';
|
||||
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 { ModalImagesComponent } from 'apps/modal/images/src/public-api';
|
||||
import { ProductImageService } from 'apps/cdn/product-image/src/public-api';
|
||||
@@ -20,7 +20,8 @@ import { DateAdapter } from '@ui/common';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-details',
|
||||
@@ -66,10 +67,6 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.store.isDeliveryB2BAvailabilityAvailable$,
|
||||
]).pipe(map(([digDelivery, b2bDelivery]) => b2bDelivery && !digDelivery));
|
||||
|
||||
customerFeatures$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId }))
|
||||
);
|
||||
|
||||
showSubscriptionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'PFO')));
|
||||
|
||||
showPromotionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'Promotion')));
|
||||
@@ -118,37 +115,18 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
price$ = combineLatest([
|
||||
this.store.item$,
|
||||
this.store.takeAwayAvailability$,
|
||||
this.store.deliveryAvailability$,
|
||||
this.store.deliveryDigAvailability$,
|
||||
this.store.deliveryB2BAvailability$,
|
||||
]).pipe(
|
||||
map(([item, takeAway, delivery, deliveryDig, deliveryB2B]) => {
|
||||
if (item?.catalogAvailability?.price?.value?.value) {
|
||||
return item?.catalogAvailability?.price;
|
||||
}
|
||||
get isTablet$() {
|
||||
return this._environment.matchTablet$.pipe(
|
||||
map((state) => state?.matches),
|
||||
shareReplay()
|
||||
);
|
||||
}
|
||||
|
||||
if (takeAway?.price?.value?.value) {
|
||||
return takeAway.price;
|
||||
}
|
||||
get resultsPath() {
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId);
|
||||
}
|
||||
|
||||
if (delivery?.price?.value?.value) {
|
||||
return delivery.price;
|
||||
}
|
||||
|
||||
if (deliveryDig?.price?.value?.value) {
|
||||
return deliveryDig.price;
|
||||
}
|
||||
|
||||
if (deliveryB2B?.price?.value?.value) {
|
||||
return deliveryB2B.price;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
);
|
||||
showMore: boolean = false;
|
||||
|
||||
constructor(
|
||||
public readonly applicationService: ApplicationService,
|
||||
@@ -163,8 +141,9 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
public elementRef: ElementRef,
|
||||
private _purchaseOptionsModalService: PurchaseOptionsModalService,
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _router: Router,
|
||||
private _domainCheckoutService: DomainCheckoutService
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _environment: EnvironmentService,
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -199,16 +178,30 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
filter((f) => !!f)
|
||||
);
|
||||
|
||||
const more$ = this.activatedRoute.params.subscribe(() => (this.showMore = false));
|
||||
|
||||
this.subscriptions.add(processIdSubscription);
|
||||
this.subscriptions.add(more$);
|
||||
this.subscriptions.add(this.store.loadItemById(id$));
|
||||
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() {
|
||||
this.subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
getDetailsPath(ean?: string) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, ean });
|
||||
}
|
||||
|
||||
async updateBreadcrumb(item: ItemDTO) {
|
||||
const crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details', `${item.id}`])
|
||||
@@ -222,13 +215,41 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this.breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
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,
|
||||
tags: ['catalog', 'details', `${item.id}`],
|
||||
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() {
|
||||
const item = await this.store.item$.pipe(first()).toPromise();
|
||||
this.uiModal.open({
|
||||
@@ -308,23 +329,14 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
type: 'add',
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
items: [item],
|
||||
pickupBranch: selectedBranch,
|
||||
inStoreBranch: selectedBranch,
|
||||
preSelectOption: !!selectedBranch ? { option: 'in-store', showOptionOnly: true } : undefined,
|
||||
})
|
||||
.afterClosed$.subscribe(async (result) => {
|
||||
.afterClosed$.subscribe((result) => {
|
||||
if (result?.data === 'continue') {
|
||||
const customer = await this._domainCheckoutService
|
||||
.getBuyer({ processId: this.applicationService.activatedProcessId })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
if (customer) {
|
||||
this.navigateToShoppingCart();
|
||||
} else {
|
||||
this.navigateToCustomerSearch();
|
||||
}
|
||||
this.navigateToShoppingCart();
|
||||
console.log('continue');
|
||||
} else if (result?.data === 'continue-shopping') {
|
||||
this.navigateToResultList();
|
||||
console.log('continue-shopping');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -333,24 +345,6 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
this._router.navigate([`/kunde/${this.applicationService.activatedProcessId}/cart/review`]);
|
||||
}
|
||||
|
||||
async navigateToCustomerSearch() {
|
||||
try {
|
||||
const response = await this.customerFeatures$
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((customerFeatures) => {
|
||||
return this._domainCheckoutService.canSetCustomer({ processId: this.applicationService.activatedProcessId, customerFeatures });
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
|
||||
queryParams: { filter_customertype: response.filter.customertype },
|
||||
});
|
||||
} catch (error) {
|
||||
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search']);
|
||||
}
|
||||
}
|
||||
|
||||
async navigateToResultList() {
|
||||
let crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
|
||||
@@ -367,9 +361,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
scrollTop() {
|
||||
const element = this.elementRef.nativeElement.closest('.main-wrapper');
|
||||
element?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
scrollTop(div: HTMLDivElement) {
|
||||
div?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { ArticleRecommendationsComponent } from './recommendations/article-recom
|
||||
import { PipesModule } from '../shared/pipes/pipes.module';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -23,7 +22,6 @@ import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
|
||||
UiCommonModule,
|
||||
UiTooltipModule,
|
||||
PipesModule,
|
||||
OrderDeadlinePipeModule,
|
||||
],
|
||||
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],
|
||||
|
||||
@@ -129,11 +129,7 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
|
||||
|
||||
//#region Abholung
|
||||
readonly fetchingPickUpAvailability$ = this.select((s) => s.fetchingPickUpAvailability);
|
||||
readonly pickUpAvailability$: Observable<AvailabilityDTO & { orderDeadline?: string }> = combineLatest([
|
||||
this.itemData$,
|
||||
this.branch$,
|
||||
this.isDownload$,
|
||||
]).pipe(
|
||||
readonly pickUpAvailability$: Observable<AvailabilityDTO> = combineLatest([this.itemData$, this.branch$, this.isDownload$]).pipe(
|
||||
tap(() => this.patchState({ fetchingPickUpAvailability: true, fetchingPickUpAvailabilityError: undefined })),
|
||||
switchMap(([item, branch, isDownload]) =>
|
||||
!!item && !!branch && !isDownload
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
: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;
|
||||
height: calc(100vh - 342px);
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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 {
|
||||
@apply flex flex-col w-full 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;
|
||||
}
|
||||
@apply flex flex-col w-full h-[calc(100vh-16.5rem)] desktop-small:h-[calc(100vh-15.1rem)] box-content relative;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
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 { UiFilterAutocompleteProvider } from '@ui/filter';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { filter, first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
import { ArticleSearchService } from './article-search.store';
|
||||
import { FocusSearchboxEvent } from './focus-searchbox.event';
|
||||
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({
|
||||
selector: 'page-article-search',
|
||||
@@ -15,9 +17,8 @@ import { ArticleSearchMainAutocompleteProvider } from './providers';
|
||||
styleUrls: ['article-search.component.scss'],
|
||||
providers: [
|
||||
FocusSearchboxEvent,
|
||||
ArticleSearchService,
|
||||
{
|
||||
provide: UiFilterAutocompleteProvider,
|
||||
provide: FilterAutocompleteProvider,
|
||||
useClass: ArticleSearchMainAutocompleteProvider,
|
||||
multi: true,
|
||||
},
|
||||
@@ -28,22 +29,16 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
private _processId$: Observable<number>;
|
||||
|
||||
initialFilter$ = this._articleSearch.filter$.pipe(
|
||||
filter((filter) => !!filter),
|
||||
first()
|
||||
);
|
||||
get isTablet() {
|
||||
return this._environmentService.matchTablet();
|
||||
}
|
||||
|
||||
hasFilter$ = this._articleSearch.filter$.pipe(
|
||||
withLatestFrom(this.initialFilter$),
|
||||
map(([filter, initialFilter]) => !isEqual(filter?.getQueryParams(), initialFilter?.getQueryParams()))
|
||||
);
|
||||
|
||||
filterActive$ = new BehaviorSubject<boolean>(false);
|
||||
constructor(
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _router: Router,
|
||||
private _articleSearch: ArticleSearchService,
|
||||
private _activatedRoute: ActivatedRoute
|
||||
private _activatedRoute: ActivatedRoute,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _environmentService: EnvironmentService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -60,12 +55,17 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.subscribe(([state, processId]) => {
|
||||
if (state.searchState === '') {
|
||||
const params = state.filter.getQueryParams();
|
||||
if (state.hits === 1) {
|
||||
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 {
|
||||
const params = state.filter.getQueryParams();
|
||||
this._router.navigate(['/kunde', processId, 'product', 'search', 'results'], {
|
||||
this._navigationService.navigateToResults({
|
||||
processId,
|
||||
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() {
|
||||
this._processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||
}
|
||||
@@ -96,7 +110,7 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
|
||||
await this._breadcrumb.addBreadcrumbIfNotExists({
|
||||
key: processId,
|
||||
name: 'Artikelsuche',
|
||||
path: `/kunde/${processId}/product`,
|
||||
path: this._navigationService.getArticleSearchBasePath(processId),
|
||||
params: queryParams,
|
||||
tags: ['catalog', 'main'],
|
||||
section: 'customer',
|
||||
|
||||
@@ -6,12 +6,13 @@ import { ArticleSearchComponent } from './article-search.component';
|
||||
import { SearchResultsModule } from './search-results/search-results.module';
|
||||
import { SearchMainModule } from './search-main/search-main.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({
|
||||
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, ShellFilterOverlayModule],
|
||||
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule, SharedFilterOverlayModule],
|
||||
exports: [ArticleSearchComponent],
|
||||
declarations: [ArticleSearchComponent],
|
||||
providers: [],
|
||||
providers: [ArticleSearchService],
|
||||
})
|
||||
export class ArticleSearchModule {}
|
||||
|
||||
@@ -3,24 +3,31 @@ import { DomainCatalogService } from '@domain/catalog';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
import { UiFilter } from '@ui/filter';
|
||||
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 { BranchDTO } from '@swagger/checkout';
|
||||
import { Filter } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
export interface ArticleSearchState {
|
||||
processId: number;
|
||||
filter: UiFilter;
|
||||
filter: Filter;
|
||||
searchState: '' | 'fetching' | 'empty' | 'error';
|
||||
items: ItemDTO[];
|
||||
hits: number;
|
||||
selectedBranch: BranchDTO;
|
||||
selectedItemIds: number[];
|
||||
defaultSettings?: UISettingsDTO;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
get defaultSettings() {
|
||||
return this.get((s) => s.defaultSettings);
|
||||
}
|
||||
|
||||
readonly defaultSettings$ = this.select((s) => s.defaultSettings);
|
||||
|
||||
get processId() {
|
||||
return this.get((s) => s.processId);
|
||||
}
|
||||
@@ -100,19 +107,19 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
}
|
||||
|
||||
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
|
||||
const filter = await this.catalog
|
||||
.getSettings()
|
||||
.pipe(map((settings) => UiFilter.create(settings)))
|
||||
.toPromise();
|
||||
const defaultSettings = await this.catalog.getSettings().toPromise();
|
||||
|
||||
const filter = Filter.create(defaultSettings);
|
||||
|
||||
if (!!defaultQueryParams) {
|
||||
filter?.fromQueryParams(defaultQueryParams);
|
||||
}
|
||||
|
||||
this.setFilter(filter);
|
||||
this.patchState({ defaultSettings });
|
||||
}
|
||||
|
||||
setFilter(filter: UiFilter) {
|
||||
setFilter(filter: Filter) {
|
||||
this.patchState({ filter });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
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 { catchError, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ArticleSearchMainAutocompleteProvider extends UiFilterAutocompleteProvider {
|
||||
export class ArticleSearchMainAutocompleteProvider extends FilterAutocompleteProvider {
|
||||
for = 'catalog';
|
||||
|
||||
constructor(private domainCatalogSearch: DomainCatalogService) {
|
||||
super();
|
||||
}
|
||||
|
||||
complete(input: UiInput): Observable<UiFilterAutocomplete[]> {
|
||||
complete(input: FilterInput): Observable<FilterAutocomplete[]> {
|
||||
const token = input?.parent?.parent?.getQueryToken();
|
||||
const filter = token?.filter;
|
||||
const type = Object.keys(token?.input).join(';');
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
<div class="catalog-search-filter-content">
|
||||
<div class="w-full flex flex-row justify-end items-center">
|
||||
<button (click)="clearFilter(filter)" class="text-[#0556B4] mr-[0.8125rem]">Alle Filter entfernen</button>
|
||||
<button class="text-cool-grey p-4 outline-none border-none bg-transparent" type="button" (click)="close.emit()">
|
||||
<ui-icon icon="close" size="20px"></ui-icon>
|
||||
<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 -mt-8">
|
||||
<h1 class="text-3xl font-bold text-center py-4">Filter</h1>
|
||||
<ui-filter
|
||||
<div class="catalog-search-filter-content-main -mt-12">
|
||||
<h1 class="text-2xl text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||
<shared-filter
|
||||
[filter]="filter"
|
||||
[loading]="fetching$ | async"
|
||||
(search)="applyFilter(filter)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
resizeInputOptionsToElement="page-article-search-filter .cta-wrapper"
|
||||
[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 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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
: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 {
|
||||
@@ -13,14 +13,12 @@
|
||||
}
|
||||
|
||||
.cta-wrapper {
|
||||
@apply fixed bottom-8 whitespace-nowrap;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
@apply text-center whitespace-nowrap absolute bottom-8 left-0 w-full;
|
||||
}
|
||||
|
||||
.cta-reset-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 {
|
||||
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
|
||||
@@ -34,3 +32,7 @@
|
||||
.cta-apply-filter {
|
||||
@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 { UiFilter, UiFilterComponent } from '@ui/filter';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { first, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
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({
|
||||
selector: 'page-article-search-filter',
|
||||
@@ -10,56 +15,105 @@ import { ArticleSearchService } from '../article-search.store';
|
||||
styleUrls: ['search-filter.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArticleSearchFilterComponent implements OnInit {
|
||||
export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
close = new EventEmitter();
|
||||
|
||||
_processId$ = this._activatedRoute.parent.data.pipe(map((data) => Number(data.processId)));
|
||||
|
||||
fetching$: Observable<boolean>;
|
||||
|
||||
filter$: Observable<UiFilter>;
|
||||
filter$: Observable<Filter>;
|
||||
|
||||
searchboxHint$ = this.articleSearch.searchboxHint$;
|
||||
|
||||
@ViewChild(UiFilterComponent, { static: false })
|
||||
uiFilterComponent: UiFilterComponent;
|
||||
@ViewChild(FilterComponent, { static: false })
|
||||
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() {
|
||||
this.fetching$ = this.articleSearch.fetching$;
|
||||
this.filter$ = this.articleSearch.filter$.pipe(
|
||||
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-',
|
||||
// })
|
||||
// )
|
||||
);
|
||||
this.filter$ = this.articleSearch.filter$.pipe(map((filter) => Filter.create(filter)));
|
||||
}
|
||||
|
||||
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.articleSearch.setFilter(value);
|
||||
this.articleSearch.search({ clear: true });
|
||||
this.articleSearch.searchCompleted.pipe(take(1)).subscribe((s) => {
|
||||
if (s.searchState === '') {
|
||||
this.close.emit();
|
||||
}
|
||||
});
|
||||
this.articleSearch.searchCompleted
|
||||
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
|
||||
.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 || '' };
|
||||
this.articleSearch.setDefaultFilter(queryParams);
|
||||
}
|
||||
|
||||
clearFilter(value: UiFilter) {
|
||||
value.unselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
|
||||
import { ArticleSearchFilterComponent } from './search-filter.component';
|
||||
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiFilterNextModule, UiIconModule, UiSpinnerModule],
|
||||
imports: [CommonModule, RouterModule, FilterNextModule, UiIconModule, UiSpinnerModule],
|
||||
exports: [ArticleSearchFilterComponent],
|
||||
declarations: [ArticleSearchFilterComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,26 +1,50 @@
|
||||
<div class="card-search-article">
|
||||
<h1 class="title">Artikelsuche</h1>
|
||||
<p class="info">
|
||||
<div class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-full">
|
||||
<h1 class="text-2xl text-[1.625rem] font-bold mb-[0.375rem]">Artikelsuche</h1>
|
||||
<p class="text-lg mb-10">
|
||||
Welchen Artikel suchen Sie?
|
||||
</p>
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<ui-filter-filter-group-main [inputGroup]="filter?.filter | group: 'main'"></ui-filter-filter-group-main>
|
||||
<ui-filter-input-group-main
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search(filter)"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></ui-filter-input-group-main>
|
||||
<shared-filter-filter-group-main
|
||||
class="mb-8 w-full"
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
[inputGroup]="filter?.filter | group: 'main'"
|
||||
></shared-filter-filter-group-main>
|
||||
<div class="flex flex-row px-12 justify-center desktop:px-0">
|
||||
<shared-filter-input-group-main
|
||||
class="block w-full mr-3 desktop:mx-auto"
|
||||
[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">
|
||||
<h3 class="recent-searches-header">Deine letzten Suchanfragen</h3>
|
||||
<ul>
|
||||
<li class="recent-searches-items" *ngFor="let recentQuery of history$ | async">
|
||||
<button (click)="setQueryHistory(filter, recentQuery.friendlyName)">
|
||||
<ui-icon icon="search" size="15px"></ui-icon>
|
||||
<p>{{ recentQuery.friendlyName }}</p>
|
||||
<div class="flex flex-col items-start ml-12 desktop:ml-8 py-6 bg-white">
|
||||
<h3 class="text-sm font-bold mb-3">Deine letzten Suchanfragen</h3>
|
||||
<ul class="flex flex-col justify-start overflow-hidden items-start m-0 p-0 bg-white w-full">
|
||||
<li class="list-none pb-3" *ngFor="let recentQuery of history$ | async">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-base m-0 p-0"
|
||||
(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>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,71 +1,13 @@
|
||||
:host {
|
||||
@apply flex flex-col box-border;
|
||||
@apply flex flex-col box-border h-full;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-page-heading font-bold;
|
||||
}
|
||||
|
||||
.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;
|
||||
.page-search-main__filter {
|
||||
&.active {
|
||||
@apply bg-[#596470] text-white ml-px-5;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep page-article-search-main ui-filter-filter-group-main .ui-filter-chip:not(.selected) {
|
||||
@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 { ApplicationService } from '@core/application';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { UiFilter, UiFilterInputGroupMainComponent } from '@ui/filter';
|
||||
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 { isEqual } from 'lodash';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
|
||||
@Component({
|
||||
selector: 'page-article-search-main',
|
||||
@@ -26,15 +28,39 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
|
||||
subscriptions = new Subscription();
|
||||
|
||||
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
|
||||
uiInputGroupMain: UiFilterInputGroupMainComponent;
|
||||
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());
|
||||
})
|
||||
);
|
||||
|
||||
@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(
|
||||
private searchService: ArticleSearchService,
|
||||
private catalog: DomainCatalogService,
|
||||
private route: ActivatedRoute,
|
||||
private application: ApplicationService,
|
||||
private breadcrumb: BreadcrumbService
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -44,7 +70,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
.subscribe(async ([processId, queryParams]) => {
|
||||
const processChanged = processId !== this.searchService.processId;
|
||||
|
||||
if (!(this.searchService.filter instanceof UiFilter)) {
|
||||
if (!(this.searchService.filter instanceof Filter)) {
|
||||
await this.searchService.setDefaultFilter();
|
||||
}
|
||||
|
||||
@@ -57,6 +83,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
// Reset Filter on Product Search Shell Navigation click
|
||||
await this.searchService.setDefaultFilter(queryParams);
|
||||
}
|
||||
|
||||
@@ -85,16 +112,30 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
this.updateBreadcrumb(this.searchService.processId, this.searchService.filter.getQueryParams());
|
||||
}
|
||||
|
||||
search(filter: UiFilter) {
|
||||
this.uiInputGroupMain.cancelAutocomplete();
|
||||
search(filter: Filter) {
|
||||
this.sharedFilterInputGroupMain.cancelAutocomplete();
|
||||
this.searchService.setFilter(filter);
|
||||
this.searchService.search({ clear: true });
|
||||
}
|
||||
|
||||
setQueryHistory(filter: UiFilter, query: string) {
|
||||
setQueryHistory(filter: Filter, query: string) {
|
||||
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> = {}) {
|
||||
const clean = { ...params };
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ArticleSearchMainComponent } from './search-main.component';
|
||||
import { FilterNextModule } from 'apps/shared/components/filter/src/lib';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiIconModule, UiFilterNextModule],
|
||||
imports: [CommonModule, RouterModule, UiIconModule, FilterNextModule],
|
||||
exports: [ArticleSearchMainComponent],
|
||||
declarations: [ArticleSearchMainComponent],
|
||||
providers: [],
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
<div class="thumbnail animation"></div>
|
||||
<div class="col">
|
||||
<div class="author animation"></div>
|
||||
<div class="row">
|
||||
<div class="title animation"></div>
|
||||
<div class="price animation"></div>
|
||||
<ng-container *ngIf="!(mainOutletActive$ | async); else mainOutlet">
|
||||
<div class="bg-ucla-blue rounded-card w-[4.375rem] h-[5.625rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-row justify-between flex-grow">
|
||||
<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 class="space"></div>
|
||||
<div class="row">
|
||||
<div class="category animation"></div>
|
||||
<div class="stock animation"></div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #mainOutlet>
|
||||
<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 class="row">
|
||||
<div class="manufacturer animation"></div>
|
||||
<div class="ava animation"></div>
|
||||
<div class="flex flex-col ml-14 w-[30%]">
|
||||
<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>
|
||||
<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 {
|
||||
@apply flex flex-row rounded-card bg-white mb-2 p-4;
|
||||
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;
|
||||
@apply flex flex-row rounded-card bg-white mb-2 p-4 w-full h-[212px] desktop-small:h-[181px];
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
selector: 'page-search-result-item-loading',
|
||||
@@ -7,5 +10,13 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
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]">
|
||||
<div class="item-thumbnail">
|
||||
<img loading="lazy" *ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
||||
</div>
|
||||
|
||||
<div class="item-contributors">
|
||||
<a
|
||||
*ngFor="let contributor of contributors; let last = last"
|
||||
[routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'search', 'results']"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
<a
|
||||
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"
|
||||
[class.page-search-result-item__item-card-main]="mainOutletActive$ | async"
|
||||
[routerLink]="detailsPath"
|
||||
[routerLinkActive]="!isTablet && !(mainOutletActive$ | async) ? 'active' : ''"
|
||||
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[50px] h-[79px]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||
[src]="thumbnailUrl"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</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"
|
||||
class="page-search-result-item__item-grid-container"
|
||||
[class.page-search-result-item__item-grid-container-main]="mainOutletActive$ | async"
|
||||
>
|
||||
{{ 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">
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
<div
|
||||
class="page-search-result-item__item-title font-bold text-2xl"
|
||||
[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">
|
||||
<ui-select-bullet [ngModel]="selected" (ngModelChange)="setSelected($event)"></ui-select-bullet>
|
||||
</div>
|
||||
|
||||
<div class="item-stock z-dropdown" [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 class="page-search-result-item__item-format desktop-small:text-sm">
|
||||
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
|
||||
<img
|
||||
class="mr-3"
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail | substr: 25 }}
|
||||
</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="item-stock"><ui-icon icon="home" size="1em"></ui-icon> {{ item?.stockInfos | stockInfos }} x</div> -->
|
||||
</div>
|
||||
|
||||
<div class="item-ssc" [class.xs]="item?.catalogAvailability?.sscText?.length >= 60">
|
||||
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
|
||||
</div>
|
||||
<div class="page-search-result-item__item-manufacturer desktop-small:text-sm">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }}
|
||||
</div>
|
||||
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
<div class="page-search-result-item__item-misc desktop-small:text-sm">
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ publicationDate }}
|
||||
</div>
|
||||
|
||||
<div class="item-misc">
|
||||
{{ item?.product?.manufacturer | substr: 18 }} | {{ item?.product?.ean }} <br />
|
||||
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ publicationDate }}
|
||||
<div class="page-search-result-item__item-price desktop-small:text-sm font-bold justify-self-end">
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</a>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user