Merged PR 2021: feat(pickup-shelf): display Prämie label and Lesepunkte for reward items

feat(pickup-shelf): display Prämie label and Lesepunkte for reward items

- Add "Prämie" ui-label badge below product images in both list and details views
- Display Lesepunkte value instead of price for reward items
- Update getOrderItemRewardFeature helper to use structural typing for better type flexibility
- Apply to pickup-shelf-details-item and pickup-shelf-list-item components

Fixes #5467
This commit is contained in:
Lorenz Hilpert
2025-11-11 14:15:41 +00:00
committed by Nino Righi
parent cc186dbbe2
commit f261fc9987
7 changed files with 83 additions and 18 deletions

View File

@@ -40,6 +40,11 @@
[src]="orderItem.product?.ean | productImage"
[alt]="orderItem.product?.name"
/>
@if (hasRewardPoints$ | async) {
<ui-label [type]="Labeltype.Tag" [priority]="LabelPriority.High">
Prämie
</ui-label>
}
</div>
<div class="page-pickup-shelf-details-item__details">
<div class="flex flex-row justify-between items-start mb-[1.3125rem]">
@@ -117,10 +122,15 @@
<div class="value">{{ orderItem.product?.ean }}</div>
</div>
}
@if (orderItem.price !== undefined) {
@if (orderItem.price !== undefined || (hasRewardPoints$ | async)) {
<div class="detail">
<div class="label">Preis</div>
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
@if (hasRewardPoints$ | async) {
<div class="label">Prämie</div>
<div class="value">{{ rewardPoints$ | async | number: '1.0-0' }} Lesepunkte</div>
} @else {
<div class="label">Preis</div>
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
}
</div>
}
@if (!!orderItem.retailPrice?.vat?.inPercent) {

View File

@@ -21,6 +21,8 @@ button {
}
.page-pickup-shelf-details-item__thumbnail {
@apply flex flex-col items-center gap-2;
img {
@apply rounded shadow-cta w-[3.625rem] max-h-[5.9375rem];
}

View File

@@ -1,20 +1,22 @@
import { CdkTextareaAutosize, TextFieldModule } from '@angular/cdk/text-field';
import { AsyncPipe, CurrencyPipe, DatePipe } from '@angular/common';
import { AsyncPipe, CurrencyPipe, DatePipe, DecimalPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
inject, OnDestroy,
inject,
OnDestroy,
} from '@angular/core';
import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { NavigateOnClickDirective, ProductImageModule } from '@cdn/product-image';
import { DBHOrderItemListItemDTO, OrderDTO, ReceiptDTO } from '@generated/swagger/oms-api';
import { getOrderItemRewardFeature } from '@isa/oms/data-access';
import { UiCommonModule } from '@ui/common';
import { LabelComponent, Labeltype, LabelPriority } from '@isa/ui/label';
import { UiTooltipModule } from '@ui/tooltip';
import { PickupShelfPaymentTypePipe } from '../pipes/payment-type.pipe';
import { IconModule } from '@shared/components/icon';
@@ -48,6 +50,7 @@ export interface PickUpShelfDetailsItemComponentState {
ReactiveFormsModule,
CurrencyPipe,
DatePipe,
DecimalPipe,
AsyncPipe,
ProductImageModule,
TextFieldModule,
@@ -56,12 +59,13 @@ export interface PickUpShelfDetailsItemComponentState {
UiQuantityDropdownModule,
NotificationTypePipe,
NavigateOnClickDirective,
MatomoModule
MatomoModule,
LabelComponent
],
})
export class PickUpShelfDetailsItemComponent
extends ComponentStore<PickUpShelfDetailsItemComponentState>
implements OnInit, OnDestroy
implements OnDestroy
{
private _store = inject(PickupShelfDetailsStore);
@@ -117,6 +121,22 @@ export class PickUpShelfDetailsItemComponent
hasSmsNotification$ = this.smsNotificationDates$.pipe(map((dates) => dates?.length > 0));
/**
* Observable that indicates whether the order item has reward points (Lesepunkte).
* Returns true if the item has a 'praemie' feature.
*/
hasRewardPoints$ = this.orderItem$.pipe(
map((orderItem) => getOrderItemRewardFeature(orderItem) !== undefined),
);
/**
* Observable that emits the reward points (Lesepunkte) value for the order item.
* Returns the parsed numeric value from the 'praemie' feature, or undefined if not present.
*/
rewardPoints$ = this.orderItem$.pipe(
map((orderItem) => getOrderItemRewardFeature(orderItem)),
);
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1),
);
@@ -167,12 +187,12 @@ export class PickUpShelfDetailsItemComponent
return this._store.receipts;
}
readonly receipts$ = this._store.receipts$;
set receipts(receipts: ReceiptDTO[]) {
this._store.updateReceipts(receipts);
}
readonly receipts$ = this._store.receipts$;
readonly receiptCount$ = this.receipts$.pipe(map((receipts) => receipts?.length));
specialCommentControl = new UntypedFormControl();
@@ -181,7 +201,11 @@ export class PickUpShelfDetailsItemComponent
private _onDestroy$ = new Subject<void>();
expanded: boolean = false;
expanded = false;
// Expose to template
Labeltype = Labeltype;
LabelPriority = LabelPriority;
constructor(private _cdr: ChangeDetectorRef) {
super({
@@ -189,8 +213,6 @@ export class PickUpShelfDetailsItemComponent
});
}
ngOnInit() {}
ngOnDestroy() {
// Remove Prev OrderItem from selected list
this._store.selectOrderItem(this.orderItem, false);

View File

@@ -39,6 +39,7 @@
.page-pickup-shelf-list-item__item-thumbnail {
grid-area: thumbnail;
@apply flex flex-col items-center gap-2;
}
.page-pickup-shelf-list-item__item-image {

View File

@@ -10,7 +10,7 @@
[class.page-pickup-shelf-list-item__item-grid-container-main]="primaryOutletActive"
[class.page-pickup-shelf-list-item__item-grid-container-secondary]="primaryOutletActive && isItemSelectable === undefined"
>
<div class="page-pickup-shelf-list-item__item-thumbnail text-center w-[3.125rem] h-[4.9375rem]">
<div class="page-pickup-shelf-list-item__item-thumbnail text-center">
@if (item?.product?.ean | productImage; as productImage) {
<img
class="page-pickup-shelf-list-item__item-image w-[3.125rem] max-h-[4.9375rem]"
@@ -20,6 +20,11 @@
[alt]="item?.product?.name"
/>
}
@if (hasRewardPoints) {
<ui-label [type]="Labeltype.Tag" [priority]="LabelPriority.High">
Prämie
</ui-label>
}
</div>
<div

View File

@@ -5,6 +5,8 @@ import { NavigateOnClickDirective, ProductImageModule } from '@cdn/product-image
import { EnvironmentService } from '@core/environment';
import { IconModule } from '@shared/components/icon';
import { DBHOrderItemListItemDTO } from '@generated/swagger/oms-api';
import { getOrderItemRewardFeature } from '@isa/oms/data-access';
import { LabelComponent, Labeltype, LabelPriority } from '@isa/ui/label';
import { UiCommonModule } from '@ui/common';
import { PickupShelfProcessingStatusPipe } from '../pipes/processing-status.pipe';
import { FormsModule } from '@angular/forms';
@@ -29,7 +31,8 @@ import { MatomoModule } from 'ngx-matomo-client';
UiCommonModule,
PickupShelfProcessingStatusPipe,
NavigateOnClickDirective,
MatomoModule
MatomoModule,
LabelComponent
],
providers: [PickupShelfProcessingStatusPipe],
})
@@ -77,12 +80,24 @@ export class PickUpShelfListItemComponent {
return { 'background-color': this._processingStatusPipe.transform(this.item?.processingStatus, true) };
}
/**
* Indicates whether the order item has reward points (Lesepunkte).
* Returns true if the item has a 'praemie' feature.
*/
get hasRewardPoints() {
return getOrderItemRewardFeature(this.item) !== undefined;
}
selected$ = this.store.selectedListItems$.pipe(
map((selectedListItems) =>
selectedListItems?.find((item) => item?.orderItemSubsetId === this.item?.orderItemSubsetId),
),
);
// Expose to template
Labeltype = Labeltype;
LabelPriority = LabelPriority;
constructor(
private _elRef: ElementRef,
private _environment: EnvironmentService,

View File

@@ -1,7 +1,17 @@
import { OrderItemDTO, DisplayOrderItemDTO } from '@generated/swagger/oms-api';
/**
* Extracts and parses the reward points (Lesepunkte) value from an order item's features.
*
* @param orderItem - An object containing a features property with key-value pairs
* @returns The parsed numeric value of reward points, or undefined if not present
*
* @example
* ```ts
* const orderItem = { features: { praemie: '12.345' } };
* const points = getOrderItemRewardFeature(orderItem); // returns 12345
* ```
*/
export function getOrderItemRewardFeatureHelper(
orderItem: OrderItemDTO | DisplayOrderItemDTO | undefined,
orderItem: { features?: { [key: string]: string } } | undefined,
): undefined | number {
if (!orderItem || !orderItem.features) {
return undefined;