Merged PR 2060: feature(checkout-reward, core-tabs): Added back button and configured it so i...

feature(checkout-reward, core-tabs): Added back button and configured it so it can accept optional route to navigate. Added orderNumber and orderDate to reward order confirmation

Ref: #5456
This commit is contained in:
Nino Righi
2025-11-28 12:38:08 +00:00
committed by Lorenz Hilpert
parent 41630d5d7c
commit c0cc0e1bbc
9 changed files with 147 additions and 12 deletions

View File

@@ -1,3 +1,7 @@
:host { :host {
@apply w-full flex flex-row items-center justify-between; @apply w-full flex flex-row items-center justify-between;
} }
ui-item-row-data-label {
width: 12.4rem;
}

View File

@@ -1,8 +1,21 @@
<h1 class="text-isa-neutral-900 isa-text-subtitle-1-regular"> <div class="flex flex-col gap-4">
Prämienausgabe abgeschlossen <h1 class="text-isa-neutral-900 isa-text-subtitle-1-regular">
</h1> Prämienausgabe abgeschlossen
</h1>
<ui-item-row-data>
<ui-item-row-data-row>
<ui-item-row-data-label>Vorgangs-ID</ui-item-row-data-label>
<ui-item-row-data-value>{{ orderNumbers() }}</ui-item-row-data-value>
</ui-item-row-data-row>
<ui-item-row-data-row>
<ui-item-row-data-label>Bestelldatum</ui-item-row-data-label>
<ui-item-row-data-value>{{ orderDates() }}</ui-item-row-data-value>
</ui-item-row-data-row>
</ui-item-row-data>
</div>
<common-print-button <common-print-button
class="self-start"
*ifNotRole="Role.CallCenter" *ifNotRole="Role.CallCenter"
printerType="label" printerType="label"
[printFn]="printFn" [printFn]="printFn"

View File

@@ -1,20 +1,28 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
inject,
computed,
} from '@angular/core';
import { DatePipe } from '@angular/common';
import { CheckoutPrintFacade } from '@isa/checkout/data-access'; import { CheckoutPrintFacade } from '@isa/checkout/data-access';
import { PrintButtonComponent, Printer } from '@isa/common/print'; import { PrintButtonComponent, Printer } from '@isa/common/print';
import { OrderConfiramtionStore } from '../reward-order-confirmation.store'; import { OrderConfiramtionStore } from '../reward-order-confirmation.store';
import { IfRoleDirective, Role } from '@isa/core/auth'; import { IfRoleDirective, Role } from '@isa/core/auth';
import { ItemRowDataImports } from '@isa/ui/item-rows';
@Component({ @Component({
selector: 'checkout-order-confirmation-header', selector: 'checkout-order-confirmation-header',
templateUrl: './order-confirmation-header.component.html', templateUrl: './order-confirmation-header.component.html',
styleUrls: ['./order-confirmation-header.component.css'], styleUrls: ['./order-confirmation-header.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [PrintButtonComponent, IfRoleDirective], imports: [PrintButtonComponent, IfRoleDirective, ItemRowDataImports],
}) })
export class OrderConfirmationHeaderComponent { export class OrderConfirmationHeaderComponent {
protected readonly Role = Role; protected readonly Role = Role;
#checkoutPrintFacade = inject(CheckoutPrintFacade); #checkoutPrintFacade = inject(CheckoutPrintFacade);
#store = inject(OrderConfiramtionStore); #store = inject(OrderConfiramtionStore);
#datePipe = new DatePipe('de-DE');
orderIds = this.#store.orderIds; orderIds = this.#store.orderIds;
@@ -24,4 +32,35 @@ export class OrderConfirmationHeaderComponent {
data: this.orderIds() ?? [], data: this.orderIds() ?? [],
}); });
}; };
orderNumbers = computed(() => {
const orders = this.#store.orders();
if (!orders || orders.length === 0) {
return '';
}
return orders
.map((order) => order.orderNumber)
.filter(Boolean)
.join('; ');
});
orderDates = computed(() => {
const orders = this.#store.orders();
if (!orders || orders.length === 0) {
return '';
}
return orders
.map((order) => {
if (!order.orderDate) {
return null;
}
const formatted = this.#datePipe.transform(
order.orderDate,
'dd.MM.yyyy | HH:mm',
);
return formatted ? `${formatted} Uhr` : null;
})
.filter(Boolean)
.join('; ');
});
} }

View File

@@ -19,6 +19,7 @@ import {
HandleCommandService, HandleCommandService,
HandleCommand, HandleCommand,
getMainActions, getMainActions,
DisplayOrdersResource,
} from '@isa/oms/data-access'; } from '@isa/oms/data-access';
import { ButtonComponent } from '@isa/ui/buttons'; import { ButtonComponent } from '@isa/ui/buttons';
import { NgIcon } from '@ng-icons/core'; import { NgIcon } from '@ng-icons/core';
@@ -61,6 +62,7 @@ export class ConfirmationListItemActionCardComponent {
#orderRewardCollectFacade = inject(OrderRewardCollectFacade); #orderRewardCollectFacade = inject(OrderRewardCollectFacade);
#store = inject(OrderConfiramtionStore); #store = inject(OrderConfiramtionStore);
#orderItemSubsetResource = inject(OrderItemSubsetResource); #orderItemSubsetResource = inject(OrderItemSubsetResource);
#displayOrdersResource = inject(DisplayOrdersResource);
#handleCommandFacade = inject(HandleCommandFacade); #handleCommandFacade = inject(HandleCommandFacade);
item = input.required<DisplayOrderItem>(); item = input.required<DisplayOrderItem>();
@@ -165,7 +167,7 @@ export class ConfirmationListItemActionCardComponent {
} }
} }
} }
this.#orderItemSubsetResource.refresh(); this.reloadResources();
} }
} finally { } finally {
this.isLoading.set(false); this.isLoading.set(false);
@@ -175,4 +177,9 @@ export class ConfirmationListItemActionCardComponent {
async handleCommand(params: HandleCommand) { async handleCommand(params: HandleCommand) {
await this.#handleCommandFacade.handle(params); await this.#handleCommandFacade.handle(params);
} }
reloadResources() {
this.#orderItemSubsetResource.refresh();
this.#displayOrdersResource.refresh();
}
} }

View File

@@ -1,3 +1,3 @@
:host { :host {
@apply block w-full text-isa-neutral-900 mt-[1.42rem]; @apply flex flex-col w-full text-isa-neutral-900 mt-[1.42rem] gap-4;
} }

View File

@@ -1,3 +1,6 @@
@if (!hasPendingActions()) {
<tabs-navigate-back-button [navigateTo]="rewardCatalogRoute()" />
}
<div <div
class="bg-isa-white p-6 rounded-2xl flex flex-col gap-6 items-start self-stretch" class="bg-isa-white p-6 rounded-2xl flex flex-col gap-6 items-start self-stretch"
> >

View File

@@ -12,9 +12,21 @@ import { OrderConfirmationAddressesComponent } from './order-confirmation-addres
import { OrderConfirmationHeaderComponent } from './order-confirmation-header/order-confirmation-header.component'; import { OrderConfirmationHeaderComponent } from './order-confirmation-header/order-confirmation-header.component';
import { OrderConfirmationItemListComponent } from './order-confirmation-item-list/order-confirmation-item-list.component'; import { OrderConfirmationItemListComponent } from './order-confirmation-item-list/order-confirmation-item-list.component';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { TabService } from '@isa/core/tabs'; import {
NavigateBackButtonComponent,
TabService,
injectTabId,
} from '@isa/core/tabs';
import { OrderConfiramtionStore } from './reward-order-confirmation.store'; import { OrderConfiramtionStore } from './reward-order-confirmation.store';
import { DisplayOrdersResource } from '@isa/oms/data-access'; import {
DisplayOrdersResource,
getProcessingStatusState,
ProcessingStatusState,
} from '@isa/oms/data-access';
import {
hasOrderTypeFeature,
hasLoyaltyCollectCommand,
} from '@isa/checkout/data-access';
@Component({ @Component({
selector: 'checkout-reward-order-confirmation', selector: 'checkout-reward-order-confirmation',
@@ -25,13 +37,14 @@ import { DisplayOrdersResource } from '@isa/oms/data-access';
OrderConfirmationHeaderComponent, OrderConfirmationHeaderComponent,
OrderConfirmationAddressesComponent, OrderConfirmationAddressesComponent,
OrderConfirmationItemListComponent, OrderConfirmationItemListComponent,
NavigateBackButtonComponent,
], ],
providers: [OrderConfiramtionStore, DisplayOrdersResource], providers: [OrderConfiramtionStore, DisplayOrdersResource],
}) })
export class RewardOrderConfirmationComponent { export class RewardOrderConfirmationComponent {
#store = inject(OrderConfiramtionStore); #store = inject(OrderConfiramtionStore);
#displayOrdersResource = inject(DisplayOrdersResource); #displayOrdersResource = inject(DisplayOrdersResource);
#tabId = inject(TabService).activatedTabId; #tabId = injectTabId();
#activatedRoute = inject(ActivatedRoute); #activatedRoute = inject(ActivatedRoute);
params = toSignal(this.#activatedRoute.paramMap); params = toSignal(this.#activatedRoute.paramMap);
@@ -49,6 +62,43 @@ export class RewardOrderConfirmationComponent {
orderIds = this.displayOrderIds; orderIds = this.displayOrderIds;
orders = this.#store.orders; orders = this.#store.orders;
/**
* Checks if there are any items with pending actions (Rücklage items that still need to be collected).
* Returns true if at least one item requires action.
*/
hasPendingActions = computed(() => {
const orders = this.#store.orders();
if (!orders) {
return false;
}
const allItems = orders.flatMap((order) => order.items ?? []);
return allItems.some((item) => {
const isRuecklage = hasOrderTypeFeature(item.features, ['Rücklage']);
if (!isRuecklage) {
return false;
}
const hasCollectCommand = hasLoyaltyCollectCommand(item.subsetItems);
const statuses = item.subsetItems?.map(
(subset) => subset.processingStatus,
);
const processingStatus = getProcessingStatusState(statuses);
const isComplete =
processingStatus !== undefined &&
processingStatus !== ProcessingStatusState.Ordered;
// Item has pending action if it has collect command and is not complete
return hasCollectCommand && !isComplete;
});
});
/**
* Route to the reward catalog for the current tab.
*/
rewardCatalogRoute = computed(() => `/${this.#tabId()}/reward`);
constructor() { constructor() {
// Update store state // Update store state
effect(() => { effect(() => {

View File

@@ -1,4 +1,4 @@
import { Component, inject, computed } from '@angular/core'; import { Component, inject, computed, input } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { NgIcon, provideIcons } from '@ng-icons/core'; import { NgIcon, provideIcons } from '@ng-icons/core';
import { isaActionChevronLeft } from '@isa/icons'; import { isaActionChevronLeft } from '@isa/icons';
@@ -33,7 +33,18 @@ export class NavigateBackButtonComponent {
#tabService = inject(TabService); #tabService = inject(TabService);
#router = inject(Router); #router = inject(Router);
/**
* Optional URL to navigate to instead of using browser history.
* Pass a complete URL string (e.g. '/123/reward').
*/
navigateTo = input<string>();
canNavigateBack = computed(() => { canNavigateBack = computed(() => {
// If navigateTo is set, always allow navigation
if (this.navigateTo()) {
return true;
}
const tabId = this.#tabService.activatedTabId(); const tabId = this.#tabService.activatedTabId();
if (tabId === null) { if (tabId === null) {
return false; return false;
@@ -49,10 +60,18 @@ export class NavigateBackButtonComponent {
}); });
back() { back() {
const navigateTo = this.navigateTo();
if (navigateTo) {
this.#router.navigateByUrl(navigateTo);
return;
}
// Default behavior: use browser history
const tabId = this.#tabService.activatedTabId(); const tabId = this.#tabService.activatedTabId();
if (tabId === null) { if (tabId === null) {
return; return;
} }
const location = this.#tabService.navigateBack(tabId); const location = this.#tabService.navigateBack(tabId);
if (!location) { if (!location) {

View File

@@ -15,7 +15,7 @@
</ui-item-row-data-value> </ui-item-row-data-value>
</ui-item-row-data-row> </ui-item-row-data-row>
<ui-item-row-data-row> <ui-item-row-data-row>
<ui-item-row-data-label>Vorgang-ID:</ui-item-row-data-label> <ui-item-row-data-label>Vorgangs-ID:</ui-item-row-data-label>
<ui-item-row-data-value> <ui-item-row-data-value>
{{ r.order?.data?.orderNumber }} {{ r.order?.data?.orderNumber }}
</ui-item-row-data-value> </ui-item-row-data-value>