Compare commits

...

12 Commits

Author SHA1 Message Date
Nino
d0592fd353 #4539 Fix Skip Location Change After Navigating Back to Filter Page 2023-12-27 15:29:48 +01:00
Nino
477124892a Merge branch 'release/3.0' into hotfix/4539-Filter-Overlay-Triggert-Suche 2023-12-22 18:02:48 +01:00
Nino Righi
2ff033ea55 Merged PR 1701: #4539 Hotfix AHF Open Filter Page does not trigger search request
#4539 Hotfix AHF Open Filter Page does not trigger search request
2023-12-21 17:23:08 +00:00
Nino Righi
cbaac8ed9a Merged PR 1700: #4537 Relocated Component Store provision
#4537 Relocated Component Store provision
2023-12-21 17:19:35 +00:00
Nino
498e245b89 #4539 Hotfix AHF Open Filter Page does not trigger search request 2023-12-21 18:13:41 +01:00
Lorenz Hilpert
803a8e316c Skip UnitTest for some libs 2023-12-19 17:33:19 +01:00
Nino Righi
83cab7796e Merged PR 1697: #4530 Notification Batch Messages for each type
#4530 Notification Batch Messages for each type

(cherry picked from commit 5073693fc2)
2023-12-19 17:14:37 +01:00
Nino Righi
c70dd30830 Merged PR 1695: #4523 AHF, WA Added other notification types to Email Notification Badge
#4523 AHF, WA Added other notification types to Email Notification Badge

(cherry picked from commit f3cb6236a5)
2023-12-19 17:14:03 +01:00
Lorenz Hilpert
b28bb165d4 Merged PR 1696: #4532 Mitarbeiter klickt auf Einbuchen und erreicht den Tätigkeitskalender
#4532 Mitarbeiter klickt auf Einbuchen und erreicht den Tätigkeitskalender
2023-12-19 13:35:58 +00:00
Nino Righi
32d8d81f53 Merged PR 1694: #4526 Hotfix Pickup Shelf Edit show Erneut Senden CTA
#4526 Hotfix Pickup Shelf Edit show Erneut Senden CTA
2023-12-15 10:12:51 +00:00
Nino
0361aa63ff Merge Develop into Release/3.0, corrected itemSize in page catalog list 2023-12-12 16:52:03 +01:00
Nino
2f95c23910 Merge branch 'develop' into release/3.0 2023-12-12 16:35:35 +01:00
16 changed files with 129 additions and 107 deletions

View File

@@ -6,12 +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 { ArticleSearchService } from './article-search.store';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule],
exports: [ArticleSearchComponent],
declarations: [ArticleSearchComponent],
providers: [ArticleSearchService],
})
export class ArticleSearchModule {}

View File

@@ -47,7 +47,7 @@
</div>
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="106 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="103 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"
@@ -81,7 +81,7 @@
</ng-container>
<ng-template #sideOutlet>
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="222 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="191 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"

View File

@@ -11,12 +11,14 @@ import { combineLatest, fromEvent, Observable, Subject } from 'rxjs';
import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { ActionsSubject } from '@ngrx/store';
import { DomainAvailabilityService } from '@domain/availability';
import { provideComponentStore } from '@ngrx/component-store';
import { ArticleSearchService } from './article-search/article-search.store';
@Component({
selector: 'page-catalog',
templateUrl: 'page-catalog.component.html',
styleUrls: ['page-catalog.component.scss'],
providers: [],
providers: [provideComponentStore(ArticleSearchService)],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {

View File

@@ -1,58 +0,0 @@
import { Directive, HostListener, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { BranchDTO } from '@swagger/checkout';
import { PurchasingOptionsModalStore } from '../../modals/purchasing-options-modal/purchasing-options-modal.store';
/* tslint:disable: directive-selector */
@Directive({ selector: '[keyNavigation]' })
export class KeyNavigationDirective implements OnInit {
@Input() element: any;
@Input('keyNavigation') data: BranchDTO[];
@Output() closeDropdown = new EventEmitter<void>();
@Output() preselectBranch = new EventEmitter<BranchDTO>();
selectedData: BranchDTO;
position = 0;
posMarker = 0;
@HostListener('window:keyup', ['$event'])
keyEvent(event: KeyboardEvent) {
if (event.key === 'ArrowUp') {
if (this.position > 0) {
this.position--;
}
if (this.position <= this.posMarker - 4) {
this.element.scrollTop -= 44;
}
this.selectedData = this.data[this.position];
this.preselectBranch.emit(this.selectedData);
}
if (event.key === 'ArrowDown') {
if (this.position < this.data.length - 1) {
this.position++;
}
if (this.position >= 4) {
this.posMarker = this.position;
this.element.scrollTop += 44;
}
this.selectedData = this.data[this.position];
this.preselectBranch.emit(this.selectedData);
}
if (event.key === 'Enter') {
this.purchasingOptionsModalStore.setBranch(this.selectedData);
this.position = 0;
this.closeDropdown.emit();
}
}
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
ngOnInit() {
this.selectedData = this.data[this.position];
this.preselectBranch.emit(this.selectedData);
}
}

View File

@@ -1,12 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { KeyNavigationDirective } from './key-navigation.directive';
@NgModule({
imports: [CommonModule],
exports: [KeyNavigationDirective],
declarations: [KeyNavigationDirective],
providers: [],
})
export class KeyNavigationModule {}

View File

@@ -5,7 +5,6 @@ import { CustomerOrderSearchComponent } from './customer-order-search.component'
import { CustomerOrderSearchFilterComponent, OrderBranchIdInputComponent } from './customer-order-search-filter';
import { RouterModule } from '@angular/router';
import { UiSpinnerModule } from '@ui/spinner';
import { CustomerOrderSearchStore } from './customer-order-search.store';
import { IconComponent, IconModule } from '@shared/components/icon';
import { FilterModule } from '@shared/components/filter';
import { CustomerOrderSearchMainModule } from './search-main';
@@ -22,7 +21,6 @@ import { CustomerOrderSearchMainModule } from './search-main';
CustomerOrderSearchMainModule,
],
exports: [CustomerOrderSearchComponent],
providers: [CustomerOrderSearchStore],
declarations: [CustomerOrderSearchComponent, CustomerOrderSearchFilterComponent],
})
export class CustomerOrderSearchModule {}

View File

@@ -3,18 +3,21 @@ import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { EnvironmentService } from '@core/environment';
import { provideComponentStore } from '@ngrx/component-store';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
import { BranchDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { Observable, Subject, fromEvent } from 'rxjs';
import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderSearchStore } from './customer-order-search';
@Component({
selector: 'page-customer-order',
templateUrl: 'customer-order.component.html',
styleUrls: ['customer-order.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideComponentStore(CustomerOrderSearchStore)],
})
export class CustomerOrderComponent implements OnInit {
@ViewChild(BreadcrumbComponent, { read: ElementRef }) breadcrumbRef: ElementRef<HTMLElement>;

View File

@@ -120,7 +120,7 @@ export abstract class PickupShelfBaseComponent implements OnInit {
// Only Update QueryParams if the user is already on the details, edit or history page
const view: string = this.activatedRoute.snapshot.data.view;
if (['details', 'edit', 'history'].includes(view)) {
if (['filter', 'details', 'edit', 'history'].includes(view)) {
await this.router.navigate([], { queryParams: { ...queryParams, ...filterQueryParams }, skipLocationChange: true });
return;
}

View File

@@ -18,7 +18,7 @@ import { PickUpShelfListItemComponent } from '../../shared/pickup-shelf-list-ite
import { Group, GroupByPipe } from '@ui/common';
import { UiSpinnerModule } from '@ui/spinner';
import { PickupShelfInNavigationService } from '@shared/services';
import { debounceTime, map } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { DBHOrderItemListItemDTO } from '@swagger/oms';
import { Observable, combineLatest, of } from 'rxjs';
import { PickupShelfDetailsStore, PickupShelfStore } from '../../store';
@@ -124,7 +124,8 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
combineLatest([this.store.processId$, this._activatedRoute.queryParams])
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(([_, queryParams]) => {
if (!this.store.list.length || !isEqual(queryParams, this.store.filter.getQueryParams())) {
if (!this.store.list.length || !isEqual(queryParams, this.cleanupQueryParams(this.store.filter.getQueryParams()))) {
this.store.setQueryParams(queryParams);
this.store.fetchList();
}
@@ -148,6 +149,20 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
this.scrollItemIntoView();
}
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;
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN });
}

View File

@@ -0,0 +1,22 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'notificationType',
standalone: true,
})
export class NotificationTypePipe implements PipeTransform {
transform(notificationType: string): string {
switch (notificationType) {
case 'NOTIFICATION_EMAIL':
return 'Benachrichtigung';
case 'REMINDER_EMAIL':
return 'Erinnerung';
case 'ORDERCONFIRMATION_EMAIL':
return 'Bestellbestätigung';
case 'NOTIFICATION_SMS':
return 'Benachrichtigung';
default:
return notificationType;
}
}
}

View File

@@ -11,8 +11,10 @@
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
Per E-Mail benachrichtigt <br />
<ng-container *ngFor="let notification of emailNotificationDates$ | async">
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
<ng-container *ngFor="let notifications of emailNotificationDates$ | async">
<ng-container *ngFor="let notificationDate of notifications.dates">
{{ notifications.type | notificationType }} {{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
</ng-container>
</ng-container>
</ui-tooltip>
</ng-container>
@@ -20,8 +22,10 @@
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
Per SMS benachrichtigt <br />
<ng-container *ngFor="let notification of smsNotificationDates$ | async">
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
<ng-container *ngFor="let notifications of smsNotificationDates$ | async">
<ng-container *ngFor="let notificationDate of notifications.dates">
{{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
</ng-container>
</ng-container>
</ui-tooltip>
</ng-container>

View File

@@ -24,6 +24,7 @@ import { map, switchMap } from 'rxjs/operators';
import { Subject, combineLatest } from 'rxjs';
import { PickupShelfDetailsStore } from '../../store';
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { NotificationTypePipe } from './notification-type.pipe';
export interface PickUpShelfDetailsItemComponentState {
orderItem?: DBHOrderItemListItemDTO;
@@ -55,6 +56,7 @@ export interface PickUpShelfDetailsItemComponentState {
PickupShelfPaymentTypePipe,
IconModule,
UiQuantityDropdownModule,
NotificationTypePipe,
],
})
export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfDetailsItemComponentState> implements OnInit {

View File

@@ -166,22 +166,33 @@ export const selectNotifications = (orderItemSubsetId: number) => (s: PickupShel
}, {} as Record<string, Date[]>);
};
export const selectLatestNotificationDatesFor = (orderItemSubsetId: number, key: string) => (s: PickupShelfDetailsState) => {
export const selectLatestNotificationDatesFor = (orderItemSubsetId: number, keys: string[]) => (s: PickupShelfDetailsState) => {
const notifications = selectNotifications(orderItemSubsetId)(s);
return (
notifications?.[key]?.filter((date) => {
let dates: Array<{ type: string; dates: Date[] }> = [];
for (const key of keys) {
const notification = notifications?.[key] ?? [];
const validDates = notification.filter((date) => {
// check if curr is an invalid date
return !isNaN(date.getTime());
}) ?? []
);
});
const mappedDates = { type: key, dates: validDates };
if (mappedDates.dates?.length > 0) {
dates.push(mappedDates);
}
}
return dates;
};
export const selectLatestEmailNotificationDates = (orderItemSubsetId: number) => (s: PickupShelfDetailsState) => {
return selectLatestNotificationDatesFor(orderItemSubsetId, 'NOTIFICATION_EMAIL')(s);
return selectLatestNotificationDatesFor(orderItemSubsetId, ['NOTIFICATION_EMAIL', 'REMINDER_EMAIL', 'ORDERCONFIRMATION_EMAIL'])(s);
};
export const selectLatestSmsNotificationDate2 = (orderItemSubsetId: number) => (s: PickupShelfDetailsState) => {
return selectLatestNotificationDatesFor(orderItemSubsetId, 'NOTIFICATION_SMS')(s);
return selectLatestNotificationDatesFor(orderItemSubsetId, ['NOTIFICATION_SMS'])(s);
};
export const selectCanSelectAction = (s: PickupShelfDetailsState) => {

View File

@@ -100,11 +100,11 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
}
showChannelActionNameForEmailControl() {
return !!this.channelActionName && this.emailControl?.dirty;
return (!!this.channelActionName && this.emailControl?.dirty) || this.showSendAgainActionForEmail();
}
showChannelActionNameForMobileControl() {
return !!this.channelActionName && this.mobileControl?.dirty;
return (!!this.channelActionName && this.mobileControl?.dirty) || this.showSendAgainActionForMobile();
}
clear(control: FormControl) {
@@ -142,7 +142,7 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
}
setNotificationChannels(notificationChannels: NotificationChannel[]) {
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
const notificationChannel = this.getNotificationChannel(notificationChannels);
this.notificationChannelControl.setValue(notificationChannel);
this.notificationChannelControl.markAsDirty();
if (this.communicationDetails) {
@@ -150,6 +150,24 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
}
}
getNotificationChannel(notificationChannels?: NotificationChannel[]) {
let nfc = notificationChannels ?? this.notificationChannels;
return nfc.reduce((val, current) => val | current, 0) as NotificationChannel;
}
// Ticket #4526 RD // Bearbeiten - erneut senden Button fehlt
// Auf den Pickup Shelf Edit Seiten soll der Erneut senden button immer ersichtlich sein
showSendAgainActionForEmail() {
return this.channelActionName === 'Erneut senden' && (this.getNotificationChannel() as Number) !== 3;
}
// Ticket #4526 RD // Bearbeiten - erneut senden Button fehlt
// Auf den Pickup Shelf Edit Seiten soll der Erneut senden button immer ersichtlich sein
// Wenn die Felder Email und SMS angehakt wurden, soll der Button nur einmal angezeigt werden
showSendAgainActionForMobile() {
return this.channelActionName === 'Erneut senden' && (this.getNotificationChannel() === 2 || this.getNotificationChannel() !== 1);
}
toggle(value?: boolean) {
this.patchState({ open: value ?? !this.get((s) => s.open) });
}

View File

@@ -107,10 +107,14 @@ export class ShellSideMenuComponent {
})
);
taskCalenderNavigation$ = this.getLastNavigationByProcessId(this._config.get('process.ids.taskCalendar'), {
path: ['/filiale', 'task-calendar'],
queryParams: {},
});
taskCalenderNavigation$ = this.getLastNavigationByProcessId(
this._config.get('process.ids.taskCalendar'),
{
path: ['/filiale', 'task-calendar'],
queryParams: {},
},
'/filiale/task-calendar'
);
assortmentNavigation$ = this.getLastNavigationByProcessId(this._config.get('process.ids.assortment'), {
path: ['/filiale', 'assortment'],
@@ -119,7 +123,8 @@ export class ShellSideMenuComponent {
pickUpShelfInRoutePath$ = this.getLastNavigationByProcessId(
this._config.get('process.ids.pickupShelf'),
this._pickUpShelfInNavigation.defaultRoute()
this._pickUpShelfInNavigation.defaultRoute(),
'/filiale/pickup-shelf'
);
// #4478 - RD // Abholfach - Routing löst Suche aus
@@ -188,10 +193,24 @@ export class ShellSideMenuComponent {
this._cdr.markForCheck();
}
getLastNavigationByProcessId(id: number, fallback?: { path: string[]; queryParams: any }) {
getLastNavigationByProcessId(id: number, fallback?: { path: string[]; queryParams: any }, pathContainsString?: string) {
return this._breadcrumbService.getBreadcrumbByKey$(id)?.pipe(
map((breadcrumbs) => {
const lastCrumb = breadcrumbs
.filter((breadcrumb) => {
/**
* #4532 - Der optionale Filter wurde hinzugefügt Breadcrumbs mit fehlerhaften Pfad auszuschließen.
* Dieser Filter kann entfernt werden, sobald die Breadcrumbs korrekt gesetzt werden. Jedoch konnte man bisher nicht feststellen,
* woher die fehlerhaften Breadcrumbs kommen.
*/
if (!pathContainsString) {
// Wenn kein Filter gesetzt ist, dann wird der letzte Breadcrumb zurückgegeben
return true;
}
const pathStr = Array.isArray(breadcrumb.path) ? breadcrumb.path.join('/') : breadcrumb.path;
return pathStr.includes(pathContainsString);
})
.filter((breadcrumb) => !breadcrumb?.params?.hasOwnProperty('view'))
.filter((breadcrumb) => !breadcrumb?.tags?.includes('reservation'))
.filter((breadcrumb) => !breadcrumb?.tags?.includes('cleanup'))

View File

@@ -5,10 +5,10 @@
"ng": "ng",
"start": "ng serve isa-app --ssl",
"test": "npm-run-all --serial \"test:* -- --watch=false --browsers=ChromeHeadlessNoSandbox --code-coverage\" --continue-on-error --print-label",
"test:isa-app": "ng test isa-app",
"*test:isa-app": "ng test isa-app",
"test:adapter-scan": "ng test @adapter/scan",
"test:cdn-product-image": "ng test @cdn/product-image",
"test:core": "ng test core",
"*test:core": "ng test core",
"*test:domain-availability": "ng test @domain/availability",
"*test:domain-cart": "ng test @domain/cart",
"*test:domain-catalog": "ng test @domain/catalog",
@@ -19,22 +19,22 @@
"*test:domain-oms": "ng test @domain/oms",
"*test:domain-package-inspection": "ng test @domain/package-inspection",
"*test:domain-printer": "ng test @domain/printer",
"test:domain-remission": "ng test @domain/remission",
"*test:domain-remission": "ng test @domain/remission",
"*test:domain-task-calendar": "ng test @domain/task-calendar",
"test:external": "ng test external",
"*test:hub-notifications": "ng test @hub/notifications",
"*test:isa-remission": "ng test @isa/remission",
"*test:modal-availabilities": "ng test @modal/availabilities",
"test:modal-history": "ng test @modal/history",
"*test:modal-history": "ng test @modal/history",
"*test:modal-images": "ng test @modal/images",
"test:modal-notifications": "ng test @modal/notifications",
"*test:modal-printer": "ng test @modal/printer",
"*test:modal-reorder": "ng test @modal/reorder",
"*test:modal-reviews": "ng test @modal/reviews",
"test:page": "ng test page",
"test:shared": "ng test shared",
"*test:page": "ng test page",
"*test:shared": "ng test shared",
"*test:shell-breadcrumb": "ng test @shell/breadcrumb",
"test:store-search-component-store": "ng test @store/search-component-store",
"*test:store-search-component-store": "ng test @store/search-component-store",
"*test:ui": "ng test ui",
"*test:utils": "ng test utils",
"*test:native-container": "ng test native-container",