Compare commits

...

109 Commits

Author SHA1 Message Date
Nino
9ef911a5a5 #4525 Improved Scroll Position Handling in Goods In List and Remission Preview Page 2024-01-04 10:12:59 +01:00
Nino
7f8f48f393 Updated tsconfig 2024-01-03 11:54:03 +01:00
Nino
0fe0c5242d Merge branch 'release/3.0' into develop 2024-01-03 11:52:25 +01:00
Nino
d208bdaf97 Added Package Inspection Class Name in Details Page for Page Objects Targetting 2024-01-02 15:08:19 +01:00
Nino Righi
dc80df4ad4 Merged PR 1712: #4524 Page Article Search Improved Order by Filter Handling
#4524 Page Article Search Improved Order by Filter Handling
2023-12-29 16:12:53 +00:00
Nino Righi
3020609682 Merged PR 1711: #4545 #4548 Fix HSC Breadcrumb and Multiple Processes bug
#4545 #4548 Fix HSC Breadcrumb and Multiple Processes bug
2023-12-29 15:22:26 +00:00
Nino Righi
5c3e1ed2ad Merged PR 1710: #4544 Fix Proveded Store correctly, removed take from filter and cancleSearch...
#4544 Fix Proveded Store correctly, removed take from filter and cancleSearchRequests on process change
2023-12-29 15:12:51 +00:00
Nino Righi
e832feebc5 Merged PR 1709: #4545 Fix Missing Main Breadcrumb
#4545 Fix Missing Main Breadcrumb
2023-12-28 15:10:44 +00:00
Nino Righi
08580d782d Merged PR 1708: #4546 HSC Customer Orders Fixed Navigation Link Active Styling
#4546 HSC Customer Orders Fixed Navigation Link Active Styling
2023-12-28 15:01:40 +00:00
Nino Righi
2d07556341 Merged PR 1707: #4533 Fixed Breadcrumb and Searchbox after Scan
#4533 Fixed Breadcrumb and Searchbox after Scan
2023-12-28 12:23:48 +00:00
Nino Righi
9ad1256019 Merged PR 1706: #4543 Styling Fix Checkout Review for multiple Destinations per OrderType
#4543 Styling Fix Checkout Review for multiple Destinations per OrderType
2023-12-27 16:00:23 +00:00
Nino Righi
250002f057 Merged PR 1705: #4516 Fix Also Show Details Items Group in Pick Up Shelf In
#4516 Fix Also Show Details Items Group in Pick Up Shelf In
2023-12-27 15:59:11 +00:00
Nino Righi
0973b01bf0 Merged PR 1704: #4539 Fix Skip Location Change Filter
#4539 Fix Skip Location Change Filter
2023-12-27 14:39:15 +00:00
Nino Righi
d5254cc150 Merged PR 1703: #4541 Added Errorhandling to Customer Search Store
#4541 Added Errorhandling to Customer Search Store
2023-12-27 08:09:29 +00:00
Nino Righi
adc5a5a280 Merged PR 1702: #4516 WA Implementation of orderType Groups
#4516 WA Implementation of orderType Groups
2023-12-22 16:13:52 +00:00
Nino Righi
2ff033ea55 Merged PR 1701: #4539 Hotfix AHF Open Filter Page does not trigger search request
#4539 Hotfix AHF Open Filter Page does not trigger search request
2023-12-21 17:23:08 +00:00
Nino Righi
cbaac8ed9a Merged PR 1700: #4537 Relocated Component Store provision
#4537 Relocated Component Store provision
2023-12-21 17:19:35 +00:00
Lorenz Hilpert
f0b653fd0f Merged PR 1699: #4533 Breadcrumb wird falsch dargestellt
#4533 Breadcrumb wird falsch dargestellt
2023-12-20 16:17:39 +00:00
Lorenz Hilpert
14eba6e5ea Merged PR 1698: #4485 Kundendaten werden übernommen wenn welche vorhanden sind
#4485 Kundendaten werden übernommen wenn welche vorhanden sind
2023-12-20 14:55:04 +00:00
Lorenz Hilpert
803a8e316c Skip UnitTest for some libs 2023-12-19 17:33:19 +01:00
Nino Righi
83cab7796e Merged PR 1697: #4530 Notification Batch Messages for each type
#4530 Notification Batch Messages for each type

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

(cherry picked from commit f3cb6236a5)
2023-12-19 17:14:03 +01:00
Lorenz Hilpert
b28bb165d4 Merged PR 1696: #4532 Mitarbeiter klickt auf Einbuchen und erreicht den Tätigkeitskalender
#4532 Mitarbeiter klickt auf Einbuchen und erreicht den Tätigkeitskalender
2023-12-19 13:35:58 +00:00
Nino Righi
5073693fc2 Merged PR 1697: #4530 Notification Batch Messages for each type
#4530 Notification Batch Messages for each type
2023-12-19 13:34:22 +00:00
Nino Righi
f3cb6236a5 Merged PR 1695: #4523 AHF, WA Added other notification types to Email Notification Badge
#4523 AHF, WA Added other notification types to Email Notification Badge
2023-12-15 16:03:34 +00:00
Lorenz Hilpert
4ab9890313 #4485 #4500 Kundenkartenkonto anlage Verhalten and Suche angeglichen 2023-12-15 14:34:24 +01:00
Nino Righi
32d8d81f53 Merged PR 1694: #4526 Hotfix Pickup Shelf Edit show Erneut Senden CTA
#4526 Hotfix Pickup Shelf Edit show Erneut Senden CTA
2023-12-15 10:12:51 +00:00
Nino
0361aa63ff Merge Develop into Release/3.0, corrected itemSize in page catalog list 2023-12-12 16:52:03 +01:00
Nino
2f95c23910 Merge branch 'develop' into release/3.0 2023-12-12 16:35:35 +01:00
Nino Righi
a8cd6ce844 Merged PR 1693: #4522 Pickup Shelf List Added margin-bottom as gap between item groups, remov...
#4522 Pickup Shelf List Added margin-bottom as gap between item groups, removed unused code
2023-12-12 14:18:07 +00:00
Lorenz Hilpert
3bba23cc76 Merged PR 1692: #4380 Abholung als Standard wenn möglich und keine Auswahl getroffen wurde
#4380 Abholung als Standard wenn möglich und keine Auswahl getroffen wurde
2023-12-12 12:43:13 +00:00
Nino Righi
e25f176a7b Merged PR 1691: #4521 Fix Check Page Catalog List if List got already fetched completely
#4521 Fix Check Page Catalog List if List got already fetched completely
2023-12-12 09:39:22 +00:00
Nino Righi
a169d2a4e9 Merged PR 1690: #4518 Fix Pickup Shelf In List additionally Fetch Items after queryParams change
#4518 Fix Pickup Shelf In List additionally Fetch Items after queryParams change
2023-12-11 15:55:32 +00:00
Nino Righi
6a9caa432e Merged PR 1689: #4519 #4520 Page Catalog Search Breadcrumb and Navigation fixes
#4519 #4520 Page Catalog Search Breadcrumb and Navigation fixes
2023-12-11 09:23:35 +00:00
Nino Righi
389948c077 Merged PR 1688: #4510 Fix Time Format in Customer Details
#4510 Fix Time Format in Customer Details
2023-12-08 09:30:29 +00:00
Nino Righi
e7724ed8b9 Merged PR 1687: #4509 Implemented Loading Spinner on Pickup Shelf Details Pages
#4509 Implemented Loading Spinner on Pickup Shelf Details Pages
2023-12-07 10:03:39 +00:00
Nino Righi
c7f1b27fdf Merged PR 1686: #4508 Pickup Shelf In Clear Cover Items after Routing to new Details page
#4508 Pickup Shelf In Clear Cover Items after Routing to new Details page
2023-12-06 15:18:55 +00:00
Lorenz Hilpert
135f0255b8 IPad2 PDP Styling 2023-12-06 16:18:06 +01:00
Nino Righi
65a7aa569d Merged PR 1685: Scroll Position Fix After Process Change
Scroll Position Fix After Process Change
2023-12-05 18:14:32 +00:00
Nino Righi
211eaa6175 Merged PR 1684: Page Catalog Result List - Removed maxBufferSize, Updated ScrollPosition Handling
Page Catalog Result List - Removed maxBufferSize, Updated ScrollPosition Handling
2023-12-05 16:59:23 +00:00
Lorenz Hilpert
8097c6ad9e Merge branch 'performance' into develop 2023-12-05 10:48:17 +01:00
Lorenz Hilpert
b0d76b01d7 Merge branch 'release/3.0' into develop 2023-12-05 10:47:34 +01:00
Lorenz Hilpert
626fd0081f Merge branch 'develop' into release/3.0 2023-12-05 10:47:00 +01:00
Lorenz Hilpert
362fca74bc Warenausgbae list IPad 2 2023-12-04 16:29:14 +01:00
Lorenz Hilpert
b8f0a29f79 IPad 2 Scrolling 2023-12-04 16:22:39 +01:00
Lorenz Hilpert
f54400f00d Merge branch 'develop' into performance 2023-12-04 14:23:30 +01:00
Lorenz Hilpert
54094695b1 #4505 RD // Einbuchen - Bearbeiten Seite wird mal in Split Screen Ansicht mal ohne angezeigt 2023-12-04 13:27:43 +01:00
Nino Righi
1e3e9588da Merged PR 1683: #4501 #4502 Fixed by reverting Changes from #4490
#4501 #4502 Fixed by reverting Changes from #4490
2023-12-04 11:43:07 +00:00
Nino
f04705b659 Merge branch 'release/3.0' into develop 2023-12-01 17:04:02 +01:00
Nino
c22672fad0 Fixed Styling and Layout Issues 2023-12-01 13:07:59 +01:00
Nino
59673a47db Tablet size fix, Navigate After SearchCompleted on Page Catalog and Page Customer Orders 2023-11-30 18:24:32 +01:00
Nino
e56ea0bd4e MaxBufferSize Update Result List Page Catalog 2023-11-30 18:15:58 +01:00
Nino Righi
f8c4d4a842 Merged PR 1682: #4416 RD Checkout, WA, WE, Notifications Component Changes
#4416 RD Checkout, WA, WE, Notifications Component Changes
2023-11-30 16:06:37 +00:00
Lorenz Hilpert
c4dd9214a3 Splitscreen side outlet rendern nur wenn nötig 2023-11-30 16:55:08 +01:00
Nino Righi
4d74b3a89e Merged PR 1679: #4491 Mark Shelf Children Sides Active based on view param
#4491 Mark Shelf Children Sides Active based on view param
2023-11-29 15:31:35 +00:00
Nino Righi
b0b3fd40ce Merged PR 1681: #4490 Improvement added Filter parameter orderitemprocessingstatus on details...
#4490 Improvement added Filter parameter orderitemprocessingstatus on details page request
2023-11-29 15:04:58 +00:00
Lorenz Hilpert
3404c930c5 #4485 - anlage von Kundenkartenkonto - Datenübernahme 2023-11-29 16:04:18 +01:00
Nino Righi
abcd940ed3 Merged PR 1678: #4493 Fix Breadcrumb Navigation if comming from other List
#4493 Fix Breadcrumb Navigation if comming from other List
2023-11-29 10:45:51 +00:00
Nino Righi
c1756942b2 Merged PR 1680: #4492 Added Navigation based On View after certain Actions
#4492 Added Navigation based On View after certain Actions
2023-11-29 10:45:14 +00:00
Lorenz Hilpert
ea4d036066 Merge branch 'develop' into release/3.0 2023-11-24 09:51:21 +01:00
Lorenz Hilpert
101a34bd3f #4484 RD // Online Konto anlegen via "Kundensuche | Keine Suchergebnisse" wirft Fehlermeldung 2023-11-23 18:06:49 +01:00
Lorenz Hilpert
99bad149cb attribute für e2e tests 2023-11-23 13:57:55 +01:00
Lorenz Hilpert
a0bff7164c #4481 RD // Warenkorb - Klick auf "Speichern" bei Benachrichtigung "Email" oder "SMS" enabled "Bestellen"-Button nicht 2023-11-22 15:30:07 +01:00
Lorenz Hilpert
0c4a4130b9 #4488 RD // TK - Artikel-Link führt auf Dashboard 2023-11-21 15:58:38 +01:00
Lorenz Hilpert
8dd1211729 #4487 RD // Abholfach - ändern des vsl. Lieferdatums und "zurückgelegt bis"-Datum wirft Fehler 2023-11-21 10:42:14 +01:00
Lorenz Hilpert
a2f1b8b624 #4478 RD // Abholfach - Routing löst Suche aus 2023-11-20 14:22:32 +01:00
Lorenz Hilpert
98a331ffe5 #4482 RD // Abholfach - Split-Screen raus bei "Bearbeiten" + ""Historie" 2023-11-20 13:38:11 +01:00
Lorenz Hilpert
c0f97c9bae Add doNotTrack option to article search 2023-11-10 18:52:23 +01:00
Lorenz Hilpert
960ffa165f Merge branch 'develop' into release/3.0 2023-11-10 18:33:13 +01:00
Lorenz Hilpert
6bdfbe2eff Refactor breadcrumb filtering in side menu
component
2023-11-10 18:32:56 +01:00
Lorenz Hilpert
80342e61ac #4470 Updated icon names in icons.json 2023-11-10 16:31:27 +01:00
Lorenz Hilpert
6bf3894e4d Add data attributes to pickup shelf list item
component HTML
2023-11-10 15:07:11 +01:00
Lorenz Hilpert
aab29838bf #4470 Add new customer order types to icons and label
colors
2023-11-10 14:35:38 +01:00
Lorenz Hilpert
856ca5651e #4475 Refactor remission-list.component.html button
styles
2023-11-10 14:31:57 +01:00
Lorenz Hilpert
a7d4b8d7fb #4273 Refactor navigation logic in search results and
product card components
2023-11-10 14:09:01 +01:00
Lorenz Hilpert
62d260473c #4474 Refactor clearFilter method to use main_qs from
filter query params
2023-11-09 11:50:02 +01:00
Lorenz Hilpert
bde52a2526 Merge branch 'release/3.0' into develop 2023-11-09 11:39:41 +01:00
Lorenz Hilpert
6243b03cfc #4466 Revert changes to fix display issue in
PickupShelfDetailsBaseComponent
2023-11-08 16:39:03 +01:00
Lorenz Hilpert
d24841800e #4468 Refactor setSelectedOrderItemQuantity to use
updater
2023-11-08 13:39:31 +01:00
Nino Righi
f60628c769 Merged PR 1677: #4467 Fix Display Price even if Price is 0
#4467 Fix Display Price even if Price is 0
2023-11-08 11:55:55 +00:00
Nino Righi
034f697da5 Merged PR 1676: #4462 Added focusSearchbox additionally on side nav click
#4462 Added focusSearchbox additionally on side nav click
2023-11-08 11:55:13 +00:00
Lorenz Hilpert
ec9f80767b Merge branch 'release/3.0' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/3.0 2023-11-08 11:05:32 +01:00
Lorenz Hilpert
a5e569cf05 #4451 #4463 Add name method to PickupShelfInService and
PickupShelfOutService
2023-11-08 11:04:58 +01:00
Nino Righi
b62259f9b4 Merged PR 1675: #4457 Fix Filter Routing on Close
#4457 Fix Filter Routing on Close
2023-11-08 09:42:24 +00:00
Nino Righi
95baeaa8a8 Merged PR 1674: #4462 Cursor Should Select Searchbox on Initial Navigation
#4462 Cursor Should Select Searchbox on Initial Navigation
2023-11-07 16:49:56 +00:00
Nino Righi
a5b9115a91 Merged PR 1673: #4459 Fix Reorder Modal handling after Closing Modal without changes
#4459 Fix Reorder Modal handling after Closing Modal without changes
2023-11-07 16:03:23 +00:00
Lorenz Hilpert
1885c58d86 #4464 Add setSelectedOrderItemQuantity method to update
selected order item quantity
2023-11-07 17:01:57 +01:00
Lorenz Hilpert
add55a47d6 Merge branches 'release/3.0' and 'release/3.0' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/3.0 2023-11-07 16:29:37 +01:00
Lorenz Hilpert
129f49a9ee #4461 Add sharedRegexRouterLinkActive directive to
side-menu component
2023-11-07 16:29:18 +01:00
Nino Righi
9560eb7ad6 Merged PR 1671: #4458 Fixed Process Naming
#4458 Fixed Process Naming
2023-11-07 15:09:18 +00:00
Nino Righi
772ba29a8e Merged PR 1672: #4457 Fixed routing after Filter close
#4457 Fixed routing after Filter close
2023-11-07 15:08:31 +00:00
Nino Righi
8ac8f6cc1f Merged PR 1670: #4459 Removed Side Outlet after REORDER completed
#4459 Removed Side Outlet after REORDER completed
2023-11-07 12:23:46 +00:00
Lorenz Hilpert
a0f496475c #4453 Add customerInfoDTO to createCustomerRoute 2023-11-06 15:21:36 +01:00
Lorenz Hilpert
8979a388ee #4454 Fix button placement and remove unnecessary
sorting in main actions selector
2023-11-06 14:27:08 +01:00
Lorenz Hilpert
8b9a209c49 #4450 Add RunCheckTrigger to
PickupShelfInDetailsComponent and
PickupShelfOutDetailsComponent
2023-11-06 14:07:27 +01:00
Lorenz Hilpert
f4c3e3ceee #4452 Reset selected compartment info in
PickupShelfDetailsBaseComponent
2023-11-06 13:55:40 +01:00
Lorenz Hilpert
5ca8a83f25 Merge branch 'release/3.0' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/3.0 2023-11-03 15:19:10 +01:00
Lorenz Hilpert
006011885f #4448 Add "Create new customer" link to search views 2023-11-03 15:18:50 +01:00
Nino Righi
9c9e061f6d Merged PR 1669: #4431 Adjusted Process Tab Logic for Customer Order Area
#4431 Adjusted Process Tab Logic for Customer Order Area
2023-11-03 09:13:10 +00:00
Lorenz Hilpert
c9782a7d29 Merge branch 'develop' into release/3.0 2023-10-30 09:42:49 +01:00
Nino
fba465d573 Merge branch 'develop' into release/3.0 2023-10-25 16:17:18 +02:00
Lorenz Hilpert
5ab4456040 Merge branch 'develop' into release/3.0 2023-10-24 14:21:59 +02:00
Nino
fc45efb4af Merge branch 'develop' into release/3.0 2023-10-20 16:25:57 +02:00
Nino
bb81b8f826 Merge branch 'develop' into release/3.0 2023-10-19 13:26:35 +02:00
Lorenz Hilpert
486e2e5a28 Fix Build Error 2023-10-16 15:53:22 +02:00
Lorenz Hilpert
86d3b4e3f5 Merge branch 'develop' into release/3.0 2023-10-16 15:51:20 +02:00
Lorenz Hilpert
b440ddbe82 Merge branch 'hotfix/4270-4269-Archiv-Artikel'
(cherry picked from commit f4df6e8799)
2023-08-30 14:38:54 +02:00
Lorenz Hilpert
d6e0d92132 (cherry picked from commit d09b5b1ce7) 2023-08-24 20:18:19 +02:00
Lorenz Hilpert
b16ffa4352 Merge branch 'develop' into release/3.0 2023-08-11 15:34:45 +02:00
116 changed files with 15220 additions and 1695 deletions

View File

@@ -8,6 +8,10 @@ import { Filter } from '@shared/components/filter';
export class PickupShelfInService extends PickupShelfIOService {
private _abholfachService = inject(AbholfachService);
name() {
return 'PickupShelfInService';
}
getQuerySettings() {
return this._abholfachService.AbholfachWareneingangQuerySettings();
}

View File

@@ -11,6 +11,8 @@ import { Observable } from 'rxjs';
@Injectable()
export abstract class PickupShelfIOService {
abstract name(): string;
abstract getQuerySettings(): Observable<ResponseArgsOfQuerySettingsDTO>;
abstract search(queryToken: QueryTokenDTO): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;

View File

@@ -8,6 +8,10 @@ import { Filter } from '@shared/components/filter';
export class PickupShelfOutService extends PickupShelfIOService {
private _abholfachService = inject(AbholfachService);
name() {
return 'PickupShelfOutService';
}
getQuerySettings() {
return this._abholfachService.AbholfachWarenausgabeQuerySettings();
}

View File

@@ -15,11 +15,12 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
.toPromise();
if (!process) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
await this._applicationService.createProcess({
id: +route.params.processId,
type: 'customer-order',
type: 'cart',
section: 'customer',
name: `Kundenbestellungen`,
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
}
@@ -46,6 +47,18 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
// Ticket #3272 Bei Klick auf "+" bzw. neuen Prozess hinzufügen soll der neue Tab immer die höchste Nummer haben (wie aktuell im Produktiv)
// ----------------------------------------------------------------------------------------------------------------------------------------
// for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
// if (!processNumbers.find((number) => number === missingNumber)) {
// return missingNumber;
// }
// }
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -415,6 +415,10 @@
{
"name": "isa-box-out",
"alias": "Versandbestellung (oder gemischt)"
},
{
"name": "package-variant-closed",
"alias": "Bestellung ohne Konto"
},{
"name": "person",
"alias": "Onlinekonto"

View File

@@ -1,3 +1,386 @@
<div class="page-article-details__wrapper">
<div #detailsContainer class="page-article-details__container px-5" *ngIf="store.item$ | async; let item">
<div class="page-article-details__product-details mb-3">
<div class="page-article-details__product-bookmark justify-self-end">
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
</ng-container>
<ng-template #notAvailable>
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
</ng-template>
</ui-tooltip>
</button>
</div>
<div *ngIf="showSubscriptionBadge$ | async">
<button
[uiOverlayTrigger]="subscribtionTooltip"
class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]"
>
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
</button>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
>Artikel ist ein Fortsetzungsartikel,<br />
Artikel muss über eine Aboabteilung<br />
bestellt werden.
</ui-tooltip>
</div>
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
Dieser Artikel befindet sich im Prämienkatalog.
</ui-tooltip>
</button>
</div>
</div>
<div class="page-article-details__product-image-recessions flex flex-col items-center">
<div class="page-article-details__product-image">
<button class="border-none outline-none bg-transparent relative" (click)="showImages()">
<img
class="max-h-[19.6875rem] max-w-[12.1875rem] rounded"
(load)="loadImage()"
[src]="item.imageId | productImage: 195:315:true"
alt="product image"
/>
<ui-icon
class="absolute text-[#A7B9CB] inline-block bottom-[0.875rem] right-[1.125rem]"
*ngIf="imageLoaded$ | async"
icon="search_add"
size="25px"
></ui-icon>
</button>
</div>
<button
(click)="showReviews()"
class="page-article-details__product-recessions flex flex-col mt-2 items-center bg-transparent border-none outline-none"
*ngIf="item.reviews?.length > 0"
>
<ui-stars [rating]="store.reviewRating$ | async"></ui-stars>
<div class="text-p2 text-[#0556B4] font-bold">{{ item.reviews.length }} Rezensionen</div>
</button>
</div>
<div class="page-article-details__product-contributors">
<a
*ngFor="let contributor of contributors$ | async; let last = last"
class="text-[#0556B4] font-semibold no-underline text-p2"
[routerLink]="resultsPath"
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
>
{{ contributor }}{{ last ? '' : ';' }}
</a>
</div>
<div class="page-article-details__product-print justify-self-end" [class.mt-4]="isBadgeVisible$ | async">
<button class="bg-transparent text-brand font-bold text-lg outline-none border-none p-0" (click)="print()">Drucken</button>
</div>
<div class="page-article-details__product-title text-h3 font-bold mb-6">
{{ item.product?.name }}
</div>
<div class="page-article-details__product-misc flex flex-col mb-4">
<div
class="page-article-details__product-format flex items-center font-bold text-p3"
*ngIf="item?.product?.format && item?.product?.formatDetail"
>
<img
*ngIf="item?.product?.format !== '--'"
class="flex mr-2 h-[1.125rem]"
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
[alt]="item.product?.formatDetail"
/>
{{ item.product?.formatDetail }}
</div>
<div class="page-article-details__product-volume" *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
<div class="page-article-details__product-publication">{{ publicationDate$ | async }}</div>
</div>
<div class="page-article-details__product-price-info flex flex-col mb-4 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' }}
</div>
<div *ngIf="price$ | async; let price" class="page-article-details__product-price-bound self-end">
{{ price?.vat?.vatType | vat: (priceMaintained$ | async) }}
</div>
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
{{ promotionPoints }} Lesepunkte
</div>
</div>
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
<div class="page-article-details__product-manufacturer" data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
<div class="page-article-details__product-language" *ngIf="item?.product?.locale" data-name="product-language">
{{ item?.product?.locale }}
</div>
</div>
<div class="page-article-details__product-stock flex justify-end items-center">
<div class="h-5 w-16 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
<button
class="flex flex-row py-4 pl-4"
type="button"
[uiOverlayTrigger]="tooltip"
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
(click)="showTooltip()"
*ngIf="!(store.fetchingTakeAwayAvailability$ | async)"
>
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
<ui-icon class="mr-2 mb-1" icon="home" size="15px"></ui-icon>
<span class="font-bold text-p3">{{ takeAwayAvailability.inStock || 0 }}x</span>
</ng-container>
</button>
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
{{ stockTooltipText$ | async }}
</ui-tooltip>
</div>
<div class="page-article-details__product-ean-specs flex flex-col">
<div class="page-article-details__product-ean" data-name="product-ean">{{ item.product?.ean }}</div>
<div class="page-article-details__product-specs">
<ng-container *ngIf="item?.specs?.length > 0">
{{ (item?.specs)[0]?.value }}
</ng-container>
</div>
</div>
<div class="page-article-details__product-availabilities flex flex-row items-center justify-end mt-4">
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"
></div>
<ng-template #showAvailabilityTakeAwayIcon>
<div
*ngIf="store.isTakeAwayAvailabilityAvailable$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center"
>
<ui-icon class="mx-1" icon="shopping_bag" size="18px"> </ui-icon>
</div>
</ng-template>
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"
></div>
<ng-template #showAvailabilityPickUpIcon>
<div
#uiOverlayTrigger="uiOverlayTrigger"
[uiOverlayTrigger]="orderDeadlineTooltip"
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
class="page-article-details__product-pick-up-availability w-[2.25rem] h-[2.25rem] cursor-pointer bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
[class.tooltip-active]="uiOverlayTrigger.opened"
>
<shared-icon icon="isa-box-out" [size]="24"></shared-icon>
</div>
<ui-tooltip [warning]="true" yPosition="above" xPosition="after" [yOffset]="-12" #orderDeadlineTooltip [closeable]="true">
<b>{{ (store.pickUpAvailability$ | async)?.orderDeadline | orderDeadline }}</b>
</ui-tooltip>
</ng-template>
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"
></div>
<ng-template #showAvailabilityDeliveryIcon>
<div
*ngIf="showDeliveryTruck$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
>
<ui-icon class="-mb-[0.3125rem] -mt-[0.3125rem] mx-1" icon="truck" size="30px"></ui-icon>
</div>
</ng-template>
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
></div>
<ng-template #showAvailabilityDeliveryB2BIcon>
<div
*ngIf="showDeliveryB2BTruck$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
>
<ui-icon class="-mb-[0.625rem] -mt-[0.625rem] mx-1" icon="truck_b2b" size="30px"> </ui-icon>
</div>
</ng-template>
<span *ngIf="store.isDownload$ | async" class="flex flex-row items-center">
<div class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3">
<ui-icon class="mx-1" icon="download" size="18px"></ui-icon>
</div>
</span>
</div>
<div class="page-article-details__shelf-ssc">
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
<div class="w-52 h-5 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
<div class="text-right" *ngIf="store.sscText$ | async; let sscText">
{{ sscText }}
</div>
</ng-container>
</div>
<div class="page-article-details__shelfinfo text-right" *ngIf="store.isDownload$ | async">
<ng-container
*ngIf="
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
else stockInfos
"
>
<span data-name="compartment">
{{ (item?.stockInfos)[0]?.compartment }}
</span>
/
<br />
<span data-name="shelf-info-label">
{{ (item?.shelfInfos)[0]?.label }}
</span>
</ng-container>
<ng-template #stockInfos>
<ng-container *ngIf="item?.stockInfos && (item?.stockInfos)[0]?.compartment; else shelfInfos">
{{ (item?.stockInfos)[0]?.compartment }}
</ng-container>
</ng-template>
<ng-template #shelfInfos>
<ng-container *ngIf="item?.shelfInfos && (item?.shelfInfos)[0]?.label">
<span data-name="shelf-info-label">{{ (item?.shelfInfos)[0]?.label }}</span>
</ng-container>
</ng-template>
</div>
<div class="page-article-details__shelfinfo text-right" *ngIf="!(store.isDownload$ | async)">
<ng-container
*ngIf="
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
else stockInfos2
"
>
<span data-name="compartment">{{ (item?.stockInfos)[0]?.compartment }}</span>
/
<br />
<span data-name="shelf-info-label">{{ (item?.shelfInfos)[0]?.label }}</span>
</ng-container>
<ng-template #stockInfos2>
<ng-container *ngIf="item?.stockInfos && (item?.stockInfos)[0]?.compartment; else shelfInfos2">
{{ (item?.stockInfos)[0]?.compartment }}
</ng-container>
</ng-template>
<ng-template #shelfInfos2>
<ng-container *ngIf="item?.shelfInfos && (item?.shelfInfos)[0]?.label">
{{ (item?.shelfInfos)[0]?.label }}
</ng-container>
</ng-template>
</div>
</div>
</div>
<div class="page-article-details__product-formats-container mt-3" *ngIf="item.family?.length > 0">
<hr class="bg-[#E6EFF9] border-t-2" />
<div class="pt-3">
<div class="page-article-details__product-formats">
<span class="mr-2">Auch verfügbar als</span>
<ui-slider [scrollDistance]="250">
<a
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
*ngFor="let format of item.family"
[routerLink]="getDetailsPath(format.product.ean)"
queryParamsHandling="preserve"
>
<span class="flex items-center">
<img
class="mr-2"
*ngIf="!!format.product?.format"
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
alt="format icon"
/>
{{ format.product?.formatDetail }}
<span class="ml-1">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
</span>
</a>
</ui-slider>
</div>
</div>
</div>
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
<div #description class="page-article-details__product-description flex flex-col flex-grow mb-6" *ngIf="item.texts?.length > 0">
<page-article-details-text class="block box-border" [text]="item.texts[0]"> </page-article-details-text>
<div class="box-border">
<button
class="font-bold flex flex-row text-[#0556B4] items-center mt-2"
*ngIf="!showMore && item?.texts?.length > 1"
(click)="showMore = !showMore"
>
Mehr <ui-icon class="ml-2" size="15px" icon="arrow"></ui-icon>
</button>
</div>
<div *ngIf="showMore" class="page-article-details__product-description-text flex flex-col whitespace-pre-line break-words box-border">
<span *ngFor="let text of item.texts | slice: 1">
<h3 class="my-4 text-p2 font-bold">{{ text.label }}</h3>
{{ text.value }}
</span>
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" (click)="showMore = !showMore">
<ui-icon class="transform ml-0 mr-2 rotate-180" size="15px" icon="arrow"></ui-icon> Weniger
</button>
<button class="page-article-details__scroll-top-cta" (click)="scrollTop(description)">
<ui-icon class="text-[#0556B4]" icon="arrow" size="20px"></ui-icon>
</button>
</div>
<div class="h-28 box-border"></div>
</div>
</div>
<div class="page-article-details__product-recommendations relative">
<button
*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"
>
<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" />
</button>
</div>
</div>
<div
class="page-article-details__actions absolute bottom-32 left-1/2 -translate-x-1/2 whitespace-nowrap"
*ngIf="store.item$ | async; let item"
>
<button
*ngIf="!(store.isDownload$ | async)"
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-[1.875rem]"
(click)="showAvailabilities()"
>
Bestände in anderen Filialen
</button>
<button
class="text-white bg-brand border-brand font-bold text-lg px-[1.375rem] py-4 rounded-full border-none no-underline"
(click)="showPurchasingModal()"
[disabled]="!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')"
>
In den Warenkorb
</button>
</div>
<div class="page-article-details__recommendations-overlay absolute top-0 inset-x-0" @slideYAnimation *ngIf="showRecommendations">
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
</div>
<!--
<ng-container *ngIf="!showRecommendations">
<div #detailsContainer class="page-article-details__container px-5 relative">
<ng-container *ngIf="store.item$ | async; let item">
@@ -385,4 +768,4 @@
<div class="page-article-details__recommendations-overlay absolute rounded-t" @slideYAnimation *ngIf="showRecommendations">
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
</div>
</div> -->

View File

@@ -1,9 +1,13 @@
:host {
@apply box-border block h-split-screen-tablet desktop-small:h-split-screen-desktop;
@apply box-border block relative;
}
.page-article-details__wrapper {
@apply grid grid-rows-[1fr_auto] h-split-screen-tablet max-h-split-screen-tablet desktop-small:h-split-screen-desktop desktop-small:max-h-split-screen-desktop;
}
.page-article-details__container {
@apply h-full w-full overflow-y-scroll overflow-hidden bg-white rounded shadow-card flex flex-col;
@apply overflow-scroll bg-white rounded shadow-card flex flex-col;
}
.page-article-details__product-details {

View File

@@ -43,26 +43,6 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
this.removeBreadcrumbs(processId);
this.addOrUpdateBreadcrumbs(processId, queryParams);
});
this._articleSearch.searchCompleted
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(async ([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
await this._navigationService
.getArticleDetailsPath({
processId,
itemId: item.id,
extras: { queryParams: params },
})
.navigate();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}
});
}
cleanupQueryParams(params: Record<string, string> = {}) {

View File

@@ -6,12 +6,10 @@ import { ArticleSearchComponent } from './article-search.component';
import { SearchResultsModule } from './search-results/search-results.module';
import { SearchMainModule } from './search-main/search-main.module';
import { SearchFilterModule } from './search-filter/search-filter.module';
import { ArticleSearchService } from './article-search.store';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule],
exports: [ArticleSearchComponent],
declarations: [ArticleSearchComponent],
providers: [ArticleSearchService],
})
export class ArticleSearchModule {}

View File

@@ -16,7 +16,6 @@ export interface ArticleSearchState {
hits: number;
selectedBranch: BranchDTO;
selectedItemIds: number[];
scrollPosition: number;
defaultSettings?: UISettingsDTO;
}
@@ -44,12 +43,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
return this.get((s) => s.items);
}
scrollPosition$ = this.select((s) => s.scrollPosition);
get scrollPosition() {
return this.get((s) => s.scrollPosition);
}
selectedBranch$ = this.select((s) => s.selectedBranch);
get selectedBranch() {
@@ -92,7 +85,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
searchState: '',
selectedItemIds: [],
selectedBranch: undefined,
scrollPosition: 0,
});
this.setDefaultFilter();
}
@@ -113,10 +105,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
this.patchState({ selectedBranch });
}
setScrollPosition(scrollPosition: number) {
this.patchState({ scrollPosition });
}
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
const defaultSettings = await this.catalog.getSettings().toPromise();
@@ -160,7 +148,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
}
}
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean }>) =>
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean; doNotTrack?: boolean }>) =>
options$.pipe(
tap((options) => {
this.searchStarted.next({ clear: options?.clear });
@@ -178,6 +166,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
take: 25,
friendlyName: this.friendlyName,
stockId: selectedBranch?.id,
doNotTrack: options?.doNotTrack,
}).pipe(
tapResponse(
(res) => {

View File

@@ -88,6 +88,26 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
await this.articleSearch.setDefaultFilter(queryParams);
});
this.articleSearch.searchCompleted
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(async ([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
await this._navigationService
.getArticleDetailsPath({
processId,
itemId: item.id,
extras: { queryParams: params },
})
.navigate();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}
});
}
ngOnDestroy(): void {

View File

@@ -4,11 +4,12 @@ import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainCatalogService } from '@domain/catalog';
import { combineLatest, NEVER, Subscription } from 'rxjs';
import { catchError, debounceTime, first, switchMap, map } from 'rxjs/operators';
import { catchError, debounceTime, first, switchMap, map, tap } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { isEqual } from 'lodash';
import { EnvironmentService } from '@core/environment';
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
import { ProductCatalogNavigationService } from '@shared/services';
@Component({
selector: 'page-article-search-main',
@@ -17,7 +18,10 @@ import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/fi
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchMainComponent implements OnInit, OnDestroy {
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(catchError(() => NEVER));
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(
map((history) => history.filter((h) => !!h.friendlyName)),
catchError(() => NEVER)
);
fetching$ = this.searchService.fetching$;
@@ -49,7 +53,8 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private application: ApplicationService,
private breadcrumb: BreadcrumbService,
private _environment: EnvironmentService
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService
) {}
ngOnInit() {
@@ -77,6 +82,14 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
}
this.removeResultsAndDetailsBreadcrumbs(processId);
// #4519 und #4520 Durch den Performance Umbau, wird auf allen größen kleiner als der Splitscreen die Article-Search Komponente nicht geladen
// Stattdessen werden die Komponenten search-main und search-filter geladen. Einige Funktionen von Article-Search müssen trotzdem aufgerufen werden
if (!this._environment.matchDesktopLarge()) {
this.resetFilter(queryParams);
this.removeCheckoutBreadcrumb(processId);
this.addOrUpdateBreadcrumbs(processId, queryParams);
}
})
);
@@ -160,6 +173,27 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
});
}
resetFilter(queryParams: Record<string, string>) {
if (Object.keys(queryParams).length === 0) {
this.searchService.resetFilter();
}
}
async removeCheckoutBreadcrumb(processId: number) {
this.breadcrumb.removeBreadcrumbsByKeyAndTags(processId, ['checkout']);
}
async addOrUpdateBreadcrumbs(processId: number, queryParams: Record<string, string>) {
await this.breadcrumb.addBreadcrumbIfNotExists({
key: processId,
name: 'Artikelsuche',
path: this._navigationService.getArticleSearchBasePath(processId).path,
params: queryParams,
tags: ['catalog', 'main'],
section: 'customer',
});
}
async removeResultsAndDetailsBreadcrumbs(processId: number) {
const resultsCrumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'results']).pipe(first()).toPromise();
const detailCrumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'details']).pipe(first()).toPromise();

View File

@@ -1,10 +1,7 @@
<a
<div
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded"
[class.page-search-result-item__item-card-primary]="primaryOutletActive"
[routerLink]="detailsPath"
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
queryParamsHandling="preserve"
(click)="isDesktopLarge ? scrollIntoView() : ''"
[class.active]="isActive"
>
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
<img
@@ -122,4 +119,4 @@
</ng-container>
</div>
</div>
</a>
</div>

View File

@@ -1,5 +1,5 @@
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding, ElementRef } from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding } from '@angular/core';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
@@ -54,6 +54,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
@Input()
primaryOutletActive?: boolean = false;
@Input() isActive: boolean;
@Output()
selectedChange = new EventEmitter<ItemDTO>();
@@ -82,11 +84,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
return this._environment.matchDesktopLarge();
}
get detailsPath() {
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id })
.path;
}
get resultsPath() {
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId).path;
}
@@ -141,7 +138,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
private _availability: DomainAvailabilityService,
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService,
private _elRef: ElementRef<HTMLElement>,
private _store: Store
) {
super({
@@ -150,10 +146,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
});
}
scrollIntoView() {
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
setSelected() {
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });

View File

@@ -40,35 +40,34 @@
<div class="page-search-results__order-by mb-[0.125rem]" [class.page-search-results__order-by-primary]="primaryOutletActive$ | async">
<shared-order-by-filter
[orderBy]="(filter$ | async)?.orderBy"
(selectedOrderByChange)="search({ clear: true, orderBy: true }); updateBreadcrumbs()"
*ngIf="filter$ | async; let filter"
[orderBy]="filter?.orderBy"
(selectedOrderByChange)="search({ filter, clear: true, orderBy: true }); updateBreadcrumbs()"
>
</shared-order-by-filter>
</div>
<div class="h-full relative">
<cdk-virtual-scroll-viewport
#scrollContainer
class="product-list h-full"
[itemSize]="(primaryOutletActive$ | async) ? 98 : 181"
minBufferPx="1200"
[maxBufferPx]="maxBufferCdkScrollContainer$ | async"
(scrolledIndexChange)="scrolledIndexChange($event)"
>
<search-result-item
class="page-search-results__result-item"
[class.page-search-results__result-item-primary]="primaryOutletActive$ | async"
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
(selectedChange)="addToCart($event)"
[selected]="isSelected(item)"
[selectable]="isSelectable(item)"
[item]="item"
[primaryOutletActive]="primaryOutletActive$ | async"
></search-result-item>
<page-search-result-item-loading
[primaryOutletActive]="primaryOutletActive$ | async"
*ngIf="fetching$ | async"
></page-search-result-item-loading>
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="103 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"
routerLinkActive
#rla="routerLinkActive"
queryParamsHandling="preserve"
(click)="scrollToItem(i)"
>
<search-result-item
class="page-search-results__result-item page-search-results__result-item-primary"
(selectedChange)="addToCart($event)"
[selected]="isSelected(item)"
[selectable]="isSelectable(item)"
[item]="item"
[primaryOutletActive]="true"
[isActive]="rla.isActive"
></search-result-item>
</a>
<page-search-result-item-loading [primaryOutletActive]="true" *ngIf="fetching$ | async"></page-search-result-item-loading>
</cdk-virtual-scroll-viewport>
<div class="actions z-sticky h-0">
<button
@@ -80,4 +79,38 @@
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
</button>
</div>
</div>
</ng-container>
<ng-template #sideOutlet>
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="191 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"
routerLinkActive
#rla="routerLinkActive"
queryParamsHandling="preserve"
(click)="scrollToItem(i)"
>
<search-result-item
class="page-search-results__result-item"
(selectedChange)="addToCart($event)"
[selected]="isSelected(item)"
[selectable]="isSelectable(item)"
[item]="item"
[primaryOutletActive]="false"
[isActive]="rla.isActive"
></search-result-item>
</a>
<page-search-result-item-loading [primaryOutletActive]="false" *ngIf="fetching$ | async"></page-search-result-item-loading>
</cdk-virtual-scroll-viewport>
<div class="actions z-sticky h-0">
<button
[disabled]="loading$ | async"
*ngIf="(selectedItemIds$ | async)?.length > 0"
class="cta-cart cta-action-primary"
(click)="addToCart()"
>
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
</button>
</div>
</ng-template>

View File

@@ -1,5 +1,5 @@
:host {
@apply box-border grid h-split-screen-tablet desktop-small:h-split-screen-desktop;
@apply box-border grid h-split-screen-tablet desktop-small:h-split-screen-desktop relative;
grid-template-rows: auto auto 1fr;
}
@@ -8,7 +8,7 @@
}
.page-search-results__result-item {
@apply mb-px-10;
@apply mb-[0.625rem];
}
.page-search-results__result-item-primary {

View File

@@ -9,8 +9,9 @@ import {
QueryList,
TrackByFunction,
AfterViewInit,
inject,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
@@ -28,6 +29,8 @@ import { SearchResultItemComponent } from './search-result-item.component';
import { ProductCatalogNavigationService } from '@shared/services';
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
import { DomainAvailabilityService, ItemData } from '@domain/availability';
import { asapScheduler } from 'rxjs';
import { ShellService } from '@shared/shell';
@Component({
selector: 'page-search-results',
@@ -37,7 +40,7 @@ import { DomainAvailabilityService, ItemData } from '@domain/availability';
})
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
@ViewChild('scrollContainer', { static: true })
@ViewChild(CdkVirtualScrollViewport, { static: false })
scrollContainer: CdkVirtualScrollViewport;
@ViewChild(FilterInputGroupMainComponent, { static: false })
@@ -59,6 +62,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
})
);
getProcessId(): number {
return this.application.activatedProcessId;
}
loading$ = new BehaviorSubject<boolean>(false);
private subscriptions = new Subscription();
@@ -92,20 +99,11 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
}
// Ticket #4169 Splitscreen
// Render genug Artikel um bei Navigation auf Trefferliste | PDP zum angewählten Artikel zu Scrollen
maxBufferCdkScrollContainer$ = this.results$.pipe(
withLatestFrom(this.primaryOutletActive$),
map(([results, primaryOutlet]) => {
if (!primaryOutlet && results?.length > 0) {
// Splitscreen mode: Items Length * Item Pixel Height
const maxBufferSize = results.length * 181;
return maxBufferSize >= 1200 ? maxBufferSize : 1200;
} else {
return 1200;
}
})
);
private readonly SCROLL_INDEX_TOKEN = 'CATALOG_RESULTS_LIST_SCROLL_INDEX';
shellService = inject(ShellService);
scale$ = this.shellService.scale$;
constructor(
public searchService: ArticleSearchService,
@@ -117,7 +115,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
private _checkoutService: DomainCheckoutService,
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService,
private _availability: DomainAvailabilityService
private _availability: DomainAvailabilityService,
private _router: Router
) {}
ngOnInit() {
@@ -156,9 +155,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
const cleanQueryParams = this.cleanupQueryParams(queryParams);
// Scroll to scroll_position in great result list
if (!!queryParams?.scroll_position && this.route.outlet === 'primary') {
this.scrollTop(Number(queryParams.scroll_position ?? 0));
if (this.route.outlet === 'primary' && processChanged) {
this.scrollToItem(this._getScrollIndexFromCache());
}
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
@@ -174,11 +172,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
) {
this.search({ clear: true });
} else {
if (!this.isDesktopLarge || this.route.outlet === 'primary') {
this.scrollTop(Number(queryParams.scroll_position ?? 0));
} else {
this.scrollItemIntoView();
}
const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
for (const id of selectedItemIds) {
if (id) {
@@ -206,9 +199,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.subscribe(async ([searchCompleted, processId]) => {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.searchState === '') {
// Keine Navigation bei OrderBy
// Ticket 4524 Korrekte Navigation bei orderBy mit aktuellen queryParams
if (searchCompleted?.orderBy) {
return;
return await this._router.navigate([], { queryParams: params });
}
// Navigation auf Details bzw. Results | Details wenn hits 1
@@ -237,7 +230,19 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.navigate();
}
} else if (searchCompleted?.clear || this.route.outlet === 'primary') {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
const ean = this.route?.snapshot?.params?.ean;
if (ean) {
await this._navigationService
.getArticleDetailsPathByEan({
processId,
ean,
extras: { queryParams: params },
})
.navigate();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}
}
})
@@ -259,7 +264,41 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
ngAfterViewInit(): void {
this.scrollItemIntoView();
this.scrollToItem(this._getScrollIndexFromCache());
}
private _addScrollIndexToCache(index: number): void {
this.cache.set<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }, index);
}
private _getScrollIndexFromCache(): number {
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
}
scrollToItem(i?: number) {
let index = i;
if (!index) {
index = this._getScrollIndexFromCache();
} else {
this._addScrollIndexToCache(index);
}
asapScheduler.schedule(() => {
this.scrollContainer.scrollToIndex(index, 'smooth');
}, 150);
}
scrolledIndexChange(index: number) {
const completeListFetched = this.searchService.items.length === this.searchService.hits;
if (index && !completeListFetched && this.searchService.items.length <= this.scrollContainer?.getRenderedRange()?.end) {
this.search({ clear: false });
}
if (this.getProcessId() === this.searchService.processId) {
this._addScrollIndexToCache(index);
}
}
async ngOnDestroy() {
@@ -290,42 +329,17 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
this.sharedFilterInputGroupMain.cancelAutocomplete();
}
this.searchService.search({ clear, orderBy });
this.searchService.search({ clear, orderBy, doNotTrack: true });
}
scrollTop(scrollPos: number) {
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
}
scrollItemIntoView() {
setTimeout(() => {
const item = this.listItems?.find((item) => item.item.id === Number(this.route?.snapshot?.params?.id));
item?.scrollIntoView();
}, 0);
}
async scrolledIndexChange(index: number) {
const results = await this.results$.pipe(first()).toPromise();
const hits = await this.hits$.pipe(first()).toPromise();
if (results.length >= hits) {
return;
}
if (this.route.outlet === 'primary') {
this.searchService.setScrollPosition(this.scrollContainer.measureScrollOffset('top'));
}
if (index >= results.length - 20 && results.length - 20 > 0) {
this.search({ clear: false });
}
getDetailsPath(itemId: number) {
return this._navigationService.getArticleDetailsPath({ processId: this.application.activatedProcessId, itemId }).path;
}
async updateBreadcrumbs(
processId: number = this.searchService.processId,
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
) {
const scroll_position = this.searchService.scrollPosition;
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
if (queryParams) {
@@ -335,7 +349,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.toPromise();
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
const params = { ...queryParams, scroll_position, selected_item_ids };
const params = { ...queryParams, selected_item_ids };
for (const crumb of crumbs) {
this.breadcrumb.patchBreadcrumb(crumb.id, {
@@ -388,7 +402,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
cleanupQueryParams(params: Record<string, string> = {}) {
const clean = { ...params };
delete clean['scroll_position'];
delete clean['selected_item_ids'];
for (const key in clean) {

View File

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

View File

@@ -81,8 +81,11 @@
*ngIf="group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')"
>
<ng-container *ngIf="item?.destination?.data?.targetBranch?.data; let targetBranch">
<ng-container *ngIf="i === 0 || targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id">
<div class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]">
<ng-container *ngIf="i === 0 || checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)">
<div
class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]"
[class.multiple-destinations]="checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)"
>
<span class="branch-name">{{ targetBranch?.name }} | {{ targetBranch | branchAddress }}</span>
</div>
<hr />

View File

@@ -105,6 +105,10 @@ h1 {
}
}
.multiple-destinations {
@apply py-[0.875rem] mt-0;
}
.icon-order-type {
@apply text-black mr-2;
}

View File

@@ -14,7 +14,7 @@ import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { AvailabilityDTO, BranchDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
@@ -254,6 +254,10 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
});
}
checkIfMultipleDestinationsForOrderTypeExist(targetBranch: BranchDTO, group: { items: ShoppingCartItemDTO[] }, i: number) {
return i === 0 ? false : targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id;
}
async refreshAvailabilities() {
this.checkingOla$.next(true);

View File

@@ -16,7 +16,7 @@ import { NotificationChannel } from '@swagger/checkout';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutReviewDetailsComponent implements OnInit {
control = this._store.notificationsControl;
control: UntypedFormGroup;
customerFeatures$ = this._store.customerFeatures$;
@@ -96,15 +96,18 @@ export class CheckoutReviewDetailsComponent implements OnInit {
selectedNotificationChannel = 1;
}
this.control = fb.group({
notificationChannel: new UntypedFormGroup({
selected: new UntypedFormControl(selectedNotificationChannel),
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
}),
});
this._store.notificationsControl = this.control;
if (!this._store.notificationsControl) {
this.control = fb.group({
notificationChannel: new UntypedFormGroup({
selected: new UntypedFormControl(selectedNotificationChannel),
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
}),
});
this._store.notificationsControl = this.control;
} else {
this.control = this._store.notificationsControl;
}
}
setAgentComment(agentComment: string) {

View File

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

View File

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

View File

@@ -87,7 +87,7 @@
<div class="label">ISBN/EAN</div>
<div class="value">{{ orderItem.product?.ean }}</div>
</div>
<div class="detail" *ngIf="!!orderItem.price">
<div class="detail" *ngIf="orderItem.price !== undefined">
<div class="label">Preis</div>
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
</div>

View File

@@ -103,6 +103,33 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
this._customerOrdersSearchStore.setQueryParams(queryParams);
});
this._customerOrdersSearchStore.searchResultSubject.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.results.error) {
} else {
if (result.results.hits > 0) {
const queryParams = this._customerOrdersSearchStore.filter.getQueryParams();
if (result.results.hits === 1) {
const orderItem = result.results.result[0];
await this._navigationService
.getCustomerOrdersDetailsPath({
processId: this.processId,
processingStatus: orderItem?.processingStatus,
compartmentCode: orderItem?.compartmentCode ? encodeURIComponent(orderItem.compartmentCode) : undefined,
orderId: orderItem?.orderId ? orderItem.orderId : undefined,
extras: { queryParams },
})
.navigate();
} else {
await this._navigationService.getCustomerOrdersResultsPath(this.processId, { queryParams }).navigate();
}
} else {
this._customerOrdersSearchStore.setMessage('keine Suchergebnisse');
}
this._cdr.markForCheck();
}
});
this._initSettings();
this._initLoading$();
}
@@ -133,34 +160,6 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
const queryParams = filter.getQueryParams();
this._customerOrdersSearchStore.setQueryParams(queryParams);
await this.updateQueryParams(queryParams);
this._customerOrdersSearchStore.searchResultSubject.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.results.error) {
} else {
if (result.results.hits > 0) {
const queryParams = this._customerOrdersSearchStore.filter.getQueryParams();
if (result.results.hits === 1) {
const orderItem = result.results.result[0];
await this._navigationService
.getCustomerOrdersDetailsPath({
processId: this.processId,
processingStatus: orderItem?.processingStatus,
compartmentCode: orderItem?.compartmentCode ? encodeURIComponent(orderItem.compartmentCode) : undefined,
orderId: orderItem?.orderId ? orderItem.orderId : undefined,
extras: { queryParams },
})
.navigate();
} else {
await this._navigationService.getCustomerOrdersResultsPath(this.processId, { queryParams }).navigate();
}
} else {
this._customerOrdersSearchStore.setMessage('keine Suchergebnisse');
}
this._cdr.markForCheck();
}
});
this._customerOrdersSearchStore.search({ clear: true });
}

View File

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

View File

@@ -7,7 +7,7 @@ import { Filter } from '@shared/components/filter';
import { BranchDTO, ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, QuerySettingsDTO } from '@swagger/oms';
import { isResponseArgs } from '@utils/object';
import { Observable, Subject } from 'rxjs';
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
export interface CustomerOrderSearchState {
defaultSettings?: QuerySettingsDTO;
@@ -125,6 +125,8 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
searchStarted = new Subject<{ clear?: boolean; silentReload?: boolean }>();
cancelSearch$ = new Subject<void>();
constructor(private _domainGoodsInService: DomainCustomerOrderService, private _cache: CacheService) {
super({
fetching: false,
@@ -192,6 +194,11 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
});
}
cancelSearchRequest() {
this.cancelSearch$.next();
this.patchState({ fetching: false, silentFetching: false });
}
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
options$.pipe(
tap((_) => {
@@ -238,6 +245,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
}
return this._domainGoodsInService.search(queryToken).pipe(
takeUntil(this.cancelSearch$),
tapResponse(
(res) => {
let _results: OrderItemListItemDTO[] = [];

View File

@@ -17,10 +17,7 @@ import { ApplicationService } from '@core/application';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
filter$ = this._customerOrderSearchStore.filter$.pipe(
filter((f) => !!f),
take(1)
);
filter$ = this._customerOrderSearchStore.filter$.pipe(filter((f) => !!f));
loading$ = this._customerOrderSearchStore.fetching$;
@@ -62,9 +59,6 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
// Clear scroll position
localStorage.removeItem(`SCROLL_POSITION_${this.processId}`);
this._subscriptions.add(
combineLatest([this.processId$, this._activatedRoute.queryParams])
.pipe(debounceTime(50))

View File

@@ -246,8 +246,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
const process = await this._application.getProcessById$(processId).pipe(first()).toPromise();
if (!!process) {
await this.updateBreadcrumb(processId, params);
await this.createBreadcrumb(processId, params);
await this.updateBreadcrumb(processId, params);
}
if (this._activatedRoute?.outlet === 'primary') {
@@ -364,25 +364,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
return clean;
}
async removeBreadcrumbs(processId: number) {
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
const historyCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'history'])
.pipe(first())
.toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
historyCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
await this.removeDetailsBreadcrumb(processId);
}
async removeDetailsBreadcrumb(processId: number) {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'details'])
@@ -394,7 +375,19 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
});
}
async createMainBreadcrumb(processId: number, params: Record<string, string>) {
await this._breadcrumb.addBreadcrumbIfNotExists({
key: processId,
name: 'Kundenbestellung',
path: this._navigationService.getCustomerOrdersBasePath(processId).path,
params,
tags: ['customer-order', 'main', 'filter'],
section: 'customer',
});
}
async createBreadcrumb(processId: number, params: Record<string, string>) {
await this.createMainBreadcrumb(processId, params);
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: this.getBreadcrumbName(params),

View File

@@ -3,18 +3,21 @@ import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { EnvironmentService } from '@core/environment';
import { provideComponentStore } from '@ngrx/component-store';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
import { BranchDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { Observable, Subject, fromEvent } from 'rxjs';
import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderSearchStore } from './customer-order-search';
@Component({
selector: 'page-customer-order',
templateUrl: 'customer-order.component.html',
styleUrls: ['customer-order.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideComponentStore(CustomerOrderSearchStore)],
})
export class CustomerOrderComponent implements OnInit {
@ViewChild(BreadcrumbComponent, { read: ElementRef }) breadcrumbRef: ElementRef<HTMLElement>;
@@ -39,13 +42,24 @@ export class CustomerOrderComponent implements OnInit {
private _uiModal: UiModalService,
private _renderer: Renderer2,
private _environmentService: EnvironmentService,
public auth: AuthService
public auth: AuthService,
private _store: CustomerOrderSearchStore
) {}
ngOnInit(): void {
this.selectedBranch$ = this.application.activatedProcessId$.pipe(
switchMap((processId) => this.application.getSelectedBranch$(Number(processId)))
);
/* Ticket #4544 - Suchrequest abbrechen bei Prozesswechsel
/ um zu verhindern, dass die Suche in einen anderen Kundenbestellungen Prozess übernommen wird
/ bei Prozesswechsel zwischen 2 Kundenbestellungen Prozessen
*/
this.processId$.pipe(takeUntil(this._onDestroy$)).subscribe((processId) => {
if (Number(processId) !== this._store.processId) {
this._store.cancelSearchRequest();
}
});
}
ngAfterViewInit(): void {

View File

@@ -1,6 +1,7 @@
export const CustomerLabelColor = {
Abholfachbestellung: '#EDEFF0',
'Versandbestellung (oder gemischt)': '#EDEFF0',
'Bestellung ohne Konto': '#EDEFF0',
Onlinekonto: '#804279',
'Onlinekonto mit Kundenkarte': '#804279',
'Business Konto (auf Rechnung)': '#804279',
@@ -10,6 +11,7 @@ export const CustomerLabelColor = {
export const CustomerLabelTextColor = {
Abholfachbestellung: '#000000',
'Bestellung ohne Konto': '#000000',
'Versandbestellung (oder gemischt)': '#000000',
Onlinekonto: '#FFFFFF',
'Onlinekonto mit Kundenkarte': '#FFFFFF',

View File

@@ -4,10 +4,10 @@ import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CustomerDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
import { AddressDTO, CustomerDTO, CustomerInfoDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiValidators } from '@ui/validators';
import { isNull } from 'lodash';
import { isNull, merge } from 'lodash';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import {
first,
@@ -24,7 +24,12 @@ import {
} from 'rxjs/operators';
import { AddressFormBlockComponent, DeviatingAddressFormBlockComponent, DeviatingAddressFormBlockData } from '../components/form-blocks';
import { FormBlock } from '../components/form-blocks/form-block';
import { CustomerCreateFormData, decodeFormData, encodeFormData } from './customer-create-form-data';
import {
CustomerCreateFormData,
decodeFormData,
encodeFormData,
mapCustomerInfoDtoToCustomerCreateFormData,
} from './customer-create-form-data';
import { AddressSelectionModalService } from '../modals';
import { CustomerCreateNavigation, CustomerSearchNavigation } from '@shared/services';
@@ -224,7 +229,33 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
const customerId = this.formData?._meta?.customerDto?.id ?? this.formData?._meta?.customerInfoDto?.id;
return this.customerService.checkLoyaltyCard({ loyaltyCardNumber: value, customerId }).pipe(
map((response) => {
return !response?.error && (response as any)?.result === 1 ? null : { invalid: 'Kundenkartencode ist ungültig' };
if (response.error) {
throw response.message;
}
/**
* #4485 Kubi // Verhalten mit angelegte aber nicht verknüpfte Kundenkartencode in Kundensuche und Kundendaten erfassen ist nicht gleich
* Fall1: Kundenkarte hat Daten in point4more:
* Sobald Kundenkartencode in Feld "Kundenkartencode" reingegeben wird- werden die Daten von point4more in Formular "Kundendaten Erfassen" eingefügt und ersetzen (im Ganzen, nicht inkremental) die Daten in Felder, falls welche schon reingetippt werden.
* Fall2: Kundenkarte hat keine Daten in point4more:
* Sobald Kundenkartencode in Feld "Kundenkartencode" reingegeben wird- bleiben die Daten in Formular "Kundendaten Erfassen" in Felder, falls welche schon reingetippt werden.
*/
if (response.result && response.result.customer) {
const customer = response.result.customer;
const data = mapCustomerInfoDtoToCustomerCreateFormData(customer);
if (data.name.firstName && data.name.lastName) {
// Fall1
this._formData.next(data);
} else {
// Fall2 Hier müssen die Metadaten gesetzt werden um eine verknüfung zur kundenkarte zu ermöglichen.
const current = this.formData;
current._meta = data._meta;
current.p4m = data.p4m;
}
}
return null;
}),
catchError((error) => {
if (error instanceof HttpErrorResponse) {

View File

@@ -56,7 +56,7 @@ export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
async saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
const { customerDto, customerInfoDto } = this.formData?._meta ?? {};
const isUpgrade = !!(customerDto || customerInfoDto);
const isUpgrade = !!(customerDto || customerInfoDto)?.id;
if (isUpgrade) {
if (customerDto) {

View File

@@ -39,7 +39,7 @@
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
<div class="flex flex-row">
<div class="data-label">Erstellungsdatum</div>
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'hh:mm' }} Uhr</div>
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'HH:mm' }} Uhr</div>
</div>
<div class="flex flex-row">
<div class="data-label">Kundennummer</div>

View File

@@ -411,7 +411,9 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
_patchProcessName() {
let name = `${this.customer.firstName} ${this.customer.lastName}`;
if (this._store.isBusinessKonto) {
// Ticket #4458 Es kann vorkommen, dass B2B Konten keinen Firmennamen hinterlegt haben
// zusätzlich kanne es bei Mitarbeiter Konten vorkommen, dass die Namen in der Organisation statt im Kundennamen hinterlegt sind
if ((this._store.isBusinessKonto && this.customer.organisation?.name) || (!this.customer.firstName && !this.customer.lastName)) {
name = `${this.customer.organisation?.name}`;
}

View File

@@ -10,4 +10,10 @@
[loading]="fetching$ | async"
[hint]="message$ | async"
></shared-filter-input-group-main>
<p class="mt-6">
Kunde nicht gefunden?
<a class="text-brand" *ngIf="createRoute$ | async; let route" [routerLink]="route.path" [queryParams]="route.queryParams">
Neue Kundendaten erfassen
</a>
</p>
</div>

View File

@@ -3,6 +3,10 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Filter, FilterModule } from '@shared/components/filter';
import { CustomerSearchStore } from '../store';
import { map } from 'rxjs/operators';
import { CustomerCreateNavigation } from '@shared/services';
import { combineLatest } from 'rxjs';
import { CustomerInfoDTO } from '@swagger/crm';
@Component({
selector: 'page-customer-main-side-view',
@@ -19,7 +23,29 @@ export class MainSideViewComponent {
fetching$ = this._store.fetchingCustomerList$;
constructor(private _store: CustomerSearchStore) {}
createRoute$ = combineLatest(this.filter$, this._store.processId$).pipe(
map(([filter, processId]) => {
const queryParams = filter?.getQueryParams();
let customerInfo: CustomerInfoDTO;
if (queryParams?.main_qs) {
const isMail = queryParams.main_qs.includes('@');
customerInfo = {
lastName: !isMail ? queryParams.main_qs : undefined,
communicationDetails: isMail
? {
email: queryParams.main_qs,
}
: undefined,
};
}
return this._customerCreateNavigation.createCustomerRoute({ processId, customerInfo });
})
);
constructor(private _store: CustomerSearchStore, private _customerCreateNavigation: CustomerCreateNavigation) {}
search(filter: Filter) {
this._store.setFilter(filter);

View File

@@ -1,5 +1,5 @@
:host {
@apply bg-surface text-surface-content rounded grid grid-flow-row h-full;
@apply bg-surface text-surface-content rounded grid grid-flow-row h-full side-view-shadow;
}
.side-view-shadow {

View File

@@ -1,5 +1,5 @@
<div class="desktop-large:hidden">
<div class="text-center pt-10 px-8 rounded-card side-view-shadow grow">
<div class="text-center pt-10 px-8 rounded-card grow">
<h1 class="text-[1.625rem] font-bold">Kundensuche</h1>
<p class="text-lg mt-2 mb-6">
Haben Sie ein Konto bei uns?
@@ -28,6 +28,12 @@
</span>
</a>
</div>
<p class="mt-6">
Kunde nicht gefunden?
<a class="text-brand" *ngIf="createRoute$ | async; let route" [routerLink]="route.path" [queryParams]="route.queryParams">
Neue Kundendaten erfassen
</a>
</p>
</div>
</div>
<div class="hidden desktop-large:block">

View File

@@ -6,9 +6,10 @@ import { AsyncPipe, NgIf } from '@angular/common';
import { Router, RouterLink } from '@angular/router';
import { IconComponent } from '@shared/components/icon';
import { combineLatest } from 'rxjs';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchNavigation, CustomerCreateNavigation } from '@shared/services';
import { CustomerFilterMainViewModule } from '../filter-main-view/filter-main-view.module';
import { isEmpty } from 'lodash';
import { CustomerInfoDTO } from '@swagger/crm';
@Component({
selector: 'page-customer-main-view',
@@ -29,6 +30,28 @@ export class CustomerMainViewComponent {
})
);
createRoute$ = combineLatest(this._store.filter$, this._store.processId$).pipe(
map(([filter, processId]) => {
const queryParams = filter?.getQueryParams();
let customerInfo: CustomerInfoDTO;
if (queryParams?.main_qs) {
const isMail = queryParams.main_qs.includes('@');
customerInfo = {
lastName: !isMail ? queryParams.main_qs : undefined,
communicationDetails: isMail
? {
email: queryParams.main_qs,
}
: undefined,
};
}
return this._customerCreateNavigation.createCustomerRoute({ processId, customerInfo });
})
);
filter$ = this._store.filter$;
hasFilter$ = this.filter$.pipe(
@@ -43,7 +66,12 @@ export class CustomerMainViewComponent {
message$ = this._store.message$;
constructor(private _searchNavigation: CustomerSearchNavigation, private _store: CustomerSearchStore, private _router: Router) {}
constructor(
private _searchNavigation: CustomerSearchNavigation,
private _customerCreateNavigation: CustomerCreateNavigation,
private _store: CustomerSearchStore,
private _router: Router
) {}
search(filter: Filter) {
this._store.setFilter(filter);

View File

@@ -12,6 +12,7 @@ import { isEmpty } from 'lodash';
import { DomainOmsService } from '@domain/oms';
import { OrderDTO, OrderListItemDTO } from '@swagger/oms';
import { hash } from '@utils/common';
import { UiModalService } from '@ui/modal';
@Injectable()
export class CustomerSearchStore extends ComponentStore<CustomerSearchState> implements OnStoreInit, OnDestroy {
@@ -163,7 +164,7 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
selectedOrderItem$ = this.select(S.selectSelectedOrderItem);
constructor(private _customerService: CrmCustomerService, private _omsService: DomainOmsService) {
constructor(private _customerService: CrmCustomerService, private _omsService: DomainOmsService, private _modal: UiModalService) {
super({ customerListCount: 0 });
}
@@ -205,7 +206,8 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
};
handleSelectCustomerError = (err: any) => {
console.error(err);
this._modal.error('Fehler beim Auswählen des Kundens', err);
this.patchState({ fetchingCustomer: false });
};
handleSelectCustomerComplete = () => {
@@ -230,7 +232,8 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
};
handleSelectOrderError = (err: any) => {
console.error(err);
this._modal.error('Fehler beim Auswählen der Bestellung', err);
this.patchState({ fetchingOrder: false });
};
handleSelectOrderComplete = () => {
@@ -259,7 +262,8 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
};
handleFetchCustomerOrdersError = (err: any) => {
console.error(err);
this._modal.error('Fehler beim Laden der Kundenbestellungen', err);
this.patchState({ fetchingCustomerOrders: false });
};
handleFetchCustomerOrdersComplete = () => {
@@ -282,7 +286,8 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
};
handleFetchFilterError = (err: any) => {
console.error(err);
this._modal.error('Fehler beim Laden der Filter', err);
this.patchState({ fetchingFilter: false });
};
handleFetchFilterComplete = () => {
@@ -341,7 +346,8 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
};
handleSearchError = (err: any) => {
console.error(err);
this._modal.error('Fehler beim Laden der Liste', err);
this.patchState({ fetchingCustomerList: false });
};
handleSearchComplete = () => {

View File

@@ -1,6 +1,8 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ProductsFeed } from '@domain/isa';
import { ProductCatalogNavigationService } from '@shared/services';
import { first } from 'rxjs/operators';
@Component({
selector: 'page-products-card',
@@ -12,9 +14,19 @@ export class ProductsCardComponent {
@Input()
feed: ProductsFeed;
constructor(private _router: Router) {}
constructor(private _navigation: ProductCatalogNavigationService, private _app: ApplicationService) {}
navigatetToProduct(ean: string) {
this._router.navigate(['/kunde/product/details/ean', ean]);
async navigatetToProduct(ean: string) {
let processes = await this._app.getProcesses$('customer').pipe(first()).toPromise();
processes = processes.sort((a, b) => b.activated - a.activated);
this._navigation
.getArticleDetailsPathByEan({
processId: processes[0]?.id ?? Date.now(),
ean,
extras: { queryParams: { main_qs: this.feed.items.map((i) => i.product.ean).join(';') } },
})
.navigate();
}
}

View File

@@ -100,6 +100,7 @@ export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
key: this._config.get('process.ids.goodsIn'),
name: 'Abholfachbereinigungsliste',
path: '/filiale/goods/in/cleanup',
params: { view: 'cleanup' },
section: 'branch',
tags: ['goods-in', 'cleanup'],
});

View File

@@ -22,6 +22,7 @@ import { debounceTime, first, map, shareReplay, takeUntil, tap } from 'rxjs/oper
import { GoodsInListItemComponent } from './goods-in-list-item/goods-in-list-item.component';
import { GoodsInListStore } from './goods-in-list.store';
import { PickupShelfInNavigationService } from '@shared/services';
import { CacheService } from '@core/cache';
@Component({
selector: 'page-goods-in-list',
@@ -60,20 +61,21 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
private _onDestroy$ = new Subject();
private readonly SCROLL_POSITION_TOKEN = 'GOODS_IN_LIST_SCROLL_POSITION';
constructor(
private _breadcrumb: BreadcrumbService,
private _domainOmsService: DomainOmsService,
public store: GoodsInListStore,
private _router: Router,
private _route: ActivatedRoute,
private readonly _config: Config
private readonly _config: Config,
private _cache: CacheService
) {}
ngOnInit() {
this.store.setTake(Number(this._route.snapshot.queryParams.take ?? 25));
this._route.queryParams.pipe(takeUntil(this._onDestroy$), debounceTime(0)).subscribe(async (params) => {
const scrollPos = Number(params.scroll_position ?? 0);
// Initial Search - Always Search If No Params Are Set
if (
(Object.keys(params).length === 0 || this.store.results.length === 0) &&
@@ -82,7 +84,8 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.store.search({
cb: () => {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache());
this._removeScrollPositionFromCache();
}, 0);
},
});
@@ -101,14 +104,15 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.store.search({
cb: () => {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache());
this._removeScrollPositionFromCache();
}, 0);
},
});
}
await this.updateBreadcrumb({ queryParams: params });
await this.createBreadcrumb({ queryParams: params });
await this.updateBreadcrumb(params);
await this.createBreadcrumb(params);
await this.removeBreadcrumbs();
});
}
@@ -117,13 +121,15 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this._onDestroy$.next();
this._onDestroy$.complete();
this._addScrollPositionToCache();
this.updateBreadcrumb(this.store.filter.getQueryParams());
}
cleanupQueryParams(params: Record<string, string> = {}) {
const clean = { ...params };
delete clean['scroll_position'];
delete clean['take'];
delete clean['view'];
for (const key in clean) {
if (Object.prototype.hasOwnProperty.call(clean, key)) {
@@ -142,6 +148,21 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.listItems.changes.pipe(takeUntil(this._onDestroy$)).subscribe(() => this.registerEditSscDisabled());
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
private _addScrollPositionToCache(): void {
this._cache.set<number>(
{ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
);
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
if (this.editSsc) {
return;
@@ -183,7 +204,6 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
}
async updateBreadcrumb(queryParams: Record<string, string> | Params = this.store.filter?.getQueryParams()) {
const scroll_position = this.scrollContainer?.scrollPos;
const take = this._route?.snapshot?.queryParams?.take;
if (queryParams) {
@@ -191,7 +211,7 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'list'])
.pipe(first())
.toPromise();
const params = { ...queryParams, scroll_position, take };
const params = { ...queryParams, take };
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {
@@ -207,7 +227,7 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
name: 'Wareneingangsliste',
path: '/filiale/goods/in/list',
section: 'branch',
params: queryParams,
params: { ...queryParams, view: 'wareneingangsliste' },
tags: ['goods-in', 'list'],
});
}

View File

@@ -88,7 +88,11 @@ export class GoodsInListStore extends ComponentStore<GoodsInListState> {
const path = '/filiale/goods/in/list/';
if (!this._router.isActive(path, false)) {
this._router.navigate([path], {
queryParams: { ...this.filter.getQueryParams(), take: res.result.length + _results?.length },
queryParams: {
...this.filter.getQueryParams(),
take: res.result.length + _results?.length,
view: 'wareneingangsliste',
},
});
}
}

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
@@ -10,6 +10,7 @@ import { GoodsInRemissionPreviewStore } from './goods-in-remission-preview.store
import { Config } from '@core/config';
import { ToasterService } from '@shared/shell';
import { PickupShelfInNavigationService } from '@shared/services';
import { CacheService } from '@core/cache';
@Component({
selector: 'page-goods-in-remission-preview',
@@ -22,8 +23,6 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
private _scrollPosition: number;
items$ = this._store.results$;
itemLength$ = this.items$.pipe(map((items) => items?.length));
@@ -52,14 +51,16 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
private readonly SCROLL_POSITION_TOKEN = 'REMISSION_PREVIEW_SCROLL_POSITION';
constructor(
private _breadcrumb: BreadcrumbService,
private _store: GoodsInRemissionPreviewStore,
private _route: ActivatedRoute,
private _router: Router,
private _modal: UiModalService,
private _config: Config,
private _toast: ToasterService
private _toast: ToasterService,
private _cache: CacheService
) {}
ngOnInit(): void {
@@ -71,15 +72,32 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
this._addScrollPositionToCache();
this.updateBreadcrumb();
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
private _addScrollPositionToCache(): void {
this._cache.set<number>(
{ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
);
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
async createBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
name: 'Abholfachremissionsvorschau',
path: '/filiale/goods/in/preview',
section: 'branch',
params: { view: 'remission' },
tags: ['goods-in', 'preview'],
});
}
@@ -92,7 +110,6 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {
name: crumb.name,
params: { scroll_position: this.scrollContainer?.scrollPos },
});
}
}
@@ -132,14 +149,11 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
await this.createBreadcrumb();
if (this._scrollPosition) {
this.scrollContainer?.scrollTo(Number(this._scrollPosition ?? 0));
this._scrollPosition = undefined;
}
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache() ?? 0);
this._removeScrollPositionFromCache();
});
}
this._scrollPosition = this._route.snapshot.queryParams?.scroll_position;
this._store.search();
}

View File

@@ -102,6 +102,7 @@ export class GoodsInReservationComponent implements OnInit, OnDestroy {
name: 'Reservierungen',
path: '/filiale/goods/in/reservation',
section: 'branch',
params: { view: 'reservation' },
tags: ['goods-in', 'reservation'],
});
}

View File

@@ -75,7 +75,10 @@
>Zur erneuten Prüfung 7 Tage nach Avisierung.</ui-tooltip
>
</ng-container>
<div class="isa-label text-white font-bold" [class]="packageDetails.package.arrivalStatus | arrivalStatusColorClass">
<div
class="isa-label text-white font-bold page-package-details__arrival-status"
[class]="packageDetails.package.arrivalStatus | arrivalStatusColorClass"
>
{{ packageDetails.package.arrivalStatus | arrivalStatus }}
</div>
</div>

View File

@@ -12,6 +12,7 @@ import { RunCheckTrigger } from './trigger';
import { OrderItemsContext } from '@domain/oms';
import { ActionHandlerService } from './services/action-handler.service';
import { Config } from '@core/config';
import { debounce } from '@utils/common';
export type GetNameForBreadcrumbData = {
processId: number;
@@ -64,6 +65,11 @@ export abstract class PickupShelfBaseComponent implements OnInit {
this._runChecks();
}
// der debounce soll verhindern, dass die breadcrumb zu oft aktualisiert,
// besonders bei asynchronen calls kommt es sonst zu fehlern
// @debounce(500)
// Auskommentiert, da es zu anderen Problemen führt, siehe z.B. Ticket #4538 oder #4540
// Ursprungsproblem des Tickets #4533 konnte anders gelöst werden, somit wird debounce hier nicht mehr benötigt
private _runChecks() {
const processId = this._checkAndUpdateProcessId();
const queryParams = this._checkAndUpdateQueryParams();
@@ -120,7 +126,7 @@ export abstract class PickupShelfBaseComponent implements OnInit {
// Only Update QueryParams if the user is already on the details, edit or history page
const view: string = this.activatedRoute.snapshot.data.view;
if (['details', 'edit', 'history'].includes(view)) {
if (['filter', 'details', 'edit', 'history'].includes(view)) {
await this.router.navigate([], { queryParams: { ...queryParams, ...filterQueryParams }, skipLocationChange: true });
return;
}
@@ -218,7 +224,7 @@ export abstract class PickupShelfBaseComponent implements OnInit {
const name = 'Reservierungen';
const path: NavigationRoute = {
path: ['/filiale', 'goods', 'in', 'reservation'],
queryParams: { view: undefined },
queryParams: { view: 'reservation' },
urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'reservation'], {}),
};
@@ -251,7 +257,7 @@ export abstract class PickupShelfBaseComponent implements OnInit {
const name = 'Abholfachbereinigungsliste';
const path: NavigationRoute = {
path: ['/filiale', 'goods', 'in', 'cleanup'],
queryParams: { view: undefined },
queryParams: { view: 'cleanup' },
urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'cleanup'], {}),
};
@@ -284,7 +290,7 @@ export abstract class PickupShelfBaseComponent implements OnInit {
const name = 'Wareneingangsliste';
const path: NavigationRoute = {
path: ['/filiale', 'goods', 'in', 'list'],
queryParams: { view: undefined },
queryParams: { view: 'wareneingangsliste' },
urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'list'], {}),
};
@@ -317,7 +323,7 @@ export abstract class PickupShelfBaseComponent implements OnInit {
const name = 'Abholfachremissionsvorschau';
const path: NavigationRoute = {
path: ['/filiale', 'goods', 'in', 'preview'],
queryParams: { view: undefined },
queryParams: { view: 'remission' },
urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'preview'], {}),
};

View File

@@ -5,6 +5,8 @@ import { PickupShelfDetailsStore, PickupShelfStore } from './store';
import { ActionHandlerService } from './services/action-handler.service';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
import { OrderItemsContext } from '@domain/oms';
import { map } from 'rxjs/operators';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
export abstract class PickupShelfDetailsBaseComponent {
protected destroyRef = inject(DestroyRef);
@@ -18,8 +20,28 @@ export abstract class PickupShelfDetailsBaseComponent {
store = inject(PickupShelfDetailsStore);
listStore = inject(PickupShelfStore);
get side() {
if (this.activatedRoute.snapshot.queryParams.side !== undefined) {
return coerceBooleanProperty(this.activatedRoute.snapshot.queryParams.side);
}
return true;
}
side$ = this.activatedRoute.queryParams.pipe(
map((params) => {
if (params.side !== undefined) {
return coerceBooleanProperty(params.side);
}
return true;
})
);
constructor() {
this.activatedRoute.params.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
// // Fix #4508 - Always Reset Cover Items before fetching new ones inside pickup-shelf-in-details.component
if (!!this.store.coverOrderItems?.length) {
this.store.resetCoverItems();
}
this.store.fetchOrder({ orderId: Number(params.orderId) });
this.store.fetchOrderItems({
orderNumber: params.orderNumber ? decodeURIComponent(params.orderNumber) : undefined,
@@ -58,12 +80,26 @@ export abstract class PickupShelfDetailsBaseComponent {
itemQuantity: this.store.selectedOrderItemQuantity,
});
const ctxItem = ctx?.items[0];
// Ticket #4466 - Nach der nachbestellung wurde der Artikel in den Details nicht mehr angezeigt - änderung #4459 rückgängig gemacht
// Ticket #4459 - Usecase Abholfach Nachbestellen - Wenn das selektierte Item nicht verändert wurde z.B. beim schließen des nachbestellen Modals
// soll hier returned werden, da der unveränderte Stand angezeigt werden soll
// if (
// !ctxItem ||
// (action.command.includes('REORDER') &&
// ctxItem.orderItemSubsetId === this.store.selectedOrderItems.find((_) => true).orderItemSubsetId)
// ) {
// return;
// }
this.store.setFetchPartial(false);
this.store.resetSelectedOrderItems();
this.listStore.resetSelectedListItems();
this.store.resetSelectedOrderItemQuantity();
this.store.setSelectedCompartmentInfo(undefined);
const ctxItem = ctx?.items.find((_) => true);
if (!ctxItem) return;
const updatedDetailsItems = await new Promise<DBHOrderItemListItemDTO[]>((resolve, reject) => {
this.store.fetchOrderItems({

View File

@@ -1,5 +1,8 @@
:host {
@apply box-border grid overflow-y-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop;
@apply box-border overflow-y-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop;
}
.page-pickup-shelf-in-details__container {
grid-template-rows: auto 1fr;
}

View File

@@ -1,56 +1,75 @@
<div *ngIf="noOrderItemsFound$ | async" class="text-center text-2xl font-bold bg-white rounded px-4 py-8">
<h4>Posten wurde nicht gefunden</h4>
</div>
<div class="grid overflow-y-scroll h-full" [class.page-pickup-shelf-in-details__container]="!(viewFetching$ | async)">
<div *ngIf="noOrderItemsFound$ | async" class="text-center text-2xl font-bold bg-white rounded px-4 py-8">
<h4>Posten wurde nicht gefunden</h4>
</div>
<div class="mb-4">
<page-pickup-shelf-details-header
(handleAction)="handleAction({ action: $event })"
(updateDate)="updateDate($event)"
(editClick)="navigateToEditPage($event)"
></page-pickup-shelf-details-header>
<page-pickup-shelf-details-item
*ngIf="selectedItem$ | async; let item"
class="mb-px-2"
[orderItem]="item"
[selected]="true"
(historyClick)="navigateToHistoryPage($event)"
[order]="order$ | async"
(specialCommentChanged)="updateSpecialComment(item, $event)"
(sharedOnInit)="fetchNotifications(item)"
></page-pickup-shelf-details-item>
<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>
</div>
<div class="page-pickup-shelf-in-details__action-wrapper">
<ng-container *ngIf="latestCompartmentInfos$ | async; let latestCompartmentInfos">
<button
[disabled]="addToPreviousCompartmentActionDisabled$ | async"
*ngIf="addToPreviousCompartmentAction$ | async; let action"
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 })"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command"
>{{ latestDisplayedCompartmentInfos$ | async | addToPreviousCompartmentCodeLabelPipe }} zubuchen</ui-spinner
>
</button>
<ng-container *ngIf="viewFetching$ | async; else showView">
<div class="w-full flex flex-col justify-center items-center">
<ui-spinner [show]="true"></ui-spinner>
<div class="mt-6">Daten werden geladen ...</div>
</div>
</ng-container>
<button
[disabled]="actionsDisabled$ | async"
class="cta-action shadow-action"
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
*ngFor="let action of mainActions$ | async"
(click)="handleAction({action})"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
</button>
<ng-template #showView>
<div class="mb-4">
<page-pickup-shelf-details-header
(handleAction)="handleAction({ action: $event })"
(updateDate)="updateDate($event)"
(editClick)="navigateToEditPage($event)"
></page-pickup-shelf-details-header>
<ng-container *ngIf="selectedItem$ | async; let item">
<page-pickup-shelf-details-items-group
[orderType]="selectedItemOrderType$ | async"
[groupedItems]="[item]"
></page-pickup-shelf-details-items-group>
<page-pickup-shelf-details-item
class="mb-px-2"
[orderItem]="item"
[selected]="true"
(historyClick)="navigateToHistoryPage($event)"
[order]="order$ | async"
(specialCommentChanged)="updateSpecialComment(item, $event)"
(sharedOnInit)="fetchNotifications(item)"
></page-pickup-shelf-details-item>
</ng-container>
<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>
</div>
<div class="page-pickup-shelf-in-details__action-wrapper">
<button
[disabled]="actionsDisabled$ | async"
class="cta-action shadow-action"
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
*ngFor="let action of mainActions$ | async"
(click)="handleAction({action})"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
</button>
<ng-container *ngIf="latestCompartmentInfos$ | async; let latestCompartmentInfos">
<button
[disabled]="addToPreviousCompartmentActionDisabled$ | async"
*ngIf="addToPreviousCompartmentAction$ | async; let action"
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 })"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command"
>{{ latestDisplayedCompartmentInfos$ | async | addToPreviousCompartmentCodeLabelPipe }} zubuchen</ui-spinner
>
</button>
</ng-container>
</div>
</ng-template>
</div>

View File

@@ -9,12 +9,14 @@ import { PickupShelfAddToPreviousCompartmentCodeLabelPipe } from '../../shared/p
import { UiSpinnerModule } from '@ui/spinner';
import { OnInitDirective } from '@shared/directives/element-lifecycle';
import { PickupShelfInNavigationService } from '@shared/services';
import { BehaviorSubject, combineLatest } from 'rxjs';
import { BehaviorSubject, asapScheduler, combineLatest } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { ActivatedRoute } from '@angular/router';
import { RunCheckTrigger } from '../../trigger';
import { PickUpShelfDetailsItemsGroupComponent } from '../../shared/pickup-shelf-details-items-group/pickup-shelf-details-items-group.component';
@Component({
selector: 'page-pickup-shelf-in-details',
@@ -31,12 +33,15 @@ import { ActivatedRoute } from '@angular/router';
PickUpShelfDetailsItemComponent,
PickUpShelfDetailsTagsComponent,
PickUpShelfDetailsCoversComponent,
PickUpShelfDetailsItemsGroupComponent,
PickupShelfAddToPreviousCompartmentCodeLabelPipe,
UiSpinnerModule,
OnInitDirective,
],
})
export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseComponent implements OnInit, AfterViewInit {
runCheckTrigger = inject(RunCheckTrigger);
@ViewChild(PickUpShelfDetailsTagsComponent, { static: false })
pickUpShelfDetailsTags: PickUpShelfDetailsTagsComponent;
@@ -49,6 +54,12 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
noOrderItemsFound$ = this.store.noOrderItemsFound$;
fetching$ = 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)
);
selectedCompartmentInfo = this.store.selectedCompartmentInfo;
@@ -65,6 +76,8 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
selectedItem$ = this.store.selectedOrderItem$;
selectedItemOrderType$ = this.selectedItem$.pipe(map((item) => item?.features?.orderType));
coverOrderItems$ = this.store.coverOrderItems$;
displayedCompartmentInfo$ = this.store.compartmentInfo$;
@@ -128,29 +141,36 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
try {
this.changeActionLoader$.next(action.command);
this.store.setDisableHeaderStatusDropdown(true);
const context = await this.execAction({ action, latestCompartmentCode, latestCompartmentInfo });
if (!!context) {
if (action.command.includes('ARRIVED') || action.command.includes('PRINT_PRICEDIFFQRCODELABEL')) {
await this.router.navigate(this._pickupShelfInNavigationService.defaultRoute().path);
asapScheduler.schedule(async () => {
await this.navigateBasedOnCurrentView();
}, 100);
} else {
const item = context?.items.find((_) => true);
await this.router.navigate(
this._pickupShelfInNavigationService.detailRoute({
item: {
compartmentCode: item.compartmentCode,
orderId: item.orderId,
orderNumber: item.orderNumber,
processingStatus: item.processingStatus,
orderItemSubsetId: item.orderItemSubsetId,
compartmentInfo: item.compartmentInfo,
},
}).path,
{ queryParamsHandling: 'preserve' }
);
this.listStore.patchOrderItem({
orderItemSubsetId: item.orderItemSubsetId,
changes: { processingStatus: item.processingStatus },
});
if (!!item) {
await this.router.navigate(
this._pickupShelfInNavigationService.detailRoute({
item: {
compartmentCode: item.compartmentCode,
orderId: item.orderId,
orderNumber: item.orderNumber,
processingStatus: item.processingStatus,
orderItemSubsetId: item.orderItemSubsetId,
compartmentInfo: item.compartmentInfo,
},
side: false,
}).path,
{ queryParamsHandling: 'preserve' }
);
this.listStore.patchOrderItem({
orderItemSubsetId: item.orderItemSubsetId,
changes: { processingStatus: item.processingStatus },
});
}
}
}
} catch (error) {
@@ -161,12 +181,32 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
});
}
asapScheduler.schedule(() => {
this.runCheckTrigger.next();
}, 100);
setTimeout(() => {
this.store.setDisableHeaderStatusDropdown(false);
this.changeActionLoader$.next(undefined);
}, 1000);
}
async navigateBasedOnCurrentView() {
const currentView = await this._activatedRoute?.snapshot?.queryParams?.view;
switch (currentView) {
case 'reservation':
return this.router.navigate(['/filiale', 'goods', 'in', 'reservation'], { queryParamsHandling: 'preserve' });
case 'cleanup':
return this.router.navigate(['/filiale', 'goods', 'in', 'cleanup'], { queryParamsHandling: 'preserve' });
case 'remission':
return this.router.navigate(['/filiale', 'goods', 'in', 'preview'], { queryParamsHandling: 'preserve' });
case 'wareneingangsliste':
return this.router.navigate(['/filiale', 'goods', 'in', 'list'], { queryParamsHandling: 'preserve' });
default:
return this.router.navigate(this._pickupShelfInNavigationService.defaultRoute().path);
}
}
updateDate({ date, type }: { date: Date; type?: 'delivery' | 'pickup' | 'preferred' }) {
switch (type) {
case 'delivery':
@@ -196,28 +236,34 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
async navigateToEditPage(orderItem: DBHOrderItemListItemDTO) {
await this.router.navigate(
this._pickupShelfInNavigationService.editRoute({
compartmentCode: orderItem.compartmentCode,
orderId: orderItem.orderId,
orderNumber: orderItem.orderNumber,
processingStatus: orderItem.processingStatus,
orderItemSubsetId: orderItem.orderItemSubsetId,
compartmentInfo: orderItem.compartmentInfo,
}).path,
this._pickupShelfInNavigationService.editRoute(
{
compartmentCode: orderItem.compartmentCode,
orderId: orderItem.orderId,
orderNumber: orderItem.orderNumber,
processingStatus: orderItem.processingStatus,
orderItemSubsetId: orderItem.orderItemSubsetId,
compartmentInfo: orderItem.compartmentInfo,
},
{ side: this.side }
).path,
{ queryParams: { buyerNumber: orderItem?.buyerNumber }, queryParamsHandling: 'merge' }
);
}
async navigateToHistoryPage(orderItem: DBHOrderItemListItemDTO) {
await this.router.navigate(
this._pickupShelfInNavigationService.historyRoute({
compartmentCode: orderItem.compartmentCode,
orderId: orderItem.orderId,
orderNumber: orderItem.orderNumber,
processingStatus: orderItem.processingStatus,
orderItemSubsetId: orderItem.orderItemSubsetId,
compartmentInfo: orderItem.compartmentInfo,
}).path,
this._pickupShelfInNavigationService.historyRoute(
{
compartmentCode: orderItem.compartmentCode,
orderId: orderItem.orderId,
orderNumber: orderItem.orderNumber,
processingStatus: orderItem.processingStatus,
orderItemSubsetId: orderItem.orderItemSubsetId,
compartmentInfo: orderItem.compartmentInfo,
},
{ side: this.side }
).path,
{ queryParams: { orderItemSubsetId: orderItem.orderItemSubsetId }, queryParamsHandling: 'merge' }
);
}
@@ -229,6 +275,7 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
async coverClick(orderItems: DBHOrderItemListItemDTO[]) {
if (orderItems.length === 1) {
const item = orderItems.find((_) => true);
await this.router.navigate(
this._pickupShelfInNavigationService.detailRoute({
item: {
@@ -239,6 +286,7 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
processingStatus: item.processingStatus,
orderItemSubsetId: item.orderItemSubsetId,
},
side: this.side,
}).path,
{ queryParamsHandling: 'preserve' }
);

View File

@@ -40,6 +40,7 @@ export class PickupShelfInEditComponent extends PickupShelfDetailsBaseComponent
compartmentInfo,
orderItemSubsetId: this.store?.selectPreviousSelectedOrderItemSubsetId,
},
side: this.side,
}).path,
{ queryParamsHandling: 'preserve' }
);

View File

@@ -34,14 +34,3 @@ page-pickup-shelf-list-item {
.cta-action-secondary {
@apply bg-white text-brand;
}
::ng-deep page-pickup-shelf-in-list ui-scroll-container {
height: 100% !important;
overflow: hidden !important;
}
::ng-deep page-pickup-shelf-in-list ui-scroll-container .scroll-container {
height: 100% !important;
max-height: 100% !important;
gap: 0.625rem !important;
}

View File

@@ -40,57 +40,43 @@
</div>
</div>
<div class="h-full relative overflow-hidden overflow-y-scroll">
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
class="page-pickup-shelf-in-list__scroll-container m-0 p-0"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[showScrollbar]="false"
[containerHeight]="25"
[showScrollArrow]="false"
[showSpacer]="(primaryOutletActive$ | async) || (isTablet$ | async) || (isDesktopSmall$ | async)"
>
<div
class="page-pickup-shelf-in-list__items-list w-full"
*ngFor="let bueryNumberGroup of list$ | async | groupBy: byBuyerNumberFn; trackBy: trackByGroupFn"
>
<ng-container *ngIf="bueryNumberGroup.items[0]; let firstItem">
<div
class="page-pickup-shelf-in-list__item-header-group w-full grid grid-flow-col gap-x-4 items-center justify-between bg-white text-xl rounded-t p-4 font-bold mb-px-2"
>
<h3>
{{ firstItem?.organisation }}
<ng-container *ngIf="!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)"> - </ng-container>
{{ firstItem?.lastName }}
{{ firstItem?.firstName }}
</h3>
</div>
</ng-container>
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; trackBy: trackByGroupFn">
<ng-container *ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; trackBy: trackByGroupFn">
<ng-container
*ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; trackBy: trackByGroupFn"
>
<page-pickup-shelf-list-item
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
class="page-pickup-shelf-in-list__result-item mb-[0.125rem]"
[item]="item"
[primaryOutletActive]="primaryOutletActive$ | async"
[itemDetailsLink]="getItemDetailsLink(item)"
></page-pickup-shelf-list-item>
</ng-container>
</ng-container>
</ng-container>
</div>
<page-pickup-shelf-list-item-loader *ngIf="fetching$ | async"></page-pickup-shelf-list-item-loader>
</ui-scroll-container>
</div>
<ng-template #emptyMessage>
<div class="empty-message">
<div sharedScrollContainer class="overflow-scroll" (scrolledToBottom)="loadMore()">
<div class="empty-message" *ngIf="listEmpty$ | async">
Es sind im Moment keine Bestellposten vorhanden,<br />
die bearbeitet werden können.
</div>
</ng-template>
<div
class="page-pickup-shelf-in-list__items-list mb-[0.625rem]"
*ngFor="let bueryNumberGroup of list$ | async | groupBy: byBuyerNumberFn; trackBy: trackByGroupFn"
>
<ng-container *ngIf="bueryNumberGroup.items[0]; let firstItem">
<div
class="page-pickup-shelf-in-list__item-header-group w-full grid grid-flow-col gap-x-4 items-center justify-between bg-white text-xl rounded-t p-4 font-bold mb-px-2"
>
<h3>
{{ firstItem?.organisation }}
<ng-container *ngIf="!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)"> - </ng-container>
{{ firstItem?.lastName }}
{{ firstItem?.firstName }}
</h3>
</div>
</ng-container>
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; trackBy: trackByGroupFn">
<ng-container *ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; trackBy: trackByGroupFn">
<ng-container
*ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; trackBy: trackByGroupFn"
>
<page-pickup-shelf-list-item
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
class="page-pickup-shelf-in-list__result-item mb-[0.125rem]"
[item]="item"
[primaryOutletActive]="primaryOutletActive$ | async"
[itemDetailsLink]="getItemDetailsLink(item)"
></page-pickup-shelf-list-item>
</ng-container>
</ng-container>
</ng-container>
</div>
<page-pickup-shelf-list-item-loader *ngIf="fetching$ | async"></page-pickup-shelf-list-item-loader>
</div>

View File

@@ -15,11 +15,10 @@ import { ActivatedRoute, NavigationStart, Router, RouterLink } from '@angular/ro
import { Filter, FilterModule } from '@shared/components/filter';
import { IconModule } from '@shared/components/icon';
import { PickUpShelfListItemComponent } from '../../shared/pickup-shelf-list-item/pickup-shelf-list-item.component';
import { UiScrollContainerComponent, UiScrollContainerModule } from '@ui/scroll-container';
import { Group, GroupByPipe } from '@ui/common';
import { UiSpinnerModule } from '@ui/spinner';
import { PickupShelfInNavigationService } from '@shared/services';
import { debounceTime, map } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { DBHOrderItemListItemDTO } from '@swagger/oms';
import { Observable, combineLatest, of } from 'rxjs';
import { PickupShelfDetailsStore, PickupShelfStore } from '../../store';
@@ -28,6 +27,7 @@ import { EnvironmentService } from '@core/environment';
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';
@Component({
selector: 'page-pickup-shelf-in-list',
@@ -44,7 +44,7 @@ import { PickupShelfListItemLoaderComponent } from '../../shared/pickup-shelf-li
IconModule,
FilterModule,
PickUpShelfListItemComponent,
UiScrollContainerModule,
ScrollContainerDirective,
GroupByPipe,
UiSpinnerModule,
PickupShelfListItemLoaderComponent,
@@ -52,7 +52,7 @@ import { PickupShelfListItemLoaderComponent } from '../../shared/pickup-shelf-li
})
export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
@ViewChildren(PickUpShelfListItemComponent) listItems: QueryList<PickUpShelfListItemComponent>;
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
@ViewChild(ScrollContainerDirective) scrollContainer: ScrollContainerDirective;
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@@ -121,19 +121,22 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
) {}
ngOnInit() {
this.store.processId$.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(150)).subscribe((_) => {
if (!this.store.list.length) {
this.store.fetchList();
}
combineLatest([this.store.processId$, this._activatedRoute.queryParams])
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(([_, queryParams]) => {
if (!this.store.list.length || !isEqual(queryParams, this.cleanupQueryParams(this.store.filter.getQueryParams()))) {
this.store.setQueryParams(queryParams);
this.store.fetchList();
}
const scrollPos = this._getScrollPositionFromCache();
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
this._removeScrollPositionFromCache();
}, 150);
}
});
const scrollPos = this._getScrollPositionFromCache();
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
this._removeScrollPositionFromCache();
}, 150);
}
});
this._router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
if (event instanceof NavigationStart) {
@@ -146,13 +149,27 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
this.scrollItemIntoView();
}
cleanupQueryParams(params: Record<string, string> = {}) {
const clean = { ...params };
for (const key in clean) {
if (Object.prototype.hasOwnProperty.call(clean, key)) {
if (clean[key] == undefined) {
delete clean[key];
}
}
}
return clean;
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN });
}
private _addScrollPositionToCache(): void {
if (this._activatedRoute.outlet === 'primary') {
this._cache.set<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN }, this.scrollContainer?.scrollPos);
this._cache.set<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN }, this.scrollContainer?.scrollPosition);
}
}

View File

@@ -119,6 +119,7 @@ export class PickupShelfInComponent extends PickupShelfBaseComponent {
orderItemSubsetId,
compartmentInfo,
},
side: data?.queryParams?.side === 'false' ? false : true, // Fix Ticket #4493 - Wenn man von einer anderen Liste aus kommt z.B. Reservieren Liste, dann soll side in der Breadcrumb false sein
});
}

View File

@@ -1,5 +1,8 @@
:host {
@apply box-border grid overflow-y-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop;
@apply box-border overflow-y-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop;
}
.page-pickup-shelf-out-details__container {
grid-template-rows: auto 1fr;
}

View File

@@ -1,36 +1,55 @@
<div class="mb-4">
<page-pickup-shelf-details-header
[processId]="processId"
(handleAction)="handleAction($event)"
(updateDate)="updateDate($event)"
(editClick)="navigateToEditPage($event)"
></page-pickup-shelf-details-header>
<page-pickup-shelf-details-item
class="mb-px-2"
*ngFor="let item of orderItems$ | async; trackBy: trackByFnDBHOrderItemListItemDTO"
[orderItem]="item"
[selected]="true"
(historyClick)="navigateToHistoryPage($event)"
[order]="order$ | async"
(specialCommentChanged)="updateSpecialComment(item, $event)"
(sharedOnInit)="fetchNotifications(item)"
></page-pickup-shelf-details-item>
<page-pickup-shelf-details-tags
*ngIf="showTagsComponent$ | async"
[ngModel]="selectedCompartmentInfo$ | async"
(ngModelChange)="setSelectedCompartmentInfo($event)"
></page-pickup-shelf-details-tags>
</div>
<div class="grid overflow-y-scroll h-full" [class.page-pickup-shelf-out-details__container]="!(viewFetching$ | async)">
<ng-container *ngIf="viewFetching$ | async; else showView">
<div class="w-full flex flex-col justify-center items-center">
<ui-spinner [show]="true"></ui-spinner>
<div class="mt-6">Daten werden geladen ...</div>
</div>
</ng-container>
<div class="page-pickup-shelf-out-details__action-wrapper">
<button
[disabled]="actionsDisabled$ | async"
class="cta-action shadow-action"
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
*ngFor="let action of mainActions$ | async"
(click)="handleAction(action)"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
</button>
<ng-template #showView>
<div class="mb-4">
<page-pickup-shelf-details-header
[processId]="processId"
(handleAction)="handleAction($event)"
(updateDate)="updateDate($event)"
(editClick)="navigateToEditPage($event)"
></page-pickup-shelf-details-header>
<ng-container *ngFor="let group of groupedItems$ | async; trackBy: trackByFnGroupDBHOrderItemListItemDTO">
<page-pickup-shelf-details-items-group
[orderType]="group.type"
[groupedItems]="group.items"
></page-pickup-shelf-details-items-group>
<page-pickup-shelf-details-item
class="mb-px-2"
*ngFor="let item of group.items; trackBy: trackByFnDBHOrderItemListItemDTO"
[orderItem]="item"
[selected]="true"
(historyClick)="navigateToHistoryPage($event)"
[order]="order$ | async"
(specialCommentChanged)="updateSpecialComment(item, $event)"
(sharedOnInit)="fetchNotifications(item)"
></page-pickup-shelf-details-item>
</ng-container>
<page-pickup-shelf-details-tags
*ngIf="showTagsComponent$ | async"
[ngModel]="selectedCompartmentInfo$ | async"
(ngModelChange)="setSelectedCompartmentInfo($event)"
></page-pickup-shelf-details-tags>
</div>
<div class="page-pickup-shelf-out-details__action-wrapper">
<button
[disabled]="actionsDisabled$ | async"
class="cta-action shadow-action"
[class.cta-action-primary]="action.selected"
[class.cta-action-secondary]="!action.selected"
*ngFor="let action of mainActions$ | async"
(click)="handleAction(action)"
>
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
</button>
</div>
</ng-template>
</div>

View File

@@ -3,15 +3,17 @@ import { PickupShelfDetailsBaseComponent } from '../../pickup-shelf-details-base
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { PickUpShelfDetailsHeaderComponent } from '../../shared/pickup-shelf-details-header/pickup-shelf-details-header.component';
import { PickUpShelfDetailsItemComponent } from '../../shared/pickup-shelf-details-item/pickup-shelf-details-item.component';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString, OrderItemProcessingStatusValue } from '@swagger/oms';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
import { PickUpShelfOutNavigationService } from '@shared/services';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, Observable, asapScheduler, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { PickUpShelfDetailsTagsComponent } from '../../shared/pickup-shelf-details-tags/pickup-shelf-details-tags.component';
import { UiSpinnerModule } from '@ui/spinner';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
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';
@Component({
selector: 'page-pickup-shelf-out-details',
@@ -27,6 +29,7 @@ import { FormsModule } from '@angular/forms';
PickUpShelfDetailsHeaderComponent,
PickUpShelfDetailsItemComponent,
PickUpShelfDetailsTagsComponent,
PickUpShelfDetailsItemsGroupComponent,
UiSpinnerModule,
OnInitDirective,
FormsModule,
@@ -34,6 +37,8 @@ import { FormsModule } from '@angular/forms';
providers: [],
})
export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseComponent {
runCheckTrigger = inject(RunCheckTrigger);
@ViewChild(PickUpShelfDetailsTagsComponent, { static: false })
pickUpShelfDetailsTags: PickUpShelfDetailsTagsComponent;
@@ -41,9 +46,30 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
order$ = this.store.order$;
orderItems$ = this.store.orderItems$;
groupedItems$: Observable<Array<{ type: string; items: DBHOrderItemListItemDTO[] }>> = this.store.orderItems$.pipe(
map((items) => {
const groups: Array<{ type: string; items: DBHOrderItemListItemDTO[] }> = [];
// New Set to remove duplicates
const types = Array.from(new Set(items.map((item) => item?.features?.orderType)));
for (let type of types) {
const filteredItemsByType = items.filter((item) => item?.features?.orderType === type);
if (!!type && filteredItemsByType.length > 0) {
// Add items to matching orderType group
groups.push({ type, items: filteredItemsByType });
}
}
return groups;
})
);
fetching$ = this.store.fetchingOrder$;
fetchingItems$ = this.store.fetchingOrderItems$;
viewFetching$ = combineLatest([this.fetching$, this.fetchingItems$]).pipe(map(([fetching, fetchingItems]) => fetching || fetchingItems));
selectedCompartmentInfo = this.store.selectedCompartmentInfo;
@@ -60,6 +86,8 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
mainActions$ = this.store.mainActions$;
trackByFnGroupDBHOrderItemListItemDTO = (index: number, group: { type: string; items: DBHOrderItemListItemDTO[] }) => group.type;
trackByFnDBHOrderItemListItemDTO = (index: number, item: DBHOrderItemListItemDTO) => item.orderItemSubsetId;
get processId() {
@@ -105,6 +133,10 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
});
}
asapScheduler.schedule(() => {
this.runCheckTrigger.next();
}, 100);
setTimeout(() => {
this.store.setDisableHeaderStatusDropdown(false);
this.changeActionLoader$.next(undefined);

View File

@@ -34,14 +34,3 @@ page-pickup-shelf-list-item {
.cta-action-secondary {
@apply bg-white text-brand;
}
::ng-deep page-pcikup-shelf-out-list ui-scroll-container {
height: 100% !important;
overflow: hidden !important;
}
::ng-deep page-pcikup-shelf-out-list ui-scroll-container .scroll-container {
height: 100% !important;
max-height: 100% !important;
gap: 0.625rem !important;
}

View File

@@ -40,58 +40,47 @@
</div>
</div>
<div class="h-full relative overflow-hidden overflow-y-scroll">
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
class="page-pickup-shelf-out-list__scroll-container m-0 p-0"
(reachEnd)="loadMore()"
[deltaEnd]="150"
[showScrollbar]="false"
[containerHeight]="25"
[showScrollArrow]="false"
[showSpacer]="(primaryOutletActive$ | async) || (isTablet$ | async) || (isDesktopSmall$ | async)"
<div sharedScrollContainer class="overflow-scroll" (scrolledToBottom)="loadMore()">
<div class="empty-message" *ngIf="listEmpty$ | async">
Es sind im Moment keine Bestellposten vorhanden,<br />
die bearbeitet werden können.
</div>
<div
class="page-pickup-shelf-out-list__items-list w-full mb-[0.625rem]"
*ngFor="let bueryNumberGroup of list$ | async | groupBy: byBuyerNumberFn; trackBy: trackByGroupFn"
>
<ng-container *ngIf="processId$ | async; let processId">
<ng-container *ngIf="bueryNumberGroup.items[0]; let firstItem">
<div
class="page-pickup-shelf-out-list__items-list w-full"
*ngFor="let bueryNumberGroup of list$ | async | groupBy: byBuyerNumberFn; trackBy: trackByGroupFn"
class="page-pickup-shelf-out-list__item-header-group w-full grid grid-flow-col gap-x-4 items-center justify-between bg-white text-xl rounded-t p-4 font-bold mb-px-2"
>
<ng-container *ngIf="bueryNumberGroup.items[0]; let firstItem">
<div
class="page-pickup-shelf-out-list__item-header-group w-full grid grid-flow-col gap-x-4 items-center justify-between bg-white text-xl rounded-t p-4 font-bold mb-px-2"
>
<h3>
{{ firstItem?.organisation }}
<ng-container *ngIf="!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)"> - </ng-container>
{{ firstItem?.lastName }}
{{ firstItem?.firstName }}
</h3>
</div>
</ng-container>
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; trackBy: trackByGroupFn">
<ng-container
*ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; trackBy: trackByGroupFn"
>
<ng-container
*ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; trackBy: trackByGroupFn"
>
<page-pickup-shelf-list-item
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
class="page-pickup-shelf-out-list__result-item mb-[0.125rem]"
[item]="item"
[primaryOutletActive]="primaryOutletActive$ | async"
[itemDetailsLink]="getItemDetailsLink(item)"
[selectedItem]="getSelectedItem$(item) | async"
[isItemSelectable]="getIsItemSelectable$(item) | async"
></page-pickup-shelf-list-item>
</ng-container>
</ng-container>
</ng-container>
<h3>
{{ firstItem?.organisation }}
<ng-container *ngIf="!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)"> - </ng-container>
{{ firstItem?.lastName }}
{{ firstItem?.firstName }}
</h3>
</div>
</ng-container>
<page-pickup-shelf-list-item-loader *ngIf="fetching$ | async"></page-pickup-shelf-list-item-loader>
</ui-scroll-container>
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; trackBy: trackByGroupFn">
<ng-container *ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; trackBy: trackByGroupFn">
<ng-container
*ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; trackBy: trackByGroupFn"
>
<page-pickup-shelf-list-item
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
class="page-pickup-shelf-out-list__result-item mb-[0.125rem]"
[item]="item"
[primaryOutletActive]="primaryOutletActive$ | async"
[itemDetailsLink]="getItemDetailsLink(item)"
[selectedItem]="getSelectedItem$(item) | async"
[isItemSelectable]="getIsItemSelectable$(item) | async"
></page-pickup-shelf-list-item>
</ng-container>
</ng-container>
</ng-container>
</div>
<page-pickup-shelf-list-item-loader *ngIf="fetching$ | async"></page-pickup-shelf-list-item-loader>
<div class="actions z-sticky h-0 gap-4" *ngIf="actions$ | async; let actions">
<button
[disabled]="(loadingFetchedActionButton$ | async) || (fetching$ | async)"
@@ -105,10 +94,3 @@
</button>
</div>
</div>
<ng-template #emptyMessage>
<div class="empty-message">
Es sind im Moment keine Bestellposten vorhanden,<br />
die bearbeitet werden können.
</div>
</ng-template>

View File

@@ -21,7 +21,6 @@ import { Filter, FilterModule } from '@shared/components/filter';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { isEqual } from 'lodash';
import { PickUpShelfListItemComponent } from '../../shared/pickup-shelf-list-item/pickup-shelf-list-item.component';
import { UiScrollContainerComponent, UiScrollContainerModule } from '@ui/scroll-container';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString, OrderItemProcessingStatusValue } from '@swagger/oms';
import { Group, GroupByPipe } from '@shared/pipes/group-by';
import { UiSpinnerModule } from '@ui/spinner';
@@ -30,6 +29,7 @@ import { PickupShelfListItemLoaderComponent } from '../../shared/pickup-shelf-li
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';
@Component({
selector: 'page-pcikup-shelf-out-list',
@@ -39,6 +39,7 @@ import { CacheService } from '@core/cache';
host: { class: 'page-pcikup-shelf-out-list' },
standalone: true,
imports: [
ScrollContainerDirective,
AsyncPipe,
NgFor,
RouterLink,
@@ -46,7 +47,6 @@ import { CacheService } from '@core/cache';
IconModule,
FilterModule,
PickUpShelfListItemComponent,
UiScrollContainerModule,
GroupByPipe,
UiSpinnerModule,
PickupShelfListItemLoaderComponent,
@@ -54,7 +54,7 @@ import { CacheService } from '@core/cache';
})
export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
@ViewChildren(PickUpShelfListItemComponent) listItems: QueryList<PickUpShelfListItemComponent>;
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
@ViewChild(ScrollContainerDirective) scrollContainer: ScrollContainerDirective;
private _pickupShelfOutNavigationService = inject(PickUpShelfOutNavigationService);
@@ -171,7 +171,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
private _addScrollPositionToCache(): void {
if (this._activatedRoute.outlet === 'primary') {
this._cache.set<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN }, this.scrollContainer?.scrollPos);
this._cache.set<number>({ processId: this.store.processId, token: this.SCROLL_POSITION_TOKEN }, this.scrollContainer?.scrollPosition);
}
}

View File

@@ -145,96 +145,6 @@
</div>
</div>
</div>
<div class="flex flex-row items-center relative bg-[#F5F7FA] p-4 rounded-t">
<div *ngIf="showFeature" class="flex flex-row items-center mr-3">
<ng-container [ngSwitch]="order.features.orderType">
<ng-container *ngSwitchCase="'Versand'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
</div>
<p class="font-bold text-p1">Versand</p>
</ng-container>
<ng-container *ngSwitchCase="'DIG-Versand'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
</div>
<p class="font-bold text-p1">Versand</p>
</ng-container>
<ng-container *ngSwitchCase="'B2B-Versand'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-b2b-truck"></shared-icon>
</div>
<p class="font-bold text-p1">B2B-Versand</p>
</ng-container>
<ng-container *ngSwitchCase="'Abholung'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-box-out"></shared-icon>
</div>
<p class="font-bold text-p1 mr-3">Abholung</p>
{{ orderItem.targetBranch }}
</ng-container>
<ng-container *ngSwitchCase="'Rücklage'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-shopping-bag"></shared-icon>
</div>
<p class="font-bold text-p1">Rücklage</p>
</ng-container>
<ng-container *ngSwitchCase="'Download'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-download"></shared-icon>
</div>
<p class="font-bold text-p1">Download</p>
</ng-container>
</ng-container>
</div>
<div class="page-pickup-shelf-details-header__additional-addresses" *ngIf="showAddresses">
<button (click)="openAddresses = !openAddresses" class="text-[#0556B4]">
Lieferadresse / Rechnungsadresse {{ openAddresses ? 'ausblenden' : 'anzeigen' }}
</button>
<div class="page-pickup-shelf-details-header__addresses-popover" *ngIf="openAddresses">
<button (click)="openAddresses = !openAddresses" class="close">
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
<div class="page-pickup-shelf-details-header__addresses-popover-data">
<div *ngIf="order.shipping" class="page-pickup-shelf-details-header__addresses-popover-delivery">
<p>Lieferadresse</p>
<div class="page-pickup-shelf-details-header__addresses-popover-delivery-data">
<ng-container *ngIf="order.shipping?.data?.organisation">
<p>{{ order.shipping?.data?.organisation?.name }}</p>
<p>{{ order.shipping?.data?.organisation?.department }}</p>
</ng-container>
<p>{{ order.shipping?.data?.firstName }} {{ order.shipping?.data?.lastName }}</p>
<p>{{ order.shipping?.data?.address?.info }}</p>
<p>{{ order.shipping?.data?.address?.street }} {{ order.shipping?.data?.address?.streetNumber }}</p>
<p>{{ order.shipping?.data?.address?.zipCode }} {{ order.shipping?.data?.address?.city }}</p>
</div>
</div>
<div *ngIf="order.billing" class="page-pickup-shelf-details-header__addresses-popover-billing">
<p>Rechnungsadresse</p>
<div class="page-pickup-shelf-details-header__addresses-popover-billing-data">
<ng-container *ngIf="order.billing?.data?.organisation">
<p>{{ order.billing?.data?.organisation?.name }}</p>
<p>{{ order.billing?.data?.organisation?.department }}</p>
</ng-container>
<p>{{ order.billing?.data?.firstName }} {{ order.billing?.data?.lastName }}</p>
<p>{{ order.billing?.data?.address?.info }}</p>
<p>{{ order.billing?.data?.address?.street }} {{ order.billing?.data?.address?.streetNumber }}</p>
<p>{{ order.billing?.data?.address?.zipCode }} {{ order.billing?.data?.address?.city }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="page-pickup-shelf-details-header__select grow" *ngIf="showMultiselect$ | async">
<button class="cta-select-all" (click)="selectAll()">Alle auswählen</button>
{{ selectedOrderItemCount$ | async }} von {{ orderItemCount$ | async }} Titeln
</div>
</div>
</div>
<ng-template #featureLoading>

View File

@@ -81,51 +81,6 @@
}
}
.page-pickup-shelf-details-header__select {
@apply flex flex-col items-end;
}
.page-pickup-shelf-details-header__additional-addresses {
.page-pickup-shelf-details-header__addresses-popover {
@apply absolute inset-x-0 top-16 bottom-0 z-popover;
.close {
@apply bg-white absolute right-0 p-6;
}
.page-pickup-shelf-details-header__addresses-popover-data {
@apply flex flex-col bg-white p-6 z-popover min-h-[200px];
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
.page-pickup-shelf-details-header__addresses-popover-delivery {
@apply grid mb-6;
grid-template-columns: 9.5625rem auto;
.page-pickup-shelf-details-header__addresses-popover-delivery-data {
p {
@apply font-bold;
}
}
}
.page-pickup-shelf-details-header__addresses-popover-billing {
@apply grid;
grid-template-columns: 9.5625rem auto;
.page-pickup-shelf-details-header__addresses-popover-billing-data {
p {
@apply font-bold;
}
}
}
}
}
}
.cta-select-all {
@apply text-brand bg-transparent text-p2 font-bold outline-none border-none;
}
.fetch-wrapper {
@apply grid grid-flow-col gap-4;
}

View File

@@ -92,10 +92,6 @@ export class PickUpShelfDetailsHeaderComponent {
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
today = this.dateAdapter.today();
selectedOrderItemCount$ = this._store.selectedOrderItemIds$.pipe(map((ids) => ids?.length ?? 0));
orderItemCount$ = this._store.orderItems$.pipe(map((items) => items?.length ?? 0));
orderItem$ = this._store.orderItems$.pipe(map((orderItems) => orderItems?.find((_) => true)));
changeDateLoader$ = new BehaviorSubject<boolean>(false);
@@ -115,14 +111,6 @@ export class PickUpShelfDetailsHeaderComponent {
statusActions$ = this.orderItem$.pipe(map((orderItem) => orderItem?.actions?.filter((action) => action.enabled === false)));
get isItemSelectable() {
return this._store.orderItems?.some((item) => !!item?.actions && item?.actions?.length > 0);
}
showMultiselect$ = combineLatest([this._store.orderItems$, this._store.fetchPartial$]).pipe(
map(([orderItems, fetchPartial]) => this.isItemSelectable && fetchPartial && orderItems?.length > 1)
);
crudaUpdate$ = this.orderItem$.pipe(map((orederItem) => !!(orederItem?.cruda & 4)));
editButtonDisabled$ = combineLatest([this.changeStatusLoader$, this.crudaUpdate$]).pipe(
@@ -133,20 +121,6 @@ export class PickUpShelfDetailsHeaderComponent {
map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate)
);
openAddresses: boolean = false;
get digOrderNumber(): string {
return this.order?.linkedRecords?.find((_) => true)?.number;
}
get showAddresses(): boolean {
return (this.order?.orderType === 2 || this.order?.orderType === 4) && (!!this.order?.shipping || !!this.order?.billing);
}
get showFeature(): boolean {
return !!this.order?.features && !!this.order?.features?.orderType;
}
constructor(private dateAdapter: DateAdapter, private cdr: ChangeDetectorRef) {}
async handleActionClick(action?: KeyValueDTOOfStringAndString) {
@@ -156,10 +130,6 @@ export class PickUpShelfDetailsHeaderComponent {
this.cdr.markForCheck();
}
selectAll() {
this._store.selectAllOrderItemIds();
}
updatePickupDeadline(date: Date) {
this.updateDate.emit({ date, type: 'pickup' });
}

View File

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

View File

@@ -11,8 +11,10 @@
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
Per E-Mail benachrichtigt <br />
<ng-container *ngFor="let notification of emailNotificationDates$ | async">
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
<ng-container *ngFor="let notifications of emailNotificationDates$ | async">
<ng-container *ngFor="let notificationDate of notifications.dates">
{{ notifications.type | notificationType }} {{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
</ng-container>
</ng-container>
</ui-tooltip>
</ng-container>
@@ -20,8 +22,10 @@
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
Per SMS benachrichtigt <br />
<ng-container *ngFor="let notification of smsNotificationDates$ | async">
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
<ng-container *ngFor="let notifications of smsNotificationDates$ | async">
<ng-container *ngFor="let notificationDate of notifications.dates">
{{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr<br />
</ng-container>
</ng-container>
</ui-tooltip>
</ng-container>
@@ -86,7 +90,7 @@
<div class="label">ISBN/EAN</div>
<div class="value">{{ orderItem.product?.ean }}</div>
</div>
<div class="detail" *ngIf="!!orderItem.price">
<div class="detail" *ngIf="orderItem.price !== undefined">
<div class="label">Preis</div>
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
</div>

View File

@@ -24,6 +24,7 @@ import { map, switchMap } from 'rxjs/operators';
import { Subject, combineLatest } from 'rxjs';
import { PickupShelfDetailsStore } from '../../store';
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { NotificationTypePipe } from './notification-type.pipe';
export interface PickUpShelfDetailsItemComponentState {
orderItem?: DBHOrderItemListItemDTO;
@@ -55,6 +56,7 @@ export interface PickUpShelfDetailsItemComponentState {
PickupShelfPaymentTypePipe,
IconModule,
UiQuantityDropdownModule,
NotificationTypePipe,
],
})
export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfDetailsItemComponentState> implements OnInit {
@@ -117,6 +119,7 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
set quantity(quantity: number) {
if (this.quantity !== quantity) {
this.patchState({ quantity });
this._store.setSelectedOrderItemQuantity({ orderItemSubsetId: this.orderItem.orderItemSubsetId, quantity });
}
}

View File

@@ -0,0 +1,89 @@
<div class="flex flex-row items-center relative bg-[#F5F7FA] p-4 rounded-t mb-[0.125rem]">
<div *ngIf="showFeature" class="flex flex-row items-center mr-3">
<ng-container [ngSwitch]="orderType">
<ng-container *ngSwitchCase="'Versand'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
</div>
<p class="font-bold text-p1">Versand</p>
</ng-container>
<ng-container *ngSwitchCase="'DIG-Versand'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-truck"></shared-icon>
</div>
<p class="font-bold text-p1">Versand</p>
</ng-container>
<ng-container *ngSwitchCase="'B2B-Versand'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-b2b-truck"></shared-icon>
</div>
<p class="font-bold text-p1">B2B-Versand</p>
</ng-container>
<ng-container *ngSwitchCase="'Abholung'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-box-out"></shared-icon>
</div>
<p class="font-bold text-p1 mr-3">Abholung</p>
{{ targetBranches }}
</ng-container>
<ng-container *ngSwitchCase="'Rücklage'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-shopping-bag"></shared-icon>
</div>
<p class="font-bold text-p1">Rücklage</p>
</ng-container>
<ng-container *ngSwitchCase="'Download'">
<div class="flex items-center justify-center bg-[#D8DFE5] w-[2.25rem] h-[2.25rem] rounded rounded-br-none mr-2">
<shared-icon [size]="24" icon="isa-download"></shared-icon>
</div>
<p class="font-bold text-p1">Download</p>
</ng-container>
</ng-container>
</div>
<div class="page-pickup-shelf-details-items-group__additional-addresses" *ngIf="showAddresses">
<button (click)="openAddresses = !openAddresses" class="text-[#0556B4]">
Lieferadresse / Rechnungsadresse {{ openAddresses ? 'ausblenden' : 'anzeigen' }}
</button>
<div class="page-pickup-shelf-details-items-group__addresses-popover" *ngIf="openAddresses">
<button (click)="openAddresses = !openAddresses" class="close">
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
<div class="page-pickup-shelf-details-items-group__addresses-popover-data">
<div *ngIf="order.shipping" class="page-pickup-shelf-details-items-group__addresses-popover-delivery">
<p>Lieferadresse</p>
<div class="page-pickup-shelf-details-items-group__addresses-popover-delivery-data">
<ng-container *ngIf="order.shipping?.data?.organisation">
<p>{{ order.shipping?.data?.organisation?.name }}</p>
<p>{{ order.shipping?.data?.organisation?.department }}</p>
</ng-container>
<p>{{ order.shipping?.data?.firstName }} {{ order.shipping?.data?.lastName }}</p>
<p>{{ order.shipping?.data?.address?.info }}</p>
<p>{{ order.shipping?.data?.address?.street }} {{ order.shipping?.data?.address?.streetNumber }}</p>
<p>{{ order.shipping?.data?.address?.zipCode }} {{ order.shipping?.data?.address?.city }}</p>
</div>
</div>
<div *ngIf="order.billing" class="page-pickup-shelf-details-items-group__addresses-popover-billing">
<p>Rechnungsadresse</p>
<div class="page-pickup-shelf-details-items-group__addresses-popover-billing-data">
<ng-container *ngIf="order.billing?.data?.organisation">
<p>{{ order.billing?.data?.organisation?.name }}</p>
<p>{{ order.billing?.data?.organisation?.department }}</p>
</ng-container>
<p>{{ order.billing?.data?.firstName }} {{ order.billing?.data?.lastName }}</p>
<p>{{ order.billing?.data?.address?.info }}</p>
<p>{{ order.billing?.data?.address?.street }} {{ order.billing?.data?.address?.streetNumber }}</p>
<p>{{ order.billing?.data?.address?.zipCode }} {{ order.billing?.data?.address?.city }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="page-pickup-shelf-details-items-group__select grow" *ngIf="showMultiselect$ | async">
<button class="cta-select-all" (click)="selectAll()">Alle auswählen</button>
{{ selectedOrderItemCount$ | async }} von {{ groupedItems.length }} Titeln
</div>
</div>

View File

@@ -0,0 +1,48 @@
:host {
@apply grid grid-flow-row;
}
.page-pickup-shelf-details-items-group__select {
@apply flex flex-col items-end;
}
.page-pickup-shelf-details-items-group__additional-addresses {
.page-pickup-shelf-details-items-group__addresses-popover {
@apply absolute inset-x-0 top-16 bottom-0 z-popover;
.close {
@apply bg-white absolute right-0 p-6;
}
.page-pickup-shelf-details-items-group__addresses-popover-data {
@apply flex flex-col bg-white p-6 z-popover min-h-[200px];
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
.page-pickup-shelf-details-items-group__addresses-popover-delivery {
@apply grid mb-6;
grid-template-columns: 9.5625rem auto;
.page-pickup-shelf-details-items-group__addresses-popover-delivery-data {
p {
@apply font-bold;
}
}
}
.page-pickup-shelf-details-items-group__addresses-popover-billing {
@apply grid;
grid-template-columns: 9.5625rem auto;
.page-pickup-shelf-details-items-group__addresses-popover-billing-data {
p {
@apply font-bold;
}
}
}
}
}
}
.cta-select-all {
@apply text-brand bg-transparent text-p2 font-bold outline-none border-none;
}

View File

@@ -0,0 +1,65 @@
import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core';
import { PickupShelfDetailsStore } from '../../store';
import { map } from 'rxjs/operators';
import { DBHOrderItemListItemDTO, OrderDTO } from '@swagger/oms';
import { AsyncPipe, NgFor, NgIf, NgSwitch, NgSwitchCase } from '@angular/common';
import { IconModule } from '@shared/components/icon';
@Component({
selector: 'page-pickup-shelf-details-items-group',
templateUrl: 'pickup-shelf-details-items-group.component.html',
styleUrls: ['pickup-shelf-details-items-group.component.scss'],
standalone: true,
host: { class: 'page-pickup-shelf-details-items-group' },
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgIf, NgFor, NgSwitch, NgSwitchCase, IconModule, AsyncPipe],
})
export class PickUpShelfDetailsItemsGroupComponent implements OnInit {
private _store = inject(PickupShelfDetailsStore);
get order(): OrderDTO {
return this._store.order;
}
@Input() orderType: string;
@Input() groupedItems: DBHOrderItemListItemDTO[];
get firstGroupedItem() {
return this.groupedItems?.find((_) => true);
}
openAddresses: boolean = false;
get showAddresses(): boolean {
return (this.order?.orderType === 2 || this.order?.orderType === 4) && (!!this.order?.shipping || !!this.order?.billing);
}
get showFeature(): boolean {
return !!this.firstGroupedItem?.features && !!this.firstGroupedItem?.features?.orderType;
}
get targetBranches(): string {
return Array.from(new Set(this.groupedItems?.map((item) => item?.targetBranch))).join('; ');
}
get isItemSelectable() {
return this.groupedItems?.some((item) => !!item?.actions && item?.actions?.length > 0);
}
showMultiselect$ = this._store.fetchPartial$.pipe(
map((fetchPartial) => this.isItemSelectable && fetchPartial && this.groupedItems?.length > 1)
);
selectedOrderItemCount$ = this._store.selectedOrderItemIds$.pipe(
map((ids) => this.groupedItems?.filter((groupedItem) => ids?.includes(groupedItem?.orderItemSubsetId))?.length ?? 0)
);
constructor() {}
ngOnInit() {}
selectAll() {
this._store.selectAllOrderItemIds();
}
}

View File

@@ -41,6 +41,10 @@ export class PickupShelfFilterComponent {
return this._environment.matchDesktopLarge$.pipe(map((matches) => !(matches && this.sideOutlet === 'search')));
}
get isDesktopLarge() {
return this._environment.matchDesktopLarge();
}
get hasProcessId$() {
return this._activatedRoute?.parent?.params?.pipe(map((params) => !!params?.processId));
}
@@ -53,7 +57,9 @@ export class PickupShelfFilterComponent {
return this._activatedRoute?.parent?.params?.pipe(
map((params) => {
const hasProcessId = !!params?.processId;
if (!!this.order) {
// Ticket #4457 only route to details view if in split-screen
if (!!this.order && this.isDesktopLarge) {
return this._routeToShelfDetails(hasProcessId);
}
@@ -134,7 +140,8 @@ export class PickupShelfFilterComponent {
}
clearFilter(filter: Filter) {
this.store.setQueryParams({});
const { main_qs } = filter.getQueryParams();
this.store.setQueryParams({ main_qs });
}
hasSelectedOptions(filter: Filter) {

View File

@@ -10,6 +10,7 @@ import { DBHOrderItemListItemDTO } from '@swagger/oms';
import { Observable, combineLatest } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { PickupShelfStore } from '../../store';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({
selector: 'page-pickup-shelf-history',
@@ -90,6 +91,7 @@ export class PickUpShelfHistoryComponent {
orderItemSubsetId: item.orderItemSubsetId,
compartmentInfo: item.compartmentInfo,
},
side: coerceBooleanProperty(this._activatedRoute?.snapshot?.queryParams?.side),
}).path,
{ queryParamsHandling: 'preserve' }
);

View File

@@ -39,23 +39,23 @@
</div>
<div class="page-pickup-shelf-list-item__item-ean-quantity-changed flex flex-col">
<div class="page-pickup-shelf-list-item__item-ean text-p2 flex flex-row mb-[0.375rem]">
<div class="page-pickup-shelf-list-item__item-ean text-p2 flex flex-row mb-[0.375rem]" [attr.data-ean]="item?.product?.ean">
<div class="min-w-[7.5rem]">EAN</div>
<div class="font-bold">{{ item?.product?.ean }}</div>
</div>
<div class="page-pickup-shelf-list-item__item-quantity flex flex-row text-p2 mb-[0.375rem]">
<div class="page-pickup-shelf-list-item__item-quantity flex flex-row text-p2 mb-[0.375rem]" [attr.data-menge]="item.quantity">
<div class="min-w-[7.5rem]">Menge</div>
<div class="font-bold">{{ item.quantity }} x</div>
</div>
<div class="page-pickup-shelf-list-item__item-changed text-p2">
<div class="page-pickup-shelf-list-item__item-changed text-p2" [attr.data-geaendert]="item?.processingStatusDate">
<div *ngIf="showChangeDate; else showOrderDate" class="flex flex-row">
<div class="min-w-[7.5rem]">Geändert</div>
<div class="font-bold">{{ item?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
</div>
<ng-template #showOrderDate>
<div class="flex flex-row">
<div class="flex flex-row" [attr.data-bestelldatum]="item?.orderDate">
<div class="min-w-[7.5rem]">Bestelldatum</div>
<div class="font-bold">{{ item?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
</div>
@@ -67,6 +67,8 @@
<div
*ngIf="showCompartmentCode"
class="page-pickup-shelf-list-item__item-order-number text-h3 mb-[0.375rem] self-end font-bold break-all text-right"
[attr.data-compartment-code]="item?.compartmentCode"
[attr.data-compartment-info]="item?.compartmentInfo"
>
{{ item?.compartmentCode }}{{ item?.compartmentInfo && '_' + item?.compartmentInfo }}
</div>
@@ -75,6 +77,7 @@
<div
class="page-pickup-shelf-list-item__item-processing-status flex flex-row mb-[0.375rem] rounded p-3 py-[0.125rem] text-white"
[style]="processingStatusColor"
[attr.data-processing-status]="item.processingStatus"
>
{{ item.processingStatus | processingStatus }}
</div>
@@ -83,6 +86,7 @@
<div
class="font-bold flex flex-row items-center justify-center text-p2 text-[#26830C]"
*ngIf="item.features?.paid && (isTablet || isDesktopSmall || primaryOutletActive)"
[attr.data-paid]="item.features?.paid"
>
<shared-icon class="flex items-center justify-center mr-[0.375rem]" [size]="24" icon="credit-card"></shared-icon>
{{ item.features?.paid }}
@@ -105,7 +109,10 @@
/>
</div>
<div class="page-pickup-shelf-list-item__item-special-comment break-words font-bold text-p2 mt-[0.375rem] text-[#996900]">
<div
[attr.data-special-comment]="item?.specialComment"
class="page-pickup-shelf-list-item__item-special-comment break-words font-bold text-p2 mt-[0.375rem] text-[#996900]"
>
{{ item?.specialComment }}
</div>
</div>

View File

@@ -166,22 +166,33 @@ export const selectNotifications = (orderItemSubsetId: number) => (s: PickupShel
}, {} as Record<string, Date[]>);
};
export const selectLatestNotificationDatesFor = (orderItemSubsetId: number, key: string) => (s: PickupShelfDetailsState) => {
export const selectLatestNotificationDatesFor = (orderItemSubsetId: number, keys: string[]) => (s: PickupShelfDetailsState) => {
const notifications = selectNotifications(orderItemSubsetId)(s);
return (
notifications?.[key]?.filter((date) => {
let dates: Array<{ type: string; dates: Date[] }> = [];
for (const key of keys) {
const notification = notifications?.[key] ?? [];
const validDates = notification.filter((date) => {
// check if curr is an invalid date
return !isNaN(date.getTime());
}) ?? []
);
});
const mappedDates = { type: key, dates: validDates };
if (mappedDates.dates?.length > 0) {
dates.push(mappedDates);
}
}
return dates;
};
export const selectLatestEmailNotificationDates = (orderItemSubsetId: number) => (s: PickupShelfDetailsState) => {
return selectLatestNotificationDatesFor(orderItemSubsetId, 'NOTIFICATION_EMAIL')(s);
return selectLatestNotificationDatesFor(orderItemSubsetId, ['NOTIFICATION_EMAIL', 'REMINDER_EMAIL', 'ORDERCONFIRMATION_EMAIL'])(s);
};
export const selectLatestSmsNotificationDate2 = (orderItemSubsetId: number) => (s: PickupShelfDetailsState) => {
return selectLatestNotificationDatesFor(orderItemSubsetId, 'NOTIFICATION_SMS')(s);
return selectLatestNotificationDatesFor(orderItemSubsetId, ['NOTIFICATION_SMS'])(s);
};
export const selectCanSelectAction = (s: PickupShelfDetailsState) => {
@@ -231,8 +242,7 @@ export const selectMainActions = (s: PickupShelfDetailsState) => {
return firstItem?.actions
?.filter((action) => typeof action?.enabled !== 'boolean')
?.filter((action) => (fetchPartial ? !action.command.includes('FETCHED_PARTIAL') : true))
?.sort((a, b) => (a.selected === b.selected ? 0 : a.selected ? -1 : 1));
?.filter((action) => (fetchPartial ? !action.command.includes('FETCHED_PARTIAL') : true));
};
export const selectCustomerNumber = (s: PickupShelfDetailsState) => {

View File

@@ -251,6 +251,10 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
this.patchState({ fetchPartial });
}
resetCoverItems() {
this.patchState({ coverOrderItems: [] });
}
resetSelectedOrderItems() {
this.patchState({ selectedOrderItemIds: [] });
}
@@ -259,6 +263,10 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
this.patchState({ selectedOrderItemQuantity: {} });
}
setSelectedOrderItemQuantity = this.updater((state, { orderItemSubsetId, quantity }: { orderItemSubsetId: number; quantity: number }) => {
return { ...state, selectedOrderItemQuantity: { ...state.selectedOrderItemQuantity, [orderItemSubsetId]: quantity } };
});
setPreviousSelectedOrderItemSubsetId(previousSelectedOrderItemSubsetId: number) {
this.patchState({ previousSelectedOrderItemSubsetId });
}
@@ -541,10 +549,10 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
this.patchOrderItemSubsetInState({
orderItemSubsetId: item.orderItemSubsetId,
changes: {
specialComment: res.result.specialComment,
estimatedShippingDate: res.result.estimatedShippingDate,
estimatedDelivery: res.result.estimatedDelivery,
pickUpDeadline: res.result.compartmentStop,
specialComment: res?.result?.specialComment ?? item.specialComment,
estimatedShippingDate: res.result?.estimatedShippingDate ?? item.estimatedShippingDate,
estimatedDelivery: res.result?.estimatedDelivery ?? item.estimatedDelivery,
pickUpDeadline: res.result?.compartmentStop ?? item.pickUpDeadline,
},
});
@@ -554,7 +562,8 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
};
private patchOrderItemSubsetError = (err: any) => {
this._modal.error('Fehler beim Speichern des Kommentars', err);
this._modal.error('Fehler beim Speichern der Position', err);
console.error(err);
};
patchPreferredPickUpDateOnOrderSubsetItemInState({
@@ -566,11 +575,11 @@ export class PickupShelfDetailsStore extends ComponentStore<PickupShelfDetailsSt
}) {
// Filter selected order subset items from order
const items = this.order.items;
const subsetItems: EntityDTOContainerOfOrderItemSubsetDTO[] = items
.reduce((acc, item) => {
return [...acc, ...item.data.subsetItems];
}, [])
.filter((item) => this.selectedOrderItemIds.find((id) => id === item.data.id));
const subsetItems: EntityDTOContainerOfOrderItemSubsetDTO[] = items.reduce((acc, item) => {
return [...acc, ...item.data.subsetItems];
}, []);
// #4487 - RD // Abholfach - ändern des vsl. Lieferdatums und "zurückgelegt bis"-Datum wirft Fehler
// .filter((item) => this.selectedOrderItemIds.find((id) => id === item.data.id));
// Update preferredPickUpDate on subsetItem from order and patch the state
const subsetItem = subsetItems.find((subsetItem) => subsetItem.data.id === item.orderItemSubsetId);

View File

@@ -41,10 +41,7 @@ export function selectFilter(state: PickupShelfState) {
}
// Wenn queryParams ein leeres Objekt ist, dann wird der Filter gesetzt, aber ohne Werte (leerer Filter)
if (isEmpty(queryParams)) {
filter.unselectAllFilterOptions();
return filter;
}
filter.unselectAllFilterOptions();
// Wenn queryParams ein Objekt mit Werten ist, dann wird der Filter gesetzt
filter.fromQueryParams(queryParams);

View File

@@ -113,7 +113,13 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
}
setQueryParams(queryParams: Record<string, string> | undefined) {
this.patchState({ queryParams });
// #4533 Wenn ein Abholschein gescannt wird, soll ORD: nicht in der Suchbox stehen und somit auch nicht in der Breadcrumb enthalten sein
const isScannedPickUpCode = queryParams?.main_qs?.includes('ORD:');
if (isScannedPickUpCode) {
this.patchState({ queryParams: { ...queryParams, main_qs: queryParams?.main_qs?.replace('ORD:', '') } });
} else {
this.patchState({ queryParams });
}
}
cancelListRequests() {
@@ -164,7 +170,7 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
private beforeFetchQuerySettings = () => {
const cachedQuerySettings = this._cacheService.get<QuerySettingsDTO>({
name: 'pickup-shelf',
providerName: this._pickupShelfIOService.constructor.name,
providerName: this._pickupShelfIOService.name(),
});
if (!!cachedQuerySettings) {
@@ -179,10 +185,7 @@ export class PickupShelfStore extends ComponentStore<PickupShelfState> implement
private fetchQuerySettingsDone = (resp: ResponseArgsOfQuerySettingsDTO) => {
this.patchState({ fetchingQuerySettings: false, querySettings: resp.result });
this._cacheService.set<QuerySettingsDTO>(
{ name: 'pickup-shelf', providerName: this._pickupShelfIOService.constructor.name },
resp.result
);
this._cacheService.set<QuerySettingsDTO>({ name: 'pickup-shelf', providerName: this._pickupShelfIOService.name() }, resp.result);
};
private fetchQuerySettingsError = (err: any) => {

View File

@@ -31,9 +31,10 @@
<div class="inline-flex flex-row bg-white rounded-md mt-4">
<button
class="w-48 py-2 bg-white rounded-md font-bold"
class="w-48 py-2 rounded-md font-bold"
type="button"
*ngFor="let source of sources$ | async"
[class.bg-white]="(selectedSource$ | async) !== source"
[class.bg-active-branch]="(selectedSource$ | async) === source"
[class.text-white]="(selectedSource$ | async) === source"
(click)="setSource(source)"

View File

@@ -1,5 +1,5 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
@@ -8,7 +8,7 @@ import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { ArticleDTO, DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef, UiModalService } from '@ui/modal';
import { first, map } from 'rxjs/operators';
import { ProductCatalogNavigationService } from '@shared/services';
@Component({
selector: 'page-article-list-modal',
templateUrl: 'article-list-modal.component.html',
@@ -16,6 +16,8 @@ import { first, map } from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleListModalComponent {
productCatalogNavigationService = inject(ProductCatalogNavigationService);
articles$ = this.domainTaskCalendarService.getArticles({ infoId: this.modalRef.data.id }).pipe(map((response) => response.result));
expandedArticle: ArticleDTO;
@@ -64,15 +66,10 @@ export class ArticleListModalComponent {
this.modalRef.close('closeAll');
if (!lastActivatedProcessId) {
const processId = Date.now();
this.router.navigate(['/kunde', processId, 'product', 'search', 'results'], {
this.productCatalogNavigationService
.getArticleSearchResultsPath(lastActivatedProcessId, {
queryParams: { main_qs: taskCalendarSearch },
});
} else {
this.router.navigate(['/kunde', String(lastActivatedProcessId), 'product', 'search', 'results'], {
queryParams: { main_qs: taskCalendarSearch },
});
}
})
.navigate();
}
}

View File

@@ -10,8 +10,8 @@
</button>
</div>
<div class="shared-breadcrumb__crumbs">
<ng-container *ngFor="let crumb of breadcrumbs$ | async; let last = last">
<a class="shared-breadcrumb__crumb" [routerLink]="crumb.path" [queryParams]="crumb.params">
<ng-container *ngFor="let crumb of breadcrumbs$ | async; let idx = index; let last = last">
<a class="shared-breadcrumb__crumb" [attr.data-index]="idx" [routerLink]="crumb.path" [queryParams]="crumb.params">
<span [class.font-bold]="last">
{{ crumb.name }}
</span>

View File

@@ -24,12 +24,17 @@
<span class="error" *ngIf="errors.pattern">Keine gültige E-Mail Adresse</span>
</ng-container>
</div>
<div class="pl-4" *ngIf="channelActionName && notificationChannels.length !== 2">
<button type="reset" class="text-black pl-4" *ngIf="!emailDisabled && !!emailControl.value" (click)="clear(emailControl)">
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
<div class="pl-4" *ngIf="showChannelActionNameForEmailControl()">
<button
data-cta-type="save"
data-cta-form="email"
class="text-p1 font-bold text-brand outline-none border-none bg-transparent right-0"
[disabled]="channelActionLoading || emailControl?.errors?.required || emailControl?.errors?.pattern"
[disabled]="emailDisabled"
type="button"
(click)="channelActionEvent.emit(notificationChannels)"
(click)="save()"
>
{{ channelActionName }}
</button>
@@ -44,18 +49,17 @@
<span class="error" *ngIf="errors.pattern">Keine gültige Mobilnummer</span>
</ng-container>
</div>
<div class="pl-4" *ngIf="channelActionName">
<button type="reset" class="text-black pl-4" *ngIf="!mobileDisabled && !!mobileControl.value" (click)="clear(mobileControl)">
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
<div class="pl-4" *ngIf="showChannelActionNameForMobileControl()">
<button
data-cta-type="save"
data-cta-form="mobile"
class="text-p1 font-bold text-brand outline-none border-none bg-transparent right-0"
[disabled]="
channelActionLoading ||
mobileControl?.errors?.required ||
mobileControl?.errors?.pattern ||
emailControl?.errors?.required ||
emailControl?.errors?.pattern
"
[disabled]="mobileDisabled"
type="button"
(click)="channelActionEvent.emit(notificationChannels)"
(click)="save()"
>
{{ channelActionName }}
</button>

View File

@@ -43,6 +43,10 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
return !!(this.notificationChannelControl.value & 1) && this.emailControl;
}
get emailDisabled() {
return this.channelActionLoading || this.emailControl?.errors?.required || this.emailControl?.errors?.pattern;
}
get mobileControl() {
return this.notificationGroup.get('mobile') as FormControl;
}
@@ -51,6 +55,10 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
return !!(this.notificationChannelControl.value & 2) && this.mobileControl;
}
get mobileDisabled() {
return this.channelActionLoading || this.mobileControl?.errors?.required || this.mobileControl?.errors?.pattern;
}
get displayToggle() {
return this.displayEmail || this.displayMobile;
}
@@ -91,6 +99,20 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
this.initNotificationChannels$();
}
showChannelActionNameForEmailControl() {
return (!!this.channelActionName && this.emailControl?.dirty) || this.showSendAgainActionForEmail();
}
showChannelActionNameForMobileControl() {
return (!!this.channelActionName && this.mobileControl?.dirty) || this.showSendAgainActionForMobile();
}
clear(control: FormControl) {
control.setValue('');
control.markAsDirty();
this._cdr.markForCheck();
}
initNotificationChannels$() {
if (this.notificationGroup) {
this.notificationChannels$ = this.notificationChannelControl.valueChanges.pipe(startWith(this.notificationChannelControl.value)).pipe(
@@ -120,7 +142,7 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
}
setNotificationChannels(notificationChannels: NotificationChannel[]) {
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
const notificationChannel = this.getNotificationChannel(notificationChannels);
this.notificationChannelControl.setValue(notificationChannel);
this.notificationChannelControl.markAsDirty();
if (this.communicationDetails) {
@@ -128,6 +150,24 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
}
}
getNotificationChannel(notificationChannels?: NotificationChannel[]) {
let nfc = notificationChannels ?? this.notificationChannels;
return nfc.reduce((val, current) => val | current, 0) as NotificationChannel;
}
// Ticket #4526 RD // Bearbeiten - erneut senden Button fehlt
// Auf den Pickup Shelf Edit Seiten soll der Erneut senden button immer ersichtlich sein
showSendAgainActionForEmail() {
return this.channelActionName === 'Erneut senden' && (this.getNotificationChannel() as Number) !== 3;
}
// Ticket #4526 RD // Bearbeiten - erneut senden Button fehlt
// Auf den Pickup Shelf Edit Seiten soll der Erneut senden button immer ersichtlich sein
// Wenn die Felder Email und SMS angehakt wurden, soll der Button nur einmal angezeigt werden
showSendAgainActionForMobile() {
return this.channelActionName === 'Erneut senden' && (this.getNotificationChannel() === 2 || this.getNotificationChannel() !== 1);
}
toggle(value?: boolean) {
this.patchState({ open: value ?? !this.get((s) => s.open) });
}
@@ -136,4 +176,11 @@ export class SharedNotificationChannelControlComponent extends ComponentStore<Sh
this.emailControl?.updateValueAndValidity();
this.mobileControl?.updateValueAndValidity();
}
save() {
this.channelActionEvent.emit(this.notificationChannels);
this.emailControl?.markAsPristine();
this.mobileControl?.markAsPristine();
this._cdr.markForCheck();
}
}

View File

@@ -1,6 +1,13 @@
<div class="hidden desktop-large:block side-content" [class.hide-side]="sideOutletNotActivated">
<router-outlet #sideOutlet="outlet" name="side"></router-outlet>
<div class="shared-splitscreen__side" *ngIf="desktopLarge()" [class.shared-splitscreen__hidden]="!sideActivated()">
<router-outlet
*ngIf="desktopLarge()"
#sideOutlet="outlet"
(activate)="onActivate()"
(deactivate)="onDeactivate()"
name="side"
></router-outlet>
</div>
<div class="col-span-2 desktop-large:col-span-1 main-content" [class.expand-primary]="sideOutletNotActivated">
<div class="shared-splitscreen__gap" *ngIf="desktopLarge()" [class.shared-splitscreen__hidden]="!sideActivated()"></div>
<div class="shared-splitscreen__primary">
<router-outlet #primaryOutlet="outlet"></router-outlet>
</div>

View File

@@ -1,11 +1,19 @@
:host {
@apply grid grid-cols-split-screen gap-split-screen h-split-screen-tablet max-h-split-screen-tablet desktop-small:h-split-screen-desktop desktop-small:max-h-split-screen-desktop overflow-scroll;
@apply flex flex-row h-split-screen-tablet max-h-split-screen-tablet desktop-small:h-split-screen-desktop desktop-small:max-h-split-screen-desktop overflow-scroll;
}
.hide-side {
@apply hidden;
.shared-splitscreen__side {
@apply w-[31rem] min-w-[31rem] flex-grow-0 flex-shrink;
}
.expand-primary {
@apply col-span-2;
.shared-splitscreen__gap {
@apply w-[1.5rem] min-w-[1.5rem] flex-grow-0 flex-shrink;
}
.shared-splitscreen__primary {
@apply flex-grow;
}
.shared-splitscreen__hidden {
@apply min-w-0 w-0;
}

View File

@@ -1,5 +1,24 @@
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AsyncPipe, NgIf } from '@angular/common';
import {
AfterContentInit,
AfterViewInit,
ChangeDetectionStrategy,
Component,
DestroyRef,
OnInit,
QueryList,
ViewChild,
ViewChildren,
computed,
inject,
signal,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, RouterOutlet } from '@angular/router';
import { EnvironmentService } from '@core/environment';
import { OnInitDirective } from '@shared/directives/element-lifecycle';
import { NEVER } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Component({
selector: 'shared-splitscreen',
@@ -8,18 +27,34 @@ import { RouterOutlet } from '@angular/router';
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
host: { class: 'shared-splitscreen' },
imports: [RouterOutlet],
imports: [RouterOutlet, AsyncPipe, NgIf],
})
export class SharedSplitscreenComponent {
@ViewChild('sideOutlet', { static: true, read: RouterOutlet })
sideOutlet: RouterOutlet;
export class SharedSplitscreenComponent implements AfterViewInit {
destroyRef = inject(DestroyRef);
@ViewChild('primaryOutlet', { static: true, read: RouterOutlet })
primaryOutlet: RouterOutlet;
environment = inject(EnvironmentService);
get sideOutletNotActivated() {
return !(this.sideOutlet && this.sideOutlet.isActivated);
@ViewChildren('sideOutlet', { read: RouterOutlet })
side: QueryList<RouterOutlet>;
desktopLarge = signal(false);
sideActivated = signal(false);
ngAfterViewInit() {
this.environment.matchDesktopLarge$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((desktopLarge) => {
this.desktopLarge.set(desktopLarge);
if (!desktopLarge) {
this.sideActivated.set(false);
}
});
}
constructor() {}
onActivate() {
this.sideActivated.set(true);
}
onDeactivate() {
this.sideActivated.set(false);
}
}

View File

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

View File

@@ -0,0 +1,78 @@
import { coerceStringArray } from '@angular/cdk/coercion';
import { DestroyRef, Directive, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router } from '@angular/router';
import { ComponentStore } from '@ngrx/component-store';
import { filter, switchMap } from 'rxjs/operators';
export interface SharedRouterLinkActiveDirectiveState {
classList: string[];
test: RegExp | undefined;
}
@Directive({ selector: '[sharedRegexRouterLinkActive]', standalone: true })
export class RegexRouterLinkActiveDirective extends ComponentStore<SharedRouterLinkActiveDirectiveState> implements OnInit {
destroyRef = inject(DestroyRef);
router = inject(Router);
elementRef = inject(ElementRef);
renderer = inject(Renderer2);
@Input('sharedRegexRouterLinkActive')
set classList(value: string[] | string) {
this.patchState({ classList: coerceStringArray(value) });
}
get classList() {
return this.get((s) => s.classList);
}
@Input('sharedRegexRouterLinkActiveTest')
set test(value: RegExp | string) {
const test = typeof value === 'string' ? new RegExp(value) : value;
this.patchState({ test });
}
get test() {
return this.get((s) => s.test);
}
@Output()
isActiveChange = new EventEmitter<boolean>();
constructor() {
super({
classList: [],
test: undefined,
});
}
ngOnInit() {
this.router.events
.pipe(
takeUntilDestroyed(this.destroyRef),
filter((event) => event instanceof NavigationEnd),
switchMap(() => this.select((s) => s))
)
.subscribe(() => this.checkActiveLink());
this.checkActiveLink();
}
checkActiveLink() {
const { classList, test } = this.get((s) => s);
let isActive = test?.test(this.router.url) ?? false;
this.isActiveChange.emit(isActive);
classList.forEach((className) => {
if (isActive) {
this.renderer.addClass(this.elementRef.nativeElement, className);
} else {
this.renderer.removeClass(this.elementRef.nativeElement, className);
}
});
}
}

View File

@@ -0,0 +1 @@
export * from './lib/regex-router-link-active.directive';

View File

@@ -1,9 +1,9 @@
import { Directive, EventEmitter, Output } from '@angular/core';
import { NumberInput, coerceElement, coerceNumberProperty } from '@angular/cdk/coercion';
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, inject } from '@angular/core';
@Directive({ selector: '[sharedScrollContainer]', standalone: true })
@Directive({ selector: '[sharedScrollContainer]', standalone: true, host: { class: 'shared-scroll-container' } })
export class ScrollContainerDirective {
@Output()
scrollIndexChange = new EventEmitter<{ start: number; end: number }>();
elementRef = inject(ElementRef);
@Output()
scrolledToTop = new EventEmitter<void>();
@@ -11,17 +11,50 @@ export class ScrollContainerDirective {
@Output()
scrolledToBottom = new EventEmitter<void>();
constructor() {}
private _delta: number = 0;
@Input()
set delta(value: NumberInput) {
this._delta = coerceNumberProperty(value);
}
get scrollPosition() {
const element: HTMLElement = coerceElement(this.elementRef);
return element.scrollTop;
}
@HostListener('scroll', ['$event'])
onScroll(_: Event) {
const element: HTMLElement = coerceElement(this.elementRef);
const { scrollTop, scrollHeight, clientHeight } = element;
if (scrollTop === 0) {
this.scrolledToTop.emit();
} else if (scrollTop + clientHeight + this._delta >= scrollHeight) {
this.scrolledToBottom.emit();
}
}
scrollToIndex(index: number) {
throw new Error('not implemented');
const element: HTMLElement = coerceElement(this.elementRef);
const { scrollHeight, clientHeight } = element;
const itemHeight = scrollHeight / clientHeight;
const scrollPosition = index * itemHeight;
element.scrollTo({ top: scrollPosition });
}
scrollTo(position: number) {
const element: HTMLElement = coerceElement(this.elementRef);
element.scrollTo({ top: position });
}
scrollToTop() {
throw new Error('not implemented');
const element: HTMLElement = coerceElement(this.elementRef);
element.scrollTo({ top: 0 });
}
scrollToBottom() {
throw new Error('not implemented');
const element: HTMLElement = coerceElement(this.elementRef);
element.scrollTo({ top: element.scrollHeight });
}
}

View File

@@ -23,7 +23,16 @@ export function getPurchaseOption(state: PurchaseOptionsState): PurchaseOption {
return options[0];
}
return state.purchaseOption;
if (state.purchaseOption) {
return state.purchaseOption;
}
// #4380 Abholung als Standard wenn möglich und keine Auswahl getroffen wurde
if (options.includes('pickup')) {
return 'pickup';
}
return undefined;
}
export function getSelectedItemIds(state: PurchaseOptionsState): number[] {

View File

@@ -1,7 +1,7 @@
import { NumberInput, coerceNumberProperty } from '@angular/cdk/coercion';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { CustomerInfoDTO } from '@swagger/crm';
import { CustomerDTO, CustomerInfoDTO } from '@swagger/crm';
import { encodeFormData, mapCustomerInfoDtoToCustomerCreateFormData } from 'apps/page/customer/src/lib/create-customer';
import { NavigationRoute } from './navigation-route';
@@ -36,7 +36,7 @@ export class CustomerCreateNavigation {
return this._router.navigate(route.path, { queryParams: route.queryParams });
}
createCustomerRoute(params: { processId: NumberInput; customerType?: string }): NavigationRoute {
createCustomerRoute(params: { processId: NumberInput; customerType?: string; customerInfo?: CustomerInfoDTO }): NavigationRoute {
const path = [
'/kunde',
coerceNumberProperty(params.processId),
@@ -49,12 +49,20 @@ export class CustomerCreateNavigation {
},
];
const urlTree = this._router.createUrlTree(path, { queryParams: {} });
let formData = params?.customerInfo ? encodeFormData(mapCustomerInfoDtoToCustomerCreateFormData(params.customerInfo)) : undefined;
const urlTree = this._router.createUrlTree(path, {
queryParams: {
formData,
},
});
return {
path,
urlTree,
queryParams: {},
queryParams: {
formData,
},
};
}

View File

@@ -104,7 +104,9 @@ export class PickupShelfInNavigationService {
].filter((v) => !!v);
}
const queryParams = {};
const queryParams = {
side: String(side),
};
const urlTree = this._router.createUrlTree(path, { queryParams });
@@ -115,14 +117,17 @@ export class PickupShelfInNavigationService {
};
}
editRoute(item: {
orderId: number;
orderNumber: string;
compartmentCode: string;
processingStatus: OrderItemProcessingStatusValue;
orderItemSubsetId: number;
compartmentInfo: string;
}): NavigationRoute {
editRoute(
item: {
orderId: number;
orderNumber: string;
compartmentCode: string;
processingStatus: OrderItemProcessingStatusValue;
orderItemSubsetId: number;
compartmentInfo: string;
},
{ side }: { side?: boolean } = { side: true }
): NavigationRoute {
let path: any[];
if (!item.orderItemSubsetId) {
@@ -145,7 +150,7 @@ export class PickupShelfInNavigationService {
item.orderItemSubsetId,
'edit',
].filter((v) => !!v),
side: ['list'],
side: side ? ['list'] : null,
},
},
].filter((v) => !!v);
@@ -165,7 +170,7 @@ export class PickupShelfInNavigationService {
item.orderItemSubsetId,
'edit',
].filter((v) => !!v),
side: ['list'],
side: side ? ['list'] : null,
},
},
].filter((v) => !!v);
@@ -182,14 +187,17 @@ export class PickupShelfInNavigationService {
};
}
historyRoute(item: {
orderId: number;
orderNumber: string;
compartmentCode: string;
processingStatus: OrderItemProcessingStatusValue;
orderItemSubsetId: number;
compartmentInfo: string;
}): NavigationRoute {
historyRoute(
item: {
orderId: number;
orderNumber: string;
compartmentCode: string;
processingStatus: OrderItemProcessingStatusValue;
orderItemSubsetId: number;
compartmentInfo: string;
},
{ side }: { side?: boolean } = { side: true }
): NavigationRoute {
let path: any[];
if (!item.orderItemSubsetId) {
@@ -212,7 +220,7 @@ export class PickupShelfInNavigationService {
item.orderItemSubsetId,
'history',
].filter((v) => !!v),
side: ['list'],
side: side ? ['list'] : null,
},
},
].filter((v) => !!v);
@@ -232,7 +240,7 @@ export class PickupShelfInNavigationService {
item.orderItemSubsetId,
'history',
].filter((v) => !!v),
side: ['list'],
side: side ? ['list'] : null,
},
},
].filter((v) => !!v);

View File

@@ -1,6 +1,7 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
@@ -14,6 +15,32 @@ export class ShellService {
return this._fontSize$.value;
}
get scale() {
if (this._fontSize$.value === 'small') {
return 13 / 16;
} else if (this._fontSize$.value === 'normal') {
return 1;
} else if (this._fontSize$.value === 'large') {
return 19 / 16;
} else {
return 1;
}
}
scale$ = this._fontSize$.pipe(
map((size) => {
if (size === 'small') {
return 13 / 16;
} else if (size === 'normal') {
return 1;
} else if (size === 'large') {
return 19 / 16;
} else {
return 1;
}
})
);
private _sideMenuOpen$ = new BehaviorSubject<boolean>(false);
sideMenuOpen$ = this._sideMenuOpen$.asObservable();

View File

@@ -38,7 +38,8 @@
@apply px-[0.438rem] py-3;
}
.side-menu-group-item.active,
.side-menu-group-item.active:not(.has-child-view),
.side-menu-group-item.active-child,
.side-menu-group-item:hover,
.side-menu-group-item:focus {
@apply bg-[#596470] text-white;
@@ -52,7 +53,8 @@
@apply rotate-180;
}
.side-menu-group-sub-items .side-menu-group-item.active,
.side-menu-group-sub-items .side-menu-group-item.active:not(.has-child-view),
.side-menu-group-sub-items .side-menu-group-item.active-child,
.side-menu-group-sub-items .side-menu-group-item:hover,
.side-menu-group-sub-items .side-menu-group-item:focus {
@apply bg-[#89949E] text-white;

View File

@@ -7,7 +7,9 @@
class="side-menu-group-item"
(click)="closeSideMenu(); resetBranch(); focusSearchBox()"
[routerLink]="productRoutePath$ | async"
routerLinkActive="active"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/product"
(isActiveChange)="focusSearchBox()"
>
<div class="side-menu-group-item-icon">
<shared-icon icon="import-contacts"></shared-icon>
@@ -22,8 +24,9 @@
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="customerSearchRoute.path"
[queryParams]="customerSearchRoute.queryParams"
routerLinkActive="active"
(isActiveChange)="customerActive($event)"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/customer"
(isActiveChange)="customerActive($event); focusSearchBox()"
>
<span class="side-menu-group-item-icon">
<shared-icon icon="person"></shared-icon>
@@ -47,7 +50,9 @@
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="customerSearchRoute.path"
[queryParams]="customerSearchRoute.queryParams"
routerLinkActive="active"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/customer\/(\(search|search)"
(isActiveChange)="focusSearchBox()"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">
@@ -57,10 +62,11 @@
<a
*ngIf="customerCreateRoute$ | async; let customerCreateRoute"
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
(click)="closeSideMenu()"
[routerLink]="customerCreateRoute.path"
[queryParams]="customerCreateRoute.queryParams"
routerLinkActive="active"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/customer\/(\(create|create)"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">
@@ -75,7 +81,9 @@
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="pickUpShelfOutRoutePath$ | async"
routerLinkActive="active"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/pickup-shelf"
(isActiveChange)="focusSearchBox()"
>
<span class="side-menu-group-item-icon">
<shared-icon icon="unarchive"></shared-icon>
@@ -90,7 +98,9 @@
class="side-menu-group-item"
(click)="closeSideMenu(); resetBranch(); focusSearchBox()"
[routerLink]="customerOrdersRoutePath$ | async"
routerLinkActive="active"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/kunde\/\d*\/order"
(isActiveChange)="focusSearchBox()"
>
<span class="side-menu-group-item-icon">
<shared-icon icon="deployed-code"></shared-icon>
@@ -114,6 +124,7 @@
[routerLink]="taskCalenderNavigation.path"
[queryParams]="taskCalenderNavigation.queryParams"
routerLinkActive="active"
(isActiveChange)="focusSearchBox()"
>
<span class="side-menu-group-item-icon">
<shared-icon icon="event-available"></shared-icon>
@@ -145,8 +156,9 @@
*ngIf="pickUpShelfInRoutePath$ | async; let pickUpShelfInNavigation"
[routerLink]="pickUpShelfInNavigation.path"
[queryParams]="pickUpShelfInNavigation.queryParams"
routerLinkActive="active"
(isActiveChange)="shelfActive($event)"
sharedRegexRouterLinkActive="active"
sharedRegexRouterLinkActiveTest="^\/filiale\/(pickup-shelf|goods\/in)"
(isActiveChange)="shelfActive($event); focusSearchBox()"
>
<span class="side-menu-group-item-icon">
<shared-icon icon="isa-abholfach"></shared-icon>
@@ -166,11 +178,14 @@
<div class="side-menu-group-sub-items" [class.hidden]="!shelfExpanded">
<a
class="side-menu-group-item"
*ngIf="pickUpShelfInListRoutePath$ | async; let pickUpShelfInListNavigation"
*ngIf="pickUpShelfInRoutePath$ | async; let pickUpShelfInListNavigation"
(click)="closeSideMenu(); focusSearchBox()"
[routerLink]="pickUpShelfInListNavigation.path"
[queryParams]="pickUpShelfInListNavigation.queryParams"
routerLinkActive="active"
[class.has-child-view]="currentShelfView$ | async"
sharedRegexRouterLinkActive="active"
[sharedRegexRouterLinkActiveTest]="'^\/filiale\/pickup-shelf'"
(isActiveChange)="shelfActive($event); focusSearchBox()"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">
@@ -179,9 +194,12 @@
</a>
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
(click)="closeSideMenu()"
[routerLink]="['/filiale', 'goods', 'in', 'reservation']"
[queryParams]="{ view: 'reservation' }"
[class.active-child]="(currentShelfView$ | async) === 'reservation'"
routerLinkActive="active"
(isActiveChange)="shelfActive($event)"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">
@@ -190,9 +208,12 @@
</a>
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
(click)="closeSideMenu()"
[routerLink]="['/filiale', 'goods', 'in', 'cleanup']"
[queryParams]="{ view: 'cleanup' }"
[class.active-child]="(currentShelfView$ | async) === 'cleanup'"
routerLinkActive="active"
(isActiveChange)="shelfActive($event)"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">
@@ -201,9 +222,12 @@
</a>
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
(click)="closeSideMenu()"
[routerLink]="['/filiale', 'goods', 'in', 'preview']"
[queryParams]="{ view: 'remission' }"
[class.active-child]="(currentShelfView$ | async) === 'remission'"
routerLinkActive="active"
(isActiveChange)="shelfActive($event)"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">
@@ -212,9 +236,12 @@
</a>
<a
class="side-menu-group-item"
(click)="closeSideMenu(); focusSearchBox()"
(click)="closeSideMenu()"
[routerLink]="['/filiale', 'goods', 'in', 'list']"
[queryParams]="{ view: 'wareneingangsliste' }"
[class.active-child]="(currentShelfView$ | async) === 'wareneingangsliste'"
routerLinkActive="active"
(isActiveChange)="shelfActive($event)"
>
<span class="side-menu-group-item-icon"></span>
<span class="side-menu-group-item-label">

View File

@@ -19,6 +19,7 @@ import { CommonModule, DOCUMENT } from '@angular/common';
import { Config } from '@core/config';
import { BreadcrumbService } from '@core/breadcrumb';
import { IconComponent } from '@shared/components/icon';
import { RegexRouterLinkActiveDirective } from '@shared/directives/router-link-active';
@Component({
selector: 'shell-side-menu',
@@ -26,7 +27,7 @@ import { IconComponent } from '@shared/components/icon';
styleUrls: ['side-menu.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, IconComponent, RouterModule, AuthModule],
imports: [CommonModule, IconComponent, RouterModule, AuthModule, RegexRouterLinkActiveDirective],
})
export class ShellSideMenuComponent {
branchKey$ = this._stockService.StockCurrentBranch().pipe(
@@ -106,10 +107,14 @@ export class ShellSideMenuComponent {
})
);
taskCalenderNavigation$ = this.getLastNavigationByProcessId(this._config.get('process.ids.taskCalendar'), {
path: ['/filiale', 'task-calendar'],
queryParams: {},
});
taskCalenderNavigation$ = this.getLastNavigationByProcessId(
this._config.get('process.ids.taskCalendar'),
{
path: ['/filiale', 'task-calendar'],
queryParams: {},
},
'/filiale/task-calendar'
);
assortmentNavigation$ = this.getLastNavigationByProcessId(this._config.get('process.ids.assortment'), {
path: ['/filiale', 'assortment'],
@@ -118,13 +123,15 @@ export class ShellSideMenuComponent {
pickUpShelfInRoutePath$ = this.getLastNavigationByProcessId(
this._config.get('process.ids.pickupShelf'),
this._pickUpShelfInNavigation.defaultRoute()
this._pickUpShelfInNavigation.defaultRoute(),
'/filiale/pickup-shelf'
);
pickUpShelfInListRoutePath$ = this.getLastNavigationByProcessId(
this._config.get('process.ids.pickupShelf'),
this._pickUpShelfInNavigation.listRoute()
);
// #4478 - RD // Abholfach - Routing löst Suche aus
// pickUpShelfInListRoutePath$ = this.getLastNavigationByProcessId(
// this._config.get('process.ids.pickupShelf'),
// this._pickUpShelfInNavigation.listRoute()
// );
remissionNavigation$ = this.getLastNavigationByProcessId(this._config.get('process.ids.remission'), {
path: ['/filiale', 'remission'],
@@ -136,6 +143,10 @@ export class ShellSideMenuComponent {
queryParams: {},
});
get currentShelfView$() {
return this._route.queryParams.pipe(map((params) => params.view));
}
shelfExpanded: boolean = false;
customerExpanded: boolean = false;
@@ -158,11 +169,7 @@ export class ShellSideMenuComponent {
private _pickUpShelfInNavigation: PickupShelfInNavigationService,
private _cdr: ChangeDetectorRef,
@Inject(DOCUMENT) private readonly _document: Document
) {
this._router.events.subscribe((event) => {
// console.log(event);
});
}
) {}
customerActive(isActive: boolean) {
if (isActive) {
@@ -186,11 +193,29 @@ export class ShellSideMenuComponent {
this._cdr.markForCheck();
}
getLastNavigationByProcessId(id: number, fallback?: { path: string[]; queryParams: any }) {
getLastNavigationByProcessId(id: number, fallback?: { path: string[]; queryParams: any }, pathContainsString?: string) {
return this._breadcrumbService.getBreadcrumbByKey$(id)?.pipe(
map((breadcrumbs) => {
const lastCrumb = breadcrumbs
.filter((breadcrumb) => {
/**
* #4532 - Der optionale Filter wurde hinzugefügt Breadcrumbs mit fehlerhaften Pfad auszuschließen.
* Dieser Filter kann entfernt werden, sobald die Breadcrumbs korrekt gesetzt werden. Jedoch konnte man bisher nicht feststellen,
* woher die fehlerhaften Breadcrumbs kommen.
*/
if (!pathContainsString) {
// Wenn kein Filter gesetzt ist, dann wird der letzte Breadcrumb zurückgegeben
return true;
}
const pathStr = Array.isArray(breadcrumb.path) ? breadcrumb.path.join('/') : breadcrumb.path;
return pathStr.includes(pathContainsString);
})
.filter((breadcrumb) => !breadcrumb?.params?.hasOwnProperty('view'))
.filter((breadcrumb) => !breadcrumb?.tags?.includes('reservation'))
.filter((breadcrumb) => !breadcrumb?.tags?.includes('cleanup'))
.filter((breadcrumb) => !breadcrumb?.tags?.includes('wareneingangsliste'))
.filter((breadcrumb) => !breadcrumb?.tags?.includes('preview'))
.reduce((last, current) => {
if (!last) return current;
@@ -246,7 +271,7 @@ export class ShellSideMenuComponent {
}
focusSearchBox() {
this._document.getElementById('searchbox')?.focus();
setTimeout(() => this._document.getElementById('searchbox')?.focus(), 0);
}
async createProcess() {

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