Merged PR 1969: Reward Shopping Cart Implementation with Navigation State Management and Shipping Address Integration

1. Reward Shopping Cart Implementation
  - New shopping cart with quantity control and availability checking
  - Responsive shopping cart item component with improved CSS styling
  - Shipping address integration in cart
  - Customer reward card and billing/shipping address components

  2. Navigation State Management Library (@isa/core/navigation)
  - New library with type-safe navigation context service (373 lines)
  - Navigation state service (287 lines) for temporary state between routes
  - Comprehensive test coverage (668 + 227 lines of tests)
  - Documentation (792 lines in README.md)
  - Replaces query parameters for passing temporary navigation context

  3. CRM Shipping Address Services
  - New ShippingAddressService with fetching and validation
  - CustomerShippingAddressResource and CustomerShippingAddressesResource
  - Zod schemas for data validation

  4. Additional Improvements
  - Enhanced searchbox accessibility with ARIA support
  - Availability data access rework for better fetching/mapping
  - Storybook tooltip variant support
  - Vitest JUnit and Cobertura reporting configuration

Related work items: #5382, #5383, #5384
This commit is contained in:
Lorenz Hilpert
2025-10-15 14:59:34 +00:00
committed by Nino Righi
parent f15848d5c0
commit 596ae1da1b
45 changed files with 3793 additions and 344 deletions

View File

@@ -1,3 +1,7 @@
:host {
@apply flex flex-col items-start gap-2 flex-grow;
}
.address-container {
@apply line-clamp-2 break-words text-ellipsis;
}

View File

@@ -11,12 +11,18 @@
orderType()
}}</span>
</div>
<div class="text-isa-neutral-600 isa-text-body-2-regular">
<div class="text-isa-neutral-600 isa-text-body-2-regular address-container">
@if (displayAddress()) {
{{ branchName() }} |
{{ name() }} |
<shared-inline-address [address]="address()"></shared-inline-address>
} @else if (estimatedDelivery()) {
Zustellung zwischen {{ estimatedDelivery().start | date: 'E, dd.MM.' }} und
{{ estimatedDelivery().stop | date: 'E, dd.MM.' }}
} @else {
@if (estimatedDelivery(); as delivery) {
@if (delivery.stop) {
Zustellung zwischen {{ delivery.start | date: 'E, dd.MM.' }} und
{{ delivery.stop | date: 'E, dd.MM.' }}
} @else {
Zustellung voraussichtlich am {{ delivery.start | date: 'E, dd.MM.' }}
}
}
}
</div>

View File

@@ -12,6 +12,7 @@ import {
OrderType,
ShoppingCartItem,
} from '@isa/checkout/data-access';
import { SelectedCustomerShippingAddressResource } from '@isa/crm/data-access';
import {
isaDeliveryVersand,
isaDeliveryRuecklage2,
@@ -34,10 +35,12 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
isaDeliveryRuecklage1,
}),
BranchResource,
SelectedCustomerShippingAddressResource,
],
})
export class DestinationInfoComponent {
#branchResource = inject(BranchResource);
#shippingAddressResource = inject(SelectedCustomerShippingAddressResource);
underline = input<boolean, unknown>(false, {
transform: coerceBooleanProperty,
@@ -75,7 +78,7 @@ export class DestinationInfoComponent {
return (
OrderType.InStore === orderType ||
OrderType.Pickup === orderType ||
OrderType.B2BShipping
OrderType.B2BShipping === orderType
);
});
@@ -96,16 +99,30 @@ export class DestinationInfoComponent {
}
});
branchName = computed(() => {
name = computed(() => {
const orderType = this.orderType();
if (
OrderType.Delivery === orderType ||
OrderType.B2BShipping === orderType ||
OrderType.DigitalShipping === orderType
) {
const shippingAddress = this.#shippingAddressResource.resource.value();
return `${shippingAddress?.firstName || ''} ${shippingAddress?.lastName || ''}`.trim();
}
return this.branch()?.name || 'Filiale nicht gefunden';
});
address = computed(() => {
const orderType = this.orderType();
if (OrderType.B2BShipping === orderType) {
// B2B shipping doesn't use branch address
return undefined;
if (
OrderType.Delivery === orderType ||
OrderType.B2BShipping === orderType ||
OrderType.DigitalShipping === orderType
) {
const shippingAddress = this.#shippingAddressResource.resource.value();
return shippingAddress?.address || undefined;
}
const destination = this.shoppingCartItem().destination;
@@ -113,6 +130,18 @@ export class DestinationInfoComponent {
});
estimatedDelivery = computed(() => {
return this.shoppingCartItem().availability?.estimatedDelivery;
const availability = this.shoppingCartItem().availability;
const estimatedDelivery = availability?.estimatedDelivery;
const estimatedShippingDate = availability?.estimatedShippingDate;
if (estimatedDelivery?.start && estimatedDelivery?.stop) {
return { start: estimatedDelivery.start, stop: estimatedDelivery.stop };
}
if (estimatedShippingDate) {
return { start: estimatedShippingDate, stop: null };
}
return null;
});
}

View File

@@ -1,7 +1,7 @@
@let prd = item().product;
@let rPoints = points();
@if (prd) {
<div class="grid grid-cols-[auto,1fr] gap-6">
<div class="grid grid-cols-[auto,1fr] gap-6 items-start">
<div>
<img
sharedProductRouterLink
@@ -13,7 +13,7 @@
/>
</div>
<div class="flex flex-1 flex-col justify-between gap-1">
<div class="flex flex-1 flex-col gap-1">
<div class="isa-text-body-2-bold">{{ prd.contributors }}</div>
<div
[class.isa-text-body-2-regular]="orientation() === 'horizontal'"
@@ -28,7 +28,7 @@
</div>
</div>
<div
class="flex flex-1 flex-col justify-between gap-1"
class="flex flex-1 flex-col gap-2"
[class.ml-20]="orientation() === 'vertical'"
>
<shared-product-format
@@ -36,8 +36,11 @@
[formatDetail]="prd.formatDetail"
[formatDetailsBold]="true"
></shared-product-format>
<div class="isa-text-body-2-regular text-neutral-600">
{{ prd.manufacturer }} | {{ prd.ean }}
<div
class="flex items-center gap-1 isa-text-body-2-regular text-neutral-600"
>
<span class="truncate">{{ prd.manufacturer }}</span>
<span class="shrink-0">| {{ prd.ean }}</span>
</div>
<div class="isa-text-body-2-regular text-neutral-600">
{{ prd.publicationDate | date: 'dd. MMMM yyyy' }}