RD Shell - Navigation

This commit is contained in:
Lorenz Hilpert
2023-04-18 14:09:36 +02:00
parent 8bc2ea8373
commit e8020ffde6
214 changed files with 6401 additions and 3164 deletions

View File

@@ -3,6 +3,5 @@
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"eg2.vscode-npm-script"
]
}

View File

@@ -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"
]
}
}
}
}
}
}

View File

@@ -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))));

View File

@@ -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 }>());

View File

@@ -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 }) => {

View File

@@ -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);

View File

@@ -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',
};

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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],
})

View File

@@ -0,0 +1,3 @@
<shell-root>
<router-outlet></router-outlet>
</shell-root>

View 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() {}
}

View File

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

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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,
},
});
});
});
});

View File

@@ -1,174 +0,0 @@
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList, TrackByFunction, NgZone } from '@angular/core';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap, take, tap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { UiModalService } from '@ui/modal';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { combineLatest } from 'rxjs';
import { AuthService } from '@core/auth';
import { DomainAvailabilityService } from '@domain/availability';
import { ShellProcessTabComponent } from '@shell/process';
import { Config } from '@core/config';
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
@Component({
selector: 'app-shell',
templateUrl: 'shell.component.html',
styleUrls: ['shell.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShellComponent {
isDevelopment = Boolean(this._config.get('debug'));
debugOpen = false;
@ViewChildren('processTabs')
readonly processTabs: QueryList<ShellProcessTabComponent>;
notifications$ = this._notificationsHub.notifications$;
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
get activatedProcessId$() {
return this._appService.getActivatedProcessId$().pipe(
tap((activatedProcessId) => {
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
})
);
}
customerBasePath$ = this.activatedProcessId$.pipe(
switchMap((processId) => this._appService.getProcessById$(processId)),
map((process) => {
if (!!process && process.section === 'customer' && process.type !== 'cart-checkout') {
// Übernehme aktiven Prozess
return `/kunde/${process.id}`;
} else {
// Über Guards wird ein neuer Prozess erstellt
return '/kunde';
}
})
);
get section$() {
return this._appService.getSection$().pipe(shareReplay());
}
get processes$() {
return this.section$.pipe(switchMap((section) => this._appService.getProcesses$(section)));
}
get remissionProcess$() {
return this._appService.getProcessById$(this._config.get('process.ids.remission'));
}
get remissionUrl$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active ? `/filiale/remission/${process.data.active}/list` : '/filiale/remission'))
);
}
get remissionQueryParams$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active && process?.data?.queryParams ? process.data.queryParams : {}))
);
}
get addProcessLabel$() {
return combineLatest([this.section$, this.processes$]).pipe(
map(([section, processes]) => (section === 'customer' && processes.length === 0 ? 'VORGANG STARTEN' : ''))
);
}
get canAddProcess$() {
return this.section$.pipe(map((section) => section === 'customer'));
}
get currentBranch$() {
return this._availabilityService.getDefaultBranch();
}
constructor(
private readonly _appService: ApplicationService,
private readonly _config: Config,
private readonly _notificationsHub: NotificationsHub,
private readonly _modal: UiModalService,
private readonly _router: Router,
private readonly _breadcrumbService: BreadcrumbService,
private readonly _authService: AuthService,
private readonly _availabilityService: DomainAvailabilityService,
private readonly _zone: NgZone,
private readonly _wrongDestinationModalService: WrongDestinationModalService
) {}
async setSection(section: 'customer' | 'branch') {
this._appService.setSection(section);
const lastProcessId = (await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise())?.id;
if (lastProcessId) {
this.activateProcess(lastProcessId);
} else {
this._router.navigate([section === 'customer' ? '/kunde' : '/filiale']);
}
}
// Process werden über Guards erstellt und aktiviert. An dieser Stelle wird nur navigiert
async addProcess() {
const processId = Date.now();
await this._router.navigate(['/kunde', processId, 'product']);
}
async activateProcess(activatedProcessId: number) {
try {
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(take(1)).toPromise();
await this._zone.run(async () => {
if (latestCrumb) {
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
} else {
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
}
});
} catch (error) {}
}
async closeProcess(processId: number) {
this._appService.removeProcess(processId);
const processes = await this.processes$.pipe(first()).toPromise();
if (processes.length === 0) {
await this._router.navigate(['/kunde', 'dashboard']);
return;
}
const section = await this.section$.pipe(first()).toPromise();
const lastActivatedProcess = await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise();
this.activateProcess(lastActivatedProcess?.id);
}
processAction(process: ApplicationProcess) {
if (process?.type === 'cart') {
this._router.navigate(['/kunde', process.id, 'cart']);
}
}
async logout() {
await this._authService.logout();
}
async openNotifications() {
const notifications = await this.notifications$.pipe(first()).toPromise();
this._modal.open({
content: ModalNotificationsComponent,
data: notifications,
config: {
showScrollbarY: false,
},
});
}
trackByIdFn: TrackByFunction<ApplicationProcess> = (_, process) => process.id;
fetchAndOpenPackages = () => this._wrongDestinationModalService.fetchAndOpen();
}

View File

@@ -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 {}

View File

@@ -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');
}

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

@@ -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>

View File

@@ -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> {

View File

@@ -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

View File

@@ -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: [],

View File

@@ -6,10 +6,10 @@ 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 { 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: [],

View File

@@ -38,6 +38,8 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
this.selectedBranch$ = this.activatedProcessId$.pipe(switchMap((processId) => this.application.getSelectedBranch$(Number(processId))));
this.application.setTitle('Artikelsuche');
}
ngAfterViewInit(): void {

View File

@@ -2,22 +2,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { ArticleDetailsModule } from './article-details/article-details.module';
import { ArticleSearchModule } from './article-search/article-search.module';
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
import { PageCatalogComponent } from './page-catalog.component';
@NgModule({
imports: [
CommonModule,
PageCatalogRoutingModule,
ShellBreadcrumbModule,
ArticleSearchModule,
ArticleDetailsModule,
BreadcrumbModule,
BranchSelectorComponent,
],
imports: [CommonModule, PageCatalogRoutingModule, ArticleSearchModule, ArticleDetailsModule, BreadcrumbModule, BranchSelectorComponent],
exports: [],
declarations: [PageCatalogComponent],
})

View File

@@ -1 +1 @@
<shell-breadcrumb [key]="breadcrumbKey$ | async" [includesTags]="['checkout']"></shell-breadcrumb> <router-outlet></router-outlet>
<shared-breadcrumb [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb> <router-outlet></router-outlet>

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { map } from 'rxjs/operators';
@@ -8,8 +8,12 @@ import { map } from 'rxjs/operators';
styleUrls: ['page-checkout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCheckoutComponent {
export class PageCheckoutComponent implements OnInit {
readonly breadcrumbKey$ = this.applicationService.activatedProcessId$.pipe(map((processId) => String(processId)));
constructor(private applicationService: ApplicationService) {}
ngOnInit() {
this.applicationService.setTitle('Warenkorb');
}
}

View File

@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { CheckoutDummyModule } from './checkout-dummy/checkout-dummy.module';
import { CheckoutReviewModule } from './checkout-review/checkout-review.module';
import { CheckoutSummaryModule } from './checkout-summary/checkout-summary.module';
@@ -8,14 +8,7 @@ import { PageCheckoutRoutingModule } from './page-checkout-routing.module';
import { PageCheckoutComponent } from './page-checkout.component';
@NgModule({
imports: [
CommonModule,
CheckoutSummaryModule,
PageCheckoutRoutingModule,
CheckoutReviewModule,
CheckoutDummyModule,
ShellBreadcrumbModule,
],
imports: [CommonModule, CheckoutSummaryModule, PageCheckoutRoutingModule, CheckoutReviewModule, CheckoutDummyModule, BreadcrumbModule],
declarations: [PageCheckoutComponent],
exports: [],
})

View File

@@ -6,7 +6,7 @@ import { CustomerOrderSearchFilterComponent, OrderBranchIdInputComponent } from
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
@@ -15,7 +15,7 @@ import { UiSpinnerModule } from '@ui/spinner';
UiIconModule,
RouterModule,
UiFilterNextModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
UiSpinnerModule,
OrderBranchIdInputComponent,
],

View File

@@ -38,6 +38,8 @@ export class CustomerOrderComponent implements OnInit {
this.selectedBranch$ = this.application.activatedProcessId$.pipe(
switchMap((processId) => this.application.getSelectedBranch$(Number(processId)))
);
this.application.setTitle('Kundenbestellung');
}
async patchProcessData(selectedBranch: BranchDTO) {

View File

@@ -11,7 +11,7 @@ import { UiCommonModule } from '@ui/common';
import { CustomerResultCardComponent } from './search-results/customer-result-card/customer-result-card.component';
import { CustomerSearchFilterComponent } from './search-filter/search-filter.component';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiScrollContainerModule } from '@ui/scroll-container';
import { UiSpinnerModule } from '@ui/spinner';
@@ -23,7 +23,7 @@ import { UiSpinnerModule } from '@ui/spinner';
UiCommonModule,
UiIconModule,
ReactiveFormsModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
UiFilterNextModule,
UiScrollContainerModule,
UiSpinnerModule,

View File

@@ -1,4 +1,4 @@
<shell-breadcrumb [key]="activatedProcessId$ | async" [includesTags]="['customer']"></shell-breadcrumb>
<shared-breadcrumb [key]="activatedProcessId$ | async" tags="customer"></shared-breadcrumb>
<div class="content-container">
<router-outlet></router-outlet>

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { map } from 'rxjs/operators';
@@ -9,8 +9,12 @@ import { map } from 'rxjs/operators';
providers: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCustomerComponent {
export class PageCustomerComponent implements OnInit {
activatedProcessId$ = this.application.activatedProcessId$.pipe(map((p) => String(p)));
constructor(public application: ApplicationService) {}
ngOnInit() {
this.application.setTitle('Kundensuche');
}
}

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { PageCustomerComponent } from './page-customer.component';
import { PageCustomerRoutingModule } from './page-customer-routing.module';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { CustomerSearchModule } from './customer-search/customer-search.module';
import { CustomerDetailsModule } from './customer-details/customer-details.module';
import { UiInputModule } from '@ui/input';
@@ -14,7 +14,7 @@ import { CustomerModalModuleModule } from './modals';
imports: [
CommonModule,
PageCustomerRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
CustomerSearchModule,
CustomerDetailsModule,
UiInputModule,

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { DomainDashboardService } from '@domain/isa';
import { of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@@ -14,7 +15,12 @@ export class DashboardComponent implements OnInit {
catchError(() => of([]))
);
constructor(private readonly _domainIsaDashboardService: DomainDashboardService) {}
constructor(private readonly _domainIsaDashboardService: DomainDashboardService, private _app: ApplicationService) {}
ngOnInit(): void {}
ngOnInit(): void {
this._app.setSection('customer');
this._app.activateProcess(undefined);
this._app.setTitle('Dashboard');
}
}

View File

@@ -6,11 +6,11 @@ import { UiIconModule } from '@ui/icon';
import { GoodsInSearchFilterComponent } from './goods-in-search-filter';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, RouterModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [GoodsInSearchComponent],
declarations: [GoodsInSearchComponent, GoodsInSearchFilterComponent],
})

View File

@@ -1,3 +1,3 @@
<shell-breadcrumb [key]="goodsInKey"></shell-breadcrumb>
<shared-breadcrumb [key]="goodsInKey"></shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -1,4 +1,5 @@
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
@Component({
@@ -10,7 +11,9 @@ import { Config } from '@core/config';
export class GoodsInComponent implements OnInit {
goodsInKey = this._config.get('process.ids.goodsIn');
constructor(private readonly _config: Config) {}
constructor(private readonly _config: Config, private _app: ApplicationService) {}
ngOnInit() {}
ngOnInit() {
this._app.setTitle('Abholfach');
}
}

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { GoodsInRoutingModule } from './good-in-routing.module';
import { GoodsInComponent } from './goods-in.component';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { CoreCommandModule } from '@core/command';
import {
AcceptedActionHandler,
@@ -42,11 +42,12 @@ import {
CreateReturnItemActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
} from '@domain/oms';
@NgModule({
declarations: [GoodsInComponent],
imports: [
GoodsInRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
CoreCommandModule.forChild([
AcceptedActionHandler,
ArrivedActionHandler,

View File

@@ -6,11 +6,11 @@ import { GoodsOutSearchFilterComponent } from './goods-out-search-filter';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiFilterNextModule } from '@ui/filter';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiSpinnerModule } from '@ui/spinner';
@NgModule({
imports: [CommonModule, UiIconModule, RouterModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, UiIconModule, RouterModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [GoodsOutSearchComponent],
declarations: [GoodsOutSearchComponent, GoodsOutSearchFilterComponent],
})

View File

@@ -1,3 +1,3 @@
<shell-breadcrumb [key]="processId$ | async" [includesTags]="['goods-out']"></shell-breadcrumb>
<shared-breadcrumb [key]="processId$ | async" tags="goods-out"></shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -1,5 +1,6 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { map } from 'rxjs/operators';
@Component({
@@ -8,8 +9,12 @@ import { map } from 'rxjs/operators';
styleUrls: ['goods-out.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsOutComponent {
export class GoodsOutComponent implements OnInit {
processId$ = this._activatedRoute.data.pipe(map((data) => String(data.processId)));
constructor(private _activatedRoute: ActivatedRoute) {}
constructor(private _activatedRoute: ActivatedRoute, private _app: ApplicationService) {}
ngOnInit() {
this._app.setTitle('Warenausgabe');
}
}

View File

@@ -41,7 +41,7 @@ import {
PrintPriceDiffQrCodeLabelActionHandler,
} from '@domain/oms';
import { CoreCommandModule } from '@core/command';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { GoodsInRoutingModule } from './good-out-routing.module';
@NgModule({
@@ -49,7 +49,7 @@ import { GoodsInRoutingModule } from './good-out-routing.module';
imports: [
CommonModule,
GoodsInRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
CoreCommandModule.forChild([
AcceptedActionHandler,
ArrivedActionHandler,

View File

@@ -1,13 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PackageInspectionPipesModule } from '../../pipes/package-inspection-pipes.module';
import { PackageDetailsListComponent } from './package-details-list.component';
import { PackageDetailsListItemComponent } from './package-details-list-item.component';
import { ProductImageModule } from '@cdn/product-image';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { PackageInspectionPipesModule } from '@shared/pipes/package-inspection';
@NgModule({
imports: [CommonModule, ProductImageModule, PackageInspectionPipesModule, ScrollingModule, UiTooltipModule, UiCommonModule],

View File

@@ -1,2 +0,0 @@
export * from './package-list.component';
export * from './package-list.module';

View File

@@ -2,12 +2,12 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PackageDetailsComponent } from './package-details.component';
import { PackageInspectionPipesModule } from '../pipes';
import { PackageDetailsListModule } from '../components/package-details-list';
import { UiIconModule } from '@ui/icon';
import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { ElementLifecycleModule } from '@shared/directives/element-lifecycle';
import { PackageInspectionPipesModule } from '@shared/pipes/package-inspection';
@NgModule({
imports: [

View File

@@ -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,12 @@ export class PackageInspectionComponent implements OnInit {
return this._config.get('process.ids.packageInspection');
}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService) {}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService, private _app: ApplicationService) {}
ngOnInit(): void {
this.createBreadcrumbIfNotExists();
this._app.setTitle('Packstück Prüfung');
}
async createBreadcrumbIfNotExists(): Promise<void> {

View File

@@ -4,16 +4,15 @@ import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { CacheService } from '@core/cache';
import { Config } from '@core/config';
import { provideComponentStore } from '@ngrx/component-store';
import { ShellFilterOverlayComponent } from '@shell/filter-overlay';
import { ArrivalStatus, ListResponseArgsOfPackageDTO2 } from '@swagger/wws';
import { UiFilter, UiFilterComponent } from '@ui/filter';
import { isNumber, isString } from 'lodash';
import { UiFilter } from '@ui/filter';
import moment from 'moment';
import { combineLatest, Subject, Subscription } from 'rxjs';
import { filter, first, map } from 'rxjs/operators';
import { PackageListComponent } from '../components/package-list';
import { PackageResultCacheData } from './package-result-cache-data';
import { PackageResultComponentStore } from './package-result.component.store';
import { SharedFilterOverlayComponent } from '@shared/components/filter-overlay';
import { PackageListComponent } from '@shared/components/package-inspection/package-list';
@Component({
selector: 'page-package-result',
@@ -31,8 +30,8 @@ export class PackageResultComponent implements OnInit, AfterViewInit, OnDestroy
hint$ = new Subject<string>();
@ViewChild(ShellFilterOverlayComponent)
filterOverlay: ShellFilterOverlayComponent;
@ViewChild(SharedFilterOverlayComponent)
filterOverlay: SharedFilterOverlayComponent;
/**
* Zeigt die liste an, wenn entweder keine packages geladen werden oder wenn packages geladen wurden

View File

@@ -2,14 +2,14 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PackageResultComponent } from './package-result.component';
import { PackageListModule } from '../components/package-list';
import { UiIconModule } from '@ui/icon';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiFilterNextModule } from '@ui/filter';
import { UiSpinnerModule } from '@ui/spinner';
import { PackageListModule } from '@shared/components/package-inspection/package-list';
@NgModule({
imports: [CommonModule, PackageListModule, UiIconModule, UiFilterNextModule, ShellFilterOverlayModule, UiSpinnerModule],
imports: [CommonModule, PackageListModule, UiIconModule, UiFilterNextModule, SharedFilterOverlayModule, UiSpinnerModule],
exports: [PackageResultComponent],
declarations: [PackageResultComponent],
})

View File

@@ -1,3 +0,0 @@
export * from './arrival-status-color-class.pipe';
export * from './arrival-status.pipe';
export * from './package-inspection-pipes.module';

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { RemissionListComponent } from './remission-list.component';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiIconModule } from '@ui/icon';
import { RemissionFilterModule } from './remission-filter/remission-filter.module';
import { RemissionListItemModule } from './remission-list-item';
@@ -14,7 +14,7 @@ import { RemissionListItemLoadingModule } from './remission-list-item-loading/re
@NgModule({
imports: [
CommonModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
UiIconModule,
RemissionFilterModule,
RemissionListItemModule,

View File

@@ -1 +1 @@
<shell-breadcrumb key="4000"></shell-breadcrumb> <router-outlet></router-outlet>
<shared-breadcrumb key="4000"></shared-breadcrumb> <router-outlet></router-outlet>

View File

@@ -23,7 +23,6 @@ export class RemissionComponent implements OnInit, OnDestroy {
}
constructor(
private readonly _remissionListComponentStore: RemissionListComponentStore,
private readonly _breadcrumb: BreadcrumbService,
private readonly _config: Config,
private _activatedRoute: ActivatedRoute,
@@ -46,6 +45,8 @@ export class RemissionComponent implements OnInit, OnDestroy {
});
this.updateProcess();
this._applicationService.setTitle('Remission');
}
ngOnDestroy(): void {

View File

@@ -1,11 +1,11 @@
import { NgModule } from '@angular/core';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { RemissionRoutingModule } from './remission-routing.module';
import { RemissionComponent } from './remission.component';
@NgModule({
declarations: [RemissionComponent],
imports: [RemissionRoutingModule, ShellBreadcrumbModule],
imports: [RemissionRoutingModule, BreadcrumbModule],
exports: [RemissionComponent],
})
export class PageRemissionModule {}

View File

@@ -1,5 +1,5 @@
<div class="pt-2">
<shell-breadcrumb [key]="taskCalendarKey" [includesTags]="['task-calendar']"></shell-breadcrumb>
<shared-breadcrumb [key]="taskCalendarKey" tags="task-calendar"></shared-breadcrumb>
</div>
<div class="content-header">

View File

@@ -7,6 +7,7 @@ import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
import { first, map, shareReplay, takeUntil, debounceTime } from 'rxjs/operators';
import { TaskSearchbarComponent } from './components/task-searchbar/task-searchbar.component';
import { TaskCalendarStore } from './task-calendar.store';
import { ApplicationService } from '@core/application';
@Component({
selector: 'page-task-calendar',
@@ -37,7 +38,12 @@ export class PageTaskCalendarComponent implements OnInit, OnDestroy {
@ViewChild(TaskSearchbarComponent)
searchbar: TaskSearchbarComponent;
constructor(private taskCalendarStore: TaskCalendarStore, private _activatedRoute: ActivatedRoute, private readonly _config: Config) {
constructor(
private taskCalendarStore: TaskCalendarStore,
private _activatedRoute: ActivatedRoute,
private readonly _config: Config,
private _app: ApplicationService
) {
this.taskCalendarStore.loadFilter();
}
@@ -55,6 +61,8 @@ export class PageTaskCalendarComponent implements OnInit, OnDestroy {
});
this.taskCalendarStore.loadItems();
this._app.setTitle('Tätigkeitenkalender');
}
ngOnDestroy(): void {

View File

@@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { SharedFilterOverlayModule } from '@shared/components/filter-overlay';
import { UiFilterNextModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { TaskSearchbarModule } from './components/task-searchbar/task-searchbar.module';
@@ -15,11 +15,11 @@ import { PageTaskCalendarComponent } from './page-task-calendar.component';
imports: [
CommonModule,
PageTaskCalendarRoutingModule,
ShellBreadcrumbModule,
BreadcrumbModule,
UiIconModule,
ModalsModule,
UiFilterNextModule,
ShellFilterOverlayModule,
SharedFilterOverlayModule,
TaskSearchbarModule,
],
exports: [PageTaskCalendarComponent],

View File

@@ -1,19 +1,11 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
QueryList,
ViewChildren,
ViewEncapsulation,
} from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, QueryList, ViewChildren } from '@angular/core';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { ComponentStore } from '@ngrx/component-store';
import { isEqual } from 'lodash';
import { combineLatest, Observable } from 'rxjs';
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
import { BreadcrumbComponentState, DEFAULT_BREADCRUMB_COMPONENT_STATE } from './breadcrumb.component-state';
import { coerceArray } from '@angular/cdk/coercion';
@Component({
selector: 'shared-breadcrumb',
@@ -47,13 +39,14 @@ export class BreadcrumbComponent extends ComponentStore<BreadcrumbComponentState
* Hier können auch nut Teile der Tags angeben werden die in der Breadcrumb enthalten sein sollen.
*/
@Input()
get tags(): string[] {
get tags(): string[] | string {
return this.get((s) => s.tags);
}
set tags(tags: string[]) {
if (isEqual(this.tags, tags)) return;
this.patchState({ tags });
set tags(tags: string[] | string) {
const tagsArray = coerceArray(tags);
if (isEqual(this.tags, tagsArray)) return;
this.patchState({ tags: tagsArray });
}
readonly tags$: Observable<string[]> = this.select((s) => s.tags);

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ShellFilterOverlayComponent } from './filter-overlay.component';
import { SharedFilterOverlayComponent } from './filter-overlay.component';
describe('ShellFilterOverlayComponent', () => {
let component: ShellFilterOverlayComponent;
let fixture: ComponentFixture<ShellFilterOverlayComponent>;
let component: SharedFilterOverlayComponent;
let fixture: ComponentFixture<SharedFilterOverlayComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ShellFilterOverlayComponent],
declarations: [SharedFilterOverlayComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShellFilterOverlayComponent);
fixture = TestBed.createComponent(SharedFilterOverlayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -27,7 +27,7 @@ import { Subject } from 'rxjs';
]),
],
})
export class ShellFilterOverlayComponent implements OnInit, OnDestroy {
export class SharedFilterOverlayComponent implements OnInit, OnDestroy {
@ViewChild('filterOverlay')
filterOverlay: TemplateRef<any>;

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core';
import { SharedFilterOverlayComponent } from './filter-overlay.component';
@NgModule({
declarations: [SharedFilterOverlayComponent],
imports: [],
exports: [SharedFilterOverlayComponent],
})
export class SharedFilterOverlayModule {}

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,30 @@
import { Highlightable } from '@angular/cdk/a11y';
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';
@Directive({ selector: '[menuItem]', host: { class: 'menu-item', role: 'menuitem', tabindex: '-1' } })
export class MenuItemDirective implements Highlightable {
private _onClick = (_: MenuItemDirective) => {};
constructor(private _elementRef: ElementRef, private _renderer: Renderer2) {}
setActiveStyles(): void {
this._renderer.addClass(this._elementRef.nativeElement, 'active');
}
setInactiveStyles(): void {
this._renderer.removeClass(this._elementRef.nativeElement, 'active');
}
getLabel?(): string {
return this._elementRef.nativeElement.innerText;
}
registerOnClick(fn: (item: MenuItemDirective) => void) {
this._onClick = fn;
}
@HostListener('click')
onClick() {
this._onClick.call(this, this);
}
}

View File

@@ -0,0 +1,19 @@
import { Component, OnInit, ContentChild, ViewChild, TemplateRef } from '@angular/core';
import { MenuComponent } from './menu.component';
@Component({
selector: 'menu-template',
template: `<ng-template #menuTpl><ng-content selector="menu"></ng-content></ng-template>`,
exportAs: 'menuTemplate',
})
export class MenuTemplateComponent implements OnInit {
@ContentChild(MenuComponent)
menu: MenuComponent;
@ViewChild('menuTpl', { read: TemplateRef, static: true })
templateRef: TemplateRef<any>;
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,77 @@
import { Directive, ElementRef, HostListener, Input, OnDestroy, ViewContainerRef } from '@angular/core';
import { MenuTemplateComponent } from './menu-template.component';
import { HorizontalConnectionPos, Overlay, OverlayRef, VerticalConnectionPos } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { asapScheduler } from 'rxjs';
@Directive({ selector: '[menuTrigger]' })
export class MenuTriggerDirective implements OnDestroy {
@Input('menuTrigger')
menuTemplate: MenuTemplateComponent;
private _overlayRef: OverlayRef;
@Input()
originX: HorizontalConnectionPos = 'start';
@Input()
originY: VerticalConnectionPos = 'bottom';
@Input()
overlayX: HorizontalConnectionPos = 'start';
@Input()
overlayY: VerticalConnectionPos = 'top';
constructor(private _elementRef: ElementRef, private _overlay: Overlay, private _viewContainerRef: ViewContainerRef) {}
ngOnDestroy() {
this._overlayRef?.dispose();
}
createOverlayRef() {
if (!this._overlayRef) {
this._overlayRef = this._overlay.create({
positionStrategy: this._overlay
.position()
.flexibleConnectedTo(this._elementRef)
.withPositions([{ originX: this.originX, originY: this.originY, overlayX: this.overlayX, overlayY: this.overlayY }]),
scrollStrategy: this._overlay.scrollStrategies.reposition(),
hasBackdrop: false,
});
}
return this._overlayRef;
}
@HostListener('click')
click() {
if (this._overlayRef && this._overlayRef.hasAttached()) {
this.close();
} else {
this.open();
}
}
open() {
const overlayRef = this.createOverlayRef();
const portal = new TemplatePortal(this.menuTemplate.templateRef, this._viewContainerRef);
this.menuTemplate.menu.registerOnClick((item) => {
asapScheduler.schedule(() => {
this.close();
});
});
portal.attach(overlayRef);
}
close() {
this._overlayRef?.detach();
}
@HostListener('focusout', ['$event'])
onKeyUp(event: Event) {
this.close();
}
}

View File

@@ -0,0 +1,26 @@
import { Component, ContentChildren, QueryList } from '@angular/core';
import { MenuItemDirective } from './menu-item.directive';
@Component({
selector: 'menu',
template: `<ng-content [selector]="[menuItem]"></ng-content>`,
host: { class: 'menu', role: 'menu' },
exportAs: 'menu',
})
export class MenuComponent {
private _menuItems: QueryList<MenuItemDirective>;
@ContentChildren(MenuItemDirective, { descendants: true })
set menuItems(items: QueryList<MenuItemDirective>) {
this._menuItems = items;
}
get menuItems() {
return this._menuItems;
}
constructor() {}
registerOnClick(fn: (item: MenuItemDirective) => void) {
this.menuItems.forEach((item) => item.registerOnClick(fn));
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MenuComponent } from './menu.component';
import { MenuItemDirective } from './menu-item.directive';
import { MenuTemplateComponent } from './menu-template.component';
import { MenuTriggerDirective } from './menu-trigger.directive';
@NgModule({
imports: [CommonModule],
exports: [MenuComponent, MenuItemDirective, MenuTemplateComponent, MenuTriggerDirective],
declarations: [MenuComponent, MenuItemDirective, MenuTemplateComponent, MenuTriggerDirective],
})
export class SharedMenuModule {}

View File

@@ -0,0 +1,5 @@
export * from './lib/menu-item.directive';
export * from './lib/menu-template.component';
export * from './lib/menu-trigger.directive';
export * from './lib/menu.component';
export * from './lib/menu.module';

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -1,8 +1,8 @@
import { DatePipe } from '@angular/common';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { MockPipe } from 'ng-mocks';
import { ArrivalStatusColorClassPipe, ArrivalStatusPipe } from '../../pipes';
import { PackageListItemComponent } from './package-list-item.component';
import { ArrivalStatusColorClassPipe, ArrivalStatusPipe } from '@shared/pipes/package-inspection';
describe('PackageListItemComponent', () => {
let spectator: Spectator<PackageListItemComponent>;

View File

@@ -1,20 +1,8 @@
import {
Component,
ChangeDetectionStrategy,
Input,
EventEmitter,
Output,
ViewChild,
AfterViewInit,
OnDestroy,
OnChanges,
SimpleChanges,
NgZone,
} from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { PackageDTO2 } from '@swagger/wws';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { asapScheduler, Subject } from 'rxjs';
import { debounceTime, first, takeUntil } from 'rxjs/operators';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'page-package-list',

View File

@@ -4,9 +4,9 @@ import { CommonModule } from '@angular/common';
import { PackageListComponent } from './package-list.component';
import { PackageListItemComponent } from './package-list-item.component';
import { PackageListItemLoaderComponent } from './package-list-item-loader.component';
import { PackageInspectionPipesModule } from '../../pipes';
import { RouterModule } from '@angular/router';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { PackageInspectionPipesModule } from '@shared/pipes/package-inspection';
@NgModule({
imports: [CommonModule, PackageInspectionPipesModule, RouterModule, ScrollingModule],

View File

@@ -0,0 +1,4 @@
export * from './lib/package-list-item-loader.component';
export * from './lib/package-list-item.component';
export * from './lib/package-list.component';
export * from './lib/package-list.module';

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -2,8 +2,8 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
import { DomainPackageInspectionService } from '@domain/package-inspection';
import { PackageArrivalStatusDTO, PackageDTO2 } from '@swagger/wws';
import { UiModalRef } from '@ui/modal';
import { PackageListModule } from '../package-list';
import { WrongDestinationModalData } from './wrong-destination-modal.data';
import { PackageListModule } from '@shared/components/package-inspection/package-list';
@Component({
selector: 'page-package-inspection-wrong-destination-modal',

View File

@@ -0,0 +1,3 @@
export * from './lib/wrong-destination-modal.component';
export * from './lib/wrong-destination-modal.data';
export * from './lib/wrong-destination-modal.service';

View File

@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}

Some files were not shown because too many files have changed in this diff Show More