Compare commits

...

19 Commits

Author SHA1 Message Date
Lorenz Hilpert
67cf380948 Update Matomo Docker Konfiguration 2024-06-25 14:06:18 +02:00
Lorenz Hilpert
e0ae79bc2a Update Docker Compose 2024-06-25 13:00:54 +02:00
Lorenz Hilpert
8ccc29c85a Tracking - Filter, Artikelsuche, Abholfach(Einbuchen, Reservierung, Ausräumen,Fehlende) 2024-06-24 15:10:51 +02:00
Lorenz Hilpert
c68706b54f Merged PR 1786: #4760 Fehler bei Abholpreisberechnung in Filiale Darmstadt Ernst-Ludwig-Straße
#4760 Fehler bei Abholpreisberechnung in Filiale Darmstadt Ernst-Ludwig-Straße
2024-06-10 12:22:36 +00:00
Lorenz Hilpert
b271ce9711 Merged PR 1785: Angular 17 Update + Cleanup Packages 2024-06-06 13:31:02 +00:00
Nino Righi
94888213b1 Merged PR 1784: #4758 Loading Indicator if Date gets Patched on OrderItemSubset
#4758 Loading Indicator if Date gets Patched on OrderItemSubset
2024-06-05 17:04:41 +00:00
Nino Righi
1041d92486 Merged PR 1783: #4750 Code improvements, Check if Icon gets loaded
#4750 Code improvements, Check if Icon gets loaded
2024-06-04 16:02:53 +00:00
Nino Righi
43d8d220c9 Merged PR 1782: #4750 Check if Icon File exists
#4750 Check if Icon File exists
2024-05-28 16:03:15 +00:00
Nino Righi
e0993d9c46 Merged PR 1781: #4599 Fix Routing After Click on Continue Shopping CTA - On Tablet Size or Sm...
#4599 Fix Routing After Click on Continue Shopping CTA - On Tablet Size or Smaller
2024-05-28 15:57:54 +00:00
Nino Righi
82656d9b27 Merged PR 1780: #4314 Navigation on Product Cover Click
#4314 Navigation on Product Cover Click
2024-05-28 15:57:31 +00:00
Lorenz Hilpert
df36d0934d Merged PR 1779: #4752 Performance in der WA verbessert -> Details Seite
#4752 Performance in der WA verbessert -> Details Seite
2024-05-28 13:48:00 +00:00
Nino Righi
3a9820aa54 Merged PR 1776: #4721 Removed unnecessary check for scrolling to item
#4721 Removed unnecessary check for scrolling to item
2024-05-02 19:21:52 +00:00
Nino Righi
30ad99332e Merged PR 1775: #3751 ProductGroup and ProductGroupDetails Update
#3751 ProductGroup and ProductGroupDetails Update
2024-04-22 13:31:20 +00:00
Lorenz Hilpert
4b48275910 Merged PR 1774: AHF Performance 2024-04-19 09:54:54 +00:00
Lorenz Hilpert
d3e3316459 Merge branch 'master' into develop 2024-04-17 16:25:02 +02:00
Nino Righi
4ef1bd4df6 Merged PR 1771: #4720 Hotfix Navigation Pickup Shelf Out
#4720 Hotfix Navigation Pickup Shelf Out
2024-04-17 14:21:30 +00:00
Nino Righi
0c2a23e5d2 Merged PR 1773: #3751 Reservation List Show Compartment and ProductGroup from InStock
#3751 Reservation List Show Compartment and ProductGroup from InStock
2024-04-17 08:46:32 +00:00
Nino Righi
36bd2c1eba Merged PR 1772: #4004 Kubi Check If Customer is 18 Years old
#4004 Kubi Check If Customer is 18 Years old
2024-04-15 12:34:14 +00:00
Nino Righi
a38d2eede6 Merged PR 1770: #4709 Removed Save Special Comment CTA on Checkout Cart Review
#4709 Removed Save Special Comment CTA on Checkout Cart Review
2024-04-12 10:37:43 +00:00
135 changed files with 7724 additions and 10371 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
.matomo
# compiled output
/dist
/tmp

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
npm run pretty-quick

View File

@@ -1,4 +0,0 @@
- Neue Icon Module (z.B. mit SVG sprites)
- Breadcrumb Navigation (Neu)
- Remissions Produkt Liste (Refactoring / Neu)
- Angular Version (Upgrade)

View File

@@ -2,6 +2,7 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "apps",
"projects": {
"@swagger/availability": {
"root": "apps/swagger/availability",
@@ -959,10 +960,10 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "isa-app:build:production"
"buildTarget": "isa-app:build:production"
},
"development": {
"browserTarget": "isa-app:build:development"
"buildTarget": "isa-app:build:development"
}
},
"defaultConfiguration": "development"
@@ -970,7 +971,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "isa-app:build"
"buildTarget": "isa-app:build"
}
},
"test": {
@@ -1470,39 +1471,6 @@
}
}
}
},
"shell": {
"projectType": "library",
"root": "apps/shell",
"sourceRoot": "apps/shell/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
}
},
"cli": {

View File

@@ -0,0 +1,31 @@
import { Directive, HostListener, Input } from '@angular/core';
import { ProductCatalogNavigationService } from '@shared/services';
@Directive({
selector: '[productImageNavigation]',
standalone: true,
})
export class NavigateOnClickDirective {
@Input('productImageNavigation') ean: string;
constructor(private readonly _productCatalogNavigation: ProductCatalogNavigationService) {}
@HostListener('click', ['$event'])
async onClick(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
if (this.ean) {
await this._navigateToProductSearchDetails();
}
}
private async _navigateToProductSearchDetails() {
await this._productCatalogNavigation
.getArticleDetailsPathByEan({
processId: Date.now(),
ean: this.ean,
extras: { queryParams: { main_qs: this.ean } },
})
.navigate();
}
}

View File

@@ -5,4 +5,5 @@
export * from './lib/product-image.service';
export * from './lib/product-image.module';
export * from './lib/product-image.pipe';
export * from './lib/product-image-navigation.directive';
export * from './lib/tokens';

View File

@@ -36,7 +36,7 @@ export class DomainAvailabilityService {
private _logisticanService: LogisticianService,
private _stockService: StockService,
private _supplierService: StoreCheckoutSupplierService,
private _branchService: StoreCheckoutBranchService
private _branchService: StoreCheckoutBranchService,
) {}
@memorize({ ttl: 10000 })
@@ -48,7 +48,7 @@ export class DomainAvailabilityService {
getSuppliers(): Observable<SupplierDTO[]> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map((response) => response.result),
shareReplay(1)
shareReplay(1),
);
}
@@ -56,7 +56,7 @@ export class DomainAvailabilityService {
getTakeAwaySupplier(): Observable<SupplierDTO> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
shareReplay(1)
shareReplay(1),
);
}
@@ -64,7 +64,7 @@ export class DomainAvailabilityService {
getBranches(): Observable<BranchDTO[]> {
return this._branchService.StoreCheckoutBranchGetBranches({}).pipe(
map((response) => response.result),
shareReplay(1)
shareReplay(1),
);
}
@@ -73,7 +73,7 @@ export class DomainAvailabilityService {
return this._stockService.StockGetStocksByBranch({ branchId }).pipe(
map((response) => response.result),
map((result) => result?.find((_) => true)),
shareReplay(1)
shareReplay(1),
);
}
@@ -81,7 +81,7 @@ export class DomainAvailabilityService {
getDefaultStock(): Observable<StockDTO> {
return this._stockService.StockCurrentStock().pipe(
map((response) => response.result),
shareReplay(1)
shareReplay(1),
);
}
@@ -105,7 +105,7 @@ export class DomainAvailabilityService {
status: response.result.status,
version: response.result.version,
})),
shareReplay(1)
shareReplay(1),
);
}
@@ -113,7 +113,7 @@ export class DomainAvailabilityService {
getLogisticians(): Observable<LogisticianDTO> {
return this._logisticanService.LogisticianGetLogisticians({}).pipe(
map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
shareReplay(1)
shareReplay(1),
);
}
@@ -146,7 +146,7 @@ export class DomainAvailabilityService {
});
return availabilities;
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -167,13 +167,13 @@ export class DomainAvailabilityService {
this._stockService.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
this.getTakeAwaySupplier(),
this.getDefaultBranch(),
])
]),
),
map(([response, supplier, defaultBranch]) => {
const price = item?.price;
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch?.id ?? defaultBranch?.id, quantity, price });
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -196,7 +196,7 @@ export class DomainAvailabilityService {
map(([response, supplier]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id, quantity, price });
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -218,7 +218,7 @@ export class DomainAvailabilityService {
map(([response, supplier, defaultBranch]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branchId ?? defaultBranch.id, quantity, price });
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -228,7 +228,7 @@ export class DomainAvailabilityService {
switchMap((s) => this._stockService.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getDefaultBranch()),
map((response) => response[0].result),
shareReplay(1)
shareReplay(1),
);
}
@@ -254,7 +254,7 @@ export class DomainAvailabilityService {
])
.pipe(
map((r) => this._mapToPickUpAvailability(r.result)?.find((_) => true)),
shareReplay(1)
shareReplay(1),
);
}
@@ -270,7 +270,7 @@ export class DomainAvailabilityService {
]).pipe(
timeout(5000),
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
shareReplay(1)
shareReplay(1),
);
}
@@ -305,7 +305,7 @@ export class DomainAvailabilityService {
priceMaintained: preferred?.priceMaintained,
};
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -329,12 +329,12 @@ export class DomainAvailabilityService {
this.getPickUpAvailability({ item, quantity, branch: branch ?? defaultBranch }).pipe(
mergeMap((availability) =>
logistician$.pipe(
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } }))
)
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } })),
),
),
shareReplay(1)
)
)
shareReplay(1),
),
),
);
}
@@ -367,7 +367,7 @@ export class DomainAvailabilityService {
priceMaintained: preferred?.priceMaintained,
};
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -378,7 +378,7 @@ export class DomainAvailabilityService {
switchMap((stockId) =>
stockId
? this._stockService.StockInStock({ articleIds: items.map((i) => i.id), stockId })
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO),
),
timeout(20000),
withLatestFrom(this.getTakeAwaySupplier()),
@@ -389,10 +389,10 @@ export class DomainAvailabilityService {
supplier,
quantity: 1,
price: items?.find((i) => i.id === stockInfo.itemId)?.price,
})
}),
);
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -400,7 +400,7 @@ export class DomainAvailabilityService {
getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
return this._availabilityService.AvailabilityStoreAvailability(payload).pipe(
timeout(20000),
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result)),
);
}
@@ -408,7 +408,7 @@ export class DomainAvailabilityService {
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
map((response) => this._mapToShippingAvailability(response.result)),
);
}
@@ -416,7 +416,7 @@ export class DomainAvailabilityService {
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
map((response) => this._mapToShippingAvailability(response.result)),
);
}
@@ -427,16 +427,16 @@ export class DomainAvailabilityService {
return this.getPickUpAvailabilities(payload, true).pipe(
timeout(20000),
switchMap((availability) =>
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } })))
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } }))),
),
shareReplay(1)
shareReplay(1),
);
}
getPriceForAvailability(
purchasingOption: string,
catalogAvailability: CatAvailabilityDTO | AvailabilityDTO,
availability: AvailabilityDTO
availability: AvailabilityDTO,
): PriceDTO {
switch (purchasingOption) {
case 'take-away':
@@ -567,12 +567,12 @@ export class DomainAvailabilityService {
if (!params.branchId) {
branchId$ = this.getDefaultBranch().pipe(
first(),
map((b) => b.id)
map((b) => b.id),
);
}
const stock$ = branchId$.pipe(
mergeMap((branchId) => this._stockService.StockGetStocksByBranch({ branchId }).pipe(map((response) => response.result?.[0])))
mergeMap((branchId) => this._stockService.StockGetStocksByBranch({ branchId }).pipe(map((response) => response.result?.[0]))),
);
return stock$.pipe(
@@ -589,17 +589,17 @@ export class DomainAvailabilityService {
acc[stockInfo.ean] = stockInfo;
return acc;
}, {});
})
)
)
}),
),
),
);
}
getInStock({ itemIds, branchId }: { itemIds: number[]; branchId: number }): Observable<StockInfoDTO[]> {
return this.getStockByBranch(branchId).pipe(
mergeMap((stock) =>
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result))
)
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result)),
),
);
}
}

View File

@@ -11,7 +11,7 @@ export class ThumbnailUrlPipe implements PipeTransform, OnDestroy {
private input$ = new BehaviorSubject<{ width?: number; height?: number; ean?: string }>(undefined);
private result: string;
private onDestroy$ = new Subject();
private onDestroy$ = new Subject<void>();
constructor(private domainCatalogThumbnailService: DomainCatalogThumbnailService, private cdr: ChangeDetectorRef) {}

View File

@@ -1,4 +1,4 @@
import { inject, isDevMode, NgModule } from '@angular/core';
import { isDevMode, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
CanActivateCartGuard,
@@ -8,8 +8,6 @@ import {
CanActivateCustomerOrdersWithProcessIdGuard,
CanActivateCustomerWithProcessIdGuard,
CanActivateGoodsInGuard,
CanActivateGoodsOutGuard,
CanActivateGoodsOutWithProcessIdGuard,
CanActivateProductGuard,
CanActivateProductWithProcessIdGuard,
CanActivateRemissionGuard,
@@ -22,9 +20,9 @@ import { MainComponent } from './main.component';
import { PreviewComponent } from './preview';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
import { TokenLoginComponent, TokenLoginModule } from './token-login';
import { ApplicationService } from '@core/application';
import { ProcessIdGuard } from './guards/process-id.guard';
import { ActivateProcessIdGuard, ActivateProcessIdWithConfigKeyGuard } from './guards/activate-process-id.guard';
import { MatomoRouteData } from 'ngx-matomo-client';
const routes: Routes = [
{
@@ -45,6 +43,11 @@ const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
data: {
matomo: {
title: 'Dashboard',
} as MatomoRouteData,
},
},
{
path: 'product',

View File

@@ -28,7 +28,7 @@ export const metaReducers: MetaReducer<RootState>[] = !environment.production ?
imports: [
StoreModule.forRoot(rootReducer, { metaReducers }),
EffectsModule.forRoot([]),
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Application Store' }),
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Application Store', connectInZone: true }),
],
})
export class AppStoreModule {}

View File

@@ -1,18 +1,17 @@
import { DOCUMENT } from '@angular/common';
import { Component, HostListener, Inject, OnInit, Renderer2 } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
import { SwUpdate } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { asapScheduler, interval, Observable, Subscription } from 'rxjs';
import { asapScheduler, interval, Subscription } from 'rxjs';
import { UserStateService } from '@swagger/isa';
import { IsaLogProvider } from './providers';
import { EnvironmentService } from '@core/environment';
import { AuthService } from '@core/auth';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { tap } from 'rxjs/operators';
@Component({
selector: 'app-root',
@@ -21,7 +20,6 @@ import { tap } from 'rxjs/operators';
})
export class AppComponent implements OnInit {
private _checkForUpdates: number = this._config.get('checkForUpdates');
updateAvailableObs: Observable<UpdateAvailableEvent>;
get checkForUpdates(): number {
return this._checkForUpdates;

View File

@@ -37,6 +37,8 @@ import { NativeContainerService } from 'native-container';
import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
import { IconModule } from '@shared/components/icon';
import { provideMatomo } from 'ngx-matomo-client';
import { withRouter, withRouteData } from 'ngx-matomo-client';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -46,7 +48,7 @@ export function _appInitializerFactory(
auth: AuthService,
injector: Injector,
scanAdapter: ScanAdapterService,
nativeContainer: NativeContainerService
nativeContainer: NativeContainerService,
) {
return async () => {
const statusElement = document.querySelector('#init-status');
@@ -135,6 +137,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useClass: IsaErrorHandler,
},
{ provide: LOCALE_ID, useValue: 'de-DE' },
provideMatomo({ trackerUrl: 'http://localhost:8080', siteId: '1' }, withRouter(), withRouteData()),
],
bootstrap: [AppComponent],
})

View File

@@ -23,7 +23,7 @@
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
@@ -57,4 +57,3 @@ import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
import 'hammerjs';

View File

@@ -47,7 +47,7 @@ export class MockRemissionService extends RemissionService {
>();
private remissionSubjectIdRef = new Map<number, number>();
private reloadProductsSubject = new Subject();
private reloadProductsSubject = new Subject<void>();
private productSubject = new BehaviorSubject<RemissionProduct[]>(
remissionProducts
);

View File

@@ -9,9 +9,7 @@
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
</ng-container>
<ng-template #notAvailable>
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
</ng-template>
<ng-template #notAvailable> Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar. </ng-template>
</ui-tooltip>
</button>
</div>
@@ -44,7 +42,7 @@
<img
class="max-h-[19.6875rem] max-w-[12.1875rem] rounded"
(load)="loadImage()"
[src]="item.imageId | productImage: 195:315:true"
[src]="item.imageId | productImage: 195 : 315 : true"
alt="product image"
/>
<ui-icon
@@ -107,7 +105,7 @@
<div class="page-article-details__product-price-info flex flex-col mb-4 flex-nowrap self-end">
<div class="page-article-details__product-price font-bold text-xl self-end" *ngIf="price$ | async; let price">
{{ price?.value?.value | currency: price?.value?.currency:'code' }}
{{ price?.value?.value | currency: price?.value?.currency : 'code' }}
</div>
<div *ngIf="price$ | async; let price" class="page-article-details__product-price-bound self-end">
{{ price?.vat?.vatType | vat: (priceMaintained$ | async) }}
@@ -349,6 +347,9 @@
*ngIf="store.item$ | async; let item"
class="shadow-[#dce2e9_0px_-2px_18px_0px] mb-5 border-none outline-none flex items-center px-5 h-14 min-h-[3.5rem] bg-white w-full"
(click)="showRecommendations = true"
matomoClickCategory="prodict-details"
matomoClickAction="click"
matomoClickName="recommendations"
>
<span class="uppercase text-[#0556B4] font-bold text-p3">Empfehlungen</span>
<img class="absolute right-5 -top-[0.125rem] h-12" src="assets/images/recommendation_tag.png" alt="recommendation icon" />
@@ -364,6 +365,9 @@
*ngIf="!(store.isDownload$ | async)"
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-[1.875rem]"
(click)="showAvailabilities()"
matomoClickCategory="prodict-details"
matomoClickAction="click"
matomoClickName="bestaende-in-anderen-filialen"
>
Bestände in anderen Filialen
</button>
@@ -371,6 +375,9 @@
class="text-white bg-brand border-brand font-bold text-lg px-[1.375rem] py-4 rounded-full border-none no-underline"
(click)="showPurchasingModal()"
[disabled]="!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')"
matomoClickCategory="prodict-details"
matomoClickAction="click"
matomoClickName="in-den-Warenkorb"
>
In den Warenkorb
</button>

View File

@@ -141,7 +141,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
this.store.deliveryB2BAvailability$,
]).pipe(
map((availabilities) => {
return availabilities?.some((availability) => availability?.priceMaintained) ?? false;
return availabilities?.some((availability) => (availability as any)?.priceMaintained) ?? false;
})
);
@@ -439,11 +439,10 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
async navigateToResultList() {
const processId = this.applicationService.activatedProcessId;
let crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])
.pipe(first())
.toPromise();
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
const crumb = crumbs[crumbs.length - 1];
if (!!crumb) {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: crumb.params }).navigate();

View File

@@ -14,6 +14,7 @@ import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
import { IconModule } from '@shared/components/icon';
import { ArticleDetailsTextComponent } from './article-details-text/article-details-text.component';
import { IconBadgeComponent } from 'apps/shared/components/icon/src/lib/badge/icon-badge.component';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [
@@ -30,6 +31,7 @@ import { IconBadgeComponent } from 'apps/shared/components/icon/src/lib/badge/ic
OrderDeadlinePipeModule,
ArticleDetailsTextComponent,
IconBadgeComponent,
MatomoModule,
],
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],

View File

@@ -24,7 +24,7 @@ import { FilterAutocompleteProvider } from '@shared/components/filter';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
private _processId$: Observable<number>;
constructor(

View File

@@ -58,7 +58,7 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
}
}
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
constructor(
private articleSearch: ArticleSearchService,

View File

@@ -1,8 +1,6 @@
<div class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-full">
<h1 class="text-h3 text-[1.625rem] font-bold mb-[0.375rem]">Artikelsuche</h1>
<p class="text-lg mb-10">
Welchen Artikel suchen Sie?
</p>
<p class="text-lg mb-10">Welchen Artikel suchen Sie?</p>
<ng-container *ngIf="filter$ | async; let filter">
<shared-filter-filter-group-main
class="mb-8 w-full"
@@ -38,6 +36,9 @@
<button
class="flex flex-row items-center outline-none border-none bg-white text-black text-p2 m-0 p-0"
(click)="setQueryHistory(filter, recentQuery.friendlyName)"
matomoClickCategory="search"
matomoClickAction="click"
matomoClickName="recent-search"
>
<shared-icon
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"

View File

@@ -4,9 +4,10 @@ import { ArticleSearchMainComponent } from './search-main.component';
import { FilterModule } from '@shared/components/filter';
import { RouterModule } from '@angular/router';
import { IconComponent, IconModule } from '@shared/components/icon';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [CommonModule, RouterModule, IconComponent, FilterModule, IconModule],
imports: [CommonModule, RouterModule, IconComponent, FilterModule, IconModule, MatomoModule],
exports: [ArticleSearchMainComponent],
declarations: [ArticleSearchMainComponent],
providers: [],

View File

@@ -67,7 +67,7 @@
class="page-search-result-item__item-price desktop-small:text-p3 font-bold justify-self-end"
[class.page-search-result-item__item-price-primary]="primaryOutletActive"
>
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR':'code' }}
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR' : 'code' }}
</div>
<div class="page-search-result-item__item-select-bullet justify-self-end">
@@ -75,9 +75,14 @@
*ngIf="selectable"
(click)="$event.stopPropagation()"
[ngModel]="selected"
(ngModelChange)="setSelected()"
(ngModelChange)="
setSelected();
tracker.trackEvent({ category: 'Trefferliste', action: 'select', name: item.product.name, value: selected ? 1 : 0 })
"
class="isa-select-bullet"
type="checkbox"
matomoTracker
#tracker="matomo"
/>
</div>

View File

@@ -11,7 +11,7 @@
[hint]="searchboxHint$ | async"
[loading]="fetching$ | async"
[inputGroup]="filter?.input | group: 'main'"
(search)="search({filter, clear: true})"
(search)="search({ filter, clear: true })"
[showDescription]="false"
[scanner]="true"
></shared-filter-input-group-main>
@@ -32,8 +32,7 @@
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
[class.mb-4]="primaryOutletActive$ | async"
>
{{ hits ??
0 }}
{{ hits ?? 0 }}
Titel
</div>
</div>
@@ -75,6 +74,9 @@
*ngIf="(selectedItemIds$ | async)?.length > 0"
class="cta-cart cta-action-primary"
(click)="addToCart()"
matomoClickCategory="Trefferliste"
matomoClickAction="click"
matomoClickName="In den Warenkorb legen"
>
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
</button>

View File

@@ -155,7 +155,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
const cleanQueryParams = this.cleanupQueryParams(queryParams);
if (this.route.outlet === 'primary' || processChanged) {
if (processChanged) {
this.scrollToItem(this._getScrollIndexFromCache());
}
@@ -271,7 +271,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
private _getScrollIndexFromCache(): number {
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }) ?? 0;
}
scrollToItem(i?: number) {

View File

@@ -19,6 +19,7 @@ import { FilterAutocompleteProvider, FilterModule, OrderByFilterModule } from '@
import { FocusSearchboxEvent } from '../focus-searchbox.event';
import { ArticleSearchMainAutocompleteProvider } from '../providers';
import { IconComponent } from '@shared/components/icon';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [
@@ -35,6 +36,7 @@ import { IconComponent } from '@shared/components/icon';
UiTooltipModule,
FilterModule,
IconComponent,
MatomoModule,
],
exports: [ArticleSearchResultsComponent, SearchResultItemComponent],
declarations: [

View File

@@ -6,24 +6,45 @@ import { ArticleSearchFilterComponent } from './article-search/search-filter/sea
import { ArticleSearchMainComponent } from './article-search/search-main/search-main.component';
import { ArticleSearchResultsComponent } from './article-search/search-results/search-results.component';
import { PageCatalogComponent } from './page-catalog.component';
import { MatomoRouteData } from 'ngx-matomo-client';
const routes: Routes = [
{
path: '',
component: PageCatalogComponent,
data: {
matomo: {
title: 'Artikelsuche',
} as MatomoRouteData,
},
children: [
{
path: 'filter',
component: ArticleSearchFilterComponent,
data: {
matomo: {
title: 'Artikelsuche - Filter',
} as MatomoRouteData,
},
},
{
path: 'filter/:id',
component: ArticleSearchFilterComponent,
data: {
matomo: {
title: 'Artikelsuche - Filter',
} as MatomoRouteData,
},
},
{
path: 'search',
component: ArticleSearchComponent,
outlet: 'side',
data: {
matomo: {
title: 'Artikelsuche',
} as MatomoRouteData,
},
children: [
{
path: '',
@@ -34,29 +55,59 @@ const routes: Routes = [
{
path: 'results',
component: ArticleSearchResultsComponent,
data: {
matomo: {
title: 'Artikelsuche - Trefferliste',
} as MatomoRouteData,
},
},
{
path: 'results',
component: ArticleSearchResultsComponent,
outlet: 'side',
data: {
matomo: {
title: 'Artikelsuche - Trefferliste',
} as MatomoRouteData,
},
},
{
path: 'results/:id',
component: ArticleSearchResultsComponent,
outlet: 'side',
data: {
matomo: {
title: 'Artikelsuche - Artikeldetails',
} as MatomoRouteData,
},
},
{
path: 'results/:ean/ean',
component: ArticleSearchResultsComponent,
outlet: 'side',
data: {
matomo: {
title: 'Artikelsuche - Artikeldetails (EAN)',
} as MatomoRouteData,
},
},
{
path: 'details/:id',
component: ArticleDetailsComponent,
data: {
matomo: {
title: 'Artikelsuche - Artikeldetails (ID)',
} as MatomoRouteData,
},
},
{
path: 'details/:ean/ean',
component: ArticleDetailsComponent,
data: {
matomo: {
title: 'Artikelsuche - Artikeldetails (EAN)',
} as MatomoRouteData,
},
},
{
path: '',

View File

@@ -33,7 +33,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
}
_onDestroy$ = new Subject<boolean>();
_onDestroy$ = new Subject<void>();
get isTablet$() {
return this._environmentService.matchTablet$;

View File

@@ -19,17 +19,14 @@
placeholder="Eine Anmerkung hinzufügen"
[(ngModel)]="value"
[rows]="rows"
(ngModelChange)="check()"
(blur)="save()"
(ngModelChange)="updateValue()"
(blur)="updateValue()"
></textarea>
<div class="comment-actions py-4">
<button type="reset" class="clear pl-4" *ngIf="!disabled && !!value" (click)="clear(); triggerResize()">
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
<button class="cta-save ml-4" type="submit" *ngIf="!disabled && isDirty" (click)="save()">
Speichern
</button>
</div>
</div>

View File

@@ -61,7 +61,7 @@ export class SpecialCommentComponent implements ControlValueAccessor {
clear() {
this.value = '';
this.save();
this.updateValue();
}
check() {
@@ -80,11 +80,12 @@ export class SpecialCommentComponent implements ControlValueAccessor {
this.cdr.markForCheck();
}
save() {
updateValue() {
this.initialValue = this.value;
this.onChange(this.value);
this.check();
}
setIsDirty(isDirty: boolean) {
this.isDirty = isDirty;
this.isDirtyChange.emit(isDirty);

View File

@@ -33,7 +33,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
private _toaster = inject(ToasterService);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
processId = Date.now();
selectedDate = this.dateAdapter.today();
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);

View File

@@ -141,7 +141,7 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
more$ = this.select((s) => s.more);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
get isNative() {
return this._environment.isNative();

View File

@@ -22,7 +22,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
processId = Number(this._activatedRoute?.parent?.snapshot?.data?.processId);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
@ViewChild(FilterComponent, { static: false })
uiFilter: FilterComponent;

View File

@@ -23,7 +23,7 @@ import { CustomerOrdersNavigationService } from '@shared/services';
],
})
export class CustomerOrderSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
private _processId$: Observable<number>;
get isTablet() {

View File

@@ -201,8 +201,8 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
options$.pipe(
tap((_) => {
this.searchStarted.next();
tap((opt) => {
this.searchStarted.next(opt);
this.patchState({ message: undefined });
}),
withLatestFrom(this.results$, this.filter$, this.selectedBranch$),

View File

@@ -99,7 +99,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
trackByFn: TrackByFunction<OrderItemListItemDTO> = (index, item) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;

View File

@@ -30,7 +30,7 @@ export class CustomerOrderComponent implements OnInit {
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
}
_onDestroy$ = new Subject<boolean>();
_onDestroy$ = new Subject<void>();
get isTablet$() {
return this._environmentService.matchTablet$;

View File

@@ -197,7 +197,7 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
setValue(value: { p4mUser?: boolean; customerType?: string } | string) {
const initial = { p4mUser: this.p4mUser, customerType: this.customerType };
if (isString(value)) {
if (typeof value === 'string') {
this.value = value;
} else {
if (isBoolean(value.p4mUser)) {

View File

@@ -1,6 +1,6 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ChangeDetectorRef, Directive, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
@@ -190,6 +190,47 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
this.cdr.markForCheck();
}
minBirthDateValidator = (): ValidatorFn => {
return (control: AbstractControl): ValidationErrors | null => {
const minAge = 18; // 18 years
if (!control.value) {
return null;
}
const controlBirthDate = new Date(control.value);
const minBirthDate = new Date();
minBirthDate.setFullYear(minBirthDate.getFullYear() - minAge);
// Check if customer is over 18 years old
if (this._checkIfAgeOver18(controlBirthDate, minBirthDate)) {
return null;
} else {
return { minBirthDate: `Teilnahme ab ${minAge} Jahren` };
}
};
};
private _checkIfAgeOver18(inputDate: Date, minBirthDate: Date): boolean {
// Check year
if (inputDate.getFullYear() < minBirthDate.getFullYear()) {
return true;
}
// Check Year + Month
else if (inputDate.getFullYear() === minBirthDate.getFullYear() && inputDate.getMonth() < minBirthDate.getMonth()) {
return true;
}
// Check Year + Month + Day
else if (
inputDate.getFullYear() === minBirthDate.getFullYear() &&
inputDate.getMonth() === minBirthDate.getMonth() &&
inputDate.getDate() <= minBirthDate.getDate()
) {
return true;
}
return false;
}
emailExistsValidator: AsyncValidatorFn = (control) => {
return of(control.value).pipe(
tap((_) => this.customerExists$.next(false)),

View File

@@ -70,9 +70,9 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
agbValidatorFns = [Validators.requiredTrue];
birthDateValidatorFns = [Validators.required];
birthDateValidatorFns = [];
existingCustomer$: Observable<CustomerInfoDTO | null>;
existingCustomer$: Observable<CustomerInfoDTO | CustomerDTO | null>;
ngOnInit(): void {
super.ngOnInit();
@@ -138,6 +138,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
initMarksAndValidators() {
this.asyncLoyaltyCardValidatorFn = [this.checkLoyalityCardValidator];
this.birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
if (this._customerType === 'webshop') {
this.emailRequiredMark = true;
this.emailValidatorFn = [Validators.required, Validators.email, validateEmail];

View File

@@ -22,7 +22,7 @@ export class UpdateP4MWebshopCustomerComponent extends AbstractCreateCustomer im
agbValidatorFns = [Validators.requiredTrue];
birthDateValidatorFns = [Validators.required];
birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
nameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];

View File

@@ -29,7 +29,7 @@ export interface CustomerDetailsViewMainState {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDetailsViewMainState> implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
customerService = inject(CrmCustomerService);

View File

@@ -1,8 +1,6 @@
<div class="header">
<div class="cleanup-headline">
<h1 class="cleanup-title">
Abholfachbereinigung
</h1>
<h1 class="cleanup-title">Abholfachbereinigung</h1>
<p class="cleanup-paragraph">
Bitte nehmen Sie die<br />
Artikel auf die Fläche.
@@ -30,12 +28,17 @@
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first"
[item]="item"
[showCompartmentCode]="firstItem"
(selectedChange)="setSelectedItem(item, $event)"
[selectable]="item | goodsInCleanupSelectable: selectionRules:selectedItems"
(selectedChange)="
setSelectedItem(item, $event);
tracker.trackEvent({ category: 'goods-in-cleanup', action: 'select', name: 'order-item', value: $event ? 1 : 0 })
"
[selectable]="item | goodsInCleanupSelectable: selectionRules : selectedItems"
[selected]="item | goodsInCleanupSelected: selectedOrderItemSubsetIds"
(click)="navigateToDetails(item)"
[showSupplier]="true"
[quantityEditable]="item.overallQuantity > 1 && (selectedOrderItemSubsetIds$ | async)?.includes(item.orderItemSubsetId)"
matomoTracker
#tracker="matomo"
>
</shared-goods-in-out-order-group-item>
<div class="divider" *ngIf="!lastCompartmentCode"></div>
@@ -63,6 +66,9 @@
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
(click)="handleAction(action)"
matomoClickCategory="goods-in-cleanup"
matomoClickAction="click"
[matomoClickName]="action.label"
>
<ui-spinner [show]="(changeActionLoader$ | async) || (loading$ | async)">{{ action.label }}</ui-spinner>
</button>

View File

@@ -8,9 +8,10 @@ import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { GoodsInCleanupListComponent } from './goods-in-cleanup-list.component';
import { GoodsInCleanupSelectablePipe } from './goods-in-cleanup-selectable.pipe';
import { GoodsInCleanupSelectedPipe } from './goods-in-cleanup-selected.pipe';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [CommonModule, UiCommonModule, GoodsInOutOrderGroupModule, UiSpinnerModule, UiScrollContainerModule, RouterModule],
imports: [CommonModule, UiCommonModule, GoodsInOutOrderGroupModule, UiSpinnerModule, UiScrollContainerModule, RouterModule, MatomoModule],
exports: [GoodsInCleanupListComponent],
declarations: [GoodsInCleanupListComponent, GoodsInCleanupSelectablePipe, GoodsInCleanupSelectedPipe],
providers: [],

View File

@@ -25,14 +25,16 @@
type="text"
[ngModel]="ssc"
(ngModelChange)="sscChange($event)"
[matomoTracker]="['blur']"
matomoCategory="goods-in-list-item"
matomoAction="input"
matomoName="meldenummer"
/>
<span class="ssc-text" [class.err]="sscInvalid$ | async">{{ sscText$ | async }}</span>
</div>
<div class="err" *ngIf="supplierIdError$ | async">
Kein Lieferant vorhanden
</div>
<div class="err" *ngIf="supplierIdError$ | async">Kein Lieferant vorhanden</div>
</ng-container>
</div>
</div>
@@ -42,6 +44,7 @@
<img
class="thumbnail"
loading="lazy"
[productImageNavigation]="orderItem?.product?.ean"
*ngIf="orderItem?.product?.ean | productImage; let productImage"
[src]="productImage"
[alt]="orderItem?.product?.name"
@@ -73,7 +76,7 @@
</div>
<div class="price">
<strong>{{ orderItem.price | currency: 'EUR':'code' }}</strong>
<strong>{{ orderItem.price | currency: 'EUR' : 'code' }}</strong>
</div>
<div class="status spec">
<span>Status</span>
@@ -98,7 +101,14 @@
</div>
<div class="footer">
<button class="cta-reorder" (click)="showReorderModal(); $event.stopPropagation()" [disabled]="editSsc$ | async">
<button
class="cta-reorder"
(click)="showReorderModal(); $event.stopPropagation()"
[disabled]="editSsc$ | async"
matomoClickCategory="goods-in-list-item"
matomoClickAction="click"
matomoClickName="nachbestellen"
>
Nachbestellen
</button>
</div>

View File

@@ -123,7 +123,7 @@ export class GoodsInListItemComponent extends ComponentStore<GoodsInListItemComp
shareReplay()
);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
constructor(
private _omsService: DomainOmsService,

View File

@@ -1,14 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductImageModule } from 'apps/cdn/product-image/src/public-api';
import { NavigateOnClickDirective, ProductImageModule } from 'apps/cdn/product-image/src/public-api';
import { UiIconModule } from '@ui/icon';
import { UiInputModule } from '@ui/input';
import { FormsModule } from '@angular/forms';
import { GoodsInListItemComponent } from './goods-in-list-item.component';
import { PipesModule } from 'apps/shared/components/goods-in-out/src/lib/pipes/pipes.module';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [CommonModule, PipesModule, UiIconModule, ProductImageModule, FormsModule, UiInputModule],
imports: [
CommonModule,
PipesModule,
UiIconModule,
ProductImageModule,
FormsModule,
UiInputModule,
NavigateOnClickDirective,
MatomoModule,
],
exports: [GoodsInListItemComponent],
declarations: [GoodsInListItemComponent],
providers: [],

View File

@@ -1,7 +1,5 @@
<div class="goods-in-list-headline">
<h1 class="goods-in-list-title">
Wareneingangsliste
</h1>
<h1 class="goods-in-list-title">Wareneingangsliste</h1>
<p class="goods-in-list-paragraph">
Vergeben Sie Meldenummern oder<br />
bestellen Sie Artikel nach
@@ -45,18 +43,36 @@
</ng-template>
<div *ngIf="!(listEmpty$ | async)" class="actions">
<button *ngIf="!editSsc" class="cta-edit-ssc cta-action-primary" (click)="editSsc = true">
<button
*ngIf="!editSsc"
class="cta-edit-ssc cta-action-primary"
(click)="editSsc = true"
matomoClickCategory="goods-in-list"
matomoClickAction="click"
matomoClickName="meldenummer-vergeben"
>
Meldenummern vergeben
</button>
<ng-container *ngIf="editSsc">
<button class="cta-cancel-ssc cta-action-secondary" (click)="cancelSsc()">
<button
class="cta-cancel-ssc cta-action-secondary"
(click)="cancelSsc()"
matomoClickCategory="goods-in-list"
matomoClickAction="click"
matomoClickName="meldenummer-vergeben-abbrechen"
>
Abbrechen
</button>
<button class="cta-save-ssc cta-action-primary" (click)="saveSsc()" [disabled]="editSscDisabled$ | async">
<ui-spinner [show]="showSaveSscSpinner$ | async">
Meldenummern speichern
</ui-spinner>
<button
class="cta-save-ssc cta-action-primary"
(click)="saveSsc()"
[disabled]="editSscDisabled$ | async"
matomoClickCategory="goods-in-list"
matomoClickAction="click"
matomoClickName="meldenummer-vergeben-speichern"
>
<ui-spinner [show]="showSaveSscSpinner$ | async"> Meldenummern speichern </ui-spinner>
</button>
</ng-container>
</div>

View File

@@ -59,7 +59,7 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
showSaveSscSpinner$ = new BehaviorSubject<boolean>(false);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
private readonly SCROLL_POSITION_TOKEN = 'GOODS_IN_LIST_SCROLL_POSITION';

View File

@@ -8,6 +8,7 @@ import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { GoodsInListItemModule } from './goods-in-list-item/goods-in-list-item.module';
import { GoodsInListComponent } from './goods-in-list.component';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [
@@ -18,6 +19,7 @@ import { GoodsInListComponent } from './goods-in-list.component';
UiScrollContainerModule,
UiOrderByFilterModule,
UiSpinnerModule,
MatomoModule,
],
exports: [],
declarations: [GoodsInListComponent],

View File

@@ -1,8 +1,6 @@
<div class="header">
<div class="reservations-headline">
<h1 class="reservations-title">
Reservierungsliste
</h1>
<h1 class="reservations-title">Reservierungsliste</h1>
<p class="reservations-paragraph">
Hier sehen Sie alle zu<br />
reservierenden Bestellposten.
@@ -31,12 +29,17 @@
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first"
[item]="item"
[showCompartmentCode]="firstItem"
(selectedChange)="setSelectedItem(item, $event)"
[selectable]="item | goodsInReservationSelectable: selectionRules:selectedItems"
(selectedChange)="
setSelectedItem(item, $event);
tracker.trackEvent({ category: 'goods-in-reservation', action: 'select', name: 'order-item', value: $event ? 1 : 0 })
"
[selectable]="item | goodsInReservationSelectable: selectionRules : selectedItems"
[selected]="item | goodsInReservationSelected: selectedOrderItemSubsetIds"
[showInStock]="takeAwayAvailabilities$ | async"
(click)="navigateToDetails(item)"
[quantityEditable]="item.overallQuantity > 1 && (selectedOrderItemSubsetIds$ | async)?.includes(item.orderItemSubsetId)"
matomoTracker
#tracker="matomo"
>
</shared-goods-in-out-order-group-item>
<div class="divider" *ngIf="!lastCompartmentCode"></div>
@@ -49,9 +52,7 @@
</ui-scroll-container>
<ng-template #emptyMessage>
<div class="empty-message">
Es sind derzeit keine Reservierungen offen.
</div>
<div class="empty-message">Es sind derzeit keine Reservierungen offen.</div>
</ng-template>
<div class="actions" *ngIf="actions$ | async; let actions">
@@ -62,6 +63,9 @@
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
(click)="handleAction(action)"
matomoClickCategory="goods-in-reservation"
matomoClickAction="click"
[matomoClickName]="action.label"
>
<ui-spinner [show]="(changeActionLoader$ | async) || (loading$ | async)">{{ action.label }}</ui-spinner>
</button>

View File

@@ -8,9 +8,10 @@ import { GoodsInReservationSelectablePipe } from './goods-in-reservation-selecta
import { GoodsInReservationSelectedPipe } from './goods-in-reservation-selected.pipe';
import { GoodsInReservationComponent } from './goods-in-reservation.component';
import { MatomoModule } from 'ngx-matomo-client';
@NgModule({
imports: [CommonModule, UiCommonModule, UiScrollContainerModule, GoodsInOutOrderGroupModule, UiSpinnerModule],
imports: [CommonModule, UiCommonModule, UiScrollContainerModule, GoodsInOutOrderGroupModule, UiSpinnerModule, MatomoModule],
exports: [GoodsInReservationComponent],
declarations: [GoodsInReservationComponent, GoodsInReservationSelectablePipe, GoodsInReservationSelectedPipe],
})

View File

@@ -59,7 +59,7 @@ export class GoodsOutDetailsComponent extends ComponentStore<GoodsOutDetailsComp
processId$ = this._activatedRoute.parent.data.pipe(map((params) => +params.processId));
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
constructor(
private _activatedRoute: ActivatedRoute,

View File

@@ -37,7 +37,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
@Input()
processId: number;
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
@ViewChild(UiFilterComponent, { static: false })
uiFilter: UiFilterComponent;

View File

@@ -30,7 +30,7 @@ import { GoodsOutSearchMainAutocompleteProvider } from './providers/goods-out-se
],
})
export class GoodsOutSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
hasFilter$ = combineLatest([this._goodsOutSearchStore.filter$, this._goodsOutSearchStore.defaultSettings$]).pipe(
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), UiFilter.create(defaultFilter).getQueryParams()))

View File

@@ -70,7 +70,7 @@ export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearc
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
trackByFn: TrackByFunction<OrderItemListItemDTO> = (index, item) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;

View File

@@ -36,12 +36,22 @@
<page-pickup-shelf-details-tags class="mb-px-2" *ngIf="showTagsComponent$ | async"></page-pickup-shelf-details-tags>
<page-pickup-shelf-details-covers
*ngIf="(coverOrderItems$ | async)?.length > 0"
[coverItems]="coverOrderItems$ | async"
[selectedOrderItem]="selectedItem$ | async"
(coverClick)="coverClick($event)"
></page-pickup-shelf-details-covers>
<ng-container *ngIf="fetchingCoverItems$ | async; else coverItemsTmpl">
<div class="bg-white grid grid-flow-col gap-5 justify-center items-center h-40">
<shared-skeleton-loader class="h-16 w-12"></shared-skeleton-loader>
<shared-skeleton-loader class="h-16 w-12"></shared-skeleton-loader>
<shared-skeleton-loader class="h-16 w-12"></shared-skeleton-loader>
</div>
</ng-container>
<ng-template #coverItemsTmpl>
<page-pickup-shelf-details-covers
*ngIf="(coverOrderItems$ | async)?.length > 0"
[coverItems]="coverOrderItems$ | async"
[selectedOrderItem]="selectedItem$ | async"
(coverClick)="coverClick($event)"
></page-pickup-shelf-details-covers>
</ng-template>
</div>
<div class="page-pickup-shelf-in-details__action-wrapper">
@@ -51,7 +61,10 @@
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
*ngFor="let action of mainActions$ | async"
(click)="handleAction({action})"
(click)="handleAction({ action })"
matomoClickCategory="pickup-shelf-in-details"
matomoClickAction="click"
[matomoClickName]="action.label"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
</button>
@@ -63,7 +76,13 @@
class="cta-action shadow-action"
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
(click)="handleAction({action, latestCompartmentCode: latestCompartmentInfos?.latestCompartmentCode, latestCompartmentInfo: latestCompartmentInfos?.latestCompartmentInfo })"
(click)="
handleAction({
action,
latestCompartmentCode: latestCompartmentInfos?.latestCompartmentCode,
latestCompartmentInfo: latestCompartmentInfos?.latestCompartmentInfo,
})
"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command"
>{{ latestDisplayedCompartmentInfos$ | async | addToPreviousCompartmentCodeLabelPipe }} zubuchen</ui-spinner

View File

@@ -18,6 +18,8 @@ import { ActivatedRoute } from '@angular/router';
import { RunCheckTrigger } from '../../trigger';
import { PickUpShelfDetailsItemsGroupComponent } from '../../shared/pickup-shelf-details-items-group/pickup-shelf-details-items-group.component';
import { isEqual } from 'lodash';
import { SkeletonLoaderComponent } from '@shared/components/loader';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pickup-shelf-in-details',
@@ -38,6 +40,8 @@ import { isEqual } from 'lodash';
PickupShelfAddToPreviousCompartmentCodeLabelPipe,
UiSpinnerModule,
OnInitDirective,
SkeletonLoaderComponent,
MatomoModule,
],
})
export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseComponent implements OnInit, AfterViewInit {
@@ -54,12 +58,12 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
noOrderItemsFound$ = this.store.noOrderItemsFound$;
fetching$ = this.store.fetchingOrder$;
fetchingOrder$ = this.store.fetchingOrder$;
fetchingItems$ = this.store.fetchingOrderItems$;
fetchingCoverItems$ = this.store.fetchingCoverOrderItems$;
viewFetching$ = combineLatest([this.fetching$, this.fetchingItems$, this.fetchingCoverItems$]).pipe(
map(([fetching, fetchingItems, fetchingCoverItems]) => fetching || fetchingItems || fetchingCoverItems)
viewFetching$ = combineLatest([this.fetchingItems$, this.orderItems$]).pipe(
map(([fetchingItems, orderItems]) => fetchingItems && orderItems.length === 0),
);
selectedCompartmentInfo = this.store.selectedCompartmentInfo;
@@ -91,14 +95,17 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
return `${latestCompartmentInfos?.latestCompartmentCode}_${latestCompartmentInfos?.latestCompartmentInfo}`;
}
return latestCompartmentInfos?.latestCompartmentCode;
})
}),
);
addToPreviousCompartmentActionDisabled$ = combineLatest([this.displayedCompartmentInfo$, this.changeActionDisabled$]).pipe(
map(([compartmentInfo, changeActionDisabled]) => !!compartmentInfo || changeActionDisabled)
map(([compartmentInfo, changeActionDisabled]) => !!compartmentInfo || changeActionDisabled),
);
constructor(private _uiModal: UiModalService, private _activatedRoute: ActivatedRoute) {
constructor(
private _uiModal: UiModalService,
private _activatedRoute: ActivatedRoute,
) {
super();
}
@@ -108,7 +115,7 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
.subscribe(([params, items]) => {
const orderItemSubsetId = +params?.orderItemSubsetId;
const selectedItem: DBHOrderItemListItemDTO = items?.find(
(item: DBHOrderItemListItemDTO) => item?.orderItemSubsetId === orderItemSubsetId
(item: DBHOrderItemListItemDTO) => item?.orderItemSubsetId === orderItemSubsetId,
);
// Trigger functions only if a new item gets selected
@@ -174,7 +181,7 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
},
side: false,
}).path,
{ queryParamsHandling: 'preserve' }
{ queryParamsHandling: 'preserve' },
);
this.listStore.patchOrderItem({
orderItemSubsetId: item.orderItemSubsetId,
@@ -218,20 +225,21 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
}
updateDate({ date, type }: { date: Date; type?: 'delivery' | 'pickup' | 'preferred' }) {
this.store.updateOrderItemSubsetLoading(true);
switch (type) {
case 'delivery':
this.store.selectedOrderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { estimatedShippingDate: date.toISOString() } })
this.store.patchOrderItemSubset({ item, changes: { estimatedShippingDate: date.toISOString() } }),
);
break;
case 'pickup':
this.store.selectedOrderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { compartmentStop: date.toISOString() } })
this.store.patchOrderItemSubset({ item, changes: { compartmentStop: date.toISOString() } }),
);
break;
case 'preferred':
this.store.selectedOrderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { preferredPickUpDate: date.toISOString() } })
this.store.patchOrderItemSubset({ item, changes: { preferredPickUpDate: date.toISOString() } }),
);
break;
default:
@@ -255,9 +263,9 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
orderItemSubsetId: orderItem.orderItemSubsetId,
compartmentInfo: orderItem.compartmentInfo,
},
{ side: this.side }
{ side: this.side },
).path,
{ queryParams: { buyerNumber: orderItem?.buyerNumber }, queryParamsHandling: 'merge' }
{ queryParams: { buyerNumber: orderItem?.buyerNumber }, queryParamsHandling: 'merge' },
);
}
@@ -272,9 +280,9 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
orderItemSubsetId: orderItem.orderItemSubsetId,
compartmentInfo: orderItem.compartmentInfo,
},
{ side: this.side }
{ side: this.side },
).path,
{ queryParams: { orderItemSubsetId: orderItem.orderItemSubsetId }, queryParamsHandling: 'merge' }
{ queryParams: { orderItemSubsetId: orderItem.orderItemSubsetId }, queryParamsHandling: 'merge' },
);
}
@@ -298,7 +306,7 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
},
side: this.side,
}).path,
{ queryParamsHandling: 'preserve' }
{ queryParamsHandling: 'preserve' },
);
} else {
if (orderItems.every((item) => item.processingStatus === 128)) {

View File

@@ -50,7 +50,7 @@ export class PickupShelfInEditComponent extends PickupShelfDetailsBaseComponent
},
side: this.side,
}).path,
{ queryParamsHandling: 'preserve' }
{ queryParamsHandling: 'preserve' },
);
}
}

View File

@@ -28,6 +28,7 @@ import { CacheService } from '@core/cache';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PickupShelfListItemLoaderComponent } from '../../shared/pickup-shelf-list-item/pickup-shelf-list-item-loader.component';
import { ScrollContainerDirective } from '@shared/directives/scroll-container';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pickup-shelf-in-list',
@@ -76,7 +77,7 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
map(([filter, defaultFilter]) => {
const filterQueryParams = filter?.getQueryParams();
return !isEqual(filterQueryParams, Filter.create(defaultFilter).getQueryParams());
})
}),
);
fetching$: Observable<boolean> = this.store.fetchingList$;
@@ -117,7 +118,7 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
private _activatedRoute: ActivatedRoute,
private _pickUpShelfInNavigation: PickupShelfInNavigationService,
private _cache: CacheService,
private _router: Router
private _router: Router,
) {}
ngOnInit() {

View File

@@ -26,6 +26,8 @@
(click)="showFilter.emit()"
class="page-pickup-shelf-in-main__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
[class.active]="hasFilter$ | async"
matomoClickAction="open"
matomoClickCategory="filter"
>
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
Filter

View File

@@ -7,6 +7,7 @@ import { isEqual } from 'lodash';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { PickupShelfStore } from '../../store';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pickup-shelf-in-main-side-view',
@@ -15,7 +16,7 @@ import { PickupShelfStore } from '../../store';
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page-pickup-shelf-in-main-side-view' },
imports: [FilterModule, AsyncPipe, NgIf, IconModule],
imports: [FilterModule, AsyncPipe, NgIf, IconModule, MatomoModule],
})
export class PickUpShelfInMainSideViewComponent implements OnInit {
@Output() showFilter = new EventEmitter<void>();
@@ -35,7 +36,7 @@ export class PickUpShelfInMainSideViewComponent implements OnInit {
map(([filter, defaultFilter]) => {
const filterQueryParams = filter?.getQueryParams();
return !isEqual(filterQueryParams, Filter.create(defaultFilter).getQueryParams());
})
}),
);
fetching$: Observable<boolean> = this.store.fetchingList$;

View File

@@ -8,6 +8,7 @@ import { PickUpShelfInMainSideViewComponent } from './pickup-shelf-in-main-side-
import { PickUpShelfInMainComponent } from './pickup-shelf-in-main/pickup-shelf-in-main.component';
import { PickUpShelfInListComponent } from './pickup-shelf-in-list/pickup-shelf-in-list.component';
import { PickupShelfInEditComponent } from './pickup-shelf-in-edit/pickup-shelf-in-edit.component';
import { MatomoRouteData } from 'ngx-matomo-client';
export const routes: Routes = [
{
@@ -18,53 +19,125 @@ export const routes: Routes = [
},
runGuardsAndResolvers: 'always',
children: [
{ path: 'main', component: PickUpShelfInMainComponent, data: { view: 'main' } },
{ path: 'list', component: PickUpShelfInListComponent, data: { view: 'list' } },
{ path: 'list/filter', component: PickupShelfFilterComponent, data: { view: 'filter' } },
{
path: 'main',
component: PickUpShelfInMainComponent,
data: {
view: 'main',
matomo: {
title: 'Abholfach',
} as MatomoRouteData,
},
},
{
path: 'list',
component: PickUpShelfInListComponent,
data: {
view: 'list',
matomo: {
title: 'Abholfach - Trefferliste',
} as MatomoRouteData,
},
},
{
path: 'list/filter',
component: PickupShelfFilterComponent,
data: {
view: 'filter',
matomo: {
title: 'Abholfach - Filter',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/:orderNumber/item/status/:orderItemProcessingStatus/:orderItemSubsetId/edit',
component: PickupShelfInEditComponent,
data: { view: 'edit' },
data: {
view: 'edit',
matomo: {
title: 'Abholfach - Bearbeiten',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/compartment/:compartmentCode/:orderItemSubsetId/edit',
component: PickupShelfInEditComponent,
data: { view: 'edit' },
data: {
view: 'edit',
matomo: {
title: 'Abholfach - Bearbeiten',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/compartment/:compartmentCode/:compartmentInfo/:orderItemSubsetId/edit',
component: PickupShelfInEditComponent,
data: { view: 'edit' },
data: {
view: 'edit',
matomo: {
title: 'Abholfach - Bearbeiten',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/:orderNumber/item/status/:orderItemProcessingStatus/:orderItemSubsetId/history',
component: PickUpShelfHistoryComponent,
data: { view: 'history' },
data: {
view: 'history',
matomo: {
title: 'Abholfach - Historie',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/compartment/:compartmentCode/:orderItemSubsetId/history',
component: PickUpShelfHistoryComponent,
data: { view: 'history' },
data: {
view: 'history',
matomo: {
title: 'Abholfach - Historie',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/compartment/:compartmentCode/:compartmentInfo/:orderItemSubsetId/history',
component: PickUpShelfHistoryComponent,
data: { view: 'history' },
data: {
view: 'history',
matomo: {
title: 'Abholfach - Historie',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/:orderNumber/item/status/:orderItemProcessingStatus/:orderItemSubsetId',
component: PickupShelfInDetailsComponent,
data: { view: 'details' },
data: {
view: 'details',
matomo: {
title: 'Abholfach - Details',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/compartment/:compartmentCode/:orderItemSubsetId',
component: PickupShelfInDetailsComponent,
data: { view: 'details' },
data: {
view: 'details',
matomo: {
title: 'Abholfach - Details',
} as MatomoRouteData,
},
},
{
path: 'order/:orderId/compartment/:compartmentCode/:compartmentInfo/:orderItemSubsetId',
component: PickupShelfInDetailsComponent,
data: { view: 'details' },
data: {
view: 'details',
matomo: {
title: 'Abholfach - Details',
} as MatomoRouteData,
},
},
{ path: 'search', component: PickUpShelfInMainSideViewComponent, outlet: 'side' },
{ path: 'list', component: PickUpShelfInListComponent, data: { view: 'list' }, outlet: 'side' },

View File

@@ -47,6 +47,9 @@
[class.cta-action-secondary]="!action.selected"
*ngFor="let action of mainActions$ | async"
(click)="handleAction(action)"
matomoClickCategory="pickup-shelf-out-details"
matomoClickAction="click"
[matomoClickName]="action.label"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
</button>

View File

@@ -14,6 +14,7 @@ import { OnInitDirective } from '@shared/directives/element-lifecycle';
import { FormsModule } from '@angular/forms';
import { RunCheckTrigger } from '../../trigger';
import { PickUpShelfDetailsItemsGroupComponent } from '../../shared/pickup-shelf-details-items-group/pickup-shelf-details-items-group.component';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pickup-shelf-out-details',
@@ -33,6 +34,7 @@ import { PickUpShelfDetailsItemsGroupComponent } from '../../shared/pickup-shelf
UiSpinnerModule,
OnInitDirective,
FormsModule,
MatomoModule,
],
providers: [],
})
@@ -46,7 +48,9 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
order$ = this.store.order$;
groupedItems$: Observable<Array<{ type: string; items: DBHOrderItemListItemDTO[] }>> = this.store.orderItems$.pipe(
orderItems$ = this.store.orderItems$;
groupedItems$: Observable<Array<{ type: string; items: DBHOrderItemListItemDTO[] }>> = this.orderItems$.pipe(
map((items) => {
const groups: Array<{ type: string; items: DBHOrderItemListItemDTO[] }> = [];
@@ -63,13 +67,15 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
}
return groups;
})
}),
);
fetching$ = this.store.fetchingOrder$;
fetchingOrder$ = this.store.fetchingOrder$;
fetchingItems$ = this.store.fetchingOrderItems$;
viewFetching$ = combineLatest([this.fetching$, this.fetchingItems$]).pipe(map(([fetching, fetchingItems]) => fetching || fetchingItems));
viewFetching$ = combineLatest([this.orderItems$, this.fetchingItems$]).pipe(
map(([orderItems, fetchingItems]) => orderItems?.length === 0 && fetchingItems),
);
selectedCompartmentInfo = this.store.selectedCompartmentInfo;
@@ -125,7 +131,7 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
compartmentInfo: item.compartmentInfo,
},
}).path,
{ queryParamsHandling: 'preserve' }
{ queryParamsHandling: 'preserve' },
);
}
}
@@ -148,20 +154,21 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
}
updateDate({ date, type }: { date: Date; type?: 'delivery' | 'pickup' | 'preferred' }) {
this.store.updateOrderItemSubsetLoading(true);
switch (type) {
case 'delivery': // vsl. Lieferdatum
this.store.orderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { estimatedShippingDate: date.toISOString() } })
this.store.patchOrderItemSubset({ item, changes: { estimatedShippingDate: date.toISOString() } }),
);
break;
case 'pickup': // Abholfrist
this.store.orderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { compartmentStop: date.toISOString() } })
this.store.patchOrderItemSubset({ item, changes: { compartmentStop: date.toISOString() } }),
);
break;
case 'preferred': // Zurücklegen bis
this.store.orderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { preferredPickUpDate: date.toISOString() } })
this.store.patchOrderItemSubset({ item, changes: { preferredPickUpDate: date.toISOString() } }),
);
break;
default:
@@ -186,7 +193,7 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
compartmentInfo: orderItem.compartmentInfo,
},
}).path,
{ queryParams: { buyerNumber: orderItem?.buyerNumber }, queryParamsHandling: 'merge' }
{ queryParams: { buyerNumber: orderItem?.buyerNumber }, queryParamsHandling: 'merge' },
);
}
@@ -202,7 +209,7 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
compartmentInfo: orderItem.compartmentInfo,
},
}).path,
{ queryParams: { orderItemSubsetId: orderItem.orderItemSubsetId }, queryParamsHandling: 'merge' }
{ queryParams: { orderItemSubsetId: orderItem.orderItemSubsetId }, queryParamsHandling: 'merge' },
);
}

View File

@@ -34,8 +34,7 @@
class="page-pickup-shelf-out-list__items-count inline-flex flex-row items-center pr-5 text-p3"
[class.mb-4]="primaryOutletActive$ | async"
>
{{ hits ??
0 }}
{{ hits ?? 0 }}
Titel
</div>
</div>
@@ -89,6 +88,9 @@
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
(click)="handleAction(action)"
matomoClickCategory="pickup-shelf-out-list"
matomoClickAction="click"
[matomoClickName]="action.label"
>
<ui-spinner [show]="(loadingFetchedActionButton$ | async) || (fetching$ | async)">{{ action.label }}</ui-spinner>
</button>

View File

@@ -30,6 +30,7 @@ import { ActionHandlerService } from '../../services/action-handler.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CacheService } from '@core/cache';
import { ScrollContainerDirective } from '@shared/directives/scroll-container';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pcikup-shelf-out-list',
@@ -50,6 +51,7 @@ import { ScrollContainerDirective } from '@shared/directives/scroll-container';
GroupByPipe,
UiSpinnerModule,
PickupShelfListItemLoaderComponent,
MatomoModule,
],
})
export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
@@ -80,7 +82,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
map(([filter, defaultFilter]) => {
const filterQueryParams = filter?.getQueryParams();
return !isEqual(filterQueryParams, Filter.create(defaultFilter).getQueryParams());
})
}),
);
fetching$: Observable<boolean> = this.store.fetchingList$;
@@ -120,9 +122,9 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
actions$ = combineLatest([this.list$, this.selectedIds$]).pipe(
map(([items, selectedIds]) =>
items?.find((item) => selectedIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId))
items?.find((item) => selectedIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId)),
),
map((item) => item?.actions?.filter((action) => this.selectionRules(action)))
map((item) => item?.actions?.filter((action) => this.selectionRules(action))),
);
loadingFetchedActionButton$ = new BehaviorSubject<boolean>(false);
@@ -135,7 +137,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
private _uiModal: UiModalService,
private _pickUpShelfOutNavigation: PickUpShelfOutNavigationService,
private _cache: CacheService,
private _router: Router
private _router: Router,
) {}
ngOnInit() {
@@ -190,7 +192,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
getSelectedItem$(item: DBHOrderItemListItemDTO) {
return this.store.selectedListItems$.pipe(
map((selectedListItems) => selectedListItems?.find((i) => i?.orderItemSubsetId === item?.orderItemSubsetId))
map((selectedListItems) => selectedListItems?.find((i) => i?.orderItemSubsetId === item?.orderItemSubsetId)),
);
}
@@ -204,7 +206,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
} else {
return item?.actions?.some((action) => this.selectionRules(action));
}
})
}),
);
}

View File

@@ -7,7 +7,7 @@ import { PickupShelfIOService, PickupShelfOutService } from '@domain/pickup-shel
import { GetNameForBreadcrumbData, GetPathForBreadcrumbData, PickupShelfBaseComponent } from '../pickup-shelf-base.component';
import { NavigationRoute, PickUpShelfOutNavigationService } from '@shared/services';
import { AsyncPipe } from '@angular/common';
import { DBHOrderItemListItemDTO, ListResponseArgsOfDBHOrderItemListItemDTO } from '@swagger/oms';
import { DBHOrderItemListItemDTO } from '@swagger/oms';
import { Observable, of } from 'rxjs';
import { take } from 'rxjs/operators';
import { provideActionHandlers } from '@core/command';
@@ -212,7 +212,7 @@ export class PickupShelfOutComponent extends PickupShelfBaseComponent {
*/
const filterQueryParams = this.listStore.filter.getQueryParams();
if (response.hits === 1 || this._hasSameOrderNumber(response)) {
if (response.hits === 1) {
const detailsPath = await this.getPathForDetail(response.result[0]).pipe(take(1)).toPromise();
await this.router.navigate(detailsPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...detailsPath.queryParams } });
} else if (response.hits > 1) {
@@ -224,10 +224,13 @@ export class PickupShelfOutComponent extends PickupShelfBaseComponent {
});
}
// Fix Ticket #4684 Navigate on Details if items contain same OrderNumber
private _hasSameOrderNumber(response: ListResponseArgsOfDBHOrderItemListItemDTO) {
if (response.hits === 0) return false;
const orderNumbers = new Set(response.result.map((item) => item.orderNumber));
return orderNumbers.size === 1;
}
// Ticket 4720 WA // Trefferliste wird übersprungen - Bei mehreren Bestellposten pro Bestellung soll IMMER auf Trefferliste navigiert werden
// Damit werden #4684 und #4688 überflüssig
// REMOVED: Fix Ticket #4684 Navigate on Details if items contain same OrderNumber
// private _hasSameOrderNumber(response: ListResponseArgsOfDBHOrderItemListItemDTO) {
// if (response.hits === 0) return false;
// const orderNumbers = new Set(response.result.map((item) => item.orderNumber));
// return orderNumbers.size === 1;
// }
}

View File

@@ -19,6 +19,9 @@
[routerLink]="customerDetailsRoute.path"
[queryParams]="customerDetailsRoute.queryParams"
[queryParamsHandling]="'merge'"
matomoClickCategory="pickup-shelf-details-header-nav-menu"
matomoClickAction="click"
matomoClickName="customer-details"
>Kundendetails</a
>
<a
@@ -27,6 +30,9 @@
[routerLink]="ordersRoute.path"
[queryParams]="ordersRoute.queryParams"
[queryParamsHandling]="'merge'"
matomoClickCategory="pickup-shelf-details-header-nav-menu"
matomoClickAction="click"
matomoClickName="customer-orders"
>Bestellungen</a
>
</shared-menu>

View File

@@ -8,6 +8,7 @@ import { IconComponent } from '@shared/components/icon';
import { SharedMenuModule } from '@shared/components/menu';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerInfoDTO } from '@swagger/crm';
import { MatomoModule } from 'ngx-matomo-client';
import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -23,7 +24,7 @@ export interface PickUpShelfDetailsHeaderNavMenuComponentState {
standalone: true,
host: { class: 'page-pickup-shelf-details-header-nav-menu' },
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CdkMenuModule, SharedMenuModule, IconComponent, RouterLink, NgIf, AsyncPipe],
imports: [CdkMenuModule, SharedMenuModule, IconComponent, RouterLink, NgIf, AsyncPipe, MatomoModule],
})
export class PickUpShelfDetailsHeaderNavMenuComponent extends ComponentStore<PickUpShelfDetailsHeaderNavMenuComponentState> {
@Input() set customer(value: CustomerInfoDTO) {
@@ -39,14 +40,14 @@ export class PickUpShelfDetailsHeaderNavMenuComponent extends ComponentStore<Pic
readonly showCustomerDetails$ = this.select((state) => state.showCustomerDetails);
ordersRoute$ = this.customer$.pipe(
map((customer) => !!customer && this._navigation.ordersRoute({ processId: Date.now(), customerId: customer?.id, customer }))
map((customer) => !!customer && this._navigation.ordersRoute({ processId: Date.now(), customerId: customer?.id, customer })),
);
customerDetailsRoute$ = combineLatest([this.showCustomerDetails$, this.customer$]).pipe(
map(
([showCustomerDetails, customer]) =>
showCustomerDetails && !!customer && this._navigation.detailsRoute({ processId: Date.now(), customerId: customer?.id, customer })
)
showCustomerDetails && !!customer && this._navigation.detailsRoute({ processId: Date.now(), customerId: customer?.id, customer }),
),
);
constructor(private _navigation: CustomerSearchNavigation) {

View File

@@ -1,11 +1,13 @@
<ng-container *ngIf="orderItem$ | async; let orderItem">
<div class="grid grid-flow-row gap-px-2">
<div class="bg-[#F5F7FA] flex flex-row justify-between items-center p-4 rounded-t">
<div class="grid grid-flow-col gap-[0.4375rem] items-center" *ngIf="features$ | async; let features; else: featureLoading">
<shared-icon *ngIf="features?.length > 0" [size]="24" icon="person"></shared-icon>
<div class="grid grid-flow-col gap-2 items-center font-bold text-p2" *ngFor="let feature of features">
{{ feature?.description }}
</div>
<div class="grid grid-flow-col gap-[0.4375rem] items-center" *ngIf="fetchingCustomerDone$ | async; else featureLoading">
<ng-container *ngIf="features$ | async; let features">
<shared-icon *ngIf="features?.length > 0" [size]="24" icon="person"></shared-icon>
<div class="grid grid-flow-col gap-2 items-center font-bold text-p2" *ngFor="let feature of features">
{{ feature?.description }}
</div>
</ng-container>
</div>
<button
@@ -13,6 +15,9 @@
class="page-pickup-shelf-details-header__edit-cta bg-transparent text-brand font-bold border-none text-p1"
*ngIf="editClick.observers.length"
(click)="editClick.emit(orderItem)"
matomoClickCategory="pickup-shelf-details-header"
matomoClickAction="click"
matomoClickName="edit"
>
Bearbeiten
</button>
@@ -74,6 +79,9 @@
[uiOverlayTrigger]="statusDropdown"
[disabled]="changeStatusDisabled$ | async"
#dropdown="uiOverlayTrigger"
matomoClickCategory="pickup-shelf-details-header"
matomoClickAction="click"
matomoClickName="status-dropdown"
>
<div class="mr-[0.375rem]">
{{ orderItem?.processingStatus | processingStatus }}
@@ -86,7 +94,14 @@
></shared-icon>
</button>
<ui-dropdown #statusDropdown yPosition="below" xPosition="after" [xOffset]="8">
<button uiDropdownItem *ngFor="let action of statusActions$ | async" (click)="handleActionClick(action)">
<button
uiDropdownItem
*ngFor="let action of statusActions$ | async"
(click)="handleActionClick(action)"
matomoClickCategory="pickup-shelf-details-header"
matomoClickAction="select"
[matomoClickName]="action.label"
>
{{ action.label }}
</button>
</ui-dropdown>
@@ -96,7 +111,12 @@
</div>
<div class="flex flex-row page-pickup-shelf-details-header__order-source" data-detail-id="Bestellkanal">
<div class="min-w-[9rem]">Bestellkanal</div>
<div class="flex flex-row font-bold">{{ order?.features?.orderSource }}</div>
<div class="flex flex-row font-bold">
<shared-skeleton-loader class="w-32" *ngIf="fetchingOrder$ | async; else orderSourceTmpl"></shared-skeleton-loader>
<ng-template #orderSourceTmpl>
{{ order()?.features?.orderSource }}
</ng-template>
</div>
</div>
<div
class="flex flex-row page-pickup-shelf-details-header__change-date justify-between"
@@ -137,29 +157,29 @@
data-detail-id="Benachrichtigung"
>
<div class="min-w-[9rem]">Benachrichtigung</div>
<div class="flex flex-row font-bold">{{ (notificationsChannel$ | async | notificationsChannel) || '-' }}</div>
<div class="flex flex-row font-bold">
<shared-skeleton-loader class="w-32" *ngIf="fetchingOrder$ | async; else notificationsChannelTpl"></shared-skeleton-loader>
<ng-template #notificationsChannelTpl>
{{ (notificationsChannel$ | async | notificationsChannel) || '-' }}
</ng-template>
</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #featureLoading>
<div class="fetch-wrapper">
<div class="fetching"></div>
<div class="fetching"></div>
<div class="fetching"></div>
</div>
</ng-template>
<ng-template #abholfrist>
<div class="min-w-[9rem]">Abholfrist</div>
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">
<div *ngIf="!(orderItemSubsetLoading$ | async); else featureLoading" class="flex flex-row font-bold">
<button
[uiOverlayTrigger]="deadlineDatepicker"
#deadlineDatepickerTrigger="uiOverlayTrigger"
[disabled]="!isKulturpass && (!!orderItem?.features?.paid || (changeDateDisabled$ | async))"
class="cta-pickup-deadline"
matomoClickCategory="pickup-shelf-details-header"
matomoClickAction="click"
matomoClickName="pickup-deadline"
>
<strong class="border-r border-[#AEB7C1] pr-4">
{{ orderItem?.pickUpDeadline | date: 'dd.MM.yy' }}
@@ -178,17 +198,19 @@
>
</ui-datepicker>
</div>
<ui-spinner *ngIf="changeDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
</ng-template>
<ng-template #preferredPickUpDate>
<div class="min-w-[9rem]">Zurücklegen bis</div>
<div *ngIf="!(changePreferredDateLoader$ | async)" class="flex flex-row font-bold">
<div *ngIf="!(orderItemSubsetLoading$ | async); else featureLoading" class="flex flex-row font-bold">
<button
[uiOverlayTrigger]="preferredPickUpDatePicker"
#preferredPickUpDatePickerTrigger="uiOverlayTrigger"
[disabled]="(!isKulturpass && !!orderItem?.features?.paid) || (changeDateDisabled$ | async)"
class="cta-pickup-preferred"
matomoClickCategory="pickup-shelf-details-header"
matomoClickAction="click"
matomoClickName="pickup-preferred"
>
<strong class="border-r border-[#AEB7C1] pr-4" *ngIf="findLatestPreferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
{{ pickUpDate | date: 'dd.MM.yy' }}
@@ -210,17 +232,19 @@
>
</ui-datepicker>
</div>
<ui-spinner *ngIf="changePreferredDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"> </ui-spinner>
</ng-template>
<ng-template #vslLieferdatum>
<div class="min-w-[9rem]">vsl. Lieferdatum</div>
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">
<div *ngIf="!(orderItemSubsetLoading$ | async); else featureLoading" class="flex flex-row font-bold">
<button
class="cta-datepicker"
[disabled]="changeDateDisabled$ | async"
[uiOverlayTrigger]="uiDatepicker"
#datepicker="uiOverlayTrigger"
matomoClickCategory="pickup-shelf-details-header"
matomoClickAction="click"
matomoClickName="vsl-lieferdatum"
>
<span class="border-r border-[#AEB7C1] pr-4">
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
@@ -239,6 +263,9 @@
>
</ui-datepicker>
</div>
<ui-spinner *ngIf="changeDateLoader$ | async; let loader" class="flex flex-row font-bold loader" [show]="loader"></ui-spinner>
</ng-template>
</ng-container>
<ng-template #featureLoading>
<shared-skeleton-loader class="w-64 h-6"></shared-skeleton-loader>
</ng-template>

View File

@@ -12,6 +12,9 @@ import { PickupShelfNotificationsChannelPipe } from '../pipes/notifications-chan
import { PickupShelfProcessingStatusPipe } from '../pipes/processing-status.pipe';
import { UiDatepickerModule } from '@ui/datepicker';
import { PickUpShelfDetailsHeaderNavMenuComponent } from '../pickup-shelf-details-header-nav-menu/pickup-shelf-details-header-nav-menu.component';
import { SkeletonLoaderComponent } from '@shared/components/loader';
import { toSignal } from '@angular/core/rxjs-interop';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pickup-shelf-details-header',
@@ -36,6 +39,8 @@ import { PickUpShelfDetailsHeaderNavMenuComponent } from '../pickup-shelf-detail
UiCommonModule,
UiDatepickerModule,
PickUpShelfDetailsHeaderNavMenuComponent,
SkeletonLoaderComponent,
MatomoModule,
],
})
export class PickUpShelfDetailsHeaderComponent {
@@ -50,13 +55,13 @@ export class PickUpShelfDetailsHeaderComponent {
@Output()
updateDate = new EventEmitter<{ date: Date; type?: 'delivery' | 'pickup' | 'preferred' }>();
get order$(): Observable<OrderDTO> {
return this._store.order$;
}
orderItemSubsetLoading$ = this._store.orderItemSubsetLoading$;
get order(): OrderDTO {
return this._store.order;
}
fetchingOrder$ = this._store.fetchingOrder$;
order$ = this._store.order$;
order = toSignal(this.order$);
findLatestPreferredPickUpDate$: Observable<Date> = this.order$.pipe(
withLatestFrom(this._store.selectedOrderItemIds$),
@@ -72,12 +77,12 @@ export class PickUpShelfDetailsHeaderComponent {
latestDate = new Date(
subsetItems?.reduce((a, b) => {
return new Date(a.data.preferredPickUpDate) > new Date(b.data.preferredPickUpDate) ? a : b;
})?.data?.preferredPickUpDate
})?.data?.preferredPickUpDate,
);
}
return latestDate;
})
}),
);
notificationsChannel$: Observable<NotificationChannel> = this.order$.pipe(map((order) => order?.notificationChannels ?? 0));
@@ -86,7 +91,7 @@ export class PickUpShelfDetailsHeaderComponent {
processId?: number;
get isKulturpass() {
return this.order?.features?.orderSource === 'KulturPass';
return this.order()?.features?.orderSource === 'KulturPass';
}
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
@@ -105,12 +110,14 @@ export class PickUpShelfDetailsHeaderComponent {
changeDateDisabled$ = this.changeStatusDisabled$;
fetchingCustomerDone$ = this._store.fetchingCustomer$.pipe(map((fetchingCustomer) => !fetchingCustomer));
customer$ = this._store.customer$;
features$ = this.customer$.pipe(
map((customer) => customer?.features || []),
map((features) => features.filter((f) => f.enabled && !!f.description)),
shareReplay()
shareReplay(),
);
statusActions$ = this.orderItem$.pipe(map((orderItem) => orderItem?.actions?.filter((action) => action.enabled === false)));
@@ -118,14 +125,17 @@ export class PickUpShelfDetailsHeaderComponent {
crudaUpdate$ = this.orderItem$.pipe(map((orederItem) => !!(orederItem?.cruda & 4)));
editButtonDisabled$ = combineLatest([this.changeStatusLoader$, this.crudaUpdate$]).pipe(
map(([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate)
map(([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate),
);
canEditStatus$ = combineLatest([this.statusActions$, this.crudaUpdate$]).pipe(
map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate)
map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate),
);
constructor(private dateAdapter: DateAdapter, private cdr: ChangeDetectorRef) {}
constructor(
private dateAdapter: DateAdapter,
private cdr: ChangeDetectorRef,
) {}
async handleActionClick(action?: KeyValueDTOOfStringAndString) {
this.changeStatusLoader$.next(true);

View File

@@ -32,7 +32,11 @@
</div>
<div class="page-pickup-shelf-details-item__item-container">
<div class="page-pickup-shelf-details-item__thumbnail">
<img [src]="orderItem.product?.ean | productImage" [alt]="orderItem.product?.name" />
<img
[productImageNavigation]="orderItem?.product?.ean"
[src]="orderItem.product?.ean | productImage"
[alt]="orderItem.product?.name"
/>
</div>
<div class="page-pickup-shelf-details-item__details">
<div class="flex flex-row justify-between items-start mb-[1.3125rem]">
@@ -46,16 +50,24 @@
<div>{{ orderItem.product?.name }}</div>
</h3>
<div class="history-wrapper flex flex-col items-end justify-center">
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">
Historie
</button>
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">Historie</button>
<input
*ngIf="selectable$ | async"
[ngModel]="selected$ | async"
(ngModelChange)="setSelected($event)"
(ngModelChange)="
setSelected($event);
tracker.trackEvent({
category: 'pickup-shelf-list-item',
action: 'select',
name: orderItem?.product?.name,
value: $event ? 1 : 0,
})
"
class="isa-select-bullet mt-4"
type="checkbox"
matomoTracker
#tracker="matomo"
/>
</div>
</div>
@@ -68,7 +80,12 @@
[showTrash]="false"
[range]="orderItem?.quantity"
[(ngModel)]="quantity"
(ngModelChange)="
tracker.trackEvent({ category: 'pickup-shelf-list-item', action: 'quantity', name: orderItem?.product?.name, value: $event })
"
[showSpinner]="false"
matomoTracker
#tracker="matomo"
>
</ui-quantity-dropdown>
<span class="overall-quantity">(von {{ orderItem?.overallQuantity }})</span>
@@ -253,6 +270,9 @@
type="submit"
*ngIf="specialCommentControl?.enabled && specialCommentControl.dirty"
(click)="saveSpecialComment()"
matomoClickCategory="pickup-shelf-details-item"
matomoClickAction="save"
matomoClickName="special-comment"
>
Speichern
</button>

View File

@@ -12,7 +12,7 @@ import {
inject,
} from '@angular/core';
import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { ProductImageModule } from '@cdn/product-image';
import { NavigateOnClickDirective, ProductImageModule } from '@cdn/product-image';
import { DBHOrderItemListItemDTO, OrderDTO, ReceiptDTO } from '@swagger/oms';
import { UiCommonModule } from '@ui/common';
import { UiTooltipModule } from '@ui/tooltip';
@@ -25,6 +25,7 @@ import { Subject, combineLatest } from 'rxjs';
import { PickupShelfDetailsStore } from '../../store';
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { NotificationTypePipe } from './notification-type.pipe';
import { MatomoModule } from 'ngx-matomo-client';
export interface PickUpShelfDetailsItemComponentState {
orderItem?: DBHOrderItemListItemDTO;
@@ -57,6 +58,8 @@ export interface PickUpShelfDetailsItemComponentState {
IconModule,
UiQuantityDropdownModule,
NotificationTypePipe,
NavigateOnClickDirective,
MatomoModule,
],
})
export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfDetailsItemComponentState> implements OnInit {
@@ -103,7 +106,7 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
readonly orderItem$ = this.select((s) => s.orderItem);
emailNotificationDates$ = this.orderItem$.pipe(
switchMap((orderItem) => this._store.getEmailNotificationDate$(orderItem?.orderItemSubsetId))
switchMap((orderItem) => this._store.getEmailNotificationDate$(orderItem?.orderItemSubsetId)),
);
hasEmailNotification$ = this.emailNotificationDates$.pipe(map((dates) => dates?.length > 0));
@@ -113,7 +116,7 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
hasSmsNotification$ = this.smsNotificationDates$.pipe(map((dates) => dates?.length > 0));
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1)
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1),
);
get quantity() {
@@ -140,7 +143,7 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
}
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedOrderItemIds$]).pipe(
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId))
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId)),
);
@Output()
@@ -155,7 +158,7 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
}
readonly selectable$ = combineLatest([this._store.orderItems$, this._store.fetchPartial$]).pipe(
map(([orderItems, fetchPartial]) => orderItems.length > 1 && this.isItemSelectable && fetchPartial)
map(([orderItems, fetchPartial]) => orderItems.length > 1 && this.isItemSelectable && fetchPartial),
);
get receipts() {
@@ -174,7 +177,7 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
more$ = this.select((s) => s.more);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
expanded: boolean = false;

View File

@@ -5,6 +5,10 @@
[class.selected]="tag === (selected$ | async) && !inputFocus.focused"
*ngFor="let tag of defaultTags"
(click)="setCompartmentInfo(tag)"
matomoClickCategory="pickup-shelf-details-tags"
matomoClickAction="click"
[matomoClickName]="tag"
[matomoClickValue]="(selected$ | async) === tag ? 0 : 1"
>
{{ tag }}
</button>
@@ -23,6 +27,10 @@
placeholder="..."
[size]="controlSize$ | async"
maxlength="15"
[matomoTracker]="['blur']"
matomoCategory="pickup-shelf-details-tags"
matomoAction="blur"
matomoName="tag-input"
/>
</button>
</div>

View File

@@ -2,6 +2,7 @@ import { AsyncPipe, NgFor } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
import { MatomoModule } from 'ngx-matomo-client';
import { BehaviorSubject, Subscription } from 'rxjs';
import { first, map } from 'rxjs/operators';
@@ -12,7 +13,7 @@ import { first, map } from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page-pickup-shelf-details-tags' },
standalone: true,
imports: [NgFor, UiCommonModule, FormsModule, AsyncPipe],
imports: [NgFor, UiCommonModule, FormsModule, AsyncPipe, MatomoModule],
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => PickUpShelfDetailsTagsComponent), multi: true }],
})
export class PickUpShelfDetailsTagsComponent implements OnInit, OnDestroy, ControlValueAccessor {
@@ -59,7 +60,7 @@ export class PickUpShelfDetailsTagsComponent implements OnInit, OnDestroy, Contr
ngOnInit() {
this.subscription.add(
this.selected$.pipe(map((selected) => (this.defaultTags.includes(selected) ? '' : selected))).subscribe(this.inputValueSubject)
this.selected$.pipe(map((selected) => (this.defaultTags.includes(selected) ? '' : selected))).subscribe(this.inputValueSubject),
);
}

View File

@@ -3,7 +3,7 @@
[routerLink]="itemDetailsLink"
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
queryParamsHandling="preserve"
(click)="isDesktopLarge ? scrollIntoView() : ''"
(click)="onDetailsClick()"
>
<div
class="page-pickup-shelf-list-item__item-grid-container"
@@ -15,6 +15,7 @@
class="page-pickup-shelf-list-item__item-image w-[3.125rem] max-h-[4.9375rem]"
loading="lazy"
*ngIf="item?.product?.ean | productImage; let productImage"
[productImageNavigation]="item?.product?.ean"
[src]="productImage"
[alt]="item?.product?.name"
/>
@@ -103,9 +104,14 @@
<input
(click)="$event.stopPropagation()"
[ngModel]="selectedItem"
(ngModelChange)="setSelected()"
(ngModelChange)="
setSelected();
tracker.trackEvent({ category: 'pickup-shelf-list-item', action: 'select', name: item?.product?.name, value: $event ? 1 : 0 })
"
class="isa-select-bullet"
type="checkbox"
matomoTracker
#tracker="matomo"
/>
</div>

View File

@@ -1,7 +1,7 @@
import { CurrencyPipe, DatePipe, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, ElementRef, Input, inject } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { ProductImageModule } from '@cdn/product-image';
import { NavigateOnClickDirective, ProductImageModule } from '@cdn/product-image';
import { EnvironmentService } from '@core/environment';
import { IconModule } from '@shared/components/icon';
import { DBHOrderItemListItemDTO } from '@swagger/oms';
@@ -10,6 +10,8 @@ import { PickupShelfProcessingStatusPipe } from '../pipes/processing-status.pipe
import { FormsModule } from '@angular/forms';
import { PickupShelfStore } from '../../store';
import { map } from 'rxjs/operators';
import { CacheService } from '@core/cache';
import { MatomoModule } from 'ngx-matomo-client';
@Component({
selector: 'page-pickup-shelf-list-item',
@@ -30,10 +32,14 @@ import { map } from 'rxjs/operators';
ProductImageModule,
UiCommonModule,
PickupShelfProcessingStatusPipe,
NavigateOnClickDirective,
MatomoModule,
],
providers: [PickupShelfProcessingStatusPipe],
})
export class PickUpShelfListItemComponent {
private cache = inject(CacheService);
store = inject(PickupShelfStore);
@Input() item: DBHOrderItemListItemDTO;
@@ -76,15 +82,54 @@ export class PickUpShelfListItemComponent {
}
selected$ = this.store.selectedListItems$.pipe(
map((selectedListItems) => selectedListItems?.find((item) => item?.orderItemSubsetId === this.item?.orderItemSubsetId))
map((selectedListItems) => selectedListItems?.find((item) => item?.orderItemSubsetId === this.item?.orderItemSubsetId)),
);
constructor(
private _elRef: ElementRef,
private _environment: EnvironmentService,
private _processingStatusPipe: PickupShelfProcessingStatusPipe
private _processingStatusPipe: PickupShelfProcessingStatusPipe,
) {}
onDetailsClick() {
if (this.isDesktopLarge) {
this.scrollIntoView();
}
if (!this.hasOrderItemInCache()) {
this.addOrderItemIntoCache();
}
}
hasOrderItemInCache() {
const items =
this.cache.get<DBHOrderItemListItemDTO[]>({
name: 'orderItems',
orderId: this.item.orderId,
compartmentCode: this.item.compartmentCode,
compartmentInfo: this.item.compartmentInfo,
orderItemProcessingStatus: this.item.processingStatus,
store: 'PickupShelfDetailsStore',
}) ?? [];
return items.some((i) => i.orderItemSubsetId === this.item.orderItemSubsetId);
}
addOrderItemIntoCache() {
this.cache.set(
{
name: 'orderItems',
orderId: this.item.orderId,
compartmentCode: this.item.compartmentCode,
compartmentInfo: this.item.compartmentInfo,
orderItemProcessingStatus: this.item.processingStatus,
store: 'PickupShelfDetailsStore',
},
[this.item],
{ persist: false, ttl: 1000 },
);
}
scrollIntoView() {
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

View File

@@ -89,6 +89,8 @@ export const selectSelectedOrderItemIds = (s: PickupShelfDetailsState) => s.sele
export const selectPreviousSelectedOrderItemSubsetId = (s: PickupShelfDetailsState) => s.previousSelectedOrderItemSubsetId;
export const selectOrderItemSubsetLoading = (s: PickupShelfDetailsState) => s.orderItemSubsetLoading;
export const selectDisableHeaderStatusDropdown = (s: PickupShelfDetailsState) => s.disableHeaderStatusDropdown;
export const selectedOrderItems = (s: PickupShelfDetailsState) => {

View File

@@ -34,6 +34,7 @@ export interface PickupShelfDetailsState {
fetchingOrderItemSubsetTasks: { [orderItemSubsetId: number]: boolean };
orderItemSubsetTasks: { [orderItemSubsetId: number]: OrderItemSubsetTaskListItemDTO[] };
orderItemSubsetLoading?: boolean;
coverOrderItems?: DBHOrderItemListItemDTO[];
fetchingCoverOrderItems?: boolean;

View File

@@ -1,6 +1,6 @@
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { PickupShelfDetailsState } from './pickup-shelf-details.state';
import { Observable } from 'rxjs';
import { Observable, combineLatest } from 'rxjs';
import {
DBHOrderItemListItemDTO,
EntityDTOContainerOfOrderItemSubsetDTO,
@@ -18,7 +18,7 @@ import {
} from '@swagger/oms';
import { PickupShelfIOService, PickupShelfService } from '@domain/pickup-shelf';
import { Injectable, inject } from '@angular/core';
import { debounceTime, delayWhen, distinctUntilChanged, filter, mergeMap, switchMap, take, tap, withLatestFrom } from 'rxjs/operators';
import { delayWhen, filter, map, mergeMap, switchMap, take, tap, withLatestFrom } from 'rxjs/operators';
import { UiModalService } from '@ui/modal';
import { CrmCustomerService } from '@domain/crm';
import * as Selectors from './pickup-shelf-details.selectors';
@@ -27,7 +27,6 @@ import { CacheService } from '@core/cache';
import { RunCheckTrigger } from '../trigger';
import { DomainReceiptService } from '@domain/oms';
import { PickupShelfStore } from './pickup-shelf.store';
import { isEqual } from 'lodash';
@Injectable()
export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsState> {
@@ -57,10 +56,20 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
return this.get(Selectors.selectFetchingOrder);
}
orderItems$ = this.select(Selectors.selectOrderItems);
// orderItems$ = this.select(Selectors.selectOrderItems);
// get orderItems() {
// return this.get(Selectors.selectOrderItems);
// }
orderItems$ = combineLatest([
this.select(Selectors.selectOrderItems),
this.select((s) => s).pipe(switchMap((s) => this._listStore.itemsForPreview$(s))),
]).pipe(map(([orderItems, itemsForPreview]) => (orderItems?.length ? orderItems : itemsForPreview)));
get orderItems() {
return this.get(Selectors.selectOrderItems);
const orderItems = this.get(Selectors.selectOrderItems);
return orderItems?.length ? orderItems : this.get((s) => this._listStore.itemsForPreview(s));
}
fetchingOrderItems$ = this.select(Selectors.selectFetchingOrderItems);
@@ -236,6 +245,12 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
return this.get(Selectors.selectCoverOrderItems);
}
orderItemSubsetLoading$ = this.select(Selectors.selectOrderItemSubsetLoading);
get orderItemSubsetLoading() {
return this.get(Selectors.selectOrderItemSubsetLoading);
}
// Wird benöitgt um das Status Dropdown in der Details Header Komponente zu disablen wenn eine Action gehandled wird
disableHeaderStatusDropdown$ = this.select(Selectors.selectDisableHeaderStatusDropdown);
@@ -340,6 +355,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
fetchOrder = this.effect((trigger$: Observable<{ orderId: number }>) =>
trigger$.pipe(
tap(({ orderId }) => this.beforeFetchOrder(orderId)),
// delay(10000),
switchMap(({ orderId }) =>
this._pickupShelfService.getOrderByOrderId(orderId).pipe(tapResponse(this.fetchOrderSuccess, this.fetchOrderFailed))
)
@@ -347,7 +363,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
);
private beforeFetchOrder = (orderId) => {
const order = this._cacheService.get<OrderDTO>({ name: 'order', orderId, store: 'PickupShelfDetailsStore' });
const order = this._cacheService.get<OrderDTO>({ name: 'order', orderId, store: 'PickupShelfDetailsStore' }) ?? { id: orderId };
const customer = this._cacheService.get<CustomerInfoDTO>({
name: 'customer',
orderId,
@@ -480,7 +496,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
fetchCustomer = this.effect((trigger$: Observable<{ buyerNumber: string }>) =>
trigger$.pipe(
filter(({ buyerNumber }) => this.customer?.customerNumber !== buyerNumber),
tap(this.beforeFetchCustomer),
tap(() => this.beforeFetchCustomer()),
switchMap(({ buyerNumber }) =>
this._customerService.getCustomers(buyerNumber).pipe(tapResponse(this.fetchCustomerSuccess, this.fetchCustomerFailed))
)
@@ -493,7 +509,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
orderId: this.order.id,
store: 'PickupShelfDetailsStore',
});
this.patchState({ fetchingCustomer: true, customer });
this.patchState({ fetchingCustomer: !customer, customer: customer });
};
fetchCustomerSuccess = (res: ListResponseArgsOfCustomerInfoDTO) => {
@@ -502,6 +518,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
// check if response contains exactly one customer
if (customers.length > 1) {
this._modal.error('Fehler beim Laden des Kunden', new Error('Es wurde mehr als ein Kunde gefunden.'));
this.patchState({ fetchingCustomer: false });
return;
}
@@ -518,6 +535,7 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
// also check if the order is a KulturPass order, then there may be no customer
if (!isKulturpass && customer?.customerNumber !== this.order.buyer.buyerNumber) {
this._modal.error('Fehler beim Laden des Kunden', new Error('Der Kunde ist nicht der Bestellung zugeordnet.'));
this.patchState({ fetchingCustomer: false });
return;
}
@@ -567,6 +585,8 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
if (res.result.preferredPickUpDate) {
this.patchPreferredPickUpDateOnOrderSubsetItemInState({ item, newPreferredPickUpDate: res.result.preferredPickUpDate });
}
this.updateOrderItemSubsetLoading(false);
};
private patchOrderItemSubsetError = (err: any) => {
@@ -711,6 +731,10 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
this.patchState({ fetchingCoverOrderItems: true });
};
updateOrderItemSubsetLoading(orderItemSubsetLoading: boolean) {
this.patchState({ orderItemSubsetLoading });
}
private fetchCoverOrderItemsDone = (res: ListResponseArgsOfDBHOrderItemListItemDTO) => {
this.patchState({ fetchingCoverOrderItems: false, coverOrderItems: res.result });
};

View File

@@ -1,6 +1,13 @@
import { Filter } from '@shared/components/filter';
import { PickupShelfState } from './pickup-shelf.state';
import { isEmpty } from 'lodash';
import { PickupShelfDetailsState } from './pickup-shelf-details.state';
import {
selectDisplayedCompartmentCode,
selectDisplayedCompartmentInfo,
selectDisplayedOrderItemProcessingStatus,
selectOrder,
} from './pickup-shelf-details.selectors';
export function selectProcessId(state: PickupShelfState) {
return state.processId;
@@ -64,3 +71,27 @@ export function selectListHits(state: PickupShelfState) {
export function selectSelectedListItems(state: PickupShelfState) {
return state.selectedListItems ?? [];
}
export const selectItemsForPreview = (detailsState: PickupShelfDetailsState) => (state: PickupShelfState) => {
const orderId = selectOrder(detailsState)?.id;
let items = state.list.filter((item) => item.orderId === orderId);
const processingStatus = selectDisplayedOrderItemProcessingStatus(detailsState);
if (processingStatus) {
items = items?.filter((oi) => oi.processingStatus === processingStatus);
}
const compartmentCode = selectDisplayedCompartmentCode(detailsState);
if (compartmentCode) {
items = items?.filter((oi) => oi.compartmentCode === compartmentCode);
const compartmentInfo = selectDisplayedCompartmentInfo(detailsState);
if (compartmentInfo) {
items = items?.filter((oi) => oi.compartmentInfo === compartmentInfo);
}
}
return items;
};

View File

@@ -28,6 +28,7 @@ import * as Selectors from './pickup-shelf.selectors';
import { Observable, Subject, combineLatest } from 'rxjs';
import { CacheService } from '@core/cache';
import { Filter } from '@shared/components/filter';
import { PickupShelfDetailsState } from './pickup-shelf-details.state';
@Injectable()
export class PickupShelfStore extends ComponentStore<PickupShelfState> implements OnStoreInit {
@@ -104,6 +105,10 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
return this.get(Selectors.selectSelectedListItems);
}
itemsForPreview$ = (state: PickupShelfDetailsState) => this.select(Selectors.selectItemsForPreview(state));
itemsForPreview = (state: PickupShelfDetailsState) => this.get(Selectors.selectItemsForPreview(state));
constructor() {
// Nicht entfernen sonst wird der Store nicht initialisiert
super({});

View File

@@ -16,7 +16,7 @@ import { AddProductModalData } from './add-product-modal.data';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddProductModalComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
get processId() {
return this._config.get('process.ids.remission');

View File

@@ -11,6 +11,7 @@ import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { UiDropdownModule } from '@ui/dropdown';
import { UiIconModule } from '@ui/icon';
import { SharedProductGroupPipe } from '@shared/pipes/product-group';
@NgModule({
imports: [
@@ -25,6 +26,7 @@ import { UiIconModule } from '@ui/icon';
UiTooltipModule,
UiDropdownModule,
UiIconModule,
SharedProductGroupPipe,
],
exports: [AddProductModalComponent],
declarations: [AddProductModalComponent],

View File

@@ -1,2 +1 @@
export * from './product-group.pipe';
export * from './remission-pipe.module';

View File

@@ -1,11 +1,10 @@
import { NgModule } from '@angular/core';
import { AssortmentPipe } from './assortment.pipe';
import { ProductGroupPipe } from './product-group.pipe';
import { ShortReceiptNumberPipe } from './short-receipt-number.pipe';
import { SupplierPipe } from './supplier.pipe';
@NgModule({
declarations: [ProductGroupPipe, AssortmentPipe, ShortReceiptNumberPipe, SupplierPipe],
exports: [ProductGroupPipe, AssortmentPipe, ShortReceiptNumberPipe, SupplierPipe],
declarations: [AssortmentPipe, ShortReceiptNumberPipe, SupplierPipe],
exports: [AssortmentPipe, ShortReceiptNumberPipe, SupplierPipe],
})
export class RemissionPipeModule {}

View File

@@ -16,7 +16,7 @@ export class RemissionFilterComponent implements OnDestroy {
filter$ = this.store.filter$.pipe(map((filter) => UiFilter.create(filter)));
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
constructor(public store: RemissionListComponentStore) {}

View File

@@ -21,16 +21,24 @@
</div>
<div class="product-data-grid grid gap-6 items-start">
<div>
<img class="max-w-full max-h-full shadow rounded" src="{{ item.ean | productImage }}" alt="item.dto.product.name" />
<img
[productImageNavigation]="item?.ean"
class="max-w-full max-h-full shadow rounded"
src="{{ item.ean | productImage }}"
alt="item.dto.product.name"
/>
</div>
<div class="grid grid-flow-row gap-1 w-48">
<div
class="overflow-hidden overflow-ellipsis whitespace-nowrap"
*ngIf="!!item.format && !!item.formatDetail && item.format !== 'UNKNOWN'"
>
<img class="inline" src="/assets/images/Icon_{{ item.dto.product.format }}.svg" [alt]="item.formatDetail" />
<span class="ml-1 font-bold format-detail">{{ item.formatDetail }}</span>
<div class="overflow-hidden overflow-ellipsis whitespace-nowrap" *ngIf="formatAvailable">
<img
*ngIf="formatIconAvailable"
class="inline mr-1"
[src]="formatIconUrl"
[alt]="item.formatDetail"
(error)="formatIconAvailable = false"
/>
<span class="font-bold format-detail">{{ item.formatDetail }}</span>
</div>
<div class="font-bold ean">
{{ item.ean }}

View File

@@ -10,7 +10,7 @@ import {
} from '@swagger/remi';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { mapFromReturnItemDTO, mapFromReturnSuggestionDTO } from 'apps/domain/remission/src/lib/mappings';
import { BehaviorSubject, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { first, takeUntil } from 'rxjs/operators';
import { AddProductToShippingDocumentModalComponent } from '../../modals/add-product-to-shipping-document-modal/add-product-to-shipping-document-modal.component';
import { RemissionListComponentStore } from '../remission-list.component-store';
@@ -23,7 +23,7 @@ import { RemissionListComponent } from '../remission-list.component';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RemissionListItemComponent implements OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
@Input()
item: RemissionListItem;
@@ -55,6 +55,16 @@ export class RemissionListItemComponent implements OnDestroy {
loading$ = this._listComponent.remittingItem$.asObservable();
get formatIconUrl() {
return `/assets/images/Icon_${this.item.dto.product.format}.svg`;
}
get formatAvailable() {
return !!this.item.format && !!this.item.formatDetail && this.item.format !== 'UNKNOWN';
}
formatIconAvailable: boolean = true;
constructor(
private _modal: UiModalService,
private _remissionService: DomainRemissionService,

View File

@@ -2,12 +2,13 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RemissionListItemComponent } from './remission-list-item.component';
import { ProductImageModule } from '@cdn/product-image';
import { NavigateOnClickDirective, ProductImageModule } from '@cdn/product-image';
import { RemissionPipeModule } from '../../pipes';
import { UiTooltipModule } from '@ui/tooltip';
import { UiCommonModule } from '@ui/common';
import { AddProductToShippingDocumentModalModule } from '../../modals/add-product-to-shipping-document-modal/add-product-to-shipping-document-modal.module';
import { UiSpinnerModule } from '@ui/spinner';
import { SharedProductGroupPipe } from '@shared/pipes/product-group';
@NgModule({
imports: [
@@ -18,6 +19,8 @@ import { UiSpinnerModule } from '@ui/spinner';
RemissionPipeModule,
UiTooltipModule,
AddProductToShippingDocumentModalModule,
SharedProductGroupPipe,
NavigateOnClickDirective,
],
exports: [RemissionListItemComponent],
declarations: [RemissionListItemComponent],

View File

@@ -37,7 +37,7 @@ export class RemissionListComponent implements OnInit, OnDestroy {
@ViewChild('scrollContainer', { static: true })
scrollContainer: CdkVirtualScrollViewport;
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
get processId() {
return this._config.get('process.ids.remission');

View File

@@ -16,7 +16,7 @@ import { ToasterService } from '@shared/shell';
providers: [RemissionListComponentStore],
})
export class RemissionComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
get processId() {
return this._config.get('process.ids.remission');

View File

@@ -5,9 +5,10 @@ import { UiSpinnerModule } from '@ui/spinner';
import { RemissionPipeModule } from '../../pipes';
import { SharedShippingDocumentDetailsItemComponent } from './shipping-document-details-item/shipping-document-details-item.component';
import { SharedShippingDocumentDetailsComponent } from './shipping-document-details.component';
import { SharedProductGroupPipe } from '@shared/pipes/product-group';
@NgModule({
imports: [CommonModule, RemissionPipeModule, ProductImageModule, UiSpinnerModule],
imports: [CommonModule, RemissionPipeModule, ProductImageModule, UiSpinnerModule, SharedProductGroupPipe],
exports: [SharedShippingDocumentDetailsComponent, SharedShippingDocumentDetailsItemComponent],
declarations: [SharedShippingDocumentDetailsComponent, SharedShippingDocumentDetailsItemComponent],
providers: [],

View File

@@ -111,7 +111,7 @@ export class TaskInfoComponent implements OnChanges {
indicatorColor$ = this.info$.pipe(map((info) => this.domainTaskCalendarService.getProcessingStatusColorCode(info)));
private noteAdded$ = new Subject();
private noteAdded$ = new Subject<void>();
notes$ = combineLatest([this.info$, this.noteAdded$.pipe(startWith([undefined]))]).pipe(
switchMap(([info]) => this.domainTaskCalendarService.getComments({ infoId: info.id })),

View File

@@ -17,7 +17,7 @@ import { TaskCalendarStore } from '../../task-calendar.store';
exportAs: 'taskSearchbar',
})
export class TaskSearchbarComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
control = new UntypedFormControl('', [Validators.minLength(3)]);

View File

@@ -15,7 +15,7 @@ import { TaskCalendarStore } from '../../task-calendar.store';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskCalendarFilterComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
@Output() exitFilter = new EventEmitter<void>();
filter$ = new BehaviorSubject<UiFilter>(undefined);

View File

@@ -16,7 +16,7 @@ import { ApplicationService } from '@core/application';
providers: [TaskCalendarStore],
})
export class PageTaskCalendarComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;

View File

@@ -18,7 +18,7 @@ import { TaskCalendarStore } from '../../task-calendar.store';
providers: [DatePipe],
})
export class TaskSearchComponent implements OnInit, OnDestroy, AfterViewInit {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
private _taskCalendarKey = this._config.get('process.ids.taskCalendar');
today = this.dateAdapter.today();

View File

@@ -95,7 +95,7 @@ export class BranchSelectorComponent implements OnInit, OnDestroy, AfterViewInit
}
private _value: BranchDTO;
private _onDestroy$ = new Subject<boolean>();
private _onDestroy$ = new Subject<void>();
@Output() valueChange = new EventEmitter<BranchDTO>();

View File

@@ -11,15 +11,19 @@
[scanner]="scanner"
>
<ui-autocomplete *ngIf="autocompleteProvider">
<button *ngFor="let item of autocompleteResults$ | async" [uiAutocompleteItem]="item.query">
<button
*ngFor="let item of autocompleteResults$ | async"
[uiAutocompleteItem]="item.query"
matomoClickAction="select"
matomoClickCategory="autocomplete"
[matomoClickName]="item.display"
>
{{ item.display }}
</button>
</ui-autocomplete>
</shared-searchbox>
<ng-container *ngIf="showDescription && uiInput?.description">
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">
i
</button>
<button [uiOverlayTrigger]="tooltipContent" #tooltip="uiOverlayTrigger" class="info-tooltip-button" type="button">i</button>
<ui-tooltip #tooltipContent yPosition="above" xPosition="after" [yOffset]="-16">
{{ uiInput.description }}
</ui-tooltip>

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